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.
Files changed (50) hide show
  1. mrok/agent/devtools/inspector/app.py +2 -2
  2. mrok/agent/sidecar/app.py +61 -35
  3. mrok/agent/sidecar/main.py +35 -9
  4. mrok/agent/ziticorn.py +9 -3
  5. mrok/cli/commands/__init__.py +2 -2
  6. mrok/cli/commands/admin/bootstrap.py +3 -2
  7. mrok/cli/commands/admin/utils.py +2 -2
  8. mrok/cli/commands/agent/run/sidecar.py +59 -1
  9. mrok/cli/commands/{proxy → frontend}/__init__.py +1 -1
  10. mrok/cli/commands/frontend/run.py +91 -0
  11. mrok/constants.py +0 -2
  12. mrok/controller/openapi/examples.py +13 -0
  13. mrok/controller/schemas.py +2 -2
  14. mrok/frontend/__init__.py +3 -0
  15. mrok/frontend/app.py +75 -0
  16. mrok/{proxy → frontend}/main.py +12 -10
  17. mrok/proxy/__init__.py +0 -3
  18. mrok/proxy/app.py +158 -83
  19. mrok/proxy/asgi.py +96 -0
  20. mrok/proxy/backend.py +45 -0
  21. mrok/proxy/event_publisher.py +66 -0
  22. mrok/proxy/exceptions.py +22 -0
  23. mrok/{master.py → proxy/master.py} +36 -81
  24. mrok/{metrics.py → proxy/metrics.py} +38 -50
  25. mrok/{http/middlewares.py → proxy/middleware.py} +17 -26
  26. mrok/{datastructures.py → proxy/models.py} +43 -10
  27. mrok/proxy/stream.py +68 -0
  28. mrok/{http → proxy}/utils.py +1 -1
  29. mrok/proxy/worker.py +64 -0
  30. mrok/{http/config.py → proxy/ziticorn.py} +29 -6
  31. mrok/types/proxy.py +20 -0
  32. mrok/types/ziti.py +1 -0
  33. mrok/ziti/api.py +15 -18
  34. mrok/ziti/bootstrap.py +3 -2
  35. mrok/ziti/identities.py +5 -4
  36. mrok/ziti/services.py +3 -2
  37. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/METADATA +2 -5
  38. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/RECORD +43 -39
  39. mrok/cli/commands/proxy/run.py +0 -49
  40. mrok/http/forwarder.py +0 -354
  41. mrok/http/lifespan.py +0 -39
  42. mrok/http/pool.py +0 -239
  43. mrok/http/protocol.py +0 -11
  44. mrok/http/server.py +0 -14
  45. mrok/http/types.py +0 -18
  46. /mrok/{http → proxy}/constants.py +0 -0
  47. /mrok/{http → types}/__init__.py +0 -0
  48. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/WHEEL +0 -0
  49. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/entry_points.txt +0 -0
  50. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/licenses/LICENSE.txt +0 -0
mrok/ziti/identities.py CHANGED
@@ -5,8 +5,9 @@ from typing import Any
5
5
  import jwt
6
6
 
7
7
  from mrok.conf import Settings
8
+ from mrok.types.ziti import Tags
8
9
  from mrok.ziti import pki
