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.
@@ -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
- err_console.print(f"[bold red]Error:[/bold red] {e}")
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 = must_capture_request(method, req_headers)
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
- state["capture_resp_body"] = must_capture_response(resp_headers)
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() if state["capture_resp_body"] else None,
95
- body_truncated=resp_buf.overflow if state["capture_resp_body"] else None,
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: bytes
160
+ query_string: Base64Bytes | None = None
135
161
  start_time: float
136
- body: bytes | None = None
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: bytes | None = None
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(f"Connect to Ziti service '{self.service_name} ({self.instance_id})'")
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.service_name)
70
+ sock = ctx.bind(self.identity.mrok.extension)
80
71
  sock.listen(self.backlog)
81
- logger.info(f"listening on ziti service {self.service_name} for connections")
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
@@ -62,7 +62,7 @@ class BaseZitiAPI(ABC):
62
62
  auth=self.auth,
63
63
  verify=self.settings.ziti.ssl_verify,
64
64
  timeout=httpx.Timeout(
65
- connect=0.25,
65
+ connect=self.settings.ziti.connect_timeout,
66
66
  read=self.settings.ziti.read_timeout,
67
67
  write=2.0,
68
68
  pool=5.0,
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
- service_policy_name = f"{identity_name}:bind"
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(identity_name)
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
- "identity": identity_name,
70
- "extension": service_external_id,
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(identity_name, identity_id)
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 = f"{identity_external_id.lower()}.{service_name}"
104
- service_policy_name = f"{identity_name}:bind"
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(identity_name)
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.6.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<7.0.0,>=6.5.0
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=UTGYqs3DgEd_SN-k0JK10ekmHVWQaaARtdh1Y-0JG_s,122
3
+ mrok/constants.py,sha256=QXaMw4LuHijj_TUTCsM5uUjpgT04HBvd0wRbjvn1z9A,449
4
4
  mrok/errors.py,sha256=ruNMDFr2_0ezCGXuCG1OswCEv-bHOIzMMd02J_0ABcs,37
5
- mrok/logging.py,sha256=ZMWn0w4fJ-F_g-L37H_GM14BSXAIF2mFF_ougX5S7mg,2856
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=HeYcRf1bjXPji2LKMPCcTU61afrRH2P1RqnFmHClRTc,524
12
- mrok/agent/devtools/inspector/app.py,sha256=rmfm0GVz_iZ_tqne6E966D2He5QVprO2g7PHFrnjG0U,16484
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=DFcYPwDskXi8SKAgEsuP4GMFzaniIf_6bZaSDWvYKDk,2724
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=St8r2hY5vfn4q5FtuqeI85tVmSZmC6Vkbecpx_iX6SM,4455
74
- mrok/proxy/models.py,sha256=CA520bqexPwYUHDrkg58OXAyzBIKQSU9i-SlymU_vt0,5188
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=YwbyNUK-TL3dANntM6gtlP9Su39QvDCC0dSbuZisXIo,2873
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=ikl3l446Gu-YZuJJFRHFSBpKhh5KJxmk-Jr4YbSrqbk,16072
84
- mrok/ziti/bootstrap.py,sha256=PRZlYDtcGbix-1bQDuJ4wX4dy845VAx9SFPotc4BCeg,2715
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=9BIBQOirvcdAkRFNqZPTOkC8kvDQbq6EgaQMq22NQsQ,6730
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.6.0.dist-info/METADATA,sha256=0z3llP3xsGv5I-CZANA3tA9rhqNQ9sgH2j2sdLX5z7A,15705
91
- mrok-0.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
92
- mrok-0.6.0.dist-info/entry_points.txt,sha256=tloXwvU1uJicBJR2h-8HoVclPgwJWDwuREMHN8Zq-nU,38
93
- mrok-0.6.0.dist-info/licenses/LICENSE.txt,sha256=6PaICaoA3yNsZKLv5G6OKqSfLSoX7MakYqTDgJoTCBs,11346
94
- mrok-0.6.0.dist-info/RECORD,,
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,,