zrok2-build-debugging 22.0.2000008__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.
- test/__init__.py +0 -0
- test/test_access201_response.py +52 -0
- test/test_access_request.py +54 -0
- test/test_access_summary.py +60 -0
- test/test_accesses_list.py +63 -0
- test/test_account_api.py +79 -0
- test/test_add_frontend_grant_request.py +52 -0
- test/test_add_namespace_frontend_mapping_request.py +53 -0
- test/test_add_namespace_grant_request.py +52 -0
- test/test_add_organization_member_request.py +53 -0
- test/test_admin_api.py +199 -0
- test/test_agent_api.py +85 -0
- test/test_auth_user.py +52 -0
- test/test_change_password_request.py +53 -0
- test/test_client_version_check_request.py +51 -0
- test/test_configuration.py +56 -0
- test/test_create_frontend201_response.py +51 -0
- test/test_create_frontend_request.py +55 -0
- test/test_create_identity201_response.py +52 -0
- test/test_create_identity_request.py +51 -0
- test/test_create_namespace201_response.py +51 -0
- test/test_create_namespace_request.py +54 -0
- test/test_create_organization201_response.py +51 -0
- test/test_create_organization_request.py +51 -0
- test/test_create_share_name_request.py +52 -0
- test/test_delete_identity_request.py +51 -0
- test/test_disable_request.py +51 -0
- test/test_enable_request.py +52 -0
- test/test_enroll200_response.py +51 -0
- test/test_enroll_request.py +51 -0
- test/test_environment.py +63 -0
- test/test_environment_and_resources.py +96 -0
- test/test_environment_api.py +43 -0
- test/test_environment_summary.py +60 -0
- test/test_environments_list.py +63 -0
- test/test_frontend.py +59 -0
- test/test_get_sparklines200_response.py +62 -0
- test/test_get_sparklines_request.py +57 -0
- test/test_invite_request.py +52 -0
- test/test_invite_token_generate_request.py +53 -0
- test/test_list_frontend_namespace_mappings200_response_inner.py +54 -0
- test/test_list_frontends200_response_inner.py +58 -0
- test/test_list_memberships200_response.py +56 -0
- test/test_list_memberships200_response_memberships_inner.py +53 -0
- test/test_list_namespaces200_response_inner.py +56 -0
- test/test_list_organization_members200_response.py +55 -0
- test/test_list_organization_members200_response_members_inner.py +52 -0
- test/test_list_organizations200_response.py +55 -0
- test/test_list_organizations200_response_organizations_inner.py +52 -0
- test/test_list_share_namespaces200_response_inner.py +53 -0
- test/test_login_request.py +52 -0
- test/test_metadata_api.py +145 -0
- test/test_metrics.py +59 -0
- test/test_metrics_sample.py +53 -0
- test/test_name.py +56 -0
- test/test_name_selection.py +52 -0
- test/test_overview.py +110 -0
- test/test_overview_names_inner.py +56 -0
- test/test_ping200_response.py +51 -0
- test/test_principal.py +55 -0
- test/test_regenerate_account_token200_response.py +51 -0
- test/test_regenerate_account_token_request.py +51 -0
- test/test_register_request.py +52 -0
- test/test_remote_access_request.py +60 -0
- test/test_remote_share200_response.py +54 -0
- test/test_remote_share_request.py +74 -0
- test/test_remote_status200_response.py +82 -0
- test/test_remote_status200_response_accesses_inner.py +62 -0
- test/test_remote_status200_response_shares_inner.py +64 -0
- test/test_remote_status200_response_shares_inner_failure.py +54 -0
- test/test_remote_unaccess_request.py +52 -0
- test/test_remote_unshare_request.py +52 -0
- test/test_remove_namespace_frontend_mapping_request.py +52 -0
- test/test_remove_organization_member_request.py +52 -0
- test/test_reset_password_request.py +52 -0
- test/test_share.py +67 -0
- test/test_share_api.py +103 -0
- test/test_share_http_healthcheck200_response.py +52 -0
- test/test_share_http_healthcheck_request.py +56 -0
- test/test_share_request.py +75 -0
- test/test_share_response.py +54 -0
- test/test_share_summary.py +62 -0
- test/test_shares_list.py +65 -0
- test/test_spark_data_sample.py +52 -0
- test/test_unaccess_request.py +53 -0
- test/test_unshare_request.py +52 -0
- test/test_update_access_request.py +53 -0
- test/test_update_frontend_request.py +55 -0
- test/test_update_namespace_request.py +55 -0
- test/test_update_share_name_request.py +53 -0
- test/test_update_share_request.py +57 -0
- test/test_verify200_response.py +51 -0
- test/test_verify_request.py +51 -0
- test/test_version_inventory200_response.py +51 -0
- zrok/__init__.py +4 -0
- zrok/_version.py +21 -0
- zrok/access.py +58 -0
- zrok/decor.py +33 -0
- zrok/dialer.py +10 -0
- zrok/environment/__init__.py +1 -0
- zrok/environment/dirs.py +32 -0
- zrok/environment/root.py +182 -0
- zrok/listener.py +29 -0
- zrok/model.py +75 -0
- zrok/overview.py +77 -0
- zrok/proxy.py +203 -0
- zrok/share.py +199 -0
- zrok2_build_debugging-22.0.2000008.dist-info/METADATA +24 -0
- zrok2_build_debugging-22.0.2000008.dist-info/RECORD +213 -0
- zrok2_build_debugging-22.0.2000008.dist-info/WHEEL +5 -0
- zrok2_build_debugging-22.0.2000008.dist-info/top_level.txt +3 -0
- zrok_api/__init__.py +231 -0
- zrok_api/api/__init__.py +10 -0
- zrok_api/api/account_api.py +2261 -0
- zrok_api/api/admin_api.py +7673 -0
- zrok_api/api/agent_api.py +2547 -0
- zrok_api/api/environment_api.py +589 -0
- zrok_api/api/metadata_api.py +5614 -0
- zrok_api/api/share_api.py +3321 -0
- zrok_api/api_client.py +801 -0
- zrok_api/api_response.py +21 -0
- zrok_api/configuration.py +602 -0
- zrok_api/exceptions.py +216 -0
- zrok_api/models/__init__.py +103 -0
- zrok_api/models/access201_response.py +89 -0
- zrok_api/models/access_request.py +93 -0
- zrok_api/models/access_summary.py +105 -0
- zrok_api/models/accesses_list.py +95 -0
- zrok_api/models/add_frontend_grant_request.py +89 -0
- zrok_api/models/add_namespace_frontend_mapping_request.py +91 -0
- zrok_api/models/add_namespace_grant_request.py +89 -0
- zrok_api/models/add_organization_member_request.py +91 -0
- zrok_api/models/auth_user.py +89 -0
- zrok_api/models/change_password_request.py +91 -0
- zrok_api/models/client_version_check_request.py +87 -0
- zrok_api/models/configuration.py +97 -0
- zrok_api/models/create_frontend201_response.py +87 -0
- zrok_api/models/create_frontend_request.py +105 -0
- zrok_api/models/create_identity201_response.py +89 -0
- zrok_api/models/create_identity_request.py +87 -0
- zrok_api/models/create_namespace201_response.py +87 -0
- zrok_api/models/create_namespace_request.py +93 -0
- zrok_api/models/create_organization201_response.py +87 -0
- zrok_api/models/create_organization_request.py +87 -0
- zrok_api/models/create_share_name_request.py +89 -0
- zrok_api/models/delete_identity_request.py +87 -0
- zrok_api/models/disable_request.py +87 -0
- zrok_api/models/enable_request.py +89 -0
- zrok_api/models/enroll200_response.py +87 -0
- zrok_api/models/enroll_request.py +87 -0
- zrok_api/models/environment.py +111 -0
- zrok_api/models/environment_and_resources.py +111 -0
- zrok_api/models/environment_summary.py +105 -0
- zrok_api/models/environments_list.py +95 -0
- zrok_api/models/frontend.py +103 -0
- zrok_api/models/get_sparklines200_response.py +95 -0
- zrok_api/models/get_sparklines_request.py +91 -0
- zrok_api/models/invite_request.py +89 -0
- zrok_api/models/invite_token_generate_request.py +87 -0
- zrok_api/models/list_frontend_namespace_mappings200_response_inner.py +93 -0
- zrok_api/models/list_frontends200_response_inner.py +101 -0
- zrok_api/models/list_memberships200_response.py +95 -0
- zrok_api/models/list_memberships200_response_memberships_inner.py +91 -0
- zrok_api/models/list_namespaces200_response_inner.py +97 -0
- zrok_api/models/list_organization_members200_response.py +95 -0
- zrok_api/models/list_organization_members200_response_members_inner.py +89 -0
- zrok_api/models/list_organizations200_response.py +95 -0
- zrok_api/models/list_organizations200_response_organizations_inner.py +89 -0
- zrok_api/models/list_share_namespaces200_response_inner.py +91 -0
- zrok_api/models/login_request.py +89 -0
- zrok_api/models/metrics.py +101 -0
- zrok_api/models/metrics_sample.py +91 -0
- zrok_api/models/name.py +97 -0
- zrok_api/models/name_selection.py +89 -0
- zrok_api/models/overview.py +117 -0
- zrok_api/models/overview_names_inner.py +97 -0
- zrok_api/models/ping200_response.py +87 -0
- zrok_api/models/principal.py +95 -0
- zrok_api/models/regenerate_account_token200_response.py +87 -0
- zrok_api/models/regenerate_account_token_request.py +87 -0
- zrok_api/models/register_request.py +89 -0
- zrok_api/models/remote_access_request.py +102 -0
- zrok_api/models/remote_share200_response.py +89 -0
- zrok_api/models/remote_share_request.py +141 -0
- zrok_api/models/remote_status200_response.py +105 -0
- zrok_api/models/remote_status200_response_accesses_inner.py +101 -0
- zrok_api/models/remote_status200_response_shares_inner.py +105 -0
- zrok_api/models/remote_status200_response_shares_inner_failure.py +93 -0
- zrok_api/models/remote_unaccess_request.py +89 -0
- zrok_api/models/remote_unshare_request.py +89 -0
- zrok_api/models/remove_namespace_frontend_mapping_request.py +89 -0
- zrok_api/models/remove_organization_member_request.py +89 -0
- zrok_api/models/reset_password_request.py +89 -0
- zrok_api/models/share.py +115 -0
- zrok_api/models/share_http_healthcheck200_response.py +89 -0
- zrok_api/models/share_http_healthcheck_request.py +97 -0
- zrok_api/models/share_request.py +157 -0
- zrok_api/models/share_response.py +89 -0
- zrok_api/models/share_summary.py +105 -0
- zrok_api/models/shares_list.py +95 -0
- zrok_api/models/spark_data_sample.py +89 -0
- zrok_api/models/unaccess_request.py +91 -0
- zrok_api/models/unshare_request.py +89 -0
- zrok_api/models/update_access_request.py +91 -0
- zrok_api/models/update_frontend_request.py +95 -0
- zrok_api/models/update_namespace_request.py +95 -0
- zrok_api/models/update_share_name_request.py +91 -0
- zrok_api/models/update_share_request.py +91 -0
- zrok_api/models/verify200_response.py +87 -0
- zrok_api/models/verify_request.py +87 -0
- zrok_api/models/version_inventory200_response.py +87 -0
- zrok_api/py.typed +0 -0
- zrok_api/rest.py +258 -0
zrok/model.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
BackendMode = str
|
|
4
|
+
|
|
5
|
+
PROXY_BACKEND_MODE: BackendMode = "proxy"
|
|
6
|
+
WEB_BACKEND_MODE: BackendMode = "web"
|
|
7
|
+
TCP_TUNNEL_BACKEND_MODE: BackendMode = "tcpTunnel"
|
|
8
|
+
UDP_TUNNEL_BACKEND_MODE: BackendMode = "udpTunnel"
|
|
9
|
+
CADDY_BACKEND_MODE: BackendMode = "caddy"
|
|
10
|
+
DRIVE_BACKEND_MODE: BackendMode = "drive"
|
|
11
|
+
SOCKS_BACKEND_MODE: BackendMode = "socks"
|
|
12
|
+
|
|
13
|
+
ShareMode = str
|
|
14
|
+
|
|
15
|
+
PRIVATE_SHARE_MODE: ShareMode = "private"
|
|
16
|
+
PUBLIC_SHARE_MODE: ShareMode = "public"
|
|
17
|
+
|
|
18
|
+
PermissionMode = str
|
|
19
|
+
|
|
20
|
+
OPEN_PERMISSION_MODE: PermissionMode = "open"
|
|
21
|
+
CLOSED_PERMISSION_MODE: PermissionMode = "closed"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ShareRequest:
|
|
26
|
+
BackendMode: BackendMode
|
|
27
|
+
ShareMode: ShareMode
|
|
28
|
+
Target: str
|
|
29
|
+
Frontends: list[str] = field(default_factory=list[str])
|
|
30
|
+
BasicAuth: list[str] = field(default_factory=list[str])
|
|
31
|
+
OauthProvider: str = ""
|
|
32
|
+
OauthEmailAddressPatterns: list[str] = field(default_factory=list[str])
|
|
33
|
+
OauthAuthorizationCheckInterval: str = ""
|
|
34
|
+
Reserved: bool = False
|
|
35
|
+
UniqueName: str = ""
|
|
36
|
+
PermissionMode: PermissionMode = OPEN_PERMISSION_MODE
|
|
37
|
+
AccessGrants: list[str] = field(default_factory=list[str])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class Share:
|
|
42
|
+
Token: str
|
|
43
|
+
FrontendEndpoints: list[str]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class AccessRequest:
|
|
48
|
+
ShareToken: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class Access:
|
|
53
|
+
Token: str
|
|
54
|
+
ShareToken: str
|
|
55
|
+
BackendMode: BackendMode
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class SessionMetrics:
|
|
60
|
+
BytesRead: int
|
|
61
|
+
BytesWritten: int
|
|
62
|
+
LastUpdate: int
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class Metrics:
|
|
67
|
+
Namespace: str
|
|
68
|
+
Sessions: dict[str, SessionMetrics]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
AuthScheme = str
|
|
72
|
+
|
|
73
|
+
AUTH_SCHEME_NONE: AuthScheme = "none"
|
|
74
|
+
AUTH_SCHEME_BASIC: AuthScheme = "basic"
|
|
75
|
+
AUTH_SCHEME_OAUTH: AuthScheme = "oauth"
|
zrok/overview.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
import urllib3
|
|
6
|
+
from zrok.environment.root import Root
|
|
7
|
+
from zrok_api.models.environment import Environment
|
|
8
|
+
from zrok_api.models.environment_and_resources import EnvironmentAndResources
|
|
9
|
+
from zrok_api.models.share import Share
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Overview:
|
|
14
|
+
environments: List[EnvironmentAndResources] = field(default_factory=list)
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def create(cls, root: Root) -> 'Overview':
|
|
18
|
+
if not root.IsEnabled():
|
|
19
|
+
raise Exception("environment is not enabled; enable with 'zrok enable' first!")
|
|
20
|
+
|
|
21
|
+
http = urllib3.PoolManager()
|
|
22
|
+
apiEndpoint = root.ApiEndpoint().endpoint
|
|
23
|
+
try:
|
|
24
|
+
response = http.request(
|
|
25
|
+
'GET',
|
|
26
|
+
apiEndpoint + "/api/v2/overview",
|
|
27
|
+
headers={
|
|
28
|
+
"X-TOKEN": root.env.Token
|
|
29
|
+
})
|
|
30
|
+
except Exception as e:
|
|
31
|
+
raise Exception("unable to get account overview", e)
|
|
32
|
+
|
|
33
|
+
json_data = json.loads(response.data.decode('utf-8'))
|
|
34
|
+
overview = cls()
|
|
35
|
+
|
|
36
|
+
for env_data in json_data.get('environments', []):
|
|
37
|
+
env_dict = env_data['environment']
|
|
38
|
+
# Map the JSON keys to the Environment class parameters
|
|
39
|
+
environment = Environment(
|
|
40
|
+
description=env_dict.get('description'),
|
|
41
|
+
host=env_dict.get('host'),
|
|
42
|
+
address=env_dict.get('address'),
|
|
43
|
+
z_id=env_dict.get('zId'),
|
|
44
|
+
activity=env_dict.get('activity'),
|
|
45
|
+
limited=env_dict.get('limited'),
|
|
46
|
+
created_at=env_dict.get('createdAt'),
|
|
47
|
+
updated_at=env_dict.get('updatedAt')
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Create Shares object from share data
|
|
51
|
+
share_list = []
|
|
52
|
+
for share_data in env_data.get('shares', []):
|
|
53
|
+
share = Share(
|
|
54
|
+
share_token=share_data.get('shareToken'),
|
|
55
|
+
z_id=share_data.get('zId'),
|
|
56
|
+
share_mode=share_data.get('shareMode'),
|
|
57
|
+
backend_mode=share_data.get('backendMode'),
|
|
58
|
+
frontend_selection=share_data.get('frontendSelection'),
|
|
59
|
+
frontend_endpoint=share_data.get('frontendEndpoint'),
|
|
60
|
+
backend_proxy_endpoint=share_data.get('backendProxyEndpoint'),
|
|
61
|
+
reserved=share_data.get('reserved'),
|
|
62
|
+
activity=share_data.get('activity'),
|
|
63
|
+
limited=share_data.get('limited'),
|
|
64
|
+
created_at=share_data.get('createdAt'),
|
|
65
|
+
updated_at=share_data.get('updatedAt')
|
|
66
|
+
)
|
|
67
|
+
share_list.append(share)
|
|
68
|
+
|
|
69
|
+
# Create EnvironmentAndResources object
|
|
70
|
+
env_resources = EnvironmentAndResources(
|
|
71
|
+
environment=environment,
|
|
72
|
+
shares=share_list,
|
|
73
|
+
frontends=[]
|
|
74
|
+
)
|
|
75
|
+
overview.environments.append(env_resources)
|
|
76
|
+
|
|
77
|
+
return overview
|
zrok/proxy.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Proxy share management functionality for the zrok SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import atexit
|
|
6
|
+
import logging
|
|
7
|
+
import urllib.parse
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
from flask import Flask, Response, request
|
|
13
|
+
from waitress import serve
|
|
14
|
+
from zrok.environment.root import Root
|
|
15
|
+
from zrok.model import (PROXY_BACKEND_MODE, PUBLIC_SHARE_MODE, PRIVATE_SHARE_MODE, Share,
|
|
16
|
+
ShareRequest)
|
|
17
|
+
from zrok.overview import Overview
|
|
18
|
+
from zrok.share import CreateShare, ReleaseReservedShare
|
|
19
|
+
|
|
20
|
+
import zrok
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
# List of hop-by-hop headers that should not be returned to the viewer
|
|
25
|
+
HOP_BY_HOP_HEADERS = {
|
|
26
|
+
'connection',
|
|
27
|
+
'keep-alive',
|
|
28
|
+
'proxy-authenticate',
|
|
29
|
+
'proxy-authorization',
|
|
30
|
+
'te',
|
|
31
|
+
'trailers',
|
|
32
|
+
'transfer-encoding',
|
|
33
|
+
'upgrade'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# The proxy only listens on the zrok socket, the port used to initialize the Waitress server is not actually bound or
|
|
37
|
+
# listening
|
|
38
|
+
DUMMY_PORT = 18081
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ProxyShare:
|
|
43
|
+
"""Represents a proxy share with its configuration and state."""
|
|
44
|
+
root: Root
|
|
45
|
+
share: Share
|
|
46
|
+
target: str
|
|
47
|
+
unique_name: Optional[str] = None
|
|
48
|
+
_cleanup_registered: bool = False
|
|
49
|
+
_app: Optional[Flask] = None
|
|
50
|
+
verify_ssl: bool = True
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def create(cls, root: Root, target: str, share_mode: str = PUBLIC_SHARE_MODE, unique_name: Optional[str] = None,
|
|
54
|
+
frontends: Optional[List[str]] = None, verify_ssl: bool = True) -> 'ProxyShare':
|
|
55
|
+
"""
|
|
56
|
+
Create a new proxy share, handling reservation and cleanup logic based on unique_name.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
root: The zrok root environment
|
|
60
|
+
target: Target URL or service to proxy to
|
|
61
|
+
unique_name: Optional unique name for a reserved share
|
|
62
|
+
frontends: Optional list of frontends to use, takes precedence over root's default_frontend
|
|
63
|
+
verify_ssl: Whether to verify SSL certificates when forwarding requests.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
ProxyShare instance configured with the created share
|
|
67
|
+
"""
|
|
68
|
+
# First check if we have an existing reserved share with this name
|
|
69
|
+
if unique_name:
|
|
70
|
+
existing_share = cls._find_existing_share(root, unique_name)
|
|
71
|
+
if existing_share:
|
|
72
|
+
logger.debug(f"Found existing share with token: {existing_share.Token}")
|
|
73
|
+
return cls(
|
|
74
|
+
root=root,
|
|
75
|
+
share=existing_share,
|
|
76
|
+
target=target,
|
|
77
|
+
unique_name=unique_name,
|
|
78
|
+
verify_ssl=verify_ssl
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Compose the share request
|
|
82
|
+
share_frontends = []
|
|
83
|
+
if share_mode == PUBLIC_SHARE_MODE:
|
|
84
|
+
if frontends:
|
|
85
|
+
share_frontends = frontends
|
|
86
|
+
elif root.cfg and root.cfg.DefaultFrontend:
|
|
87
|
+
share_frontends = [root.cfg.DefaultFrontend]
|
|
88
|
+
else:
|
|
89
|
+
share_frontends = ['public']
|
|
90
|
+
|
|
91
|
+
share_request = ShareRequest(
|
|
92
|
+
BackendMode=PROXY_BACKEND_MODE,
|
|
93
|
+
ShareMode=share_mode,
|
|
94
|
+
Target=target,
|
|
95
|
+
Frontends=share_frontends,
|
|
96
|
+
Reserved=True
|
|
97
|
+
)
|
|
98
|
+
if unique_name:
|
|
99
|
+
share_request.UniqueName = unique_name
|
|
100
|
+
|
|
101
|
+
# Create the share
|
|
102
|
+
share = CreateShare(root=root, request=share_request)
|
|
103
|
+
if share_mode == PUBLIC_SHARE_MODE:
|
|
104
|
+
logger.debug(f"Created new proxy share with endpoints: {', '.join(share.FrontendEndpoints)}")
|
|
105
|
+
elif share_mode == PRIVATE_SHARE_MODE:
|
|
106
|
+
logger.debug(f"Created new private share with token: {share.Token}")
|
|
107
|
+
|
|
108
|
+
# Create class instance and setup cleanup-at-exit if we reserved a random share token
|
|
109
|
+
instance = cls(
|
|
110
|
+
root=root,
|
|
111
|
+
share=share,
|
|
112
|
+
target=target,
|
|
113
|
+
unique_name=unique_name,
|
|
114
|
+
verify_ssl=verify_ssl
|
|
115
|
+
)
|
|
116
|
+
if not unique_name:
|
|
117
|
+
instance.register_cleanup()
|
|
118
|
+
return instance
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def _find_existing_share(root: Root, unique_name: str) -> Optional[Share]:
|
|
122
|
+
"""Find an existing share with the given unique name."""
|
|
123
|
+
overview = Overview.create(root=root)
|
|
124
|
+
for env in overview.environments:
|
|
125
|
+
if env.environment.z_id == root.env.ZitiIdentity:
|
|
126
|
+
for share in env.shares:
|
|
127
|
+
if share.share_token == unique_name:
|
|
128
|
+
return Share(Token=share.share_token, FrontendEndpoints=[share.frontend_endpoint])
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
def register_cleanup(self):
|
|
132
|
+
"""Register cleanup handler to release randomly generated shares on exit."""
|
|
133
|
+
if not self._cleanup_registered:
|
|
134
|
+
def cleanup():
|
|
135
|
+
try:
|
|
136
|
+
ReleaseReservedShare(root=self.root, shr=self.share)
|
|
137
|
+
logger.info(f"Share {self.share.Token} released")
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.error(f"Error during cleanup: {e}")
|
|
140
|
+
|
|
141
|
+
# Register for normal exit only
|
|
142
|
+
atexit.register(cleanup)
|
|
143
|
+
self._cleanup_registered = True
|
|
144
|
+
return cleanup # Return the cleanup function for reuse
|
|
145
|
+
|
|
146
|
+
def _create_app(self) -> Flask:
|
|
147
|
+
"""Create and configure the Flask app for proxying."""
|
|
148
|
+
app = Flask(__name__)
|
|
149
|
+
|
|
150
|
+
@app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
|
|
151
|
+
@app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
|
|
152
|
+
def proxy(path):
|
|
153
|
+
# Construct the target URL
|
|
154
|
+
url = urllib.parse.urljoin(self.target, path)
|
|
155
|
+
|
|
156
|
+
# Forward the request
|
|
157
|
+
resp = requests.request(
|
|
158
|
+
method=request.method,
|
|
159
|
+
url=url,
|
|
160
|
+
headers={key: value for (key, value) in request.headers
|
|
161
|
+
if key.lower() not in HOP_BY_HOP_HEADERS},
|
|
162
|
+
data=request.get_data(),
|
|
163
|
+
cookies=request.cookies,
|
|
164
|
+
allow_redirects=False,
|
|
165
|
+
stream=True,
|
|
166
|
+
verify=self.verify_ssl
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Create the response
|
|
170
|
+
excluded_headers = HOP_BY_HOP_HEADERS.union({'host'})
|
|
171
|
+
headers = [(name, value) for (name, value) in resp.raw.headers.items()
|
|
172
|
+
if name.lower() not in excluded_headers]
|
|
173
|
+
|
|
174
|
+
return Response(
|
|
175
|
+
resp.iter_content(chunk_size=10*1024),
|
|
176
|
+
status=resp.status_code,
|
|
177
|
+
headers=headers
|
|
178
|
+
)
|
|
179
|
+
return app
|
|
180
|
+
|
|
181
|
+
def run(self):
|
|
182
|
+
"""Run the proxy server."""
|
|
183
|
+
if not self._app:
|
|
184
|
+
self._app = self._create_app()
|
|
185
|
+
|
|
186
|
+
zrok_opts: Dict[str, Any] = {}
|
|
187
|
+
zrok_opts['cfg'] = zrok.decor.Opts(root=self.root, shrToken=self.token, bindPort=DUMMY_PORT)
|
|
188
|
+
|
|
189
|
+
@zrok.decor.zrok(opts=zrok_opts)
|
|
190
|
+
def run_server():
|
|
191
|
+
serve(self._app, port=DUMMY_PORT, _quiet=True)
|
|
192
|
+
|
|
193
|
+
run_server()
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def endpoints(self) -> List[str]:
|
|
197
|
+
"""Get the frontend endpoints for this share."""
|
|
198
|
+
return self.share.FrontendEndpoints
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def token(self) -> str:
|
|
202
|
+
"""Get the share token."""
|
|
203
|
+
return self.share.Token
|
zrok/share.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
from zrok_api.api import ShareApi
|
|
2
|
+
from zrok.environment.root import Root
|
|
3
|
+
from zrok_api.models.auth_user import AuthUser
|
|
4
|
+
from zrok_api.models.share_request import ShareRequest
|
|
5
|
+
from zrok_api.models.unshare_request import UnshareRequest
|
|
6
|
+
import json
|
|
7
|
+
from zrok_api.exceptions import ApiException
|
|
8
|
+
import zrok.model as model
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Share():
|
|
12
|
+
root: Root
|
|
13
|
+
request: model.ShareRequest
|
|
14
|
+
share: model.Share
|
|
15
|
+
|
|
16
|
+
def __init__(self, root: Root, request: model.ShareRequest):
|
|
17
|
+
self.root = root
|
|
18
|
+
self.request = request
|
|
19
|
+
|
|
20
|
+
def __enter__(self) -> model.Share:
|
|
21
|
+
self.share = CreateShare(root=self.root, request=self.request)
|
|
22
|
+
return self.share
|
|
23
|
+
|
|
24
|
+
def __exit__(self, exception_type, exception_value, exception_traceback):
|
|
25
|
+
if not self.request.Reserved:
|
|
26
|
+
DeleteShare(root=self.root, shr=self.share)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def CreateShare(root: Root, request: model.ShareRequest) -> model.Share:
|
|
30
|
+
if not root.IsEnabled():
|
|
31
|
+
raise Exception("environment is not enabled; enable with 'zrok enable' first!")
|
|
32
|
+
|
|
33
|
+
match request.ShareMode:
|
|
34
|
+
case model.PRIVATE_SHARE_MODE:
|
|
35
|
+
out = __newPrivateShare(root, request)
|
|
36
|
+
case model.PUBLIC_SHARE_MODE:
|
|
37
|
+
out = __newPublicShare(root, request)
|
|
38
|
+
case _:
|
|
39
|
+
raise Exception("unknown share mode " + request.ShareMode)
|
|
40
|
+
out.reserved = request.Reserved
|
|
41
|
+
if request.Reserved:
|
|
42
|
+
out.unique_name = request.UniqueName
|
|
43
|
+
|
|
44
|
+
if len(request.BasicAuth) > 0:
|
|
45
|
+
out.auth_scheme = model.AUTH_SCHEME_BASIC
|
|
46
|
+
for pair in request.BasicAuth:
|
|
47
|
+
tokens = pair.split(":")
|
|
48
|
+
if len(tokens) == 2:
|
|
49
|
+
out.auth_users.append(AuthUser(username=tokens[0].strip(), password=tokens[1].strip()))
|
|
50
|
+
else:
|
|
51
|
+
raise Exception("invalid username:password pair: " + pair)
|
|
52
|
+
|
|
53
|
+
if request.OauthProvider != "":
|
|
54
|
+
out.auth_scheme = model.AUTH_SCHEME_OAUTH
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
zrok = root.Client()
|
|
58
|
+
except Exception as e:
|
|
59
|
+
raise Exception("error getting zrok client", e)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
# Use share_with_http_info to get access to the HTTP info and handle custom response format
|
|
63
|
+
share_api = ShareApi(zrok)
|
|
64
|
+
# Add Accept header to handle the custom content type
|
|
65
|
+
custom_headers = {
|
|
66
|
+
'Accept': 'application/json, application/zrok.v1+json'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
response_data = share_api.share_with_http_info(
|
|
70
|
+
body=out,
|
|
71
|
+
_headers=custom_headers
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Parse response
|
|
75
|
+
if hasattr(response_data, 'data') and response_data.data is not None:
|
|
76
|
+
res = response_data.data
|
|
77
|
+
else:
|
|
78
|
+
raise Exception("invalid response from server")
|
|
79
|
+
|
|
80
|
+
except ApiException as e:
|
|
81
|
+
# If it's a content type error, try to parse the raw JSON
|
|
82
|
+
if "Unsupported content type: application/zrok.v1+json" in str(e) and hasattr(e, 'body'):
|
|
83
|
+
try:
|
|
84
|
+
# Parse the response body directly
|
|
85
|
+
res_dict = json.loads(e.body)
|
|
86
|
+
# Create a response object with the expected fields
|
|
87
|
+
|
|
88
|
+
class ShareResponse:
|
|
89
|
+
def __init__(self, share_token, frontend_proxy_endpoints):
|
|
90
|
+
self.share_token = share_token
|
|
91
|
+
self.frontend_proxy_endpoints = frontend_proxy_endpoints
|
|
92
|
+
|
|
93
|
+
res = ShareResponse(
|
|
94
|
+
share_token=res_dict.get('shareToken', ''),
|
|
95
|
+
frontend_proxy_endpoints=res_dict.get('frontendProxyEndpoints', [])
|
|
96
|
+
)
|
|
97
|
+
except (json.JSONDecodeError, ValueError, AttributeError) as json_err:
|
|
98
|
+
raise Exception(f"unable to parse API response: {str(json_err)}") from e
|
|
99
|
+
else:
|
|
100
|
+
raise Exception("unable to create share", e)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
raise Exception("unable to create share", e)
|
|
103
|
+
|
|
104
|
+
return model.Share(Token=res.share_token,
|
|
105
|
+
FrontendEndpoints=res.frontend_proxy_endpoints)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def __newPrivateShare(root: Root, request: model.ShareRequest) -> ShareRequest:
|
|
109
|
+
return ShareRequest(env_zid=root.env.ZitiIdentity,
|
|
110
|
+
share_mode=request.ShareMode,
|
|
111
|
+
backend_mode=request.BackendMode,
|
|
112
|
+
backend_proxy_endpoint=request.Target,
|
|
113
|
+
auth_scheme=model.AUTH_SCHEME_NONE,
|
|
114
|
+
permission_mode=request.PermissionMode,
|
|
115
|
+
access_grants=request.AccessGrants
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def __newPublicShare(root: Root, request: model.ShareRequest) -> ShareRequest:
|
|
120
|
+
ret = ShareRequest(env_zid=root.env.ZitiIdentity,
|
|
121
|
+
share_mode=request.ShareMode,
|
|
122
|
+
frontend_selection=request.Frontends,
|
|
123
|
+
backend_mode=request.BackendMode,
|
|
124
|
+
backend_proxy_endpoint=request.Target,
|
|
125
|
+
auth_scheme=model.AUTH_SCHEME_NONE,
|
|
126
|
+
oauth_email_domains=request.OauthEmailAddressPatterns,
|
|
127
|
+
oauth_authorization_check_interval=request.OauthAuthorizationCheckInterval,
|
|
128
|
+
permission_mode=request.PermissionMode,
|
|
129
|
+
access_grants=request.AccessGrants
|
|
130
|
+
)
|
|
131
|
+
if request.OauthProvider != "":
|
|
132
|
+
ret.oauth_provider = request.OauthProvider
|
|
133
|
+
|
|
134
|
+
return ret
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def DeleteShare(root: Root, shr: model.Share):
|
|
138
|
+
req = UnshareRequest(env_zid=root.env.ZitiIdentity,
|
|
139
|
+
share_token=shr.Token)
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
zrok = root.Client()
|
|
143
|
+
except Exception as e:
|
|
144
|
+
raise Exception("error getting zrok client", e)
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
# Add Accept header to handle the custom content type
|
|
148
|
+
share_api = ShareApi(zrok)
|
|
149
|
+
custom_headers = {
|
|
150
|
+
'Accept': 'application/json, application/zrok.v1+json'
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# Use unshare_with_http_info to get access to the HTTP info
|
|
154
|
+
share_api.unshare_with_http_info(
|
|
155
|
+
body=req,
|
|
156
|
+
_headers=custom_headers
|
|
157
|
+
)
|
|
158
|
+
except ApiException as e:
|
|
159
|
+
# If it's a content type error but the operation was likely successful, don't propagate the error
|
|
160
|
+
if "Unsupported content type: application/zrok.v1+json" in str(e) and (200 <= e.status <= 299):
|
|
161
|
+
# The operation was likely successful despite the content type error
|
|
162
|
+
pass
|
|
163
|
+
else:
|
|
164
|
+
raise Exception("error deleting share", e)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
raise Exception("error deleting share", e)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def ReleaseReservedShare(root: Root, shr: model.Share):
|
|
170
|
+
req = UnshareRequest(env_zid=root.env.ZitiIdentity,
|
|
171
|
+
share_token=shr.Token,
|
|
172
|
+
reserved=True)
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
zrok = root.Client()
|
|
176
|
+
except Exception as e:
|
|
177
|
+
raise Exception("error getting zrok client", e)
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
# Add Accept header to handle the custom content type
|
|
181
|
+
share_api = ShareApi(zrok)
|
|
182
|
+
custom_headers = {
|
|
183
|
+
'Accept': 'application/json, application/zrok.v1+json'
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Use unshare_with_http_info to get access to the HTTP info
|
|
187
|
+
share_api.unshare_with_http_info(
|
|
188
|
+
body=req,
|
|
189
|
+
_headers=custom_headers
|
|
190
|
+
)
|
|
191
|
+
except ApiException as e:
|
|
192
|
+
# If it's a content type error but the operation was likely successful, don't propagate the error
|
|
193
|
+
if "Unsupported content type: application/zrok.v1+json" in str(e) and (200 <= e.status <= 299):
|
|
194
|
+
# The operation was likely successful despite the content type error
|
|
195
|
+
pass
|
|
196
|
+
else:
|
|
197
|
+
raise Exception("error releasing share", e)
|
|
198
|
+
except Exception as e:
|
|
199
|
+
raise Exception("error releasing share", e)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zrok2-build-debugging
|
|
3
|
+
Version: 22.0.2000008
|
|
4
|
+
Summary: zrok2
|
|
5
|
+
Home-page: https://github.com/openziti/zrok
|
|
6
|
+
Author: NetFoundry
|
|
7
|
+
Author-email: developers@openziti.org
|
|
8
|
+
License: Apache 2.0
|
|
9
|
+
Project-URL: Source, https://github.com/openziti/zrok
|
|
10
|
+
Project-URL: Tracker, https://github.com/openziti/zrok/issues
|
|
11
|
+
Project-URL: Discussion, https://openziti.discourse.group/
|
|
12
|
+
Keywords: zrok,zrok2
|
|
13
|
+
Requires-Python: >3.10.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: openziti>=1.0.0
|
|
16
|
+
Requires-Dist: urllib3>=2.1.0
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: keywords
|
|
19
|
+
Dynamic: requires-dist
|
|
20
|
+
Dynamic: requires-python
|
|
21
|
+
Dynamic: summary
|
|
22
|
+
|
|
23
|
+
Geo-scale, next-generation peer-to-peer sharing platform built on top of OpenZiti.
|
|
24
|
+
|