mrok 0.4.1__tar.gz → 0.4.2__tar.gz
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.
- {mrok-0.4.1 → mrok-0.4.2}/PKG-INFO +2 -1
- {mrok-0.4.1 → mrok-0.4.2}/mrok/proxy/app.py +14 -22
- {mrok-0.4.1 → mrok-0.4.2}/mrok/proxy/streams.py +3 -3
- {mrok-0.4.1 → mrok-0.4.2}/mrok/proxy/types.py +2 -3
- mrok-0.4.2/mrok/proxy/ziti.py +117 -0
- {mrok-0.4.1 → mrok-0.4.2}/pyproject.toml +2 -1
- {mrok-0.4.1 → mrok-0.4.2}/uv.lock +11 -0
- mrok-0.4.1/mrok/proxy/ziti.py +0 -173
- {mrok-0.4.1 → mrok-0.4.2}/.github/actions/setup-python-env/action.yml +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/.github/workflows/assets/turing_team_pr_bot.png +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/.github/workflows/notify-pr-closed.yaml +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/.github/workflows/notify-pr-reviewed.yml +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/.github/workflows/pr-build-merge.yaml +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/.github/workflows/release.yml +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/.gitignore +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/.pre-commit-config.yaml +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/.python-version +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/LICENSE.txt +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/README.md +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/dev.Dockerfile +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/docker-compose.yaml +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/entrypoint.sh +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/agent/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/agent/devtools/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/agent/devtools/__main__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/agent/devtools/inspector/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/agent/devtools/inspector/__main__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/agent/devtools/inspector/app.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/agent/devtools/inspector/server.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/agent/sidecar/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/agent/sidecar/app.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/agent/sidecar/main.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/agent/ziticorn.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/admin/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/admin/bootstrap.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/admin/list/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/admin/list/extensions.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/admin/list/instances.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/admin/register/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/admin/register/extensions.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/admin/register/instances.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/admin/unregister/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/admin/unregister/extensions.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/admin/unregister/instances.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/admin/utils.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/agent/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/agent/dev/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/agent/dev/console.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/agent/dev/web.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/agent/run/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/agent/run/asgi.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/agent/run/sidecar.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/agent/utils.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/controller/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/controller/openapi.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/controller/run.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/proxy/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/commands/proxy/run.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/main.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/rich.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/cli/utils.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/conf.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/app.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/auth.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/dependencies/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/dependencies/conf.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/dependencies/ziti.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/openapi/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/openapi/examples.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/openapi/utils.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/pagination.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/routes/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/routes/extensions.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/routes/instances.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/controller/schemas.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/datastructures.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/errors.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/http/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/http/config.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/http/constants.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/http/forwarder.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/http/lifespan.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/http/middlewares.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/http/protocol.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/http/server.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/http/types.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/http/utils.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/logging.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/master.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/metrics.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/proxy/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/proxy/dataclasses.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/proxy/main.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/ziti/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/ziti/api.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/ziti/bootstrap.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/ziti/constants.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/ziti/errors.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/ziti/identities.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/ziti/pki.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/mrok/ziti/services.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/prod.Dockerfile +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/scripts/ziti.sh +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/settings.yaml +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/snapshot_report.html +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/sonar-project.properties +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/agent/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/agent/sidecar/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/agent/sidecar/__snapshots__/test_inspector/test_inspector_app.svg +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/agent/sidecar/__snapshots__/test_inspector/test_inspector_app_empty_card.svg +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/agent/sidecar/__snapshots__/test_inspector/test_inspector_app_filed_store_connection.svg +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/agent/sidecar/__snapshots__/test_inspector/test_inspector_app_open_card.svg +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/agent/sidecar/test_app.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/agent/sidecar/test_main.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/agent/test_ziticorn.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/admin/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/admin/test_bootstrap.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/admin/test_list.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/admin/test_register.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/admin/test_unregister.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/admin/test_utils.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/agent/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/agent/test_run.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/controller/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/controller/test_openapi.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/controller/test_run.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/proxy/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/proxy/test_run.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/cli/test_main.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/conftest.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/controller/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/controller/test_auth.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/controller/test_extensions.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/controller/test_instances.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/controller/test_openapi.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/http/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/http/test_config.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/http/test_forwarder.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/http/test_lifespan.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/http/test_master.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/http/test_protocol.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/http/test_server.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/proxy/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/proxy/test_app.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/proxy/test_ziti.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/proxy/test_ziti_branches.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/types.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/ziti/__init__.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/ziti/test_api.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/ziti/test_bootstrap.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/ziti/test_identities.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/ziti/test_pki.py +0 -0
- {mrok-0.4.1 → mrok-0.4.2}/tests/ziti/test_services.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mrok
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: MPT Extensions OpenZiti Orchestrator
|
|
5
5
|
Author: SoftwareOne AG
|
|
6
6
|
License: Apache License
|
|
@@ -206,6 +206,7 @@ License: Apache License
|
|
|
206
206
|
limitations under the License.
|
|
207
207
|
License-File: LICENSE.txt
|
|
208
208
|
Requires-Python: <4,>=3.12
|
|
209
|
+
Requires-Dist: aiocache<0.13.0,>=0.12.3
|
|
209
210
|
Requires-Dist: asn1crypto<2.0.0,>=1.5.1
|
|
210
211
|
Requires-Dist: cryptography<46.0.0,>=45.0.7
|
|
211
212
|
Requires-Dist: dynaconf<4.0.0,>=3.2.11
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import os
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
|
|
5
4
|
from mrok.conf import get_settings
|
|
@@ -35,37 +34,30 @@ class ProxyApp(ForwardAppBase):
|
|
|
35
34
|
self._conn_manager = ZitiConnectionManager(
|
|
36
35
|
identity_file,
|
|
37
36
|
ttl_seconds=ziti_connection_ttl_seconds,
|
|
38
|
-
|
|
37
|
+
cleanup_interval=ziti_conn_cache_purge_interval_seconds,
|
|
39
38
|
)
|
|
40
39
|
|
|
41
|
-
def get_target_from_header(self,
|
|
42
|
-
header_value = headers.get(name)
|
|
43
|
-
if
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
)
|
|
47
|
-
if ":" in header_value:
|
|
48
|
-
header_value, _ = header_value.split(":", 1)
|
|
49
|
-
if not header_value.endswith(self._proxy_wildcard_domain):
|
|
50
|
-
raise ProxyError(f"Unexpected value for {name} header: `{header_value}`.")
|
|
51
|
-
|
|
52
|
-
return header_value[: -len(self._proxy_wildcard_domain)]
|
|
40
|
+
def get_target_from_header(self, headers: dict[str, str], name: str) -> str | None:
|
|
41
|
+
header_value = headers.get(name, "")
|
|
42
|
+
if self._proxy_wildcard_domain in header_value:
|
|
43
|
+
if ":" in header_value:
|
|
44
|
+
header_value, _ = header_value.split(":", 1)
|
|
45
|
+
return header_value[: -len(self._proxy_wildcard_domain)]
|
|
53
46
|
|
|
54
47
|
def get_target_name(self, headers: dict[str, str]) -> str:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
target = self.get_target_from_header(headers, "x-forwarded-host")
|
|
49
|
+
if not target:
|
|
50
|
+
target = self.get_target_from_header(headers, "host")
|
|
51
|
+
if not target:
|
|
52
|
+
raise ProxyError("Neither Host nor X-Forwarded-Host contain a valid target name")
|
|
53
|
+
return target
|
|
60
54
|
|
|
61
55
|
async def startup(self):
|
|
62
56
|
setup_logging(get_settings())
|
|
63
57
|
await self._conn_manager.start()
|
|
64
|
-
logger.info(f"Proxy app startup completed: {os.getpid()}")
|
|
65
58
|
|
|
66
59
|
async def shutdown(self):
|
|
67
60
|
await self._conn_manager.stop()
|
|
68
|
-
logger.info(f"Proxy app shutdown completed: {os.getpid()}")
|
|
69
61
|
|
|
70
62
|
async def select_backend(
|
|
71
63
|
self,
|
|
@@ -74,4 +66,4 @@ class ProxyApp(ForwardAppBase):
|
|
|
74
66
|
) -> tuple[StreamReader, StreamWriter] | tuple[None, None]:
|
|
75
67
|
target_name = self.get_target_name(headers)
|
|
76
68
|
|
|
77
|
-
return await self._conn_manager.
|
|
69
|
+
return await self._conn_manager.get_or_create(target_name)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
|
|
3
|
-
from mrok.proxy.types import ConnectionCache
|
|
3
|
+
from mrok.proxy.types import ConnectionCache
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class CachedStreamReader:
|
|
7
7
|
def __init__(
|
|
8
8
|
self,
|
|
9
9
|
reader: asyncio.StreamReader,
|
|
10
|
-
key:
|
|
10
|
+
key: str,
|
|
11
11
|
manager: ConnectionCache,
|
|
12
12
|
):
|
|
13
13
|
self._reader = reader
|
|
@@ -77,7 +77,7 @@ class CachedStreamWriter:
|
|
|
77
77
|
def __init__(
|
|
78
78
|
self,
|
|
79
79
|
writer: asyncio.StreamWriter,
|
|
80
|
-
key:
|
|
80
|
+
key: str,
|
|
81
81
|
manager: ConnectionCache,
|
|
82
82
|
):
|
|
83
83
|
self._writer = writer
|
|
@@ -4,9 +4,8 @@ from typing import Protocol
|
|
|
4
4
|
|
|
5
5
|
from mrok.http.types import StreamReader, StreamWriter
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
CachedStream = tuple[StreamReader, StreamWriter]
|
|
7
|
+
StreamPair = tuple[StreamReader, StreamWriter]
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
class ConnectionCache(Protocol):
|
|
12
|
-
async def invalidate(self, key:
|
|
11
|
+
async def invalidate(self, key: str) -> None: ...
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import openziti
|
|
7
|
+
from aiocache import Cache
|
|
8
|
+
|
|
9
|
+
from mrok.proxy.streams import CachedStreamReader, CachedStreamWriter
|
|
10
|
+
from mrok.proxy.types import StreamPair
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("mrok.proxy")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ZitiConnectionManager:
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
identity_file: str | Path,
|
|
19
|
+
ziti_timeout_ms: int = 10000,
|
|
20
|
+
ttl_seconds: float = 60.0,
|
|
21
|
+
cleanup_interval: float = 10.0,
|
|
22
|
+
):
|
|
23
|
+
self.identity_file = identity_file
|
|
24
|
+
self.ziti_timeout_ms = ziti_timeout_ms
|
|
25
|
+
self.ttl_seconds = ttl_seconds
|
|
26
|
+
self.cleanup_interval = cleanup_interval
|
|
27
|
+
|
|
28
|
+
self.cache = Cache(Cache.MEMORY)
|
|
29
|
+
|
|
30
|
+
self._active_pairs: dict[str, StreamPair] = {}
|
|
31
|
+
|
|
32
|
+
self._cleanup_task: asyncio.Task | None = None
|
|
33
|
+
self._ziti_ctx: openziti.context.ZitiContext | None = None
|
|
34
|
+
|
|
35
|
+
async def create_stream_pair(self, key: str) -> StreamPair:
|
|
36
|
+
if not self._ziti_ctx:
|
|
37
|
+
raise Exception("ZitiConnectionManager is not started")
|
|
38
|
+
sock = self._ziti_ctx.connect(key)
|
|
39
|
+
orig_reader, orig_writer = await asyncio.open_connection(sock=sock)
|
|
40
|
+
|
|
41
|
+
reader = CachedStreamReader(orig_reader, key, self)
|
|
42
|
+
writer = CachedStreamWriter(orig_writer, key, self)
|
|
43
|
+
return (reader, writer)
|
|
44
|
+
|
|
45
|
+
async def get_or_create(self, key: str) -> StreamPair:
|
|
46
|
+
pair = await self.cache.get(key)
|
|
47
|
+
|
|
48
|
+
if pair:
|
|
49
|
+
logger.info(f"return cached connection for {key}")
|
|
50
|
+
await self.cache.set(key, pair, ttl=self.ttl_seconds)
|
|
51
|
+
self._active_pairs[key] = pair
|
|
52
|
+
return pair
|
|
53
|
+
|
|
54
|
+
pair = await self.create_stream_pair(key)
|
|
55
|
+
await self.cache.set(key, pair, ttl=self.ttl_seconds)
|
|
56
|
+
self._active_pairs[key] = pair
|
|
57
|
+
logger.info(f"return new connection for {key}")
|
|
58
|
+
return pair
|
|
59
|
+
|
|
60
|
+
async def invalidate(self, key: str) -> None:
|
|
61
|
+
logger.info(f"invalidating connection for {key}")
|
|
62
|
+
pair = await self.cache.get(key)
|
|
63
|
+
if pair:
|
|
64
|
+
await self._close_pair(pair)
|
|
65
|
+
|
|
66
|
+
await self.cache.delete(key)
|
|
67
|
+
self._active_pairs.pop(key, None)
|
|
68
|
+
|
|
69
|
+
async def start(self) -> None:
|
|
70
|
+
if self._cleanup_task is None:
|
|
71
|
+
self._cleanup_task = asyncio.create_task(self._periodic_cleanup())
|
|
72
|
+
if self._ziti_ctx is None:
|
|
73
|
+
ctx, err = openziti.load(str(self.identity_file), timeout=self.ziti_timeout_ms)
|
|
74
|
+
if err != 0:
|
|
75
|
+
raise Exception(f"Cannot create a Ziti context from the identity file: {err}")
|
|
76
|
+
self._ziti_ctx = ctx
|
|
77
|
+
|
|
78
|
+
async def stop(self) -> None:
|
|
79
|
+
if self._cleanup_task:
|
|
80
|
+
self._cleanup_task.cancel()
|
|
81
|
+
with contextlib.suppress(Exception):
|
|
82
|
+
await self._cleanup_task
|
|
83
|
+
|
|
84
|
+
for pair in list(self._active_pairs.values()):
|
|
85
|
+
await self._close_pair(pair)
|
|
86
|
+
|
|
87
|
+
self._active_pairs.clear()
|
|
88
|
+
await self.cache.clear()
|
|
89
|
+
openziti.shutdown()
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
async def _close_pair(pair: StreamPair) -> None:
|
|
93
|
+
reader, writer = pair
|
|
94
|
+
writer.close()
|
|
95
|
+
with contextlib.suppress(Exception):
|
|
96
|
+
await writer.wait_closed()
|
|
97
|
+
|
|
98
|
+
async def _periodic_cleanup(self) -> None:
|
|
99
|
+
try:
|
|
100
|
+
while True:
|
|
101
|
+
await asyncio.sleep(self.cleanup_interval)
|
|
102
|
+
await self._cleanup_once()
|
|
103
|
+
except asyncio.CancelledError:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
async def _cleanup_once(self) -> None:
|
|
107
|
+
# Keys currently stored in aiocache
|
|
108
|
+
keys_in_cache = set(await self.cache.keys())
|
|
109
|
+
# Keys we think are alive
|
|
110
|
+
known_keys = set(self._active_pairs.keys())
|
|
111
|
+
|
|
112
|
+
expired_keys = known_keys - keys_in_cache
|
|
113
|
+
|
|
114
|
+
for key in expired_keys:
|
|
115
|
+
pair = self._active_pairs.pop(key, None)
|
|
116
|
+
if pair:
|
|
117
|
+
await self._close_pair(pair)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mrok"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.2"
|
|
4
4
|
description = "MPT Extensions OpenZiti Orchestrator"
|
|
5
5
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
6
6
|
authors = [
|
|
@@ -9,6 +9,7 @@ authors = [
|
|
|
9
9
|
license = { file = "LICENSE.txt" }
|
|
10
10
|
requires-python = ">=3.12,<4"
|
|
11
11
|
dependencies = [
|
|
12
|
+
"aiocache>=0.12.3,<0.13.0",
|
|
12
13
|
"asn1crypto>=1.5.1,<2.0.0",
|
|
13
14
|
"cryptography>=45.0.7,<46.0.0",
|
|
14
15
|
"dynaconf>=3.2.11,<4.0.0",
|
|
@@ -2,6 +2,15 @@ version = 1
|
|
|
2
2
|
revision = 3
|
|
3
3
|
requires-python = ">=3.12, <4"
|
|
4
4
|
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "aiocache"
|
|
7
|
+
version = "0.12.3"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7a/64/b945b8025a9d1e6e2138845f4022165d3b337f55f50984fbc6a4c0a1e355/aiocache-0.12.3.tar.gz", hash = "sha256:f528b27bf4d436b497a1d0d1a8f59a542c153ab1e37c3621713cb376d44c4713", size = 132196, upload-time = "2024-09-25T13:20:23.823Z" }
|
|
10
|
+
wheels = [
|
|
11
|
+
{ url = "https://files.pythonhosted.org/packages/37/d7/15d67e05b235d1ed8c3ce61688fe4d84130e72af1657acadfaac3479f4cf/aiocache-0.12.3-py2.py3-none-any.whl", hash = "sha256:889086fc24710f431937b87ad3720a289f7fc31c4fd8b68e9f918b9bacd8270d", size = 28199, upload-time = "2024-09-25T13:20:22.688Z" },
|
|
12
|
+
]
|
|
13
|
+
|
|
5
14
|
[[package]]
|
|
6
15
|
name = "aiohappyeyeballs"
|
|
7
16
|
version = "2.6.1"
|
|
@@ -1418,6 +1427,7 @@ name = "mrok"
|
|
|
1418
1427
|
version = "0.0.0.dev0"
|
|
1419
1428
|
source = { editable = "." }
|
|
1420
1429
|
dependencies = [
|
|
1430
|
+
{ name = "aiocache" },
|
|
1421
1431
|
{ name = "asn1crypto" },
|
|
1422
1432
|
{ name = "cryptography" },
|
|
1423
1433
|
{ name = "dynaconf" },
|
|
@@ -1463,6 +1473,7 @@ dev = [
|
|
|
1463
1473
|
|
|
1464
1474
|
[package.metadata]
|
|
1465
1475
|
requires-dist = [
|
|
1476
|
+
{ name = "aiocache", specifier = ">=0.12.3,<0.13.0" },
|
|
1466
1477
|
{ name = "asn1crypto", specifier = ">=1.5.1,<2.0.0" },
|
|
1467
1478
|
{ name = "cryptography", specifier = ">=45.0.7,<46.0.0" },
|
|
1468
1479
|
{ name = "dynaconf", specifier = ">=3.2.11,<4.0.0" },
|
mrok-0.4.1/mrok/proxy/ziti.py
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
"""Ziti-backed connection manager for the proxy.
|
|
2
|
-
|
|
3
|
-
This manager owns creation of connections via an OpenZiti context, wraps
|
|
4
|
-
streams to observe IO errors, evicts idle entries, and serializes creation
|
|
5
|
-
per-key.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
import logging
|
|
10
|
-
import time
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
# typing imports intentionally minimized
|
|
14
|
-
import openziti
|
|
15
|
-
|
|
16
|
-
from mrok.http.types import StreamReader, StreamWriter
|
|
17
|
-
from mrok.proxy.dataclasses import CachedStreamEntry
|
|
18
|
-
from mrok.proxy.streams import CachedStreamReader, CachedStreamWriter
|
|
19
|
-
from mrok.proxy.types import CachedStream, ConnectionKey
|
|
20
|
-
|
|
21
|
-
logger = logging.getLogger("mrok.proxy")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class ZitiConnectionManager:
|
|
25
|
-
def __init__(
|
|
26
|
-
self,
|
|
27
|
-
identity_file: str | Path,
|
|
28
|
-
ziti_timeout_ms: int = 10000,
|
|
29
|
-
ttl_seconds: float = 60.0,
|
|
30
|
-
purge_interval: float = 10.0,
|
|
31
|
-
):
|
|
32
|
-
self._identity_file = identity_file
|
|
33
|
-
self._ziti_ctx = None
|
|
34
|
-
self._ziti_timeout_ms = ziti_timeout_ms
|
|
35
|
-
self._ttl = float(ttl_seconds)
|
|
36
|
-
self._purge_interval = float(purge_interval)
|
|
37
|
-
self._cache: dict[ConnectionKey, CachedStreamEntry] = {}
|
|
38
|
-
self._lock = asyncio.Lock()
|
|
39
|
-
self._in_progress: dict[ConnectionKey, asyncio.Lock] = {}
|
|
40
|
-
self._purge_task: asyncio.Task | None = None
|
|
41
|
-
|
|
42
|
-
async def get(self, target: str) -> tuple[StreamReader, StreamWriter] | tuple[None, None]:
|
|
43
|
-
head, _, tail = target.partition(".")
|
|
44
|
-
terminator = target if head and tail else ""
|
|
45
|
-
service = tail if tail else head
|
|
46
|
-
r, w = await self._get_or_create_key((service, terminator))
|
|
47
|
-
return r, w
|
|
48
|
-
|
|
49
|
-
async def invalidate(self, key: ConnectionKey) -> None:
|
|
50
|
-
async with self._lock:
|
|
51
|
-
item = self._cache.pop(key, None)
|
|
52
|
-
if item is None:
|
|
53
|
-
return
|
|
54
|
-
await self._close_writer(item.writer)
|
|
55
|
-
|
|
56
|
-
async def start(self) -> None:
|
|
57
|
-
if self._ziti_ctx is None:
|
|
58
|
-
ctx, err = openziti.load(str(self._identity_file), timeout=self._ziti_timeout_ms)
|
|
59
|
-
if err != 0:
|
|
60
|
-
raise Exception(f"Cannot create a Ziti context from the identity file: {err}")
|
|
61
|
-
self._ziti_ctx = ctx
|
|
62
|
-
if self._purge_task is None:
|
|
63
|
-
self._purge_task = asyncio.create_task(self._purge_loop())
|
|
64
|
-
logger.info("Ziti connection manager started")
|
|
65
|
-
|
|
66
|
-
async def stop(self) -> None:
|
|
67
|
-
if self._purge_task is not None:
|
|
68
|
-
self._purge_task.cancel()
|
|
69
|
-
try:
|
|
70
|
-
await self._purge_task
|
|
71
|
-
except asyncio.CancelledError:
|
|
72
|
-
logger.debug("Purge task was cancelled")
|
|
73
|
-
except Exception as e:
|
|
74
|
-
logger.warning(f"An error occurred stopping the purge task: {e}")
|
|
75
|
-
self._purge_task = None
|
|
76
|
-
logger.info("Ziti connection manager stopped")
|
|
77
|
-
|
|
78
|
-
async with self._lock:
|
|
79
|
-
items = list(self._cache.items())
|
|
80
|
-
self._cache.clear()
|
|
81
|
-
|
|
82
|
-
for _, item in items:
|
|
83
|
-
await self._close_writer(item.writer)
|
|
84
|
-
|
|
85
|
-
async def _purge_loop(self) -> None:
|
|
86
|
-
try:
|
|
87
|
-
while True:
|
|
88
|
-
await asyncio.sleep(self._purge_interval)
|
|
89
|
-
await self._purge_once()
|
|
90
|
-
except asyncio.CancelledError:
|
|
91
|
-
return
|
|
92
|
-
|
|
93
|
-
async def _purge_once(self) -> None:
|
|
94
|
-
to_close: list[tuple[StreamReader, StreamWriter]] = []
|
|
95
|
-
async with self._lock:
|
|
96
|
-
now = time.time()
|
|
97
|
-
for key, item in list(self._cache.items()):
|
|
98
|
-
if now - item.last_access > self._ttl:
|
|
99
|
-
to_close.append((item.reader, item.writer))
|
|
100
|
-
del self._cache[key]
|
|
101
|
-
|
|
102
|
-
for _, writer in to_close:
|
|
103
|
-
writer.close()
|
|
104
|
-
await self._close_writer(writer)
|
|
105
|
-
|
|
106
|
-
def _is_writer_closed(self, writer: StreamWriter) -> bool:
|
|
107
|
-
return writer.transport.is_closing()
|
|
108
|
-
|
|
109
|
-
async def _close_writer(self, writer: StreamWriter) -> None:
|
|
110
|
-
writer.close()
|
|
111
|
-
try:
|
|
112
|
-
await writer.wait_closed()
|
|
113
|
-
except Exception as e:
|
|
114
|
-
logger.debug(f"Error closing writer: {e}")
|
|
115
|
-
|
|
116
|
-
async def _get_or_create_key(self, key: ConnectionKey) -> CachedStream:
|
|
117
|
-
"""Internal: create or return a cached wrapped pair for the concrete key."""
|
|
118
|
-
await self._purge_once()
|
|
119
|
-
to_close = None
|
|
120
|
-
async with self._lock:
|
|
121
|
-
if key in self._cache:
|
|
122
|
-
now = time.time()
|
|
123
|
-
item = self._cache[key]
|
|
124
|
-
reader, writer = item.reader, item.writer
|
|
125
|
-
if not self._is_writer_closed(writer) and not reader.at_eof():
|
|
126
|
-
self._cache[key] = CachedStreamEntry(reader, writer, now)
|
|
127
|
-
return reader, writer
|
|
128
|
-
to_close = writer
|
|
129
|
-
del self._cache[key]
|
|
130
|
-
|
|
131
|
-
lock = self._in_progress.get(key)
|
|
132
|
-
if lock is None:
|
|
133
|
-
lock = asyncio.Lock()
|
|
134
|
-
self._in_progress[key] = lock
|
|
135
|
-
|
|
136
|
-
if to_close:
|
|
137
|
-
await self._close_writer(to_close)
|
|
138
|
-
|
|
139
|
-
async with lock:
|
|
140
|
-
try:
|
|
141
|
-
# # double-check cache after acquiring the per-key lock
|
|
142
|
-
# async with self._lock:
|
|
143
|
-
# now = time.time()
|
|
144
|
-
# if key in self._cache:
|
|
145
|
-
# r, w, _ = self._cache[key]
|
|
146
|
-
# if not self._is_writer_closed(w) and not r.at_eof():
|
|
147
|
-
# self._cache[key] = (r, w, now)
|
|
148
|
-
# return r, w
|
|
149
|
-
|
|
150
|
-
# perform creation via ziti context
|
|
151
|
-
extension, instance = key
|
|
152
|
-
logger.info(f"Create connection to {extension}: {instance}")
|
|
153
|
-
# loop = asyncio.get_running_loop()
|
|
154
|
-
# sock = await loop.run_in_executor(None, self._ziti_ctx.connect,
|
|
155
|
-
# extension, instance)
|
|
156
|
-
if instance:
|
|
157
|
-
sock = self._ziti_ctx.connect(
|
|
158
|
-
extension, terminator=instance
|
|
159
|
-
) # , terminator=instance)
|
|
160
|
-
else:
|
|
161
|
-
sock = self._ziti_ctx.connect(extension)
|
|
162
|
-
orig_reader, orig_writer = await asyncio.open_connection(sock=sock)
|
|
163
|
-
|
|
164
|
-
reader = CachedStreamReader(orig_reader, key, self)
|
|
165
|
-
writer = CachedStreamWriter(orig_writer, key, self)
|
|
166
|
-
|
|
167
|
-
async with self._lock:
|
|
168
|
-
self._cache[key] = CachedStreamEntry(reader, writer, time.time())
|
|
169
|
-
|
|
170
|
-
return reader, writer
|
|
171
|
-
finally:
|
|
172
|
-
async with self._lock:
|
|
173
|
-
self._in_progress.pop(key, None)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mrok-0.4.1 → mrok-0.4.2}/tests/agent/sidecar/__snapshots__/test_inspector/test_inspector_app.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|