mrok 0.4.6__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.
- mrok/agent/devtools/inspector/app.py +2 -2
- mrok/agent/sidecar/app.py +61 -35
- mrok/agent/sidecar/main.py +35 -9
- mrok/agent/ziticorn.py +9 -3
- mrok/cli/commands/__init__.py +2 -2
- mrok/cli/commands/admin/bootstrap.py +3 -2
- mrok/cli/commands/admin/utils.py +2 -2
- mrok/cli/commands/agent/run/sidecar.py +59 -1
- mrok/cli/commands/{proxy → frontend}/__init__.py +1 -1
- mrok/cli/commands/frontend/run.py +91 -0
- mrok/constants.py +0 -2
- mrok/controller/openapi/examples.py +13 -0
- mrok/controller/schemas.py +2 -2
- mrok/frontend/__init__.py +3 -0
- mrok/frontend/app.py +75 -0
- mrok/{proxy → frontend}/main.py +12 -10
- mrok/proxy/__init__.py +0 -3
- mrok/proxy/app.py +158 -83
- mrok/proxy/asgi.py +96 -0
- mrok/proxy/backend.py +45 -0
- mrok/proxy/event_publisher.py +66 -0
- mrok/proxy/exceptions.py +22 -0
- mrok/{master.py → proxy/master.py} +36 -81
- mrok/{metrics.py → proxy/metrics.py} +38 -50
- mrok/{http/middlewares.py → proxy/middleware.py} +17 -26
- mrok/{datastructures.py → proxy/models.py} +43 -10
- mrok/proxy/stream.py +68 -0
- mrok/{http → proxy}/utils.py +1 -1
- mrok/proxy/worker.py +64 -0
- mrok/{http/config.py → proxy/ziticorn.py} +29 -6
- mrok/types/proxy.py +20 -0
- mrok/types/ziti.py +1 -0
- mrok/ziti/api.py +15 -18
- mrok/ziti/bootstrap.py +3 -2
- mrok/ziti/identities.py +5 -4
- mrok/ziti/services.py +3 -2
- {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/METADATA +2 -5
- {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/RECORD +43 -39
- mrok/cli/commands/proxy/run.py +0 -49
- mrok/http/forwarder.py +0 -354
- mrok/http/lifespan.py +0 -39
- mrok/http/pool.py +0 -239
- mrok/http/protocol.py +0 -11
- mrok/http/server.py +0 -14
- mrok/http/types.py +0 -18
- /mrok/{http → proxy}/constants.py +0 -0
- /mrok/{http → types}/__init__.py +0 -0
- {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/WHEEL +0 -0
- {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/entry_points.txt +0 -0
- {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -27,7 +27,7 @@ from textual.widgets.data_table import ColumnKey
|
|
|
27
27
|
from textual.worker import get_current_worker
|
|
28
28
|
|
|
29
29
|
from mrok import __version__
|
|
30
|
-
from mrok.
|
|
30
|
+
from mrok.proxy.models import Event, HTTPHeaders, HTTPResponse, ServiceMetadata, WorkerMetrics
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def build_tree(node, data):
|
|
@@ -185,7 +185,7 @@ class InfoPanel(Static):
|
|
|
185
185
|
# mem=int(mean([m.process.mem for m in self.workers_metrics.values()])),
|
|
186
186
|
# )
|
|
187
187
|
|
|
188
|
-
def update_meta(self, meta:
|
|
188
|
+
def update_meta(self, meta: ServiceMetadata) -> None:
|
|
189
189
|
table = self.query_one(DataTable)
|
|
190
190
|
if len(table.rows) == 0:
|
|
191
191
|
table.add_row("URL", f"https://{meta.extension}.{meta.domain}")
|
mrok/agent/sidecar/app.py
CHANGED
|
@@ -1,51 +1,77 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import logging
|
|
3
|
-
from collections.abc import AsyncGenerator
|
|
4
|
-
from contextlib import asynccontextmanager
|
|
5
2
|
from pathlib import Path
|
|
3
|
+
from typing import Literal
|
|
6
4
|
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
from mrok.
|
|
5
|
+
from httpcore import AsyncConnectionPool
|
|
6
|
+
|
|
7
|
+
from mrok.proxy.app import ProxyAppBase
|
|
8
|
+
from mrok.types.proxy import Scope
|
|
10
9
|
|
|
11
10
|
logger = logging.getLogger("mrok.agent")
|
|
12
11
|
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
TargetType = Literal["tcp", "unix"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SidecarProxyApp(ProxyAppBase):
|
|
15
17
|
def __init__(
|
|
16
18
|
self,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
target: str | Path | tuple[str, int],
|
|
20
|
+
*,
|
|
21
|
+
max_connections: int | None = 10,
|
|
22
|
+
max_keepalive_connections: int | None = None,
|
|
23
|
+
keepalive_expiry: float | None = None,
|
|
24
|
+
retries: int = 0,
|
|
25
|
+
):
|
|
26
|
+
self._target = target
|
|
27
|
+
self._target_type, self._target_address = self._parse_target()
|
|
20
28
|
super().__init__(
|
|
21
|
-
|
|
29
|
+
max_connections=max_connections,
|
|
30
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
31
|
+
keepalive_expiry=keepalive_expiry,
|
|
32
|
+
retries=retries,
|
|
22
33
|
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
|
|
35
|
+
def setup_connection_pool(
|
|
36
|
+
self,
|
|
37
|
+
max_connections: int | None,
|
|
38
|
+
max_keepalive_connections: int | None,
|
|
39
|
+
keepalive_expiry: float | None,
|
|
40
|
+
retries: int,
|
|
41
|
+
) -> AsyncConnectionPool:
|
|
42
|
+
if self._target_type == "unix":
|
|
43
|
+
return AsyncConnectionPool(
|
|
44
|
+
max_connections=max_connections,
|
|
45
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
46
|
+
keepalive_expiry=keepalive_expiry,
|
|
47
|
+
retries=retries,
|
|
48
|
+
uds=self._target_address,
|
|
49
|
+
)
|
|
50
|
+
return AsyncConnectionPool(
|
|
51
|
+
max_connections=max_connections,
|
|
52
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
53
|
+
keepalive_expiry=keepalive_expiry,
|
|
54
|
+
retries=retries,
|
|
31
55
|
)
|
|
32
56
|
|
|
33
|
-
|
|
34
|
-
if
|
|
35
|
-
return
|
|
36
|
-
return
|
|
57
|
+
def get_upstream_base_url(self, scope: Scope) -> str:
|
|
58
|
+
if self._target_type == "unix":
|
|
59
|
+
return "http://localhost"
|
|
60
|
+
return f"http://{self._target_address}"
|
|
37
61
|
|
|
38
|
-
|
|
39
|
-
|
|
62
|
+
def _parse_target(self) -> tuple[TargetType, str]:
|
|
63
|
+
if isinstance(self._target, Path) or (
|
|
64
|
+
isinstance(self._target, str) and ":" not in self._target
|
|
65
|
+
):
|
|
66
|
+
return "unix", str(self._target)
|
|
40
67
|
|
|
41
|
-
|
|
42
|
-
|
|
68
|
+
if isinstance(self._target, str) and ":" in self._target:
|
|
69
|
+
host, port = str(self._target).split(":", 1)
|
|
70
|
+
host = host or "127.0.0.1"
|
|
71
|
+
elif isinstance(self._target, tuple) and len(self._target) == 2:
|
|
72
|
+
host = self._target[0]
|
|
73
|
+
port = str(self._target[1])
|
|
74
|
+
else:
|
|
75
|
+
raise Exception(f"Invalid target address: {self._target}")
|
|
43
76
|
|
|
44
|
-
|
|
45
|
-
async def select_backend(
|
|
46
|
-
self,
|
|
47
|
-
scope: Scope,
|
|
48
|
-
headers: dict[str, str],
|
|
49
|
-
) -> AsyncGenerator[StreamPair, None]:
|
|
50
|
-
async with self._pool.acquire() as (reader, writer):
|
|
51
|
-
yield reader, writer
|
|
77
|
+
return "tcp", f"{host}:{port}"
|
mrok/agent/sidecar/main.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
from mrok.agent.sidecar.app import
|
|
5
|
-
from mrok.master import MasterBase
|
|
4
|
+
from mrok.agent.sidecar.app import SidecarProxyApp
|
|
5
|
+
from mrok.proxy.master import MasterBase
|
|
6
6
|
|
|
7
7
|
logger = logging.getLogger("mrok.proxy")
|
|
8
8
|
|
|
@@ -11,28 +11,49 @@ class SidecarAgent(MasterBase):
|
|
|
11
11
|
def __init__(
|
|
12
12
|
self,
|
|
13
13
|
identity_file: str,
|
|
14
|
-
|
|
14
|
+
target: str | Path | tuple[str, int],
|
|
15
15
|
workers: int = 4,
|
|
16
|
+
events_enabled: bool = True,
|
|
17
|
+
max_connections: int | None = 10,
|
|
18
|
+
max_keepalive_connections: int | None = None,
|
|
19
|
+
keepalive_expiry: float | None = None,
|
|
20
|
+
retries: int = 0,
|
|
16
21
|
publishers_port: int = 50000,
|
|
17
22
|
subscribers_port: int = 50001,
|
|
18
23
|
):
|
|
19
24
|
super().__init__(
|
|
20
25
|
identity_file,
|
|
21
|
-
workers,
|
|
22
|
-
False,
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
workers=workers,
|
|
27
|
+
reload=False,
|
|
28
|
+
events_enabled=events_enabled,
|
|
29
|
+
events_pub_port=publishers_port,
|
|
30
|
+
events_sub_port=subscribers_port,
|
|
25
31
|
)
|
|
26
|
-
self.
|
|
32
|
+
self._target = target
|
|
33
|
+
self._max_connections = max_connections
|
|
34
|
+
self._max_keepalive_connections = max_keepalive_connections
|
|
35
|
+
self._keepalive_expiry = keepalive_expiry
|
|
36
|
+
self._retries = retries
|
|
27
37
|
|
|
28
38
|
def get_asgi_app(self):
|
|
29
|
-
return
|
|
39
|
+
return SidecarProxyApp(
|
|
40
|
+
self._target,
|
|
41
|
+
max_connections=self._max_connections,
|
|
42
|
+
max_keepalive_connections=self._max_keepalive_connections,
|
|
43
|
+
keepalive_expiry=self._keepalive_expiry,
|
|
44
|
+
retries=self._retries,
|
|
45
|
+
)
|
|
30
46
|
|
|
31
47
|
|
|
32
48
|
def run(
|
|
33
49
|
identity_file: str,
|
|
34
50
|
target_addr: str | Path | tuple[str, int],
|
|
35
51
|
workers: int = 4,
|
|
52
|
+
events_enabled: bool = True,
|
|
53
|
+
max_connections: int | None = 10,
|
|
54
|
+
max_keepalive_connections: int | None = None,
|
|
55
|
+
keepalive_expiry: float | None = None,
|
|
56
|
+
retries: int = 0,
|
|
36
57
|
publishers_port: int = 50000,
|
|
37
58
|
subscribers_port: int = 50001,
|
|
38
59
|
):
|
|
@@ -40,6 +61,11 @@ def run(
|
|
|
40
61
|
identity_file,
|
|
41
62
|
target_addr,
|
|
42
63
|
workers=workers,
|
|
64
|
+
events_enabled=events_enabled,
|
|
65
|
+
max_connections=max_connections,
|
|
66
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
67
|
+
keepalive_expiry=keepalive_expiry,
|
|
68
|
+
retries=retries,
|
|
43
69
|
publishers_port=publishers_port,
|
|
44
70
|
subscribers_port=subscribers_port,
|
|
45
71
|
)
|
mrok/agent/ziticorn.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from mrok.
|
|
2
|
-
from mrok.
|
|
1
|
+
from mrok.proxy.master import MasterBase
|
|
2
|
+
from mrok.types.proxy import ASGIApp
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class ZiticornAgent(MasterBase):
|
|
@@ -12,7 +12,13 @@ class ZiticornAgent(MasterBase):
|
|
|
12
12
|
publishers_port: int = 50000,
|
|
13
13
|
subscribers_port: int = 50001,
|
|
14
14
|
):
|
|
15
|
-
super().__init__(
|
|
15
|
+
super().__init__(
|
|
16
|
+
identity_file,
|
|
17
|
+
workers=workers,
|
|
18
|
+
reload=reload,
|
|
19
|
+
events_pub_port=publishers_port,
|
|
20
|
+
events_sub_port=subscribers_port,
|
|
21
|
+
)
|
|
16
22
|
self.app = app
|
|
17
23
|
|
|
18
24
|
def get_asgi_app(self):
|
mrok/cli/commands/__init__.py
CHANGED
|
@@ -8,14 +8,15 @@ import typer
|
|
|
8
8
|
|
|
9
9
|
from mrok.cli.commands.admin.utils import parse_tags
|
|
10
10
|
from mrok.conf import Settings
|
|
11
|
-
from mrok.ziti
|
|
11
|
+
from mrok.types.ziti import Tags
|
|
12
|
+
from mrok.ziti.api import ZitiClientAPI, ZitiManagementAPI
|
|
12
13
|
from mrok.ziti.bootstrap import bootstrap_identity
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
async def bootstrap(
|
|
18
|
-
settings: Settings, forced: bool, tags:
|
|
19
|
+
settings: Settings, forced: bool, tags: Tags | None
|
|
19
20
|
) -> tuple[str, dict[str, Any] | None]:
|
|
20
21
|
async with ZitiManagementAPI(settings) as mgmt_api, ZitiClientAPI(settings) as client_api:
|
|
21
22
|
return await bootstrap_identity(
|
mrok/cli/commands/admin/utils.py
CHANGED
|
@@ -2,10 +2,10 @@ from datetime import datetime
|
|
|
2
2
|
|
|
3
3
|
import typer
|
|
4
4
|
|
|
5
|
-
from mrok.ziti
|
|
5
|
+
from mrok.types.ziti import Tags
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def parse_tags(pairs: list[str] | None) ->
|
|
8
|
+
def parse_tags(pairs: list[str] | None) -> Tags | None:
|
|
9
9
|
if not pairs:
|
|
10
10
|
return None
|
|
11
11
|
|
|
@@ -25,10 +25,55 @@ def register(app: typer.Typer) -> None:
|
|
|
25
25
|
typer.Option(
|
|
26
26
|
"--workers",
|
|
27
27
|
"-w",
|
|
28
|
-
help=
|
|
28
|
+
help="Number of workers.",
|
|
29
29
|
show_default=True,
|
|
30
30
|
),
|
|
31
31
|
] = default_workers,
|
|
32
|
+
max_connections: Annotated[
|
|
33
|
+
int,
|
|
34
|
+
typer.Option(
|
|
35
|
+
"--max-pool-connections",
|
|
36
|
+
help=(
|
|
37
|
+
"The maximum number of concurrent HTTP connections that "
|
|
38
|
+
"the pool should allow. Any attempt to send a request on a pool that "
|
|
39
|
+
"would exceed this amount will block until a connection is available."
|
|
40
|
+
),
|
|
41
|
+
show_default=True,
|
|
42
|
+
),
|
|
43
|
+
] = 10,
|
|
44
|
+
max_keepalive_connections: Annotated[
|
|
45
|
+
int | None,
|
|
46
|
+
typer.Option(
|
|
47
|
+
"--max-pool-keepalive-connections",
|
|
48
|
+
help=(
|
|
49
|
+
"The maximum number of idle HTTP connections "
|
|
50
|
+
"that will be maintained in the pool."
|
|
51
|
+
),
|
|
52
|
+
show_default=True,
|
|
53
|
+
),
|
|
54
|
+
] = None,
|
|
55
|
+
keepalive_expiry: Annotated[
|
|
56
|
+
float | None,
|
|
57
|
+
typer.Option(
|
|
58
|
+
"--max-pool-keepalive-expiry",
|
|
59
|
+
help=(
|
|
60
|
+
"The duration in seconds that an idle HTTP connection "
|
|
61
|
+
"may be maintained for before being expired from the pool."
|
|
62
|
+
),
|
|
63
|
+
show_default=True,
|
|
64
|
+
),
|
|
65
|
+
] = None,
|
|
66
|
+
retries: Annotated[
|
|
67
|
+
int,
|
|
68
|
+
typer.Option(
|
|
69
|
+
"--max-pool-connect-retries",
|
|
70
|
+
help=(
|
|
71
|
+
"The duration in seconds that an idle HTTP connection "
|
|
72
|
+
"may be maintained for before being expired from the pool."
|
|
73
|
+
),
|
|
74
|
+
show_default=True,
|
|
75
|
+
),
|
|
76
|
+
] = 0,
|
|
32
77
|
publishers_port: Annotated[
|
|
33
78
|
int,
|
|
34
79
|
typer.Option(
|
|
@@ -53,6 +98,14 @@ def register(app: typer.Typer) -> None:
|
|
|
53
98
|
show_default=True,
|
|
54
99
|
),
|
|
55
100
|
] = 50001,
|
|
101
|
+
no_events: Annotated[
|
|
102
|
+
bool,
|
|
103
|
+
typer.Option(
|
|
104
|
+
"--no-events",
|
|
105
|
+
help="Disable events. Default: False",
|
|
106
|
+
show_default=True,
|
|
107
|
+
),
|
|
108
|
+
] = False,
|
|
56
109
|
):
|
|
57
110
|
"""Run a Sidecar Proxy to expose a web application through OpenZiti."""
|
|
58
111
|
if ":" in str(target):
|
|
@@ -65,6 +118,11 @@ def register(app: typer.Typer) -> None:
|
|
|
65
118
|
str(identity_file),
|
|
66
119
|
target_addr,
|
|
67
120
|
workers=workers,
|
|
121
|
+
events_enabled=not no_events,
|
|
122
|
+
max_connections=max_connections,
|
|
123
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
124
|
+
keepalive_expiry=keepalive_expiry,
|
|
125
|
+
retries=retries,
|
|
68
126
|
publishers_port=publishers_port,
|
|
69
127
|
subscribers_port=subscribers_port,
|
|
70
128
|
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from mrok import frontend
|
|
7
|
+
from mrok.cli.utils import number_of_workers
|
|
8
|
+
|
|
9
|
+
default_workers = number_of_workers()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register(app: typer.Typer) -> None:
|
|
13
|
+
@app.command("run")
|
|
14
|
+
def run_frontend(
|
|
15
|
+
ctx: typer.Context,
|
|
16
|
+
identity_file: Path = typer.Argument(
|
|
17
|
+
...,
|
|
18
|
+
help="Identity json file",
|
|
19
|
+
),
|
|
20
|
+
host: Annotated[
|
|
21
|
+
str,
|
|
22
|
+
typer.Option(
|
|
23
|
+
"--host",
|
|
24
|
+
"-h",
|
|
25
|
+
help="Host to bind to. Default: 127.0.0.1",
|
|
26
|
+
show_default=True,
|
|
27
|
+
),
|
|
28
|
+
] = "127.0.0.1",
|
|
29
|
+
port: Annotated[
|
|
30
|
+
int,
|
|
31
|
+
typer.Option(
|
|
32
|
+
"--port",
|
|
33
|
+
"-P",
|
|
34
|
+
help="Port to bind to. Default: 8000",
|
|
35
|
+
show_default=True,
|
|
36
|
+
),
|
|
37
|
+
] = 8000,
|
|
38
|
+
workers: Annotated[
|
|
39
|
+
int,
|
|
40
|
+
typer.Option(
|
|
41
|
+
"--workers",
|
|
42
|
+
"-w",
|
|
43
|
+
help=f"Number of workers. Default: {default_workers}",
|
|
44
|
+
show_default=True,
|
|
45
|
+
),
|
|
46
|
+
] = default_workers,
|
|
47
|
+
max_connections: Annotated[
|
|
48
|
+
int,
|
|
49
|
+
typer.Option(
|
|
50
|
+
"--max-pool-connections",
|
|
51
|
+
help=(
|
|
52
|
+
"The maximum number of concurrent HTTP connections that "
|
|
53
|
+
"the pool should allow. Any attempt to send a request on a pool that "
|
|
54
|
+
"would exceed this amount will block until a connection is available."
|
|
55
|
+
),
|
|
56
|
+
show_default=True,
|
|
57
|
+
),
|
|
58
|
+
] = 1000,
|
|
59
|
+
max_keepalive_connections: Annotated[
|
|
60
|
+
int | None,
|
|
61
|
+
typer.Option(
|
|
62
|
+
"--max-pool-keepalive-connections",
|
|
63
|
+
help=(
|
|
64
|
+
"The maximum number of idle HTTP connections "
|
|
65
|
+
"that will be maintained in the pool."
|
|
66
|
+
),
|
|
67
|
+
show_default=True,
|
|
68
|
+
),
|
|
69
|
+
] = 100,
|
|
70
|
+
keepalive_expiry: Annotated[
|
|
71
|
+
float | None,
|
|
72
|
+
typer.Option(
|
|
73
|
+
"--max-pool-keepalive-expiry",
|
|
74
|
+
help=(
|
|
75
|
+
"The duration in seconds that an idle HTTP connection "
|
|
76
|
+
"may be maintained for before being expired from the pool."
|
|
77
|
+
),
|
|
78
|
+
show_default=True,
|
|
79
|
+
),
|
|
80
|
+
] = 300,
|
|
81
|
+
):
|
|
82
|
+
"""Run the mrok frontend with Gunicorn and Uvicorn workers."""
|
|
83
|
+
frontend.run(
|
|
84
|
+
identity_file,
|
|
85
|
+
host,
|
|
86
|
+
port,
|
|
87
|
+
workers,
|
|
88
|
+
max_connections=max_connections,
|
|
89
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
90
|
+
keepalive_expiry=keepalive_expiry,
|
|
91
|
+
)
|
mrok/constants.py
CHANGED
|
@@ -13,6 +13,7 @@ INSTANCE_RESPONSE = {
|
|
|
13
13
|
"name": "ins-1234-5678-0001.ext-1234-5678",
|
|
14
14
|
"extension": {"id": "EXT-1234-5678"},
|
|
15
15
|
"instance": {"id": "INS-1234-5678-0001"},
|
|
16
|
+
"status": "offline",
|
|
16
17
|
"tags": {
|
|
17
18
|
"account": "ACC-5555-3333",
|
|
18
19
|
MROK_VERSION_TAG_NAME: "1.0",
|
|
@@ -25,6 +26,7 @@ INSTANCE_CREATE_RESPONSE = {
|
|
|
25
26
|
"name": "ins-1234-5678-0001.ext-1234-5678",
|
|
26
27
|
"extension": {"id": "EXT-1234-5678"},
|
|
27
28
|
"instance": {"id": "INS-1234-5678-0001"},
|
|
29
|
+
"status": "online",
|
|
28
30
|
"identity": {
|
|
29
31
|
"ztAPI": "https://ziti.exts.platform.softwareone.com/edge/client/v1",
|
|
30
32
|
"ztAPIs": None,
|
|
@@ -35,6 +37,17 @@ INSTANCE_CREATE_RESPONSE = {
|
|
|
35
37
|
"ca": "pem:-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n",
|
|
36
38
|
},
|
|
37
39
|
"enableHa": None,
|
|
40
|
+
"mrok": {
|
|
41
|
+
"identity": "ins-0000-0000-0000.ext-0000-0000",
|
|
42
|
+
"extension": "ext-0000-0000",
|
|
43
|
+
"instance": "ins-0000-0000-0000",
|
|
44
|
+
"domain": "ext.s1.today",
|
|
45
|
+
"tags": {
|
|
46
|
+
"mrok-service": "ext-0000-0000",
|
|
47
|
+
"mrok-identity-type": "instance",
|
|
48
|
+
"mrok": "0.4.0",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
38
51
|
},
|
|
39
52
|
"tags": {
|
|
40
53
|
"account": "ACC-5555-3333",
|
mrok/controller/schemas.py
CHANGED
|
@@ -9,12 +9,12 @@ from pydantic import (
|
|
|
9
9
|
computed_field,
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
-
from mrok.ziti
|
|
12
|
+
from mrok.types.ziti import Tags
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class BaseSchema(BaseModel):
|
|
16
16
|
model_config = ConfigDict(from_attributes=True, extra="ignore")
|
|
17
|
-
tags:
|
|
17
|
+
tags: Tags | None = None
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class IdSchema(BaseModel):
|
mrok/frontend/app.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from httpcore import AsyncConnectionPool
|
|
4
|
+
|
|
5
|
+
from mrok.conf import get_settings
|
|
6
|
+
from mrok.proxy.app import ProxyAppBase
|
|
7
|
+
from mrok.proxy.backend import AIOZitiNetworkBackend
|
|
8
|
+
from mrok.proxy.exceptions import InvalidTargetError
|
|
9
|
+
from mrok.types.proxy import Scope
|
|
10
|
+
|
|
11
|
+
RE_SUBDOMAIN = re.compile(r"(?i)^(?:EXT-\d{4}-\d{4}|INS-\d{4}-\d{4}-\d{4})$")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FrontendProxyApp(ProxyAppBase):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
identity_file: str,
|
|
18
|
+
*,
|
|
19
|
+
max_connections: int | None = 10,
|
|
20
|
+
max_keepalive_connections: int | None = None,
|
|
21
|
+
keepalive_expiry: float | None = None,
|
|
22
|
+
retries=0,
|
|
23
|
+
):
|
|
24
|
+
self._identity_file = identity_file
|
|
25
|
+
self._proxy_domain = self._get_proxy_domain()
|
|
26
|
+
super().__init__(
|
|
27
|
+
max_connections=max_connections,
|
|
28
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
29
|
+
keepalive_expiry=keepalive_expiry,
|
|
30
|
+
retries=retries,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def setup_connection_pool(
|
|
34
|
+
self,
|
|
35
|
+
max_connections: int | None,
|
|
36
|
+
max_keepalive_connections: int | None,
|
|
37
|
+
keepalive_expiry: float | None,
|
|
38
|
+
retries: int,
|
|
39
|
+
) -> AsyncConnectionPool:
|
|
40
|
+
return AsyncConnectionPool(
|
|
41
|
+
max_connections=max_connections,
|
|
42
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
43
|
+
keepalive_expiry=keepalive_expiry,
|
|
44
|
+
retries=retries,
|
|
45
|
+
network_backend=AIOZitiNetworkBackend(self._identity_file),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def get_upstream_base_url(self, scope: Scope) -> str:
|
|
49
|
+
target = self._get_target_name(
|
|
50
|
+
{k.decode("latin1"): v.decode("latin1") for k, v in scope.get("headers", {})}
|
|
51
|
+
)
|
|
52
|
+
return f"http://{target.lower()}"
|
|
53
|
+
|
|
54
|
+
def _get_proxy_domain(self):
|
|
55
|
+
settings = get_settings()
|
|
56
|
+
return (
|
|
57
|
+
settings.proxy.domain
|
|
58
|
+
if settings.proxy.domain[0] == "."
|
|
59
|
+
else f".{settings.proxy.domain}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def _get_target_from_header(self, headers: dict[str, str], name: str) -> str | None:
|
|
63
|
+
header_value = headers.get(name, "")
|
|
64
|
+
if self._proxy_domain in header_value:
|
|
65
|
+
if ":" in header_value:
|
|
66
|
+
header_value, _ = header_value.split(":", 1)
|
|
67
|
+
return header_value[: -len(self._proxy_domain)]
|
|
68
|
+
|
|
69
|
+
def _get_target_name(self, headers: dict[str, str]) -> str:
|
|
70
|
+
target = self._get_target_from_header(headers, "x-forwarded-host")
|
|
71
|
+
if not target:
|
|
72
|
+
target = self._get_target_from_header(headers, "host")
|
|
73
|
+
if not target or not RE_SUBDOMAIN.fullmatch(target):
|
|
74
|
+
raise InvalidTargetError()
|
|
75
|
+
return target
|
mrok/{proxy → frontend}/main.py
RENAMED
|
@@ -6,9 +6,8 @@ from gunicorn.app.base import BaseApplication
|
|
|
6
6
|
from uvicorn_worker import UvicornWorker
|
|
7
7
|
|
|
8
8
|
from mrok.conf import get_settings
|
|
9
|
-
from mrok.
|
|
9
|
+
from mrok.frontend.app import FrontendProxyApp
|
|
10
10
|
from mrok.logging import get_logging_config
|
|
11
|
-
from mrok.proxy.app import ProxyApp
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class MrokUvicornWorker(UvicornWorker):
|
|
@@ -39,20 +38,23 @@ def run(
|
|
|
39
38
|
host: str,
|
|
40
39
|
port: int,
|
|
41
40
|
workers: int,
|
|
41
|
+
max_connections: int | None,
|
|
42
|
+
max_keepalive_connections: int | None,
|
|
43
|
+
keepalive_expiry: float | None,
|
|
42
44
|
):
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
proxy_app.shutdown,
|
|
45
|
+
app = FrontendProxyApp(
|
|
46
|
+
str(identity_file),
|
|
47
|
+
max_connections=max_connections,
|
|
48
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
49
|
+
keepalive_expiry=keepalive_expiry,
|
|
49
50
|
)
|
|
51
|
+
|
|
50
52
|
options = {
|
|
51
53
|
"bind": f"{host}:{port}",
|
|
52
54
|
"workers": workers,
|
|
53
|
-
"worker_class": "mrok.
|
|
55
|
+
"worker_class": "mrok.frontend.main.MrokUvicornWorker",
|
|
54
56
|
"logconfig_dict": get_logging_config(get_settings()),
|
|
55
57
|
"reload": False,
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
StandaloneApplication(
|
|
60
|
+
StandaloneApplication(app, options).run()
|
mrok/proxy/__init__.py
CHANGED