9
- from mrok.ziti.api import TagsType, ZitiClientAPI, ZitiManagementAPI
10
+ from mrok.ziti.api import ZitiClientAPI, ZitiManagementAPI
10
11
  from mrok.ziti.constants import (
11
12
  MROK_IDENTITY_TYPE_TAG_NAME,
12
13
  MROK_IDENTITY_TYPE_TAG_VALUE_INSTANCE,
@@ -29,7 +30,7 @@ async def register_identity(
29
30
  client_api: ZitiClientAPI,
30
31
  service_external_id: str,
31
32
  identity_external_id: str,
32
- tags: TagsType | None = None,
33
+ tags: Tags | None = None,
33
34
  ):
34
35
  service_name = service_external_id.lower()
35
36
  identity_tags = copy.copy(tags or {})
@@ -39,7 +40,7 @@ async def register_identity(
39
40
  if not service:
40
41
  raise ServiceNotFoundError(f"A service with name `{service_external_id}` does not exists.")
41
42
 
42
- identity_name = f"{identity_external_id.lower()}.{service_name}"
43
+ identity_name = identity_external_id.lower()
43
44
  service_policy_name = f"{identity_name}:bind"
44
45
  self_service_policy_name = f"self.{service_policy_name}"
45
46
 
@@ -129,7 +130,7 @@ async def enroll_proxy_identity(
129
130
  mgmt_api: ZitiManagementAPI,
130
131
  client_api: ZitiClientAPI,
131
132
  identity_name: str,
132
- tags: TagsType | None = None,
133
+ tags: Tags | None = None,
133
134
  ):
134
135
  identity = await mgmt_api.search_identity(identity_name)
135
136
  if identity:
mrok/ziti/services.py CHANGED
@@ -2,7 +2,8 @@ import logging
2
2
  from typing import Any
3
3
 
4
4
  from mrok.conf import Settings
5
- from mrok.ziti.api import TagsType, ZitiManagementAPI
5
+ from mrok.types.ziti import Tags
6
+ from mrok.ziti.api import ZitiManagementAPI
6
7
  from mrok.ziti.errors import (
7
8
  ConfigTypeNotFoundError,
8
9
  ProxyIdentityNotFoundError,
@@ -14,7 +15,7 @@ logger = logging.getLogger(__name__)
14
15
 
15
16
 
16
17
  async def register_service(
17
- settings: Settings, mgmt_api: ZitiManagementAPI, external_id: str, tags: TagsType | None
18
+ settings: Settings, mgmt_api: ZitiManagementAPI, external_id: str, tags: Tags | None
18
19
  ) -> dict[str, Any]:
19
20
  service_name = external_id.lower()
20
21
  registered = False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mrok
3
- Version: 0.4.6
3
+ Version: 0.6.0
4
4
  Summary: MPT Extensions OpenZiti Orchestrator
5
5
  Author: SoftwareOne AG
6
6
  License: Apache License
@@ -207,21 +207,18 @@ License: Apache License
207
207
  License-File: LICENSE.txt
208
208
  Requires-Python: <4,>=3.12
209
209
  Requires-Dist: asn1crypto<2.0.0,>=1.5.1
210
- Requires-Dist: cachetools<7.0.0,>=6.2.2
211
210
  Requires-Dist: cryptography<46.0.0,>=45.0.7
212
211
  Requires-Dist: dynaconf<4.0.0,>=3.2.11
213
212
  Requires-Dist: fastapi-pagination<0.15.0,>=0.14.1
214
213
  Requires-Dist: fastapi[standard]<0.120.0,>=0.119.0
215
214
  Requires-Dist: gunicorn<24.0.0,>=23.0.0
216
215
  Requires-Dist: hdrhistogram<0.11.0,>=0.10.3
217
- Requires-Dist: httptools<0.8.0,>=0.7.1
218
- Requires-Dist: httpx<0.29.0,>=0.28.1
216
+ Requires-Dist: httpcore<2.0.0,>=1.0.9
219
217
  Requires-Dist: openziti<2.0.0,>=1.3.1
220
218
  Requires-Dist: psutil<8.0.0,>=7.1.3
221
219
  Requires-Dist: pydantic<3.0.0,>=2.11.7
222
220
  Requires-Dist: pyfiglet<2.0.0,>=1.0.4
223
221
  Requires-Dist: pyjwt<3.0.0,>=2.10.1
224
- Requires-Dist: pytest-textual-snapshot<2.0.0,>=1.1.0
225
222
  Requires-Dist: pyyaml<7.0.0,>=6.0.2
226
223
  Requires-Dist: pyzmq<28.0.0,>=27.1.0
227
224
  Requires-Dist: rich<15.0.0,>=14.1.0
@@ -1,30 +1,27 @@
1
1
  mrok/__init__.py,sha256=D1PUs3KtMCqG4bFLceVNG62L3RN53NS95uSCNXpgvzs,181
2
2
  mrok/conf.py,sha256=_5Z-A5LyojQeY8J7W8C0QidsmrPl99r9qKYEoMf4kcI,840
3
- mrok/constants.py,sha256=65OlmploxfND686E4mt9LR9MqYn8I5k-L0H-R5KsLG8,201
4
- mrok/datastructures.py,sha256=gp8KF2JoNOxIRzYStVZLKL_XVDbcIVSIDnmpQo4FNt0,4067
3
+ mrok/constants.py,sha256=UTGYqs3DgEd_SN-k0JK10ekmHVWQaaARtdh1Y-0JG_s,122
5
4
  mrok/errors.py,sha256=ruNMDFr2_0ezCGXuCG1OswCEv-bHOIzMMd02J_0ABcs,37
6
5
  mrok/logging.py,sha256=ZMWn0w4fJ-F_g-L37H_GM14BSXAIF2mFF_ougX5S7mg,2856
7
- mrok/master.py,sha256=XuketJZuB1YWdbTs819pjLum7Qfv232F9ZCxdwRztCQ,8340
8
- mrok/metrics.py,sha256=asweK_7xiV5MtkDkvbEm9Tktqrl2KHM8VflF0AkNGI0,4036
9
6
  mrok/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- mrok/agent/ziticorn.py,sha256=marXGcr6CbDdiNi8V3CZJhg8YCUbKw6ySuO3-0-zf8g,900
7
+ mrok/agent/ziticorn.py,sha256=eHUYs9QaSp35rBzYHRV-SrYxF5ySyECaQg7U-XbdINE,1025
11
8
  mrok/agent/devtools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
9
  mrok/agent/devtools/__main__.py,sha256=R8ezbW7hCik5r45U3w2TgiTubg9SlbVsWA-bapILJXU,781
13
10
  mrok/agent/devtools/inspector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
11
  mrok/agent/devtools/inspector/__main__.py,sha256=HeYcRf1bjXPji2LKMPCcTU61afrRH2P1RqnFmHClRTc,524
15
- mrok/agent/devtools/inspector/app.py,sha256=_pzxemMqIunE5EdMq5amjqpOGsMWIOw17GgiCtRAi6Q,16464
12
+ mrok/agent/devtools/inspector/app.py,sha256=rmfm0GVz_iZ_tqne6E966D2He5QVprO2g7PHFrnjG0U,16484
16
13
  mrok/agent/devtools/inspector/server.py,sha256=C4uD6_1psSHMjJLUDCMPGvKdQYKaEwYTw27NAbwuuA0,636
17
14
  mrok/agent/sidecar/__init__.py,sha256=DrjJGhqFyxsVODW06KI20Wpr6HsD2lD6qFCKUXc7GIE,59
18
- mrok/agent/sidecar/app.py,sha256=YOQLwPPqcElbF2kU15bcw-ePzZM09eVJQZ6Z5NYg9u8,1509
19
- mrok/agent/sidecar/main.py,sha256=h31wynUCcFmRckvqLHtH97w1QgMv4fzcmYjhRPUobxY,1076
15
+ mrok/agent/sidecar/app.py,sha256=sPQqjnwETRQk0cj8hAxSVUDCkYqsVZCS6ixEqkcGY5A,2534
16
+ mrok/agent/sidecar/main.py,sha256=jeJzrCbltfXOYsKSCjcw8h5lxh4_bGT87kCC5dV4kYU,2190
20
17
  mrok/cli/__init__.py,sha256=mtFEa8IeS1x6Gm4dUYoSnAxyEzNqbUVSmWxtuZUMR84,61
21
18
  mrok/cli/main.py,sha256=DFcYPwDskXi8SKAgEsuP4GMFzaniIf_6bZaSDWvYKDk,2724
22
19
  mrok/cli/rich.py,sha256=P3Dyu8EArUR9_0j7DPK7LRx85TWdYdZ1SaJzD_S1ZCE,511
23
20
  mrok/cli/utils.py,sha256=m_olScdIUGks5IoC6p2F9D6CQIucWZ7LHyrvwm2bkJw,106
24
- mrok/cli/commands/__init__.py,sha256=jihISOj3ZZQ_dn0rogXrJOx6b283KLRUfTw9USQgAhI,134
21
+ mrok/cli/commands/__init__.py,sha256=-UOGzh38oWX7fPeI2nc5I9z8LylRdQAt868q4G6rNGk,140
25
22
  mrok/cli/commands/admin/__init__.py,sha256=WU49jpMF9p18UONjYywWEFzjF57zLpLKJ0qAZvrzcR4,414
26
- mrok/cli/commands/admin/bootstrap.py,sha256=iOnHctYajgcHrG_Idjn5Y7VVSaWYRIhdgqKSw9TWq9I,1680
27
- mrok/cli/commands/admin/utils.py,sha256=wQ-qQJGFyhikMJY_CWT-G6sTEIZb-LUdj1AUZisLPBw,1363
23
+ mrok/cli/commands/admin/bootstrap.py,sha256=9ADSeiVbFAZXh6GxHEf9h2g_XHGOIlMmg1rgsxMfdow,1699
24
+ mrok/cli/commands/admin/utils.py,sha256=Z7YTAFZKOi6nkw2oX4rJoGoUD41RYL3AOqEDhlV3jR0,1357
28
25
  mrok/cli/commands/admin/list/__init__.py,sha256=kjCMcpn1gopcrQaaHxfFh8Kyngldepnle8R2br5dJ_0,195
29
26
  mrok/cli/commands/admin/list/extensions.py,sha256=16fhDB5ucL8su2WQnSaQ1E6MhgC4vkP9-nuHAcPpzyE,4405
30
27
  mrok/cli/commands/admin/list/instances.py,sha256=kaqeyidwUxgYqfaHXqp2m76rm5h2ErBsYyZcNeaBRwY,5912
@@ -41,50 +38,57 @@ mrok/cli/commands/agent/dev/console.py,sha256=rrKAGoKXVQQBOC75H0JSuX1sYyvc2QSrV-
41
38
  mrok/cli/commands/agent/dev/web.py,sha256=O9dYk-o1FV2E_sKLOezdEmLsnexwbJNDdsYL5pATZRQ,1028
42
39
  mrok/cli/commands/agent/run/__init__.py,sha256=E_IJCl3BfMffqFASe8gzJwhhQgt5bQfjhuyekVwdEBA,164
43
40
  mrok/cli/commands/agent/run/asgi.py,sha256=dCgzwJtTLv2eyEIP7v1tDfe_PrFBS02SfN5dSDw1Jzg,2054
44
- mrok/cli/commands/agent/run/sidecar.py,sha256=Tj5inAeSX1E3yCVs2q4P3sP3trvvwk2lYMSUtyFfxo8,2098
41
+ mrok/cli/commands/agent/run/sidecar.py,sha256=UOewegTLFwAZ70VFJb6_9kV0LmsvnXuq-yqgrMlTeZo,4182
45
42
  mrok/cli/commands/controller/__init__.py,sha256=2xw-YVN0akiLiuGUU3XbYyZZ0ugOjQ6XhtTkzEKSmMA,161
46
43
  mrok/cli/commands/controller/openapi.py,sha256=QLjVao9UkB2vBaGkFi_q_jrlg4Np4ldMRwDIJsrJ7A8,1175
47
44
  mrok/cli/commands/controller/run.py,sha256=yl1p7oRHhQINWWjUKlRHtMIWUCV0KsxYdyVyazhX834,2406
48
- mrok/cli/commands/proxy/__init__.py,sha256=Y9oluemsuWSaykDniLVsI2cyTurcEO3_GJDHgf-7BdU,120
49
- mrok/cli/commands/proxy/run.py,sha256=QzKAjNCib-SS8IrGGHOxDjTtgTQxfeeeqmI3LaIkiLo,1293
45
+ mrok/cli/commands/frontend/__init__.py,sha256=0kK37yG6qs7yAa8TYlKZUA-nHrWsO4y5CjbVkXafnuk,123
46
+ mrok/cli/commands/frontend/run.py,sha256=_X1ylMe4-YCTghsu0XY-PB4nk3PL-PQq9YIgbkgJok8,2796
50
47
  mrok/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
48
  mrok/controller/app.py,sha256=XxCIB7N1YE52vSYfvGW2UPgEEOZ9jxDMe2l9D2SfXi8,1866
52
49
  mrok/controller/auth.py,sha256=hYa0OPJ5X0beGxRP6qbxwJOVXj5TmzHjmam2OjTBKn4,2704
53
50
  mrok/controller/pagination.py,sha256=raYpYa34q8Ckl4BXBOEdpWlKkFj6z7e6QLWr2HT7dzI,2187
54
- mrok/controller/schemas.py,sha256=AaF8_bEwZTHM02apVEBAzlUb2t71zoxYaG-VHtPNeMk,1705
51
+ mrok/controller/schemas.py,sha256=PZPEsSJNrGSuplfjCPF_E-VJ721AzgR1Jj8P-Shw1cg,1699
55
52
  mrok/controller/dependencies/__init__.py,sha256=voewk6gjkA0OarL6HFmfT_RLqBns0Fpl-VIqK5xVAEI,202
56
53
  mrok/controller/dependencies/conf.py,sha256=2Pa8fxJHkZ29q6UL-w6hUP_wr7WnNELfw5LlzWg1Tec,162
57
54
  mrok/controller/dependencies/ziti.py,sha256=fYoxeJb4s6p2_3gxbExbFSRabjpvp_gZMBb3ocXZV3Y,702
58
55
  mrok/controller/openapi/__init__.py,sha256=U1dw45w76CcoQagyqg_FXdMuJF3qJZZM6wG8TeTe3Zo,101
59
- mrok/controller/openapi/examples.py,sha256=ZI0BP7L6sI0z7Mq1I3uc2UrweGpzpPeGSIuf1bUKkgg,1419
56
+ mrok/controller/openapi/examples.py,sha256=-Mwj41veIeydYaRSxcihN02g_jMuUrtvMfTjs08Dzf8,1852
60
57
  mrok/controller/openapi/utils.py,sha256=Kn55ISAWlMJNwrJTum7iFrBvJvr81To76pCK8W-s79Q,1114
61
58
  mrok/controller/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
59
  mrok/controller/routes/extensions.py,sha256=zoY4sNz_BIZcbly6WXM7Rbpn2jmB89njS_0xdJkoKfs,9192
63
60
  mrok/controller/routes/instances.py,sha256=v-fn_F6JHbDZ4YUNCIZzClgHp6aC1Eu5HB7k7qBG5pk,2202
64
- mrok/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
- mrok/http/config.py,sha256=k73-4hBo6jag1RpyZagJLtTCL6EQoebZaX8Vv-CMN_k,2050
66
- mrok/http/constants.py,sha256=ao5gI2HFBWmrdd2Yc6XFK_RGaHk-omxI4AqvfIiGes8,409
67
- mrok/http/forwarder.py,sha256=vAf2nh6Fmr07JdRJkK4dPHKJilP9PnsYZcroqsnilB8,13751
68
- mrok/http/lifespan.py,sha256=UdbOqjWZsHzJJjX0CTd2hY96Jpk5QWtdHJEzPG6Z4hQ,1288
69
- mrok/http/middlewares.py,sha256=SGo4EwhTId2uJx1aMuqGbNy7MXgZlDEdZI0buzBYVv0,5011
70
- mrok/http/pool.py,sha256=Q-pRwgYPusqEKQCwZsRQ2mnGaDfyWknWpvydUu5KtEU,7696
71
- mrok/http/protocol.py,sha256=ap8jbLUvgbAH81ZJZCBkQiYR7mkV_eL3rpfwEkoE8sU,392
72
- mrok/http/server.py,sha256=Mj7C85fc-DXp-WTBWaOd7ag808oliLmFBH5bf-G2FHg,370
73
- mrok/http/types.py,sha256=A82zloEqW8KdKahdNrbW5fhlJNUo2enLNRVMWIJTatA,632
74
- mrok/http/utils.py,sha256=sOixYu3R9-nNoMFYdifrreYvcFRIHYVtb6AAmtVzaLE,2125
75
- mrok/proxy/__init__.py,sha256=vWXyImroqM1Eq8e_oFPBup8VJ3reyp8SVjFTbLzRkI8,51
76
- mrok/proxy/app.py,sha256=VvMRmYLwsItjCcecy6ccrkk564LnArIermHTRVDxh9U,3469
77
- mrok/proxy/main.py,sha256=ZXpticE6J4FABaslDB_8J5qklPsf3e7xIFSZmcPAAjQ,1588
61
+ mrok/frontend/__init__.py,sha256=SN3LoFwAye18lfJ8OKNNS-7kLc2A9OxPGIEIEYYtAOA,54
62
+ mrok/frontend/app.py,sha256=I2cvEI2ZGhbeazFhF6LavxBYywsv-4QkuNxCDo6-dkA,2627
63
+ mrok/frontend/main.py,sha256=0KtchIGLn70A_Oxekmhr_qSYUg5QqIMfrdYcyFdpj9s,1717
64
+ mrok/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
+ mrok/proxy/app.py,sha256=flnVPoUO3pSF3b0nYFBhKjZ9jp5ljijo2a-5e37gACs,6016
66
+ mrok/proxy/asgi.py,sha256=2uw5bLquyUsiYlNwq8RhJd8OqVvSJDvYjzOVGLLB3Cs,3528
67
+ mrok/proxy/backend.py,sha256=dRmIUJin2DM3PUxrVX0j4t1oB6DOX7N9JV2lIcopE38,1649
68
+ mrok/proxy/constants.py,sha256=ao5gI2HFBWmrdd2Yc6XFK_RGaHk-omxI4AqvfIiGes8,409
69
+ mrok/proxy/event_publisher.py,sha256=TAuwEqIhRYxgazJFgC3DekwUAXlJ2UFjbdx_A9vwA1g,2511
70
+ mrok/proxy/exceptions.py,sha256=61OhdihQNdnBUqAI9mbBkXD1yWg-6Eftk7EhWCU1VF0,642
71
+ mrok/proxy/master.py,sha256=HB2q_nPLim23z0mGDGKs_RshhGVAO8VgOYP4hA__zC4,6891
72
+ 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
75
+ mrok/proxy/stream.py,sha256=7V-bSAF9uNV1yVHKaEhHo95WxafSGWkyHPVABZX0djY,2134
76
+ mrok/proxy/utils.py,sha256=OxX6pJv_Wh_KgWx95YeJ3YeuSgwm2tsd00897P3fxys,2126
77
+ mrok/proxy/worker.py,sha256=uEUC2Hbx0YQiDMFNZfwkHMDnijN96b6iRoIErfI21Tg,1921
78
+ mrok/proxy/ziticorn.py,sha256=YwbyNUK-TL3dANntM6gtlP9Su39QvDCC0dSbuZisXIo,2873
79
+ mrok/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
+ mrok/types/proxy.py,sha256=40Yds4tUykMpzsoQbMtHG85r8xtm5Q3fQZ17bp7cDiM,818
81
+ mrok/types/ziti.py,sha256=EeQnTbDEJ-Y-KMS6zu1Xjxb58Up2VxUwqzUwy3H28JY,36
78
82
  mrok/ziti/__init__.py,sha256=20OWMiexRhOovZOX19zlX87-V78QyWnEnSZfyAftUdE,263
79
- mrok/ziti/api.py,sha256=KvGiT9d4oSgC3JbFWLDQyuHcLX2HuZJoJ8nHmWtCDkY,16154
80
- mrok/ziti/bootstrap.py,sha256=QIDhlkIxPW2QRuumFq2D1WDbD003P5f3z24pAUsyeBI,2696
83
+ mrok/ziti/api.py,sha256=ikl3l446Gu-YZuJJFRHFSBpKhh5KJxmk-Jr4YbSrqbk,16072
84
+ mrok/ziti/bootstrap.py,sha256=PRZlYDtcGbix-1bQDuJ4wX4dy845VAx9SFPotc4BCeg,2715
81
85
  mrok/ziti/constants.py,sha256=Urq1X3bCBQZfw8NbnEa1pqmY4oq1wmzkwPfzam3kbTw,339
82
86
  mrok/ziti/errors.py,sha256=yYCbVDwktnR0AYduqtynIjo73K3HOhIrwA_vQimvEd4,368
83
- mrok/ziti/identities.py,sha256=1BcwfqAJHMBhc3vRaf0aLaIkoHskj5Xe2Lsq2lO9Vs8,6735
87
+ mrok/ziti/identities.py,sha256=9BIBQOirvcdAkRFNqZPTOkC8kvDQbq6EgaQMq22NQsQ,6730
84
88
  mrok/ziti/pki.py,sha256=o2tySqHC8-7bvFuI2Tqxg9vX6H6ZSxWxfP_9x29e19M,1954
85
- mrok/ziti/services.py,sha256=zR1PEBYwXVou20iJK4euh0ZZFAo9UB8PZk8f6SDmiUE,3194
86
- mrok-0.4.6.dist-info/METADATA,sha256=Io64noW9WGLw9asC4xjeuLS7Wh8bFefufJmTjUK8Syo,15836
87
- mrok-0.4.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
88
- mrok-0.4.6.dist-info/entry_points.txt,sha256=tloXwvU1uJicBJR2h-8HoVclPgwJWDwuREMHN8Zq-nU,38
89
- mrok-0.4.6.dist-info/licenses/LICENSE.txt,sha256=6PaICaoA3yNsZKLv5G6OKqSfLSoX7MakYqTDgJoTCBs,11346
90
- mrok-0.4.6.dist-info/RECORD,,
89
+ 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,,
@@ -1,49 +0,0 @@
1
- from pathlib import Path
2
- from typing import Annotated
3
-
4
- import typer
5
-
6
- from mrok import proxy
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_proxy(
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
- ):
48
- """Run the mrok proxy with Gunicorn and Uvicorn workers."""
49
- proxy.run(identity_file, host, port, workers)
mrok/http/forwarder.py DELETED
@@ -1,354 +0,0 @@
1
- import abc
2
- import asyncio
3
- import logging
4
- from contextlib import AbstractAsyncContextManager
5
-
6
- from mrok.http.types import ASGIReceive, ASGISend, Scope, StreamPair
7
-
8
- logger = logging.getLogger("mrok.proxy")
9
-
10
-
11
- class BackendSelectionError(Exception):
12
- def __init__(self, status: int = 500, message: str = "Internal Server Error"):
13
- self.status = status
14
- self.message = message
15
-
16
-
17
- class InvalidBackendError(BackendSelectionError):
18
- def __init__(self):
19
- super().__init__(status=502, message="Bad Gateway")
20
-
21
-
22
- class BackendUnavailableError(BackendSelectionError):
23
- def __init__(self):
24
- super().__init__(status=503, message="Service Unavailable")
25
-
26
-
27
- class ForwardAppBase(abc.ABC):
28
- """Generic HTTP forwarder base class.
29
-
30
- Subclasses must implement `select_backend(scope)` to return an
31
- (asyncio.StreamReader, asyncio.StreamWriter) pair connected to the
32
- desired backend. The base class implements the HTTP/1.1 framing
33
- and streaming logic (requests and responses).
34
- """
35
-
36
- def __init__(
37
- self,
38
- read_chunk_size: int = 65536,
39
- lifespan_timeout: float = 10.0,
40
- ) -> None:
41
- self._read_chunk_size: int = int(read_chunk_size)
42
- self._lifespan_timeout = lifespan_timeout
43
-
44
- async def handle_lifespan(self, receive: ASGIReceive, send: ASGISend) -> None:
45
- while True:
46
- event = await receive()
47
- etype = event.get("type")
48
-
49
- if etype == "lifespan.startup":
50
- try:
51
- await asyncio.wait_for(self.startup(), self._lifespan_timeout)
52
- except TimeoutError:
53
- logger.exception("Lifespan startup timed out")
54
- await send({"type": "lifespan.startup.failed", "message": "startup timeout"})
55
- continue
56
- except Exception as e:
57
- logger.exception("Exception during lifespan startup")
58
- await send({"type": "lifespan.startup.failed", "message": str(e)})
59
- continue
60
- await send({"type": "lifespan.startup.complete"})
61
-
62
- elif etype == "lifespan.shutdown":
63
- try:
64
- await asyncio.wait_for(self.shutdown(), self._lifespan_timeout)
65
- except TimeoutError:
66
- logger.exception("Lifespan shutdown timed out")
67
- await send({"type": "lifespan.shutdown.failed", "message": "shutdown timeout"})
68
- return
69
- except Exception as exc:
70
- logger.exception("Exception during lifespan shutdown")
71
- await send({"type": "lifespan.shutdown.failed", "message": str(exc)})
72
- return
73
- await send({"type": "lifespan.shutdown.complete"})
74
- return
75
-
76
- async def startup(self):
77
- return
78
-
79
- async def shutdown(self):
80
- return
81
-
82
- @abc.abstractmethod
83
- def select_backend(
84
- self, scope: Scope, headers: dict[str, str]
85
- ) -> AbstractAsyncContextManager[StreamPair]:
86
- raise NotImplementedError()
87
-
88
- async def __call__(self, scope: Scope, receive: ASGIReceive, send: ASGISend) -> None:
89
- """ASGI callable entry point.
90
-
91
- Delegates to smaller helper methods for readability. Subclasses only
92
- need to implement backend selection.
93
- """
94
- scope_type = scope.get("type")
95
- if scope_type == "lifespan":
96
- await self.handle_lifespan(receive, send)
97
- return
98
-
99
- if scope.get("type") != "http":
100
- await send({"type": "http.response.start", "status": 500, "headers": []})
101
- await send({"type": "http.response.body", "body": b"Unsupported"})
102
- return
103
-
104
- method = scope.get("method", "GET")
105
- path_qs = self.format_path(scope)
106
-
107
- headers = list(scope.get("headers", []))
108
- headers = self.ensure_host_header(headers, scope)
109
- try:
110
- async with self.select_backend(
111
- scope, {k[0].decode().lower(): k[1].decode() for k in headers}
112
- ) as (reader, writer):
113
- if not (reader and writer):
114
- await send({"type": "http.response.start", "status": 502, "headers": []})
115
- await send({"type": "http.response.body", "body": b"Bad Gateway"})
116
- return
117
-
118
- use_chunked = self.ensure_chunked_if_needed(headers)
119
-
120
- await self.write_request_line_and_headers(writer, method, path_qs, headers)
121
-
122
- await self.stream_request_body(receive, writer, use_chunked)
123
-
124
- status_line = await reader.readline()
125
- if not status_line:
126
- await send({"type": "http.response.start", "status": 502, "headers": []})
127
- await send({"type": "http.response.body", "body": b"Bad Gateway"})
128
- return
129
-
130
- status, headers_out, raw_headers = await self.read_status_and_headers(
131
- reader, status_line
132
- )
133
-
134
- await send(
135
- {"type": "http.response.start", "status": status, "headers": headers_out}
136
- )
137
-
138
- await self.stream_response_body(reader, send, raw_headers)
139
- except BackendSelectionError as bse:
140
- await send({"type": "http.response.start", "status": bse.status, "headers": []})
141
- await send({"type": "http.response.body", "body": bse.message.encode()})
142
- return
143
-
144
- def format_path(self, scope: Scope) -> str:
145
- raw_path = scope.get("raw_path")
146
- if raw_path:
147
- return raw_path.decode()
148
- q = scope.get("query_string", b"")
149
- path = scope.get("path", "/")
150
- path_qs = path
151
- if q:
152
- path_qs += "?" + q.decode()
153
- return path_qs
154
-
155
- def ensure_host_header(
156
- self, headers: list[tuple[bytes, bytes]], scope: Scope
157
- ) -> list[tuple[bytes, bytes]]:
158
- if any(n.lower() == b"host" for n, _ in headers):
159
- return headers
160
- server = scope.get("server")
161
- if server:
162
- host = f"{server[0]}:{server[1]}" if server[1] else server[0]
163
- headers.append((b"host", host.encode()))
164
- return headers
165
-
166
- def ensure_chunked_if_needed(self, headers: list[tuple[bytes, bytes]]) -> bool:
167
- has_content_length = any(n.lower() == b"content-length" for n, _ in headers)
168
- has_transfer_encoding = any(n.lower() == b"transfer-encoding" for n, _ in headers)
169
- if not has_content_length and not has_transfer_encoding:
170
- headers.append((b"transfer-encoding", b"chunked"))
171
- return True
172
- return False
173
-
174
- async def write_request_line_and_headers(
175
- self,
176
- writer: asyncio.StreamWriter,
177
- method: str,
178
- path_qs: str,
179
- headers: list[tuple[bytes, bytes]],
180
- ) -> None:
181
- writer.write(f"{method} {path_qs} HTTP/1.1\r\n".encode())
182
- for name, value in headers:
183
- if name.lower() == b"expect":
184
- continue
185
- writer.write(name + b": " + value + b"\r\n")
186
- writer.write(b"\r\n")
187
- await writer.drain()
188
-
189
- async def stream_request_body(
190
- self, receive: ASGIReceive, writer: asyncio.StreamWriter, use_chunked: bool
191
- ) -> None:
192
- if use_chunked:
193
- await self.stream_request_chunked(receive, writer)
194
- return
195
-
196
- await self.stream_request_until_end(receive, writer)
197
-
198
- async def stream_request_chunked(
199
- self, receive: ASGIReceive, writer: asyncio.StreamWriter
200
- ) -> None:
201
- """Send request body to backend using HTTP/1.1 chunked encoding."""
202
- while True:
203
- event = await receive()
204
- if event["type"] == "http.request":
205
- body = event.get("body", b"") or b""
206
- if body:
207
- writer.write(f"{len(body):X}\r\n".encode())
208
- writer.write(body)
209
- writer.write(b"\r\n")
210
- await writer.drain()
211
- if not event.get("more_body", False):
212
- break
213
- elif event["type"] == "http.disconnect":
214
- return
215
-
216
- writer.write(b"0\r\n\r\n")
217
- await writer.drain()
218
-
219
- async def stream_request_until_end(
220
- self, receive: ASGIReceive, writer: asyncio.StreamWriter
221
- ) -> None:
222
- """Send request body to backend when content length/transfer-encoding
223
- already provided (no chunking).
224
- """
225
- while True:
226
- event = await receive()
227
- if event["type"] == "http.request":
228
- body = event.get("body", b"") or b""
229
- if body:
230
- writer.write(body)
231
- await writer.drain()
232
- if not event.get("more_body", False):
233
- break
234
- elif event["type"] == "http.disconnect":
235
- return
236
-
237
- async def read_status_and_headers(
238
- self, reader: asyncio.StreamReader, first_line: bytes
239
- ) -> tuple[int, list[tuple[bytes, bytes]], dict[bytes, bytes]]:
240
- parts = first_line.decode(errors="ignore").split(" ", 2)
241
- status = int(parts[1]) if len(parts) >= 2 and parts[1].isdigit() else 502
242
- headers: list[tuple[bytes, bytes]] = []
243
- raw_headers: dict[bytes, bytes] = {}
244
- while True:
245
- line = await reader.readline()
246
- if line in (b"\r\n", b"\n", b""):
247
- break
248
- i = line.find(b":")
249
- if i == -1:
250
- continue
251
- name = line[:i].strip().lower()
252
- value = line[i + 1 :].strip()
253
- headers.append((name, value))
254
- raw_headers[name] = value
255
-
256
- return status, headers, raw_headers
257
-
258
- def is_chunked(self, te_value: bytes) -> bool:
259
- """Return True if transfer-encoding header tokens include 'chunked'."""
260
- if not te_value:
261
- return False
262
- # split on commas, strip spaces and check tokens
263
- tokens = [t.strip() for t in te_value.split(b",")]
264
- return any(t.lower() == b"chunked" for t in tokens)
265
-
266
- def parse_content_length(self, cl_value: bytes | None) -> int | None:
267
- """Parse Content-Length header value to int, or return None if invalid."""
268
- if cl_value is None:
269
- return None
270
- try:
271
- return int(cl_value)
272
- except Exception:
273
- return None
274
-
275
- async def drain_trailers(self, reader: asyncio.StreamReader) -> None:
276
- """Consume trailer header lines until an empty line is encountered."""
277
- while True:
278
- trailer = await reader.readline()
279
- if trailer in (b"\r\n", b"\n", b""):
280
- break
281
-
282
- async def stream_response_chunked(self, reader: asyncio.StreamReader, send: ASGISend) -> None:
283
- """Read chunked-encoded response from reader, decode and forward to ASGI send."""
284
- while True:
285
- size_line = await reader.readline()
286
- if not size_line:
287
- break
288
- size_str = size_line.split(b";", 1)[0].strip()
289
- try:
290
- size = int(size_str, 16)
291
- except Exception:
292
- break
293
- if size == 0:
294
- # consume trailers
295
- await self.drain_trailers(reader)
296
- break
297
- try:
298
- chunk = await reader.readexactly(size)
299
- except Exception:
300
- break
301
- # consume the CRLF after the chunk
302
- try:
303
- await reader.readexactly(2)
304
- except Exception:
305
- logger.warning("failed to read CRLF after chunk from backend")
306
- await send({"type": "http.response.body", "body": chunk, "more_body": True})
307
-
308
- await send({"type": "http.response.body", "body": b"", "more_body": False})
309
-
310
- async def stream_response_with_content_length(
311
- self, reader: asyncio.StreamReader, send: ASGISend, content_length: int
312
- ) -> None:
313
- """Read exactly content_length bytes and forward to ASGI send events."""
314
- remaining = content_length
315
- sent_final = False
316
- while remaining > 0:
317
- to_read = min(self._read_chunk_size, remaining)
318
- chunk = await reader.read(to_read)
319
- if not chunk:
320
- break
321
- remaining -= len(chunk)
322
- more = remaining > 0
323
- await send({"type": "http.response.body", "body": chunk, "more_body": more})
324
- if not more:
325
- sent_final = True
326
-
327
- if not sent_final:
328
- await send({"type": "http.response.body", "body": b"", "more_body": False})
329
-
330
- async def stream_response_until_eof(self, reader: asyncio.StreamReader, send: ASGISend) -> None:
331
- """Read from reader until EOF and forward chunks to ASGI send events."""
332
- while True:
333
- chunk = await reader.read(self._read_chunk_size)
334
- if not chunk:
335
- break
336
- await send({"type": "http.response.body", "body": chunk, "more_body": True})
337
- await send({"type": "http.response.body", "body": b"", "more_body": False})
338
-
339
- async def stream_response_body(
340
- self, reader: asyncio.StreamReader, send: ASGISend, raw_headers: dict[bytes, bytes]
341
- ) -> None:
342
- te = raw_headers.get(b"transfer-encoding", b"").lower()
343
- cl = raw_headers.get(b"content-length")
344
-
345
- if self.is_chunked(te):
346
- await self.stream_response_chunked(reader, send)
347
- return
348
-
349
- content_length = self.parse_content_length(cl)
350
- if content_length is not None:
351
- await self.stream_response_with_content_length(reader, send, content_length)
352
- return
353
-
354
- await self.stream_response_until_eof(reader, send)
mrok/http/lifespan.py DELETED
@@ -1,39 +0,0 @@
1
- import logging
2
- from collections.abc import Awaitable, Callable
3
-
4
- from uvicorn.config import Config
5
- from uvicorn.lifespan.on import LifespanOn
6
-
7
- AsyncCallback = Callable[[], Awaitable[None]]
8
-
9
-
10
- class MrokLifespan(LifespanOn):
11
- def __init__(self, config: Config) -> None:
12
- super().__init__(config)
13
- self.logger = logging.getLogger("mrok.proxy")
14
-
15
-
16
- class LifespanWrapper:
17
- def __init__(
18
- self, app, on_startup: AsyncCallback | None = None, on_shutdown: AsyncCallback | None = None
19
- ):
20
- self.app = app
21
- self.on_startup = on_startup
22
- self.on_shutdown = on_shutdown
23
-
24
- async def __call__(self, scope, receive, send):
25
- if scope["type"] == "lifespan":
26
- while True:
27
- event = await receive()
28
- if event["type"] == "lifespan.startup":
29
- if self.on_startup:
30
- await self.on_startup()
31
- await send({"type": "lifespan.startup.complete"})
32
-
33
- elif event["type"] == "lifespan.shutdown":
34
- if self.on_shutdown:
35
- await self.on_shutdown()
36
- await send({"type": "lifespan.shutdown.complete"})
37
- break
38
- else:
39
- await self.app(scope, receive, send)