cmdbox 0.5.3.1__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cmdbox might be problematic. Click here for more details.
- cmdbox/app/auth/__init__.py +0 -0
- cmdbox/app/auth/azure_signin.py +38 -0
- cmdbox/app/auth/azure_signin_saml.py +12 -0
- cmdbox/app/auth/github_signin.py +38 -0
- cmdbox/app/auth/google_signin.py +32 -0
- cmdbox/app/auth/signin.py +490 -287
- cmdbox/app/auth/signin_saml.py +61 -0
- cmdbox/app/common.py +48 -3
- cmdbox/app/edge.py +182 -213
- cmdbox/app/edge_tool.py +177 -0
- cmdbox/app/feature.py +10 -10
- cmdbox/app/features/cli/agent_base.py +477 -0
- cmdbox/app/features/cli/audit_base.py +1 -1
- cmdbox/app/features/cli/cmdbox_audit_search.py +24 -1
- cmdbox/app/features/cli/cmdbox_client_file_download.py +1 -1
- cmdbox/app/features/cli/cmdbox_cmd_list.py +105 -0
- cmdbox/app/features/cli/cmdbox_cmd_load.py +104 -0
- cmdbox/app/features/cli/cmdbox_edge_config.py +21 -7
- cmdbox/app/features/cli/cmdbox_edge_start.py +1 -1
- cmdbox/app/features/cli/cmdbox_gui_start.py +9 -132
- cmdbox/app/features/cli/cmdbox_gui_stop.py +4 -21
- cmdbox/app/features/cli/cmdbox_server_start.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_apikey_add.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_apikey_del.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_genpass.py +0 -3
- cmdbox/app/features/cli/cmdbox_web_group_add.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_group_del.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_group_edit.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_group_list.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_start.py +119 -104
- cmdbox/app/features/cli/cmdbox_web_stop.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_user_add.py +4 -4
- cmdbox/app/features/cli/cmdbox_web_user_del.py +1 -1
- cmdbox/app/features/cli/cmdbox_web_user_edit.py +4 -4
- cmdbox/app/features/cli/cmdbox_web_user_list.py +1 -1
- cmdbox/app/features/web/cmdbox_web_agent.py +250 -0
- cmdbox/app/features/web/cmdbox_web_do_signin.py +79 -103
- cmdbox/app/features/web/cmdbox_web_exec_cmd.py +8 -3
- cmdbox/app/features/web/cmdbox_web_signin.py +26 -4
- cmdbox/app/features/web/cmdbox_web_users.py +2 -0
- cmdbox/app/options.py +55 -2
- cmdbox/app/web.py +155 -27
- cmdbox/extensions/features.yml +18 -0
- cmdbox/extensions/sample_project/sample/app/features/cli/__init__.py +0 -0
- cmdbox/extensions/sample_project/sample/app/features/web/__init__.py +0 -0
- cmdbox/extensions/sample_project/sample/extensions/features.yml +23 -0
- cmdbox/extensions/sample_project/sample/extensions/user_list.yml +40 -6
- cmdbox/extensions/user_list.yml +37 -6
- cmdbox/licenses/{LICENSE.starlette.0.41.3(BSD License).txt → LICENSE.Authlib.1.5.2(BSD License).txt } +3 -1
- cmdbox/licenses/{LICENSE.pydantic_core.2.33.0(MIT License).txt → LICENSE.Deprecated.1.2.18(MIT License).txt } +2 -2
- cmdbox/licenses/{LICENSE.more-itertools.10.6.0(MIT License).txt → LICENSE.SQLAlchemy.2.0.40(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.aiohttp.3.11.18(Apache Software License).txt +13 -0
- cmdbox/licenses/LICENSE.aiosignal.1.3.2(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.async-timeout.5.0.1(Apache Software License).txt +13 -0
- cmdbox/licenses/{LICENSE.watchfiles.1.0.0(MIT License).txt → LICENSE.attrs.25.3.0(UNKNOWN).txt} +1 -1
- cmdbox/licenses/{LICENSE.anyio.4.6.2.post1(MIT License).txt → LICENSE.cachetools.5.5.2(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.distro.1.9.0(Apache Software License).txt +202 -0
- cmdbox/licenses/{LICENSE.pydantic_core.2.33.1(MIT License).txt → LICENSE.docstring_parser.0.16(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.filelock.3.18.0(The Unlicense (Unlicense)).txt +24 -0
- cmdbox/licenses/LICENSE.frozenlist.1.6.0(Apache-2.0).txt +201 -0
- cmdbox/licenses/{LICENSE.starlette.0.46.1(BSD License).txt → LICENSE.fsspec.2025.3.2(BSD License).txt } +3 -1
- cmdbox/licenses/{LICENSE.argcomplete.3.6.1(Apache Software License).txt → LICENSE.google-adk.0.5.0(Apache Software License).txt } +25 -0
- cmdbox/licenses/LICENSE.google-api-python-client.2.169.0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.google-auth-httplib2.0.2.0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.google-auth.2.40.1(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.google-cloud-aiplatform.1.92.0(Apache 2.0).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-bigquery.3.31.0(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-core.2.4.3(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-resource-manager.1.14.2(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-secret-manager.2.23.3(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-speech.2.32.0(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-storage.2.19.0(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-cloud-trace.1.16.1(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-crc32c.1.7.1(Apache 2.0).txt +202 -0
- cmdbox/licenses/LICENSE.google-genai.1.14.0(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.google-resumable-media.2.7.2(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.googleapis-common-protos.1.70.0(Apache Software License).txt +202 -0
- cmdbox/licenses/{LICENSE.fastapi.0.115.5(MIT License).txt → LICENSE.graphviz.0.20.3(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.grpc-google-iam-v1.0.14.2(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.grpcio-status.1.71.0(Apache Software License).txt +610 -0
- cmdbox/licenses/LICENSE.grpcio.1.71.0(Apache Software License).txt +610 -0
- cmdbox/licenses/{LICENSE.uvicorn.0.34.0(BSD License).txt → LICENSE.httpcore.1.0.9(BSD License).txt } +1 -1
- cmdbox/licenses/LICENSE.httplib2.0.22.0(MIT License).txt +23 -0
- cmdbox/licenses/{LICENSE.tomli.2.1.0(MIT License).txt → LICENSE.httpx-sse.0.4.0(MIT).txt} +1 -1
- cmdbox/licenses/LICENSE.httpx.0.28.1(BSD License).txt +12 -0
- cmdbox/licenses/LICENSE.huggingface-hub.0.31.1(Apache Software License).txt +201 -0
- cmdbox/licenses/{LICENSE.charset-normalizer.3.4.0(MIT License).txt → LICENSE.jsonschema-specifications.2025.4.1(UNKNOWN).txt} +5 -7
- cmdbox/licenses/LICENSE.jsonschema.4.23.0(MIT License).txt +19 -0
- cmdbox/licenses/{LICENSE.pkginfo.1.10.0(MIT License).txt → LICENSE.litellm.1.69.0(MIT License).txt } +6 -1
- cmdbox/licenses/{LICENSE.redis.5.2.1(MIT License).txt → LICENSE.mcp.1.8.0(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.multidict.6.4.3(Apache Software License).txt +13 -0
- cmdbox/licenses/{LICENSE.argcomplete.3.5.1(Apache Software License).txt → LICENSE.openai.1.75.0(Apache Software License).txt } +25 -1
- cmdbox/licenses/LICENSE.opentelemetry-api.1.33.0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.opentelemetry-exporter-gcp-trace.1.9.0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.opentelemetry-resourcedetector-gcp.1.9.0a0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.opentelemetry-sdk.1.33.0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.opentelemetry-semantic-conventions.0.54b0(Apache Software License).txt +201 -0
- cmdbox/licenses/LICENSE.propcache.0.3.1(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.proto-plus.1.26.1(Apache Software License).txt +202 -0
- cmdbox/licenses/{LICENSE.Pygments.2.18.0(BSD License).txt → LICENSE.protobuf.5.29.4(3-Clause BSD License).txt } +15 -8
- cmdbox/licenses/LICENSE.pyasn1.0.6.1(BSD License).txt +24 -0
- cmdbox/licenses/LICENSE.pyasn1_modules.0.4.2(BSD License).txt +24 -0
- cmdbox/licenses/LICENSE.pydantic-settings.2.9.1(MIT License).txt +21 -0
- cmdbox/licenses/{LICENSE.gevent.25.4.1(MIT).txt → LICENSE.pyparsing.3.2.3(MIT License).txt } +5 -12
- cmdbox/licenses/LICENSE.python-dateutil.2.9.0.post0(Apache Software License; BSD License).txt +54 -0
- cmdbox/licenses/LICENSE.referencing.0.36.2(UNKNOWN).txt +19 -0
- cmdbox/licenses/LICENSE.regex.2024.11.6(Apache Software License).txt +208 -0
- cmdbox/licenses/LICENSE.rpds-py.0.24.0(MIT).txt +19 -0
- cmdbox/licenses/{LICENSE.python-multipart.0.0.17(Apache Software License).txt → LICENSE.rsa.4.9.1(Apache Software License).txt } +1 -2
- cmdbox/licenses/{LICENSE.sphinx-intl.2.3.0(BSD License).txt → LICENSE.shapely.2.1.0(BSD License).txt } +6 -2
- cmdbox/licenses/LICENSE.sse-starlette.2.3.4(BSD License).txt +27 -0
- cmdbox/licenses/LICENSE.tiktoken.0.9.0(MIT License).txt +21 -0
- cmdbox/licenses/LICENSE.tokenizers.0.21.1(Apache Software License).txt +1 -0
- cmdbox/licenses/{LICENSE.six.1.16.0(MIT License).txt → LICENSE.tqdm.4.67.1(MIT License; Mozilla Public License 2.0 (MPL 2.0)).txt } +32 -1
- cmdbox/licenses/{LICENSE.rich.13.9.4(MIT License).txt → LICENSE.tzlocal.5.3.1(MIT License).txt } +3 -3
- cmdbox/licenses/LICENSE.uritemplate.4.1.1(Apache Software License; BSD License).txt +3 -0
- cmdbox/licenses/LICENSE.wrapt.1.17.2(BSD License).txt +24 -0
- cmdbox/licenses/LICENSE.yarl.1.20.0(Apache Software License).txt +202 -0
- cmdbox/licenses/files.txt +111 -17
- cmdbox/logconf_agent.yml +38 -0
- cmdbox/logconf_audit.yml +13 -5
- cmdbox/logconf_client.yml +13 -5
- cmdbox/logconf_cmdbox.yml +13 -5
- cmdbox/logconf_edge.yml +13 -5
- cmdbox/logconf_gui.yml +13 -5
- cmdbox/logconf_server.yml +13 -5
- cmdbox/logconf_web.yml +13 -5
- cmdbox/version.py +3 -2
- cmdbox/web/agent.html +263 -0
- cmdbox/web/assets/cmdbox/agent.js +335 -0
- cmdbox/web/assets/cmdbox/common.js +1111 -1020
- cmdbox/web/assets/cmdbox/signin.js +16 -3
- cmdbox/web/assets/cmdbox/users.js +1 -1
- cmdbox/web/assets/filer/filer.js +4 -2
- cmdbox/web/signin.html +10 -6
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/METADATA +132 -35
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/RECORD +161 -123
- cmdbox/app/features/web/cmdbox_web_load_pin.py +0 -43
- cmdbox/app/features/web/cmdbox_web_save_pin.py +0 -42
- cmdbox/licenses/LICENSE.Jinja2.3.1.4(BSD License).txt +0 -28
- cmdbox/licenses/LICENSE.Sphinx.8.1.3(BSD License).txt +0 -31
- cmdbox/licenses/LICENSE.babel.2.16.0(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.certifi.2025.1.31(Mozilla Public License 2.0 (MPL 2.0)).txt +0 -20
- cmdbox/licenses/LICENSE.click.8.1.8(BSD License).txt +0 -28
- cmdbox/licenses/LICENSE.cryptography.44.0.2(Apache Software License; BSD License).txt +0 -3
- cmdbox/licenses/LICENSE.greenlet.3.2.0(MIT AND Python-2.0).txt +0 -30
- cmdbox/licenses/LICENSE.keyring.25.5.0(MIT License).txt +0 -17
- cmdbox/licenses/LICENSE.numpy.2.2.4(BSD License).txt +0 -950
- cmdbox/licenses/LICENSE.pillow.11.0.0(CMU License (MIT-CMU)).txt +0 -1226
- cmdbox/licenses/LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt +0 -1213
- cmdbox/licenses/LICENSE.prettytable.3.12.0(BSD License).txt +0 -30
- cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.psycopg.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +0 -165
- cmdbox/licenses/LICENSE.pydantic.2.11.1(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.pydantic.2.11.3(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.python-dotenv.1.0.1(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.twine.5.1.1(Apache Software License).txt +0 -174
- cmdbox/licenses/LICENSE.typing_extensions.4.13.0(UNKNOWN).txt +0 -279
- cmdbox/licenses/LICENSE.urllib3.2.2.3(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.urllib3.2.3.0(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.uvicorn.0.34.1(BSD License).txt +0 -27
- cmdbox/licenses/LICENSE.watchfiles.1.0.4(MIT License).txt +0 -21
- cmdbox/licenses/LICENSE.websockets.14.1(BSD License).txt +0 -24
- cmdbox/licenses/LICENSE.zope.interface.7.1.1(Zope Public License).txt +0 -44
- /cmdbox/licenses/{LICENSE.typing_extensions.4.12.2(Python Software Foundation License).txt → LICENSE.aiohappyeyeballs.2.6.1(Python Software Foundation License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.certifi.2024.8.30(Mozilla Public License 2.0 (MPL 2.0)).txt → LICENSE.certifi.2025.4.26(Mozilla Public License 2.0 (MPL 2.0)).txt} +0 -0
- /cmdbox/licenses/{LICENSE.charset-normalizer.3.4.1(MIT License).txt → LICENSE.charset-normalizer.3.4.2(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.click.8.1.7(BSD License).txt → LICENSE.click.8.2.0(UNKNOWN).txt} +0 -0
- /cmdbox/licenses/{LICENSE.cryptography.43.0.3(Apache Software License; BSD License).txt → LICENSE.cryptography.44.0.3(Apache Software License; BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.gevent.24.11.1(MIT License).txt → LICENSE.gevent.25.4.2(MIT).txt} +0 -0
- /cmdbox/licenses/{LICENSE.importlib_metadata.8.5.0(Apache Software License).txt → LICENSE.google-api-core.2.24.2(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.greenlet.3.1.1(MIT License).txt → LICENSE.greenlet.3.2.2(MIT AND Python-2.0).txt} +0 -0
- /cmdbox/licenses/{LICENSE.h11.0.14.0(MIT License).txt → LICENSE.h11.0.16.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.nh3.0.2.18(MIT).txt → LICENSE.jiter.0.9.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.more-itertools.10.5.0(MIT License).txt → LICENSE.more-itertools.10.7.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.numpy.2.1.3(BSD License).txt → LICENSE.numpy.2.2.5(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.packaging.24.2(Apache Software License; BSD License).txt → LICENSE.packaging.25.0(Apache Software License; BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.psycopg-binary.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt → LICENSE.psycopg-binary.3.2.7(GNU Lesser General Public License v3 (LGPLv3)).txt} +0 -0
- /cmdbox/licenses/{LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt → LICENSE.psycopg.3.2.7(GNU Lesser General Public License v3 (LGPLv3)).txt} +0 -0
- /cmdbox/licenses/{LICENSE.pydantic.2.10.2(MIT License).txt → LICENSE.pydantic.2.11.4(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.pydantic_core.2.27.1(MIT License).txt → LICENSE.pydantic_core.2.33.2(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.redis.5.2.0(MIT License).txt → LICENSE.redis.6.0.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.snowballstemmer.2.2.0(BSD License).txt → LICENSE.snowballstemmer.3.0.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.uvicorn.0.32.1(BSD License).txt → LICENSE.uvicorn.0.34.2(BSD License).txt} +0 -0
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/LICENSE +0 -0
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/WHEEL +0 -0
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/entry_points.txt +0 -0
- {cmdbox-0.5.3.1.dist-info → cmdbox-0.6.0.dist-info}/top_level.txt +0 -0
cmdbox/app/options.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from cmdbox.app import common, feature, web
|
|
2
2
|
from cmdbox.app.commons import module
|
|
3
|
-
from fastapi import Request
|
|
3
|
+
from fastapi import Request, WebSocket
|
|
4
4
|
from fastapi.routing import APIRoute
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
from pathlib import Path
|
|
@@ -49,6 +49,7 @@ class Options:
|
|
|
49
49
|
self.aliases_loaded_cli = False
|
|
50
50
|
self.aliases_loaded_web = False
|
|
51
51
|
self.audit_loaded = False
|
|
52
|
+
self.agentrule_loaded = False
|
|
52
53
|
self.init_options()
|
|
53
54
|
|
|
54
55
|
def get_mode_keys(self) -> List[str]:
|
|
@@ -345,6 +346,7 @@ class Options:
|
|
|
345
346
|
svcmd = fobj.get_svcmd()
|
|
346
347
|
if svcmd is not None:
|
|
347
348
|
self._options["svcmd"][svcmd] = fobj
|
|
349
|
+
opt['use_agent'] = self.check_agentrule(mode, cmd, logger)
|
|
348
350
|
self.init_debugoption()
|
|
349
351
|
|
|
350
352
|
def is_features_loaded(self, ftype:str) -> bool:
|
|
@@ -396,6 +398,8 @@ class Options:
|
|
|
396
398
|
return
|
|
397
399
|
if type(yml['features'][ftype]) is not list:
|
|
398
400
|
raise Exception(f'features.yml is invalid. (The “features.{ftype} element must be a list. {ftype}={yml["features"][ftype]})')
|
|
401
|
+
# featureモジュール読込みの前にagentruleの読み込み
|
|
402
|
+
self.load_features_agentrule(logger)
|
|
399
403
|
for data in yml['features'][ftype]:
|
|
400
404
|
if type(data) is not dict:
|
|
401
405
|
raise Exception(f'features.yml is invalid. (The “features.{ftype}” element must be a list element must be a dictionary. data={data})')
|
|
@@ -415,6 +419,7 @@ class Options:
|
|
|
415
419
|
func(data['package'], data['prefix'], exclude_modules, appcls, ver, logger, self.is_features_loaded(ftype))
|
|
416
420
|
self.features_loaded[ftype] = True
|
|
417
421
|
|
|
422
|
+
|
|
418
423
|
def load_features_args(self, args_dict:Dict[str, Any]):
|
|
419
424
|
yml = self.features_yml_data
|
|
420
425
|
if yml is None:
|
|
@@ -658,6 +663,54 @@ class Options:
|
|
|
658
663
|
self.audit_search_args['cmd'] = cmd
|
|
659
664
|
self.audit_loaded = True
|
|
660
665
|
|
|
666
|
+
def load_features_agentrule(self, logger:logging.Logger):
|
|
667
|
+
yml = self.features_yml_data
|
|
668
|
+
if yml is None: return
|
|
669
|
+
if self.agentrule_loaded: return
|
|
670
|
+
if 'agentrule' not in yml: return
|
|
671
|
+
if 'policy' not in yml['agentrule']:
|
|
672
|
+
raise Exception('features.yml is invalid. (The agentrule element must have "policy" specified.)')
|
|
673
|
+
if yml['agentrule']['policy'] not in ['allow', 'deny']:
|
|
674
|
+
raise Exception('features.yml is invalid. (The policy element must specify allow or deny.)')
|
|
675
|
+
if 'rules' not in yml['agentrule']:
|
|
676
|
+
raise Exception('features.yml is invalid. (The agentrule element must have "rules" specified.)')
|
|
677
|
+
for rule in yml['agentrule']['rules']:
|
|
678
|
+
if 'mode' not in rule:
|
|
679
|
+
rule['mode'] = None
|
|
680
|
+
if 'cmds' not in rule:
|
|
681
|
+
rule['cmds'] = []
|
|
682
|
+
if rule['mode'] is None and len(rule['cmds']) > 0:
|
|
683
|
+
raise Exception('features.yml is invalid. (When “cmds” is specified, “mode” must be specified.)')
|
|
684
|
+
if 'rule' not in rule:
|
|
685
|
+
raise Exception('features.yml is invalid. (The agentrule.rules element must have "rule" specified.)')
|
|
686
|
+
self.agentrule_loaded = True
|
|
687
|
+
|
|
688
|
+
def check_agentrule(self, mode:str, cmd:str, logger:logging.Logger) -> bool:
|
|
689
|
+
"""
|
|
690
|
+
エージェントが使用してよいコマンドかどうかをチェックします
|
|
691
|
+
|
|
692
|
+
Args:
|
|
693
|
+
mode (str): モード
|
|
694
|
+
cmd (str): コマンド
|
|
695
|
+
|
|
696
|
+
Returns:
|
|
697
|
+
bool: 認可されたかどうか
|
|
698
|
+
"""
|
|
699
|
+
if not self.agentrule_loaded:
|
|
700
|
+
return False
|
|
701
|
+
# コマンドチェック
|
|
702
|
+
jadge = self.features_yml_data['agentrule']['policy']
|
|
703
|
+
for rule in self.features_yml_data['agentrule']['rules']:
|
|
704
|
+
if rule['mode'] is not None:
|
|
705
|
+
if rule['mode'] != mode:
|
|
706
|
+
continue
|
|
707
|
+
if len([c for c in rule['cmds'] if cmd == c]) <= 0:
|
|
708
|
+
continue
|
|
709
|
+
jadge = rule['rule']
|
|
710
|
+
if logger.level == logging.DEBUG:
|
|
711
|
+
logger.debug(f"agent rule: mode={mode}, cmd={cmd}: {jadge}")
|
|
712
|
+
return jadge == 'allow'
|
|
713
|
+
|
|
661
714
|
AT_USER = 'user'
|
|
662
715
|
AT_ADMIN = 'admin'
|
|
663
716
|
AT_SYSTEM = 'system'
|
|
@@ -779,7 +832,7 @@ class Options:
|
|
|
779
832
|
elif isinstance(arg, feature.Feature):
|
|
780
833
|
func_feature = arg
|
|
781
834
|
opt['clmsg_src'] = func_feature.__class__.__name__
|
|
782
|
-
elif isinstance(arg, Request):
|
|
835
|
+
elif isinstance(arg, Request) or isinstance(arg, WebSocket):
|
|
783
836
|
if 'signin' in arg.session and arg.session['signin'] is not None and 'name' in arg.session['signin']:
|
|
784
837
|
opt['clmsg_user'] = arg.session['signin']['name']
|
|
785
838
|
if opt['audit_type'] is None:
|
cmdbox/app/web.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from cmdbox.app import common, options
|
|
2
|
-
from cmdbox.app.auth
|
|
2
|
+
from cmdbox.app.auth import signin, signin_saml
|
|
3
3
|
from cmdbox.app.commons import module
|
|
4
|
-
from fastapi import FastAPI, Request, Response
|
|
5
|
-
from fastapi.responses import RedirectResponse
|
|
4
|
+
from fastapi import FastAPI, Request, Response
|
|
6
5
|
from pathlib import Path
|
|
6
|
+
from starlette.applications import Starlette
|
|
7
7
|
from starlette.middleware.sessions import SessionMiddleware
|
|
8
|
-
from
|
|
8
|
+
from starlette.routing import Mount
|
|
9
|
+
from typing import Any, Dict, List
|
|
9
10
|
from uvicorn.config import Config
|
|
10
11
|
import asyncio
|
|
11
12
|
import copy
|
|
@@ -31,7 +32,7 @@ class Web:
|
|
|
31
32
|
def __init__(self, logger:logging.Logger, data:Path, appcls=None, ver=None,
|
|
32
33
|
redis_host:str = "localhost", redis_port:int = 6379, redis_password:str = None, svname:str = 'server',
|
|
33
34
|
client_only:bool=False, doc_root:Path=None, gui_html:str=None, filer_html:str=None, result_html:str=None, users_html:str=None,
|
|
34
|
-
audit_html:str=None, assets:List[str]=None, signin_html:str=None, signin_file:str=None, gui_mode:bool=False,
|
|
35
|
+
audit_html:str=None, agent_html:str=None, assets:List[str]=None, signin_html:str=None, signin_file:str=None, gui_mode:bool=False,
|
|
35
36
|
web_features_packages:List[str]=None, web_features_prefix:List[str]=None):
|
|
36
37
|
"""
|
|
37
38
|
cmdboxクライアント側のwebapiサービス
|
|
@@ -52,6 +53,7 @@ class Web:
|
|
|
52
53
|
result_html (str, optional): 結果のHTMLファイル. Defaults to None.
|
|
53
54
|
users_html (str, optional): ユーザーのHTMLファイル. Defaults to None.
|
|
54
55
|
audit_html (str, optional): 監査のHTMLファイル. Defaults to None.
|
|
56
|
+
agent_html (str, optional): エージェントのHTMLファイル. Defaults to None.
|
|
55
57
|
assets (List[str], optional): 静的ファイルのリスト. Defaults to None.
|
|
56
58
|
signin_html (str, optional): ログイン画面のHTMLファイル. Defaults to None.
|
|
57
59
|
signin_file (str, optional): ログイン情報のファイル. Defaults to args.signin_file.
|
|
@@ -78,6 +80,7 @@ class Web:
|
|
|
78
80
|
self.result_html = Path(result_html) if result_html is not None else Path(__file__).parent.parent / 'web' / 'result.html'
|
|
79
81
|
self.users_html = Path(users_html) if users_html is not None else Path(__file__).parent.parent / 'web' / 'users.html'
|
|
80
82
|
self.audit_html = Path(audit_html) if audit_html is not None else Path(__file__).parent.parent / 'web' / 'audit.html'
|
|
83
|
+
self.agent_html = Path(agent_html) if agent_html is not None else Path(__file__).parent.parent / 'web' / 'agent.html'
|
|
81
84
|
self.assets = []
|
|
82
85
|
if assets is not None:
|
|
83
86
|
if not isinstance(assets, list):
|
|
@@ -97,6 +100,7 @@ class Web:
|
|
|
97
100
|
self.result_html_data = None
|
|
98
101
|
self.users_html_data = None
|
|
99
102
|
self.audit_html_data = None
|
|
103
|
+
self.agent_html_data = None
|
|
100
104
|
self.assets_data = None
|
|
101
105
|
self.signin_html_data = None
|
|
102
106
|
self.gui_mode = gui_mode
|
|
@@ -106,18 +110,21 @@ class Web:
|
|
|
106
110
|
self.pipes_path = self.data / ".pipes"
|
|
107
111
|
self.users_path = self.data / ".users"
|
|
108
112
|
self.audit_path = self.data / '.audit'
|
|
113
|
+
self.agent_path = self.data / '.agent'
|
|
109
114
|
self.static_root = Path(__file__).parent.parent / 'web'
|
|
110
115
|
common.mkdirs(self.cmds_path)
|
|
111
116
|
common.mkdirs(self.pipes_path)
|
|
112
117
|
common.mkdirs(self.users_path)
|
|
113
118
|
common.mkdirs(self.audit_path)
|
|
119
|
+
common.mkdirs(self.agent_path)
|
|
114
120
|
self.pipe_th = None
|
|
115
121
|
self.img_queue = queue.Queue(1000)
|
|
116
122
|
self.cb_queue = queue.Queue(1000)
|
|
117
123
|
self.options = options.Options.getInstance()
|
|
118
124
|
self.webcap_client = requests.Session()
|
|
119
|
-
signin_file_data = Signin.load_signin_file(self.signin_file)
|
|
120
|
-
self.signin = Signin(self.logger, self.signin_file, signin_file_data, self.appcls, self.ver)
|
|
125
|
+
signin_file_data = signin.Signin.load_signin_file(self.signin_file)
|
|
126
|
+
self.signin = signin.Signin(self.logger, self.signin_file, signin_file_data, self.appcls, self.ver)
|
|
127
|
+
self.signin_saml = signin_saml.SigninSAML(self.logger, self.signin_file, signin_file_data, self.appcls, self.ver)
|
|
121
128
|
|
|
122
129
|
if self.logger.level == logging.DEBUG:
|
|
123
130
|
self.logger.debug(f"web init parameter: data={self.data} -> {self.data.absolute() if self.data is not None else None}")
|
|
@@ -131,6 +138,7 @@ class Web:
|
|
|
131
138
|
self.logger.debug(f"web init parameter: result_html={self.result_html} -> {self.result_html.absolute() if self.result_html is not None else None}")
|
|
132
139
|
self.logger.debug(f"web init parameter: users_html={self.users_html} -> {self.users_html.absolute() if self.users_html is not None else None}")
|
|
133
140
|
self.logger.debug(f"web init parameter: audit_html={self.audit_html} -> {self.audit_html.absolute() if self.audit_html is not None else None}")
|
|
141
|
+
self.logger.debug(f"web init parameter: agent_html={self.agent_html} -> {self.agent_html.absolute() if self.agent_html is not None else None}")
|
|
134
142
|
self.logger.debug(f"web init parameter: assets={self.assets} -> {[a.absolute() for a in self.assets] if self.assets is not None else None}")
|
|
135
143
|
self.logger.debug(f"web init parameter: signin_html={self.signin_html} -> {self.signin_html.absolute() if self.signin_html is not None else None}")
|
|
136
144
|
self.logger.debug(f"web init parameter: signin_file={self.signin_file} -> {self.signin_file.absolute() if self.signin_file is not None else None}")
|
|
@@ -140,6 +148,8 @@ class Web:
|
|
|
140
148
|
self.logger.debug(f"web init parameter: cmds_path={self.cmds_path} -> {self.cmds_path.absolute() if self.cmds_path is not None else None}")
|
|
141
149
|
self.logger.debug(f"web init parameter: pipes_path={self.pipes_path} -> {self.pipes_path.absolute() if self.pipes_path is not None else None}")
|
|
142
150
|
self.logger.debug(f"web init parameter: users_path={self.users_path} -> {self.users_path.absolute() if self.users_path is not None else None}")
|
|
151
|
+
self.logger.debug(f"web init parameter: audit_path={self.audit_path} -> {self.audit_path.absolute() if self.audit_path is not None else None}")
|
|
152
|
+
self.logger.debug(f"web init parameter: agent_path={self.agent_path} -> {self.agent_path.absolute() if self.agent_path is not None else None}")
|
|
143
153
|
|
|
144
154
|
def init_webfeatures(self, app:FastAPI):
|
|
145
155
|
self.filemenu = dict()
|
|
@@ -149,7 +159,10 @@ class Web:
|
|
|
149
159
|
if self.options.is_features_loaded('web'):
|
|
150
160
|
return
|
|
151
161
|
# webfeatureの読込み
|
|
162
|
+
self.wf_dep = []
|
|
152
163
|
def wf_route(pk, prefix, excludes, w, app, appcls, ver, logger):
|
|
164
|
+
if pk in w.wf_dep: return
|
|
165
|
+
w.wf_dep.append(pk)
|
|
153
166
|
for wf in module.load_webfeatures(pk, prefix, excludes, appcls=appcls, ver=ver, logger=logger):
|
|
154
167
|
wf.route(self, app)
|
|
155
168
|
self.filemenu = {**self.filemenu, **wf.filemenu(w)}
|
|
@@ -348,12 +361,12 @@ class Web:
|
|
|
348
361
|
if 'hash' not in user or user['hash'] == '':
|
|
349
362
|
raise ValueError(f"User hash is not found or empty. ({user})")
|
|
350
363
|
hash = user['hash']
|
|
351
|
-
if hash!='oauth2' and ('password' not in user or user['password'] == ''):
|
|
364
|
+
if hash!='oauth2' and hash!='saml' and ('password' not in user or user['password'] == ''):
|
|
352
365
|
raise ValueError(f"User password is not found or empty. ({user})")
|
|
353
366
|
if 'email' not in user:
|
|
354
367
|
raise ValueError(f"User email is not found. ({user})")
|
|
355
|
-
if hash=='oauth2' and (user['email'] is None or user['email']==''):
|
|
356
|
-
raise ValueError(f"Required when `email` is `oauth2`. ({user})")
|
|
368
|
+
if (hash=='oauth2' or hash=='saml') and (user['email'] is None or user['email']==''):
|
|
369
|
+
raise ValueError(f"Required when `email` is `oauth2` or `saml`. ({user})")
|
|
357
370
|
if 'groups' not in user or type(user['groups']) is not list:
|
|
358
371
|
raise ValueError(f"User groups is not found or empty. ({user})")
|
|
359
372
|
for gn in user['groups']:
|
|
@@ -363,13 +376,13 @@ class Web:
|
|
|
363
376
|
raise ValueError(f"User uid is already exists. ({user})")
|
|
364
377
|
if len([u for u in signin_data['users'] if u['name'] == user['name']]) > 0:
|
|
365
378
|
raise ValueError(f"User name is already exists. ({user})")
|
|
366
|
-
if hash not in ['oauth2', 'plain', 'md5', 'sha1', 'sha256']:
|
|
379
|
+
if hash not in ['oauth2', 'saml', 'plain', 'md5', 'sha1', 'sha256']:
|
|
367
380
|
raise ValueError(f"User hash is not supported. ({user})")
|
|
368
381
|
jadge, msg = self.signin.check_password_policy(user['name'], '', user['password'])
|
|
369
382
|
if not jadge:
|
|
370
383
|
raise ValueError(msg)
|
|
371
384
|
if hash != 'plain':
|
|
372
|
-
user['password'] = common.hash_password(user['password'], hash if hash != 'oauth2' else 'sha1')
|
|
385
|
+
user['password'] = common.hash_password(user['password'], hash if hash != 'oauth2' and hash != 'saml' else 'sha1')
|
|
373
386
|
else:
|
|
374
387
|
user['password'] = user['password']
|
|
375
388
|
signin_data['users'].append(user)
|
|
@@ -405,8 +418,8 @@ class Web:
|
|
|
405
418
|
if 'email' not in user:
|
|
406
419
|
raise ValueError(f"User email is not found. ({user})")
|
|
407
420
|
hash = user['hash']
|
|
408
|
-
if hash=='oauth2' and (user['email'] is None or user['email']==''):
|
|
409
|
-
raise ValueError(f"Required when `email` is `oauth2`. ({user})")
|
|
421
|
+
if (hash=='oauth2' or hash=='saml') and (user['email'] is None or user['email']==''):
|
|
422
|
+
raise ValueError(f"Required when `email` is `oauth2` or `saml`. ({user})")
|
|
410
423
|
if 'groups' not in user or type(user['groups']) is not list:
|
|
411
424
|
raise ValueError(f"User groups is not found or empty. ({user})")
|
|
412
425
|
for gn in user['groups']:
|
|
@@ -416,7 +429,7 @@ class Web:
|
|
|
416
429
|
raise ValueError(f"User uid is not found. ({user})")
|
|
417
430
|
if len([u for u in signin_data['users'] if u['name'] == user['name']]) <= 0:
|
|
418
431
|
raise ValueError(f"User name is not found. ({user})")
|
|
419
|
-
if hash not in ['oauth2', 'plain', 'md5', 'sha1', 'sha256']:
|
|
432
|
+
if hash not in ['oauth2', 'saml', 'plain', 'md5', 'sha1', 'sha256']:
|
|
420
433
|
raise ValueError(f"User hash is not supported. ({user})")
|
|
421
434
|
for u in signin_data['users']:
|
|
422
435
|
if u['uid'] == user['uid']:
|
|
@@ -426,7 +439,7 @@ class Web:
|
|
|
426
439
|
if not jadge:
|
|
427
440
|
raise ValueError(msg)
|
|
428
441
|
if hash != 'plain':
|
|
429
|
-
u['password'] = common.hash_password(user['password'], hash if hash != 'oauth2' else 'sha1')
|
|
442
|
+
u['password'] = common.hash_password(user['password'], hash if hash != 'oauth2' and hash != 'saml' else 'sha1')
|
|
430
443
|
else:
|
|
431
444
|
u['password'] = user['password']
|
|
432
445
|
# パスワード更新日時の保存
|
|
@@ -665,7 +678,8 @@ class Web:
|
|
|
665
678
|
def start(self, allow_host:str="0.0.0.0", listen_port:int=8081, ssl_listen_port:int=8443,
|
|
666
679
|
ssl_cert:Path=None, ssl_key:Path=None, ssl_keypass:str=None, ssl_ca_certs:Path=None,
|
|
667
680
|
session_domain:str=None, session_path:str='/', session_secure:bool=False, session_timeout:int=900, outputs_key:List[str]=[],
|
|
668
|
-
guvicorn_workers:int=-1, guvicorn_timeout:int=30
|
|
681
|
+
guvicorn_workers:int=-1, guvicorn_timeout:int=30,
|
|
682
|
+
agent_runner=None, mcp=None, mcp_listen_port=9081, mcp_ssl_listen_port=9443):
|
|
669
683
|
"""
|
|
670
684
|
Webサーバを起動する
|
|
671
685
|
|
|
@@ -684,6 +698,10 @@ class Web:
|
|
|
684
698
|
outputs_key (list, optional): 出力キー. Defaults to [].
|
|
685
699
|
guvicorn_workers (int, optional): Gunicornワーカー数. Defaults to -1.
|
|
686
700
|
guvicorn_timeout (int, optional): Gunicornタイムアウト. Defaults to 30.
|
|
701
|
+
agent_runner (Runner, optional): エージェントランナー. Defaults to None.
|
|
702
|
+
mcp (MCP, optional): MCP. Defaults to None.
|
|
703
|
+
mcp_listen_port (int, optional): MCPリスンポート. Defaults to 9081.
|
|
704
|
+
mcp_ssl_listen_port (int, optional): MCP SSLリスンポート. Defaults to 9443.
|
|
687
705
|
"""
|
|
688
706
|
self.allow_host = allow_host
|
|
689
707
|
self.listen_port = listen_port
|
|
@@ -699,6 +717,10 @@ class Web:
|
|
|
699
717
|
self.session_timeout = session_timeout
|
|
700
718
|
self.guvicorn_workers = guvicorn_workers
|
|
701
719
|
self.guvicorn_timeout = guvicorn_timeout
|
|
720
|
+
self.agent_runner = agent_runner
|
|
721
|
+
self.mcp = mcp
|
|
722
|
+
self.mcp_listen_port = mcp_listen_port
|
|
723
|
+
self.mcp_ssl_listen_port = mcp_ssl_listen_port
|
|
702
724
|
if self.logger.level == logging.DEBUG:
|
|
703
725
|
self.logger.debug(f"web start parameter: allow_host={self.allow_host}")
|
|
704
726
|
self.logger.debug(f"web start parameter: listen_port={self.listen_port}")
|
|
@@ -714,7 +736,81 @@ class Web:
|
|
|
714
736
|
self.logger.debug(f"web start parameter: session_timeout={self.session_timeout}")
|
|
715
737
|
self.logger.debug(f"web start parameter: guvicorn_worker={self.guvicorn_workers}")
|
|
716
738
|
self.logger.debug(f"web start parameter: guvicorn_timeout={self.guvicorn_timeout}")
|
|
739
|
+
self.logger.debug(f"web start parameter: agent_runner={self.agent_runner}")
|
|
740
|
+
self.logger.debug(f"web start parameter: mcp={self.mcp}")
|
|
741
|
+
self.logger.debug(f"web start parameter: mcp_listen_port={self.mcp_listen_port}")
|
|
742
|
+
self.logger.debug(f"web start parameter: mcp_ssl_listen_port={self.mcp_ssl_listen_port}")
|
|
743
|
+
|
|
744
|
+
if self.agent_runner is not None:
|
|
745
|
+
# google.adkが大きいので必要な時にだけ読込む
|
|
746
|
+
from google.adk.sessions import BaseSessionService, Session
|
|
747
|
+
async def create_agent_session(session_service:BaseSessionService, user_id:str, session_id:str=None) -> Session:
|
|
748
|
+
"""
|
|
749
|
+
セッションを作成します
|
|
750
|
+
|
|
751
|
+
Args:
|
|
752
|
+
session_service (BaseSessionService): セッションサービス
|
|
753
|
+
user_id (str): ユーザーID
|
|
754
|
+
session_id (str): セッションID
|
|
755
|
+
|
|
756
|
+
Returns:
|
|
757
|
+
Any: セッション
|
|
758
|
+
"""
|
|
759
|
+
if session_id is None:
|
|
760
|
+
session_id = common.random_string(32)
|
|
761
|
+
session = await session_service.get_session(app_name=self.ver.__appid__, user_id=user_id, session_id=session_id)
|
|
762
|
+
if session is None:
|
|
763
|
+
session = await session_service.create_session(app_name=self.ver.__appid__, user_id=user_id, session_id=session_id)
|
|
764
|
+
return session
|
|
765
|
+
self.create_agent_session = create_agent_session
|
|
766
|
+
async def list_agent_sessions(session_service:BaseSessionService, user_id:str, session_id:str=None) -> List[Session]:
|
|
767
|
+
"""
|
|
768
|
+
セッションをリストします
|
|
769
|
+
|
|
770
|
+
Args:
|
|
771
|
+
session_service (BaseSessionService): セッションサービス
|
|
772
|
+
user_id (str): ユーザーID
|
|
773
|
+
|
|
774
|
+
Returns:
|
|
775
|
+
List[Session]: セッションリスト
|
|
776
|
+
"""
|
|
777
|
+
if session_id is None:
|
|
778
|
+
sessions = await session_service.list_sessions(app_name=self.ver.__appid__, user_id=user_id)
|
|
779
|
+
ret = []
|
|
780
|
+
for s in sessions.sessions:
|
|
781
|
+
ret.append(await session_service.get_session(app_name=self.ver.__appid__, user_id=user_id, session_id=s.id))
|
|
782
|
+
return ret
|
|
783
|
+
else:
|
|
784
|
+
session = await session_service.get_session(app_name=self.ver.__appid__, user_id=user_id, session_id=session_id)
|
|
785
|
+
if session is None:
|
|
786
|
+
return []
|
|
787
|
+
return [session]
|
|
788
|
+
self.list_agent_sessions = list_agent_sessions
|
|
789
|
+
async def delete_agent_session(session_service:BaseSessionService, user_id:str, session_id:str) -> bool:
|
|
790
|
+
"""
|
|
791
|
+
セッションを削除します
|
|
792
|
+
|
|
793
|
+
Args:
|
|
794
|
+
session_service (BaseSessionService): セッションサービス
|
|
795
|
+
user_id (str): ユーザーID
|
|
796
|
+
session_id (str): セッションID
|
|
797
|
+
|
|
798
|
+
Returns:
|
|
799
|
+
bool: 成功した場合はTrue, 失敗した場合はFalse
|
|
800
|
+
"""
|
|
801
|
+
return await session_service.delete_session(app_name=self.ver.__appid__, user_id=user_id, session_id=session_id)
|
|
802
|
+
self.delete_agent_session = delete_agent_session
|
|
717
803
|
|
|
804
|
+
"""
|
|
805
|
+
if self.mcp is not None:
|
|
806
|
+
# MCPをFastAPIにマウント
|
|
807
|
+
mcp_app:Starlette = self.mcp.streamable_http_app()
|
|
808
|
+
app = FastAPI(lifespan=self.mcp.settings.lifespan)
|
|
809
|
+
app.mount("/mcp", mcp_app)
|
|
810
|
+
#app.include_router(mcp_app.router)
|
|
811
|
+
else:
|
|
812
|
+
app = FastAPI()
|
|
813
|
+
"""
|
|
718
814
|
app = FastAPI()
|
|
719
815
|
@app.middleware("http")
|
|
720
816
|
async def set_context_cookie(req:Request, call_next):
|
|
@@ -732,19 +828,32 @@ class Web:
|
|
|
732
828
|
|
|
733
829
|
self.is_running = True
|
|
734
830
|
#uvicorn.run(app, host=self.allow_host, port=self.listen_port, workers=2)
|
|
735
|
-
|
|
831
|
+
http_config = Config(app=app, host=self.allow_host, port=self.listen_port)
|
|
832
|
+
th = ThreadedUvicorn(self.logger, config=http_config,
|
|
736
833
|
guvicorn_config=dict(workers=self.guvicorn_workers, timeout=self.guvicorn_timeout))
|
|
737
834
|
th.start()
|
|
835
|
+
if self.mcp is not None and self.ssl_cert is None and self.ssl_key is None:
|
|
836
|
+
mcp_app:Starlette = self.mcp.streamable_http_app()
|
|
837
|
+
http_config = Config(app=mcp_app, host=self.allow_host, port=self.mcp_listen_port)
|
|
838
|
+
mcp_th = ThreadedUvicorn(self.logger, config=http_config, force_uvicorn=True)
|
|
839
|
+
mcp_th.start()
|
|
738
840
|
browser_port = self.listen_port
|
|
739
841
|
th_ssl = None
|
|
740
842
|
if self.ssl_cert is not None and self.ssl_key is not None:
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
843
|
+
https_config = Config(app=app, host=self.allow_host, port=self.ssl_listen_port,
|
|
844
|
+
ssl_certfile=self.ssl_cert, ssl_keyfile=self.ssl_key,
|
|
845
|
+
ssl_keyfile_password=self.ssl_keypass, ssl_ca_certs=self.ssl_ca_certs)
|
|
846
|
+
th_ssl = ThreadedUvicorn(self.logger, config=https_config,
|
|
745
847
|
guvicorn_config=dict(workers=self.guvicorn_workers, timeout=self.guvicorn_timeout))
|
|
746
848
|
th_ssl.start()
|
|
747
849
|
browser_port = self.ssl_listen_port
|
|
850
|
+
if self.mcp is not None:
|
|
851
|
+
mcp_app:Starlette = self.mcp.streamable_http_app()
|
|
852
|
+
https_config = Config(app=mcp_app, host=self.allow_host, port=self.mcp_ssl_listen_port,
|
|
853
|
+
ssl_certfile=self.ssl_cert, ssl_keyfile=self.ssl_key,
|
|
854
|
+
ssl_keyfile_password=self.ssl_keypass, ssl_ca_certs=self.ssl_ca_certs)
|
|
855
|
+
mcp_th_ssl = ThreadedUvicorn(self.logger, config=https_config, force_uvicorn=True)
|
|
856
|
+
mcp_th_ssl.start()
|
|
748
857
|
try:
|
|
749
858
|
if self.gui_mode:
|
|
750
859
|
webbrowser.open(f'http://localhost:{browser_port}/gui')
|
|
@@ -753,12 +862,20 @@ class Web:
|
|
|
753
862
|
while self.is_running:
|
|
754
863
|
gevent.sleep(1)
|
|
755
864
|
th.stop()
|
|
865
|
+
if self.mcp is not None:
|
|
866
|
+
mcp_th.stop()
|
|
756
867
|
if th_ssl is not None:
|
|
757
868
|
th_ssl.stop()
|
|
869
|
+
if self.mcp is not None:
|
|
870
|
+
mcp_th_ssl.stop()
|
|
758
871
|
except KeyboardInterrupt:
|
|
759
872
|
th.stop()
|
|
873
|
+
if self.mcp is not None:
|
|
874
|
+
mcp_th.stop()
|
|
760
875
|
if th_ssl is not None:
|
|
761
876
|
th_ssl.stop()
|
|
877
|
+
if self.mcp is not None:
|
|
878
|
+
mcp_th_ssl.stop()
|
|
762
879
|
|
|
763
880
|
def stop(self):
|
|
764
881
|
"""
|
|
@@ -779,13 +896,24 @@ class Web:
|
|
|
779
896
|
self.logger.info(f"Exit web.")
|
|
780
897
|
|
|
781
898
|
class ThreadedUvicorn:
|
|
782
|
-
def __init__(self, logger:logging.Logger, config:Config, guvicorn_config:Dict[str, Any]):
|
|
899
|
+
def __init__(self, logger:logging.Logger, config:Config, guvicorn_config:Dict[str, Any]=None, force_uvicorn:bool=False):
|
|
783
900
|
self.logger = logger
|
|
784
901
|
self.guvicorn_config = guvicorn_config
|
|
785
|
-
if platform.system() == "Windows"
|
|
902
|
+
self.force_uvicorn = True if platform.system() == "Windows" else force_uvicorn
|
|
903
|
+
stderr_handler = common.create_log_handler(stderr=True)
|
|
904
|
+
stdout_handler = common.create_log_handler(stderr=False)
|
|
905
|
+
if self.force_uvicorn:
|
|
906
|
+
# loggerの設定
|
|
907
|
+
common.reset_logger("uvicorn")
|
|
908
|
+
common.reset_logger("uvicorn.error")
|
|
909
|
+
common.reset_logger("uvicorn.access")
|
|
786
910
|
self.server = uvicorn.Server(config)
|
|
787
911
|
self.thread = RaiseThread(daemon=True, target=self.server.run)
|
|
788
912
|
else:
|
|
913
|
+
# loggerの設定
|
|
914
|
+
common.reset_logger("gunicorn.error")
|
|
915
|
+
common.reset_logger("gunicorn.access")
|
|
916
|
+
|
|
789
917
|
from gunicorn.app.wsgiapp import WSGIApplication
|
|
790
918
|
class App(WSGIApplication):
|
|
791
919
|
def __init__(self, app, options):
|
|
@@ -822,7 +950,7 @@ class ThreadedUvicorn:
|
|
|
822
950
|
#self.thread = RaiseThread(daemon=True, target=self.server.run)
|
|
823
951
|
|
|
824
952
|
def start(self):
|
|
825
|
-
if
|
|
953
|
+
if self.force_uvicorn:
|
|
826
954
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
827
955
|
self.thread.start()
|
|
828
956
|
asyncio.run(self.wait_for_started())
|
|
@@ -836,7 +964,7 @@ class ThreadedUvicorn:
|
|
|
836
964
|
await asyncio.sleep(0.1)
|
|
837
965
|
|
|
838
966
|
def stop(self):
|
|
839
|
-
if
|
|
967
|
+
if self.force_uvicorn:
|
|
840
968
|
if self.thread.is_alive():
|
|
841
969
|
self.server.should_exit = True
|
|
842
970
|
self.thread.raise_exception()
|
|
@@ -846,7 +974,7 @@ class ThreadedUvicorn:
|
|
|
846
974
|
self.server.started = False
|
|
847
975
|
|
|
848
976
|
def is_alive(self):
|
|
849
|
-
if
|
|
977
|
+
if self.force_uvicorn:
|
|
850
978
|
return self.thread.is_alive()
|
|
851
979
|
else:
|
|
852
980
|
return self.server.started
|
cmdbox/extensions/features.yml
CHANGED
|
@@ -37,6 +37,24 @@ aliases: # Specify the alias for the specified co
|
|
|
37
37
|
# e.g. /{1}_exec
|
|
38
38
|
move: # Specify whether to move the regular expression group of the source to the target.
|
|
39
39
|
# e.g. true
|
|
40
|
+
agentrule: # Specifies a list of rules that determine which commands the agent can execute.
|
|
41
|
+
policy: deny # Specify the default policy for the rule. The value can be allow or deny.
|
|
42
|
+
rules: # Specify the rules for the commands that the agent can execute according to the group to which the user belongs.
|
|
43
|
+
- mode: audit # Specify the "mode" as the condition for applying the rule.
|
|
44
|
+
cmds: [search, write] # Specify the "cmd" to which the rule applies. Multiple items can be specified in a list.
|
|
45
|
+
rule: allow # Specifies whether the specified command is allowed or not. Values are allow or deny.
|
|
46
|
+
- mode: client
|
|
47
|
+
cmds: [file_copy, file_download, file_list, file_mkdir, file_move, file_remove, file_rmdir, file_upload, server_info]
|
|
48
|
+
rule: allow
|
|
49
|
+
- mode: cmd
|
|
50
|
+
cmds: [list, load]
|
|
51
|
+
rule: allow
|
|
52
|
+
- mode: server
|
|
53
|
+
cmds: [list]
|
|
54
|
+
rule: allow
|
|
55
|
+
- mode: web
|
|
56
|
+
cmds: [gencert, genpass, group_list, user_list]
|
|
57
|
+
rule: allow
|
|
40
58
|
audit:
|
|
41
59
|
enabled: true # Specify whether to enable the audit function.
|
|
42
60
|
write:
|
|
File without changes
|
|
File without changes
|
|
@@ -46,3 +46,26 @@ aliases: # Specify the alias for the specified co
|
|
|
46
46
|
# e.g. /{1}_exec
|
|
47
47
|
move: # Specify whether to move the regular expression group of the source to the target.
|
|
48
48
|
# e.g. true
|
|
49
|
+
audit:
|
|
50
|
+
enabled: true # Specify whether to enable the audit function.
|
|
51
|
+
write:
|
|
52
|
+
mode: audit # Specify the mode of the feature to be writed.
|
|
53
|
+
cmd: write # Specify the command to be writed.
|
|
54
|
+
search:
|
|
55
|
+
mode: audit # Specify the mode of the feature to be searched.
|
|
56
|
+
cmd: search # Specify the command to be searched.
|
|
57
|
+
options: # Specify the options for the audit function.
|
|
58
|
+
host: localhost # Specify the service host of the audit Redis server.However, if it is specified as a command line argument, it is ignored.
|
|
59
|
+
port: 6379 # Specify the service port of the audit Redis server.However, if it is specified as a command line argument, it is ignored.
|
|
60
|
+
password: password # Specify the access password of the audit Redis server.However, if it is specified as a command line argument, it is ignored.
|
|
61
|
+
svname: cmdbox # Specify the audit service name of the inference server.However, if it is specified as a command line argument, it is ignored.
|
|
62
|
+
retry_count: 3 # Specifies the number of reconnections to the audit Redis server.If less than 0 is specified, reconnection is forever.
|
|
63
|
+
retry_interval: 1 # Specifies the number of seconds before reconnecting to the audit Redis server.
|
|
64
|
+
timeout: 15 # Specify the maximum waiting time until the server responds.
|
|
65
|
+
pg_enabled: False # Specify True if using the postgresql database server.
|
|
66
|
+
pg_host: localhost # Specify the postgresql host.
|
|
67
|
+
pg_port: 5432 # Specify the postgresql port.
|
|
68
|
+
pg_user: postgres # Specify the postgresql user name.
|
|
69
|
+
pg_password: password # Specify the postgresql password.
|
|
70
|
+
pg_dbname: audit # Specify the postgresql database name.
|
|
71
|
+
retention_period_days: 365 # Specify the number of days to retain audit logs.
|
|
@@ -2,9 +2,9 @@ users: # A list of users, each of which is a map that co
|
|
|
2
2
|
- uid: 1 # An ID that identifies a user. No two users can have the same ID.
|
|
3
3
|
name: admin # A name that identifies the user. No two users can have the same name.
|
|
4
4
|
password: admin # The user's password. The value is hashed with the hash function specified in the next hash field.
|
|
5
|
-
hash: plain # The hash function used to hash the password, which can be plain, md5, sha1, or sha256, or oauth2.
|
|
5
|
+
hash: plain # The hash function used to hash the password, which can be plain, md5, sha1, or sha256, or oauth2, or saml.
|
|
6
6
|
groups: [admin] # A list of groups to which the user belongs, as specified in the groups field.
|
|
7
|
-
email: admin@aaa.bbb.jp # The email address of the user, used when authenticating using the provider specified in the oauth2 field.
|
|
7
|
+
email: admin@aaa.bbb.jp # The email address of the user, used when authenticating using the provider specified in the oauth2 or saml field.
|
|
8
8
|
- uid: 101
|
|
9
9
|
name: user01
|
|
10
10
|
password: b75705d7e35e7014521a46b532236ec3
|
|
@@ -36,7 +36,6 @@ groups: # A list of groups, each of which is a map that c
|
|
|
36
36
|
- gid: 103
|
|
37
37
|
name: editor
|
|
38
38
|
parent: user
|
|
39
|
-
|
|
40
39
|
cmdrule: # A list of command rules, Specify a rule that determines whether or not a command is executable when executed by a user in web mode.
|
|
41
40
|
policy: deny # Specify the default policy for the rule. The value can be allow or deny.
|
|
42
41
|
rules: # Specify rules to allow or deny execution of the command, depending on the group the user belongs to.
|
|
@@ -50,6 +49,10 @@ cmdrule: # A list of command rules, Specify a rule that de
|
|
|
50
49
|
mode: server
|
|
51
50
|
cmds: [list]
|
|
52
51
|
rule: allow
|
|
52
|
+
- groups: [user, guest]
|
|
53
|
+
mode: audit
|
|
54
|
+
cmds: [write]
|
|
55
|
+
rule: allow
|
|
53
56
|
- groups: [user, guest]
|
|
54
57
|
mode: web
|
|
55
58
|
cmds: [genpass]
|
|
@@ -70,6 +73,7 @@ pathrule: # List of RESTAPI rules, rules that determine whe
|
|
|
70
73
|
rule: allow
|
|
71
74
|
- groups: [user]
|
|
72
75
|
paths: [/signin, /assets, /bbforce_cmd, /copyright, /dosignin, /dosignout, /password/change,
|
|
76
|
+
/gui/user_data/load, /gui/user_data/save, /gui/user_data/delete,
|
|
73
77
|
/exec_cmd, /exec_pipe, /filer, /gui, /get_server_opt, /usesignout, /versions_cmdbox, /versions_used]
|
|
74
78
|
rule: allow
|
|
75
79
|
- groups: [readonly]
|
|
@@ -105,7 +109,8 @@ oauth2: # OAuth2 settings.
|
|
|
105
109
|
client_secret: XXXXXXXXXXX # Specify Google's OAuth2 client secret.
|
|
106
110
|
redirect_uri: https://localhost:8443/oauth2/google/callback # Specify Google's OAuth2 redirect URI.
|
|
107
111
|
scope: ['email'] # Specify the scope you want to retrieve with Google's OAuth2. Usually, just reading the email is sufficient.
|
|
108
|
-
signin_module: # Specify the module name that implements the sign-in.
|
|
112
|
+
signin_module: # Specify the module name that implements the sign-in.
|
|
113
|
+
cmdbox.app.auth.google_signin
|
|
109
114
|
note: # Specify a description such as Google's OAuth2 reference site.
|
|
110
115
|
- https://developers.google.com/identity/protocols/oauth2/web-server?hl=ja#httprest
|
|
111
116
|
github: # OAuth2 settings for GitHub.
|
|
@@ -114,7 +119,8 @@ oauth2: # OAuth2 settings.
|
|
|
114
119
|
client_secret: XXXXXXXXXXX # Specify the GitHub OAuth2 client secret.
|
|
115
120
|
redirect_uri: https://localhost:8443/oauth2/github/callback # Specify the OAuth2 redirect URI for GitHub.
|
|
116
121
|
scope: ['user:email'] # Specify the scope you want to get from GitHub's OAuth2. Usually, just reading the email is sufficient.
|
|
117
|
-
signin_module: # Specify the module name that implements the sign-in.
|
|
122
|
+
signin_module: # Specify the module name that implements the sign-in.
|
|
123
|
+
cmdbox.app.auth.github_signin
|
|
118
124
|
note: # Specify a description, such as a reference site for OAuth2 on GitHub.
|
|
119
125
|
- https://docs.github.com/ja/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#scopes
|
|
120
126
|
azure: # OAuth2 settings for Azure AD.
|
|
@@ -124,6 +130,34 @@ oauth2: # OAuth2 settings.
|
|
|
124
130
|
client_secret: XXXXXXXXXXX # Specify the Azure AD OAuth2 client secret.
|
|
125
131
|
redirect_uri: https://localhost:8443/oauth2/azure/callback # Specify the OAuth2 redirect URI for Azure AD.
|
|
126
132
|
scope: ['openid', 'profile', 'email', 'https://graph.microsoft.com/mail.read']
|
|
127
|
-
signin_module: # Specify the module name that implements the sign-in.
|
|
133
|
+
signin_module: # Specify the module name that implements the sign-in.
|
|
134
|
+
cmdbox.app.auth.azure_signin
|
|
128
135
|
note: # Specify a description, such as a reference site for Azure AD's OAuth2.
|
|
129
136
|
- https://learn.microsoft.com/ja-jp/entra/identity-platform/v2-oauth2-auth-code-flow
|
|
137
|
+
saml: # SAML settings.
|
|
138
|
+
providers: # This is a per-provider setting for OAuth2.
|
|
139
|
+
azure: # SAML settings for Azure AD.
|
|
140
|
+
enabled: false # Specify whether to enable SAML authentication for Azure AD.
|
|
141
|
+
signin_module: # Specify the module name that implements the sign-in.
|
|
142
|
+
cmdbox.app.auth.azure_signin_saml # Specify the python3-saml configuration.
|
|
143
|
+
# see) https://github.com/SAML-Toolkits/python3-saml
|
|
144
|
+
sp:
|
|
145
|
+
entityId: https://localhost:8443/
|
|
146
|
+
assertionConsumerService:
|
|
147
|
+
url: https://localhost:8443/saml/azure/callback
|
|
148
|
+
binding: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST
|
|
149
|
+
attributeConsumingService: {}
|
|
150
|
+
singleLogoutService:
|
|
151
|
+
binding: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
|
|
152
|
+
NameIDFormat: urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
|
|
153
|
+
x509cert: ''
|
|
154
|
+
privateKey: ''
|
|
155
|
+
idp:
|
|
156
|
+
entityId: https://sts.windows.net/{tenant-id}/
|
|
157
|
+
singleSignOnService:
|
|
158
|
+
url: https://login.microsoftonline.com/{tenant-id}/saml2
|
|
159
|
+
binding: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
|
|
160
|
+
x509cert: XXXXXXXXXXX
|
|
161
|
+
singleLogoutService: {}
|
|
162
|
+
certFingerprint: ''
|
|
163
|
+
certFingerprintAlgorithm: sha1
|