mrok 0.6.0__py3-none-any.whl → 0.7.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.
- mrok/agent/devtools/inspector/__main__.py +2 -24
- mrok/agent/devtools/inspector/app.py +407 -112
- mrok/agent/devtools/inspector/utils.py +149 -0
- mrok/cli/main.py +17 -1
- mrok/constants.py +21 -0
- mrok/logging.py +0 -22
- mrok/proxy/middleware.py +7 -8
- mrok/proxy/models.py +36 -10
- mrok/proxy/ziticorn.py +8 -17
- mrok/ziti/api.py +1 -1
- mrok/ziti/bootstrap.py +0 -5
- mrok/ziti/identities.py +10 -9
- {mrok-0.6.0.dist-info → mrok-0.7.0.dist-info}/METADATA +8 -2
- {mrok-0.6.0.dist-info → mrok-0.7.0.dist-info}/RECORD +17 -20
- mrok/agent/devtools/__main__.py +0 -34
- mrok/cli/commands/agent/utils.py +0 -5
- mrok/proxy/constants.py +0 -22
- mrok/proxy/utils.py +0 -90
- {mrok-0.6.0.dist-info → mrok-0.7.0.dist-info}/WHEEL +0 -0
- {mrok-0.6.0.dist-info → mrok-0.7.0.dist-info}/entry_points.txt +0 -0
- {mrok-0.6.0.dist-info → mrok-0.7.0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
|
|
5
|
+
from multipart import MultipartParser
|
|
6
|
+
|
|
7
|
+
TEXTUAL_CONTENT_TYPES = {
|
|
8
|
+
"application/json",
|
|
9
|
+
"application/xml",
|
|
10
|
+
"application/javascript",
|
|
11
|
+
"application/x-www-form-urlencoded",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
TEXTUAL_PREFIXES = ("text/",)
|
|
15
|
+
|
|
16
|
+
CONTENT_TYPE_TO_LANGUAGE = {
|
|
17
|
+
"application/json": "json",
|
|
18
|
+
"application/ld+json": "json",
|
|
19
|
+
"application/problem+json": "json",
|
|
20
|
+
"application/schema+json": "json",
|
|
21
|
+
"application/xml": "xml",
|
|
22
|
+
"text/xml": "xml",
|
|
23
|
+
"application/xhtml+xml": "html",
|
|
24
|
+
"text/html": "html",
|
|
25
|
+
"text/css": "css",
|
|
26
|
+
"application/javascript": "javascript",
|
|
27
|
+
"application/x-javascript": "javascript",
|
|
28
|
+
"text/javascript": "javascript",
|
|
29
|
+
"application/ecmascript": "javascript",
|
|
30
|
+
"text/markdown": "markdown",
|
|
31
|
+
"text/x-markdown": "markdown",
|
|
32
|
+
"application/yaml": "yaml",
|
|
33
|
+
"application/x-yaml": "yaml",
|
|
34
|
+
"text/yaml": "yaml",
|
|
35
|
+
"application/toml": "toml",
|
|
36
|
+
"application/x-toml": "toml",
|
|
37
|
+
"application/sql": "sql",
|
|
38
|
+
"text/x-sql": "sql",
|
|
39
|
+
"application/java": "java",
|
|
40
|
+
"text/x-java-source": "java",
|
|
41
|
+
"application/python": "python",
|
|
42
|
+
"text/x-python": "python",
|
|
43
|
+
"application/x-python-code": "python",
|
|
44
|
+
"application/rust": "rust",
|
|
45
|
+
"text/x-rust": "rust",
|
|
46
|
+
"application/go": "go",
|
|
47
|
+
"text/x-go": "go",
|
|
48
|
+
"application/bash": "bash",
|
|
49
|
+
"application/x-sh": "bash",
|
|
50
|
+
"text/x-shellscript": "bash",
|
|
51
|
+
"application/regex": "regex",
|
|
52
|
+
"text/x-regex": "regex",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class ContentTypeInfo:
|
|
58
|
+
content_type: str
|
|
59
|
+
binary: bool
|
|
60
|
+
charset: str | None = None
|
|
61
|
+
boundary: str | None = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def parse_content_type(content_type_header: str) -> ContentTypeInfo:
|
|
65
|
+
parts = content_type_header.split(";")
|
|
66
|
+
content_type = parts[0].strip().lower()
|
|
67
|
+
|
|
68
|
+
charset = None
|
|
69
|
+
boundary = None
|
|
70
|
+
|
|
71
|
+
for part in parts[1:]:
|
|
72
|
+
part = part.strip()
|
|
73
|
+
if "=" in part:
|
|
74
|
+
key, value = part.split("=", 1)
|
|
75
|
+
key = key.strip().lower()
|
|
76
|
+
value = value.strip().strip('"')
|
|
77
|
+
if key == "charset":
|
|
78
|
+
charset = value
|
|
79
|
+
elif key == "boundary":
|
|
80
|
+
boundary = value
|
|
81
|
+
|
|
82
|
+
binary = not is_textual(content_type)
|
|
83
|
+
|
|
84
|
+
if charset is None and not binary:
|
|
85
|
+
charset = "utf-8"
|
|
86
|
+
|
|
87
|
+
return ContentTypeInfo(
|
|
88
|
+
content_type=content_type, binary=binary, charset=charset, boundary=boundary
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def parse_form_data(data: bytes, boundary: str) -> Generator[tuple[str, str]]:
|
|
93
|
+
parser = MultipartParser(BytesIO(data), boundary)
|
|
94
|
+
for part in parser:
|
|
95
|
+
if is_textual(part.content_type):
|
|
96
|
+
yield part.name, part.value
|
|
97
|
+
continue
|
|
98
|
+
yield part.name, "<binary>"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def is_textual(content_type: str) -> bool:
|
|
102
|
+
ct = content_type.lower()
|
|
103
|
+
if ct in TEXTUAL_CONTENT_TYPES:
|
|
104
|
+
return True
|
|
105
|
+
if any(ct.startswith(p) for p in TEXTUAL_PREFIXES):
|
|
106
|
+
return True
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def build_tree(node, data):
|
|
111
|
+
if isinstance(data, dict):
|
|
112
|
+
for key, value in data.items():
|
|
113
|
+
child = node.add(str(key))
|
|
114
|
+
build_tree(child, value)
|
|
115
|
+
elif isinstance(data, list):
|
|
116
|
+
for index, value in enumerate(data):
|
|
117
|
+
child = node.add(f"[{index}]")
|
|
118
|
+
build_tree(child, value)
|
|
119
|
+
else:
|
|
120
|
+
node.add(repr(data))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def hexdump(data, width=16):
|
|
124
|
+
lines = []
|
|
125
|
+
for i in range(0, len(data), width):
|
|
126
|
+
chunk = data[i : i + width]
|
|
127
|
+
hex_part = " ".join(f"{b:02x}" for b in chunk)
|
|
128
|
+
ascii_part = "".join(chr(b) if 32 <= b <= 126 else "." for b in chunk)
|
|
129
|
+
lines.append(f"{hex_part:<{width * 3}} {ascii_part}")
|
|
130
|
+
return "\n".join(lines)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def humanize_bytes(num_bytes: int) -> tuple[float, str]: # type: ignore[return-value]
|
|
134
|
+
if num_bytes < 0:
|
|
135
|
+
raise ValueError("num_bytes must be non-negative")
|
|
136
|
+
|
|
137
|
+
units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"]
|
|
138
|
+
value = float(num_bytes)
|
|
139
|
+
|
|
140
|
+
for unit in units:
|
|
141
|
+
if value < 1024 or unit == units[-1]:
|
|
142
|
+
return round(value, 2), unit
|
|
143
|
+
value /= 1024
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_highlighter_language_by_content_type(content_type: str) -> str | None:
|
|
147
|
+
if content_type in CONTENT_TYPE_TO_LANGUAGE:
|
|
148
|
+
return CONTENT_TYPE_TO_LANGUAGE[content_type]
|
|
149
|
+
return None
|
mrok/cli/main.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import sys
|
|
3
|
+
from typing import Annotated
|
|
3
4
|
|
|
4
5
|
import typer
|
|
5
6
|
from pyfiglet import Figlet
|
|
@@ -79,11 +80,23 @@ for name, module in inspect.getmembers(commands):
|
|
|
79
80
|
elif hasattr(module, "app"): # pragma: no branch
|
|
80
81
|
app.add_typer(module.app, name=name.replace("_", "-"))
|
|
81
82
|
|
|
83
|
+
_debug_mode = False
|
|
84
|
+
|
|
82
85
|
|
|
83
86
|
@app.callback()
|
|
84
87
|
def main(
|
|
85
88
|
ctx: typer.Context,
|
|
89
|
+
debug: Annotated[
|
|
90
|
+
bool,
|
|
91
|
+
typer.Option(
|
|
92
|
+
"--debug",
|
|
93
|
+
help="Run the CLI in debug mode",
|
|
94
|
+
show_default=True,
|
|
95
|
+
),
|
|
96
|
+
] = False,
|
|
86
97
|
):
|
|
98
|
+
global _debug_mode
|
|
99
|
+
_debug_mode = debug
|
|
87
100
|
settings = get_settings()
|
|
88
101
|
setup_logging(settings, cli_mode=True)
|
|
89
102
|
ctx.obj = settings
|
|
@@ -93,5 +106,8 @@ def run():
|
|
|
93
106
|
try:
|
|
94
107
|
app()
|
|
95
108
|
except Exception as e:
|
|
96
|
-
|
|
109
|
+
if _debug_mode:
|
|
110
|
+
raise
|
|
111
|
+
message = str(e) or "Unexpected error. Debug it with --debug"
|
|
112
|
+
err_console.print(f"[bold red]Error:[/bold red] {message}")
|
|
97
113
|
sys.exit(-1)
|
mrok/constants.py
CHANGED
|
@@ -2,3 +2,24 @@ import re
|
|
|
2
2
|
|
|
3
3
|
RE_EXTENSION_ID = re.compile(r"(?i)EXT-\d{4}-\d{4}")
|
|
4
4
|
RE_INSTANCE_ID = re.compile(r"(?i)INS-\d{4}-\d{4}-\d{4}")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
BINARY_CONTENT_TYPES = {
|
|
8
|
+
"application/octet-stream",
|
|
9
|
+
"application/pdf",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
BINARY_PREFIXES = (
|
|
13
|
+
"image/",
|
|
14
|
+
"video/",
|
|
15
|
+
"audio/",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
TEXTUAL_CONTENT_TYPES = {
|
|
19
|
+
"application/json",
|
|
20
|
+
"application/xml",
|
|
21
|
+
"application/javascript",
|
|
22
|
+
"application/x-www-form-urlencoded",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
TEXTUAL_PREFIXES = ("text/",)
|
mrok/logging.py
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import logging.config
|
|
2
2
|
|
|
3
|
-
from rich.console import Console
|
|
4
|
-
from rich.logging import RichHandler
|
|
5
|
-
from textual_serve.server import LogHighlighter
|
|
6
|
-
|
|
7
3
|
from mrok.conf import Settings
|
|
8
4
|
|
|
9
5
|
|
|
@@ -78,21 +74,3 @@ def get_logging_config(settings: Settings, cli_mode: bool = False) -> dict:
|
|
|
78
74
|
def setup_logging(settings: Settings, cli_mode: bool = False) -> None:
|
|
79
75
|
logging_config = get_logging_config(settings, cli_mode)
|
|
80
76
|
logging.config.dictConfig(logging_config)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def setup_inspector_logging(console: Console) -> None:
|
|
84
|
-
logging.basicConfig(
|
|
85
|
-
level="WARNING",
|
|
86
|
-
format="%(message)s",
|
|
87
|
-
datefmt="%Y-%m-%d %H:%M:%S",
|
|
88
|
-
handlers=[
|
|
89
|
-
RichHandler(
|
|
90
|
-
show_path=False,
|
|
91
|
-
show_time=True,
|
|
92
|
-
rich_tracebacks=True,
|
|
93
|
-
tracebacks_show_locals=True,
|
|
94
|
-
highlighter=LogHighlighter(),
|
|
95
|
-
console=console,
|
|
96
|
-
)
|
|
97
|
-
],
|
|
98
|
-
)
|
mrok/proxy/middleware.py
CHANGED
|
@@ -2,10 +2,8 @@ import asyncio
|
|
|
2
2
|
import logging
|
|
3
3
|
import time
|
|
4
4
|
|
|
5
|
-
from mrok.proxy.constants import MAX_REQUEST_BODY_BYTES, MAX_RESPONSE_BODY_BYTES
|
|
6
5
|
from mrok.proxy.metrics import MetricsCollector
|
|
7
6
|
from mrok.proxy.models import FixedSizeByteBuffer, HTTPHeaders, HTTPRequest, HTTPResponse
|
|
8
|
-
from mrok.proxy.utils import must_capture_request, must_capture_response
|
|
9
7
|
from mrok.types.proxy import (
|
|
10
8
|
ASGIApp,
|
|
11
9
|
ASGIReceive,
|
|
@@ -15,6 +13,9 @@ from mrok.types.proxy import (
|
|
|
15
13
|
Scope,
|
|
16
14
|
)
|
|
17
15
|
|
|
16
|
+
MAX_REQUEST_BODY_BYTES = 2 * 1024 * 1024
|
|
17
|
+
MAX_RESPONSE_BODY_BYTES = 5 * 1024 * 1024
|
|
18
|
+
|
|
18
19
|
logger = logging.getLogger("mrok.proxy")
|
|
19
20
|
|
|
20
21
|
|
|
@@ -42,7 +43,7 @@ class CaptureMiddleware:
|
|
|
42
43
|
state = {}
|
|
43
44
|
|
|
44
45
|
req_buf = FixedSizeByteBuffer(MAX_REQUEST_BODY_BYTES)
|
|
45
|
-
capture_req_body =
|
|
46
|
+
capture_req_body = method.upper() not in ("GET", "HEAD", "OPTIONS", "TRACE")
|
|
46
47
|
|
|
47
48
|
request = HTTPRequest(
|
|
48
49
|
method=method,
|
|
@@ -68,9 +69,7 @@ class CaptureMiddleware:
|
|
|
68
69
|
resp_headers = HTTPHeaders.from_asgi(msg.get("headers", []))
|
|
69
70
|
state["resp_headers_raw"] = resp_headers
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if state["capture_resp_body"] and msg["type"] == "http.response.body":
|
|
72
|
+
if msg["type"] == "http.response.body":
|
|
74
73
|
body = msg.get("body", b"")
|
|
75
74
|
resp_buf.write(body)
|
|
76
75
|
|
|
@@ -91,8 +90,8 @@ class CaptureMiddleware:
|
|
|
91
90
|
status=state["status"] or 0,
|
|
92
91
|
headers=state["resp_headers_raw"],
|
|
93
92
|
duration=duration,
|
|
94
|
-
body=resp_buf.getvalue()
|
|
95
|
-
body_truncated=resp_buf.overflow
|
|
93
|
+
body=resp_buf.getvalue(),
|
|
94
|
+
body_truncated=resp_buf.overflow,
|
|
96
95
|
)
|
|
97
96
|
asyncio.create_task(self._on_response_complete(response))
|
|
98
97
|
|
mrok/proxy/models.py
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import base64
|
|
4
|
+
import binascii
|
|
3
5
|
import json
|
|
6
|
+
from collections.abc import Mapping
|
|
4
7
|
from pathlib import Path
|
|
5
|
-
from typing import Literal
|
|
8
|
+
from typing import Annotated, Any, Literal
|
|
6
9
|
|
|
7
10
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
11
|
+
from pydantic.functional_serializers import PlainSerializer
|
|
12
|
+
from pydantic.functional_validators import PlainValidator
|
|
8
13
|
from pydantic_core import core_schema
|
|
9
14
|
|
|
10
15
|
|
|
16
|
+
def serialize_b64(v: bytes) -> str:
|
|
17
|
+
return base64.b64encode(v).decode("ascii")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def deserialize_b64(v):
|
|
21
|
+
if isinstance(v, bytes):
|
|
22
|
+
return v
|
|
23
|
+
if isinstance(v, str):
|
|
24
|
+
try:
|
|
25
|
+
return base64.b64decode(v, validate=True)
|
|
26
|
+
except binascii.Error as e: # pragma: no branch
|
|
27
|
+
raise ValueError("Invalid base64 data") from e
|
|
28
|
+
raise TypeError("Expected bytes or base64 string") # pragma: no cover
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
Base64Bytes = Annotated[
|
|
32
|
+
bytes,
|
|
33
|
+
PlainValidator(deserialize_b64),
|
|
34
|
+
PlainSerializer(serialize_b64, return_type=str),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
11
38
|
class X509Credentials(BaseModel):
|
|
12
39
|
key: str
|
|
13
40
|
cert: str
|
|
@@ -16,14 +43,13 @@ class X509Credentials(BaseModel):
|
|
|
16
43
|
@field_validator("key", "cert", "ca", mode="before")
|
|
17
44
|
@classmethod
|
|
18
45
|
def strip_pem_prefix(cls, value: str) -> str:
|
|
19
|
-
if isinstance(value, str) and value.startswith("pem:"):
|
|
46
|
+
if isinstance(value, str) and value.startswith("pem:"): # pragma: no branch
|
|
20
47
|
return value[4:]
|
|
21
|
-
return value
|
|
48
|
+
return value # pragma: no cover
|
|
22
49
|
|
|
23
50
|
|
|
24
51
|
class ServiceMetadata(BaseModel):
|
|
25
52
|
model_config = ConfigDict(extra="ignore")
|
|
26
|
-
identity: str
|
|
27
53
|
extension: str
|
|
28
54
|
instance: str
|
|
29
55
|
domain: str | None = None
|
|
@@ -34,10 +60,10 @@ class Identity(BaseModel):
|
|
|
34
60
|
model_config = ConfigDict(extra="ignore")
|
|
35
61
|
zt_api: str = Field(validation_alias="ztAPI")
|
|
36
62
|
id: X509Credentials
|
|
63
|
+
mrok: ServiceMetadata
|
|
64
|
+
enable_ha: bool = Field(default=False, validation_alias="enableHa")
|
|
37
65
|
zt_apis: str | None = Field(default=None, validation_alias="ztAPIs")
|
|
38
66
|
config_types: str | None = Field(default=None, validation_alias="configTypes")
|
|
39
|
-
enable_ha: bool = Field(default=False, validation_alias="enableHa")
|
|
40
|
-
mrok: ServiceMetadata | None = None
|
|
41
67
|
|
|
42
68
|
@staticmethod
|
|
43
69
|
def load_from_file(path: str | Path) -> Identity:
|
|
@@ -77,7 +103,7 @@ class FixedSizeByteBuffer:
|
|
|
77
103
|
|
|
78
104
|
|
|
79
105
|
class HTTPHeaders(dict):
|
|
80
|
-
def __init__(self, initial=None):
|
|
106
|
+
def __init__(self, initial: Mapping[Any, Any] | None = None):
|
|
81
107
|
super().__init__()
|
|
82
108
|
if initial:
|
|
83
109
|
for k, v in initial.items():
|
|
@@ -131,9 +157,9 @@ class HTTPRequest(BaseModel):
|
|
|
131
157
|
method: str
|
|
132
158
|
url: str
|
|
133
159
|
headers: HTTPHeaders
|
|
134
|
-
query_string:
|
|
160
|
+
query_string: Base64Bytes | None = None
|
|
135
161
|
start_time: float
|
|
136
|
-
body:
|
|
162
|
+
body: Base64Bytes | None = None
|
|
137
163
|
body_truncated: bool | None = None
|
|
138
164
|
|
|
139
165
|
|
|
@@ -143,7 +169,7 @@ class HTTPResponse(BaseModel):
|
|
|
143
169
|
status: int
|
|
144
170
|
headers: HTTPHeaders
|
|
145
171
|
duration: float
|
|
146
|
-
body:
|
|
172
|
+
body: Base64Bytes | None = None
|
|
147
173
|
body_truncated: bool | None = None
|
|
148
174
|
|
|
149
175
|
|
mrok/proxy/ziticorn.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import logging
|
|
3
2
|
import socket
|
|
4
3
|
from collections.abc import Callable
|
|
@@ -10,6 +9,7 @@ from uvicorn import config, server
|
|
|
10
9
|
from uvicorn.lifespan.on import LifespanOn
|
|
11
10
|
from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol as UvHttpToolsProtocol
|
|
12
11
|
|
|
12
|
+
from mrok.proxy.models import Identity
|
|
13
13
|
from mrok.types.proxy import ASGIApp
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger("mrok.proxy")
|
|
@@ -48,10 +48,8 @@ class BackendConfig(config.Config):
|
|
|
48
48
|
backlog: int = 2048,
|
|
49
49
|
):
|
|
50
50
|
self.identity_file = identity_file
|
|
51
|
+
self.identity = Identity.load_from_file(self.identity_file)
|
|
51
52
|
self.ziti_load_timeout_ms = ziti_load_timeout_ms
|
|
52
|
-
self.service_name, self.identity_name, self.instance_id = self.get_identity_info(
|
|
53
|
-
identity_file
|
|
54
|
-
)
|
|
55
53
|
super().__init__(
|
|
56
54
|
app,
|
|
57
55
|
loop="asyncio",
|
|
@@ -59,26 +57,19 @@ class BackendConfig(config.Config):
|
|
|
59
57
|
backlog=backlog,
|
|
60
58
|
)
|
|
61
59
|
|
|
62
|
-
def get_identity_info(self, identity_file: str | Path):
|
|
63
|
-
with open(identity_file) as f:
|
|
64
|
-
identity_data = json.load(f)
|
|
65
|
-
try:
|
|
66
|
-
identity_name = identity_data["mrok"]["identity"]
|
|
67
|
-
instance_id, service_name = identity_name.split(".", 1)
|
|
68
|
-
return service_name, identity_name, instance_id
|
|
69
|
-
except KeyError:
|
|
70
|
-
raise ValueError("Invalid identity file: identity file is not mrok compatible.")
|
|
71
|
-
|
|
72
60
|
def bind_socket(self) -> socket.socket:
|
|
73
|
-
logger.info(
|
|
61
|
+
logger.info(
|
|
62
|
+
"Connect to Ziti service "
|
|
63
|
+
f"'{self.identity.mrok.extension} ({self.identity.mrok.instance})'"
|
|
64
|
+
)
|
|
74
65
|
|
|
75
66
|
ctx, err = openziti.load(str(self.identity_file), timeout=self.ziti_load_timeout_ms)
|
|
76
67
|
if err != 0:
|
|
77
68
|
raise RuntimeError(f"Failed to load Ziti identity from {self.identity_file}: {err}")
|
|
78
69
|
|
|
79
|
-
sock = ctx.bind(self.
|
|
70
|
+
sock = ctx.bind(self.identity.mrok.extension)
|
|
80
71
|
sock.listen(self.backlog)
|
|
81
|
-
logger.info(f"listening on ziti service {self.
|
|
72
|
+
logger.info(f"listening on ziti service {self.identity.mrok.extension} for connections")
|
|
82
73
|
return sock
|
|
83
74
|
|
|
84
75
|
def configure_logging(self) -> None:
|
mrok/ziti/api.py
CHANGED
mrok/ziti/bootstrap.py
CHANGED
|
@@ -36,11 +36,6 @@ async def bootstrap_identity(
|
|
|
36
36
|
logger.info("Deleted existing identity")
|
|
37
37
|
existing_identity = None
|
|
38
38
|
|
|
39
|
-
if forced and config_type:
|
|
40
|
-
await mgmt_api.delete_config_type(config_type["id"])
|
|
41
|
-
logger.info(f"Deleted existing config type '{config_type_name}' ({config_type['id']})")
|
|
42
|
-
config_type = None
|
|
43
|
-
|
|
44
39
|
if existing_identity:
|
|
45
40
|
frontend_id = existing_identity["id"]
|
|
46
41
|
logger.info(f"Identity '{identity_name}' ({frontend_id}) is already enrolled")
|
mrok/ziti/identities.py
CHANGED
|
@@ -41,7 +41,8 @@ async def register_identity(
|
|
|
41
41
|
raise ServiceNotFoundError(f"A service with name `{service_external_id}` does not exists.")
|
|
42
42
|
|
|
43
43
|
identity_name = identity_external_id.lower()
|
|
44
|
-
|
|
44
|
+
router_policy_name = f"{identity_external_id.lower()}.{service_name}"
|
|
45
|
+
service_policy_name = f"{identity_external_id.lower()}.{service_name}:bind"
|
|
45
46
|
self_service_policy_name = f"self.{service_policy_name}"
|
|
46
47
|
|
|
47
48
|
identity = await mgmt_api.search_identity(identity_name)
|
|
@@ -52,7 +53,7 @@ async def register_identity(
|
|
|
52
53
|
service_policy = await mgmt_api.search_service_policy(self_service_policy_name)
|
|
53
54
|
if service_policy:
|
|
54
55
|
await mgmt_api.delete_service_policy(service_policy["id"])
|
|
55
|
-
router_policy = await mgmt_api.search_router_policy(
|
|
56
|
+
router_policy = await mgmt_api.search_router_policy(router_policy_name)
|
|
56
57
|
if router_policy:
|
|
57
58
|
await mgmt_api.delete_router_policy(router_policy["id"])
|
|
58
59
|
await mgmt_api.delete_identity(identity["id"])
|
|
@@ -66,9 +67,8 @@ async def register_identity(
|
|
|
66
67
|
identity_id,
|
|
67
68
|
identity,
|
|
68
69
|
mrok={
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"instance": identity_external_id,
|
|
70
|
+
"extension": service_name,
|
|
71
|
+
"instance": identity_name,
|
|
72
72
|
"domain": settings.proxy.domain,
|
|
73
73
|
"tags": identity_tags,
|
|
74
74
|
},
|
|
@@ -84,7 +84,7 @@ async def register_identity(
|
|
|
84
84
|
self_service["id"],
|
|
85
85
|
identity_id,
|
|
86
86
|
)
|
|
87
|
-
await mgmt_api.create_router_policy(
|
|
87
|
+
await mgmt_api.create_router_policy(router_policy_name, identity_id)
|
|
88
88
|
|
|
89
89
|
return identity, identity_json
|
|
90
90
|
|
|
@@ -100,8 +100,9 @@ async def unregister_identity(
|
|
|
100
100
|
if not service:
|
|
101
101
|
raise ServiceNotFoundError(f"A service with name `{service_external_id}` does not exists.")
|
|
102
102
|
|
|
103
|
-
identity_name =
|
|
104
|
-
|
|
103
|
+
identity_name = identity_external_id.lower()
|
|
104
|
+
router_policy_name = f"{identity_external_id.lower()}.{service_name}"
|
|
105
|
+
service_policy_name = f"{identity_external_id.lower()}.{service_name}:bind"
|
|
105
106
|
|
|
106
107
|
identity = await mgmt_api.search_identity(identity_name)
|
|
107
108
|
if not identity:
|
|
@@ -120,7 +121,7 @@ async def unregister_identity(
|
|
|
120
121
|
service_policy = await mgmt_api.search_service_policy(service_policy_name)
|
|
121
122
|
if service_policy:
|
|
122
123
|
await mgmt_api.delete_service_policy(service_policy["id"])
|
|
123
|
-
router_policy = await mgmt_api.search_router_policy(
|
|
124
|
+
router_policy = await mgmt_api.search_router_policy(router_policy_name)
|
|
124
125
|
if router_policy:
|
|
125
126
|
await mgmt_api.delete_router_policy(router_policy["id"])
|
|
126
127
|
await mgmt_api.delete_identity(identity["id"])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mrok
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: MPT Extensions OpenZiti Orchestrator
|
|
5
5
|
Author: SoftwareOne AG
|
|
6
6
|
License: Apache License
|
|
@@ -214,6 +214,7 @@ Requires-Dist: fastapi[standard]<0.120.0,>=0.119.0
|
|
|
214
214
|
Requires-Dist: gunicorn<24.0.0,>=23.0.0
|
|
215
215
|
Requires-Dist: hdrhistogram<0.11.0,>=0.10.3
|
|
216
216
|
Requires-Dist: httpcore<2.0.0,>=1.0.9
|
|
217
|
+
Requires-Dist: multipart<2.0.0,>=1.3.0
|
|
217
218
|
Requires-Dist: openziti<2.0.0,>=1.3.1
|
|
218
219
|
Requires-Dist: psutil<8.0.0,>=7.1.3
|
|
219
220
|
Requires-Dist: pydantic<3.0.0,>=2.11.7
|
|
@@ -223,7 +224,7 @@ Requires-Dist: pyyaml<7.0.0,>=6.0.2
|
|
|
223
224
|
Requires-Dist: pyzmq<28.0.0,>=27.1.0
|
|
224
225
|
Requires-Dist: rich<15.0.0,>=14.1.0
|
|
225
226
|
Requires-Dist: textual-serve<2.0.0,>=1.1.3
|
|
226
|
-
Requires-Dist: textual<
|
|
227
|
+
Requires-Dist: textual[syntax]<8.0.0,>=7.2.0
|
|
227
228
|
Requires-Dist: typer<0.20.0,>=0.19.2
|
|
228
229
|
Requires-Dist: uvicorn-worker<0.5.0,>=0.4.0
|
|
229
230
|
Description-Content-Type: text/markdown
|
|
@@ -240,6 +241,7 @@ It uses the [OpenZiti](https://openziti.io) zero-trust network overlay to create
|
|
|
240
241
|
- **Agent** – Runs alongside an extension in two modes:
|
|
241
242
|
- *Sidecar mode*: proxies traffic between the Ziti network and a local TCP or Unix socket.
|
|
242
243
|
- *Embeddable mode*: integrates with ASGI servers (e.g. Uvicorn) to serve a Python application directly.
|
|
244
|
+
- **Frontend** - Proxies internet request to a specific extension through the OpenZiti network.
|
|
243
245
|
- **CLI** – A command-line tool for administrative tasks and for running the agent in either mode.
|
|
244
246
|
|
|
245
247
|
## Key Features
|
|
@@ -247,5 +249,9 @@ It uses the [OpenZiti](https://openziti.io) zero-trust network overlay to create
|
|
|
247
249
|
- Zero-trust networking with automatic balancing across Extension instances.
|
|
248
250
|
- Simple API and CLI for managing services and identities.
|
|
249
251
|
|
|
252
|
+
## Development
|
|
253
|
+
The included docker compose starts a local Ziti Network (controller + router) and mrok (controller and frontend).
|
|
254
|
+
|
|
255
|
+
|
|
250
256
|
## License
|
|
251
257
|
[Apache 2.0](LICENSE)
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
mrok/__init__.py,sha256=D1PUs3KtMCqG4bFLceVNG62L3RN53NS95uSCNXpgvzs,181
|
|
2
2
|
mrok/conf.py,sha256=_5Z-A5LyojQeY8J7W8C0QidsmrPl99r9qKYEoMf4kcI,840
|
|
3
|
-
mrok/constants.py,sha256=
|
|
3
|
+
mrok/constants.py,sha256=QXaMw4LuHijj_TUTCsM5uUjpgT04HBvd0wRbjvn1z9A,449
|
|
4
4
|
mrok/errors.py,sha256=ruNMDFr2_0ezCGXuCG1OswCEv-bHOIzMMd02J_0ABcs,37
|
|
5
|
-
mrok/logging.py,sha256=
|
|
5
|
+
mrok/logging.py,sha256=4F5rviPK1-MWWMZuHfzNNQmGxg-emAPRdKz0PsWDSww,2261
|
|
6
6
|
mrok/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
mrok/agent/ziticorn.py,sha256=eHUYs9QaSp35rBzYHRV-SrYxF5ySyECaQg7U-XbdINE,1025
|
|
8
8
|
mrok/agent/devtools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
mrok/agent/devtools/__main__.py,sha256=R8ezbW7hCik5r45U3w2TgiTubg9SlbVsWA-bapILJXU,781
|
|
10
9
|
mrok/agent/devtools/inspector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
mrok/agent/devtools/inspector/__main__.py,sha256=
|
|
12
|
-
mrok/agent/devtools/inspector/app.py,sha256=
|
|
10
|
+
mrok/agent/devtools/inspector/__main__.py,sha256=MyaIi81D-ubdxuvLV1mCpA8cSVwtq07zc5xuArPU2Dw,73
|
|
11
|
+
mrok/agent/devtools/inspector/app.py,sha256=TxBBIy8lXHj6h8KmQHCeLiIM2QqMzMsCpSWFjH8cGu4,25636
|
|
13
12
|
mrok/agent/devtools/inspector/server.py,sha256=C4uD6_1psSHMjJLUDCMPGvKdQYKaEwYTw27NAbwuuA0,636
|
|
13
|
+
mrok/agent/devtools/inspector/utils.py,sha256=K-_4rTyB54Y0faEIGgutMEHGyP1W7eOhbcuUKxNnsYA,4261
|
|
14
14
|
mrok/agent/sidecar/__init__.py,sha256=DrjJGhqFyxsVODW06KI20Wpr6HsD2lD6qFCKUXc7GIE,59
|
|
15
15
|
mrok/agent/sidecar/app.py,sha256=sPQqjnwETRQk0cj8hAxSVUDCkYqsVZCS6ixEqkcGY5A,2534
|
|
16
16
|
mrok/agent/sidecar/main.py,sha256=jeJzrCbltfXOYsKSCjcw8h5lxh4_bGT87kCC5dV4kYU,2190
|
|
17
17
|
mrok/cli/__init__.py,sha256=mtFEa8IeS1x6Gm4dUYoSnAxyEzNqbUVSmWxtuZUMR84,61
|
|
18
|
-
mrok/cli/main.py,sha256=
|
|
18
|
+
mrok/cli/main.py,sha256=T029FYxK_jDrwiA14oX-Onoqp_X14XHRAA_4bbaOgV8,3123
|
|
19
19
|
mrok/cli/rich.py,sha256=P3Dyu8EArUR9_0j7DPK7LRx85TWdYdZ1SaJzD_S1ZCE,511
|
|
20
20
|
mrok/cli/utils.py,sha256=m_olScdIUGks5IoC6p2F9D6CQIucWZ7LHyrvwm2bkJw,106
|
|
21
21
|
mrok/cli/commands/__init__.py,sha256=-UOGzh38oWX7fPeI2nc5I9z8LylRdQAt868q4G6rNGk,140
|
|
@@ -32,7 +32,6 @@ mrok/cli/commands/admin/unregister/__init__.py,sha256=-GjjCPX1pISbWmJK6GpKO3ijGs
|
|
|
32
32
|
mrok/cli/commands/admin/unregister/extensions.py,sha256=GR3Iwzeksk_R0GkgmCSG7iHRcUrI7ABqDi25Gbes64Y,1016
|
|
33
33
|
mrok/cli/commands/admin/unregister/instances.py,sha256=-28wL8pTXTWHVHtw93y8-dqi-Dlf0OZOnlBCKOyGo80,1138
|
|
34
34
|
mrok/cli/commands/agent/__init__.py,sha256=ZAi7eTkKQtfwwV1c1mv3uvEEsyMMrhCQ_-id_0wksAQ,218
|
|
35
|
-
mrok/cli/commands/agent/utils.py,sha256=m_olScdIUGks5IoC6p2F9D6CQIucWZ7LHyrvwm2bkJw,106
|
|
36
35
|
mrok/cli/commands/agent/dev/__init__.py,sha256=ZfreyRuaLqO0AwPS8Ll1DIpsKacsu7_dTmbxV5QecOM,172
|
|
37
36
|
mrok/cli/commands/agent/dev/console.py,sha256=rrKAGoKXVQQBOC75H0JSuX1sYyvc2QSrV-dfMPK49p4,673
|
|
38
37
|
mrok/cli/commands/agent/dev/web.py,sha256=O9dYk-o1FV2E_sKLOezdEmLsnexwbJNDdsYL5pATZRQ,1028
|
|
@@ -65,30 +64,28 @@ mrok/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
65
64
|
mrok/proxy/app.py,sha256=flnVPoUO3pSF3b0nYFBhKjZ9jp5ljijo2a-5e37gACs,6016
|
|
66
65
|
mrok/proxy/asgi.py,sha256=2uw5bLquyUsiYlNwq8RhJd8OqVvSJDvYjzOVGLLB3Cs,3528
|
|
67
66
|
mrok/proxy/backend.py,sha256=dRmIUJin2DM3PUxrVX0j4t1oB6DOX7N9JV2lIcopE38,1649
|
|
68
|
-
mrok/proxy/constants.py,sha256=ao5gI2HFBWmrdd2Yc6XFK_RGaHk-omxI4AqvfIiGes8,409
|
|
69
67
|
mrok/proxy/event_publisher.py,sha256=TAuwEqIhRYxgazJFgC3DekwUAXlJ2UFjbdx_A9vwA1g,2511
|
|
70
68
|
mrok/proxy/exceptions.py,sha256=61OhdihQNdnBUqAI9mbBkXD1yWg-6Eftk7EhWCU1VF0,642
|
|
71
69
|
mrok/proxy/master.py,sha256=HB2q_nPLim23z0mGDGKs_RshhGVAO8VgOYP4hA__zC4,6891
|
|
72
70
|
mrok/proxy/metrics.py,sha256=Sg2aIiaj9fzkyu190YCsJvNn5P-XLun3BcvuVBsdWbA,3640
|
|
73
|
-
mrok/proxy/middleware.py,sha256=
|
|
74
|
-
mrok/proxy/models.py,sha256=
|
|
71
|
+
mrok/proxy/middleware.py,sha256=So024RvtNgMliClKz4pzau2Wb6tlfZGspmHBzzEeF4U,4208
|
|
72
|
+
mrok/proxy/models.py,sha256=VDou3LGDi9zCpzpOwihP4Og1oRvXl7gb2mgczf6_Z14,6029
|
|
75
73
|
mrok/proxy/stream.py,sha256=7V-bSAF9uNV1yVHKaEhHo95WxafSGWkyHPVABZX0djY,2134
|
|
76
|
-
mrok/proxy/utils.py,sha256=OxX6pJv_Wh_KgWx95YeJ3YeuSgwm2tsd00897P3fxys,2126
|
|
77
74
|
mrok/proxy/worker.py,sha256=uEUC2Hbx0YQiDMFNZfwkHMDnijN96b6iRoIErfI21Tg,1921
|
|
78
|
-
mrok/proxy/ziticorn.py,sha256=
|
|
75
|
+
mrok/proxy/ziticorn.py,sha256=qiHKIB7ZRR9S9f-zXTxkzBWW3qlP9CqPOc0QQr-lgvc,2437
|
|
79
76
|
mrok/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
80
77
|
mrok/types/proxy.py,sha256=40Yds4tUykMpzsoQbMtHG85r8xtm5Q3fQZ17bp7cDiM,818
|
|
81
78
|
mrok/types/ziti.py,sha256=EeQnTbDEJ-Y-KMS6zu1Xjxb58Up2VxUwqzUwy3H28JY,36
|
|
82
79
|
mrok/ziti/__init__.py,sha256=20OWMiexRhOovZOX19zlX87-V78QyWnEnSZfyAftUdE,263
|
|
83
|
-
mrok/ziti/api.py,sha256=
|
|
84
|
-
mrok/ziti/bootstrap.py,sha256=
|
|
80
|
+
mrok/ziti/api.py,sha256=CRblQUyfX_421cppA9D-LRnjh4N9P7aYVK4fG7UzQE4,16102
|
|
81
|
+
mrok/ziti/bootstrap.py,sha256=RSL8nZfI-MZ_z6h0F-rNevQoE6g9oGKLr6HlZF696_c,2499
|
|
85
82
|
mrok/ziti/constants.py,sha256=Urq1X3bCBQZfw8NbnEa1pqmY4oq1wmzkwPfzam3kbTw,339
|
|
86
83
|
mrok/ziti/errors.py,sha256=yYCbVDwktnR0AYduqtynIjo73K3HOhIrwA_vQimvEd4,368
|
|
87
|
-
mrok/ziti/identities.py,sha256=
|
|
84
|
+
mrok/ziti/identities.py,sha256=5-7Iof5a8SfkAYsy-SPirDEJKE68ZSn1Kw1wXYQbK9Q,6880
|
|
88
85
|
mrok/ziti/pki.py,sha256=o2tySqHC8-7bvFuI2Tqxg9vX6H6ZSxWxfP_9x29e19M,1954
|
|
89
86
|
mrok/ziti/services.py,sha256=TukG0vAZxgjbS8OLiyg7u1GwuVeGTco-rb9ne6a4PUA,3213
|
|
90
|
-
mrok-0.
|
|
91
|
-
mrok-0.
|
|
92
|
-
mrok-0.
|
|
93
|
-
mrok-0.
|
|
94
|
-
mrok-0.
|
|
87
|
+
mrok-0.7.0.dist-info/METADATA,sha256=bswj6K1HG4vnUbkCpqQfuH6BoAgcyrMKY3U6XIotlnI,15979
|
|
88
|
+
mrok-0.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
89
|
+
mrok-0.7.0.dist-info/entry_points.txt,sha256=tloXwvU1uJicBJR2h-8HoVclPgwJWDwuREMHN8Zq-nU,38
|
|
90
|
+
mrok-0.7.0.dist-info/licenses/LICENSE.txt,sha256=6PaICaoA3yNsZKLv5G6OKqSfLSoX7MakYqTDgJoTCBs,11346
|
|
91
|
+
mrok-0.7.0.dist-info/RECORD,,
|