ostium-ctl 0.1.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 (203) hide show
  1. ostium/__init__.py +25 -0
  2. ostium/__main__.py +4 -0
  3. ostium/_version.py +24 -0
  4. ostium/acme/__init__.py +0 -0
  5. ostium/acme/client.py +202 -0
  6. ostium/api/__init__.py +0 -0
  7. ostium/api/app.py +529 -0
  8. ostium/api/deps.py +142 -0
  9. ostium/api/errors.py +45 -0
  10. ostium/api/ratelimit.py +30 -0
  11. ostium/api/routes_alerts.py +36 -0
  12. ostium/api/routes_audit.py +25 -0
  13. ostium/api/routes_auth.py +361 -0
  14. ostium/api/routes_backups.py +166 -0
  15. ostium/api/routes_certs.py +284 -0
  16. ostium/api/routes_events.py +65 -0
  17. ostium/api/routes_inbounds.py +254 -0
  18. ostium/api/routes_jobs.py +51 -0
  19. ostium/api/routes_logs.py +56 -0
  20. ostium/api/routes_profiles.py +281 -0
  21. ostium/api/routes_rules.py +316 -0
  22. ostium/api/routes_settings.py +319 -0
  23. ostium/api/routes_stats.py +172 -0
  24. ostium/api/routes_subscriptions.py +59 -0
  25. ostium/api/routes_system.py +516 -0
  26. ostium/api/routes_users.py +423 -0
  27. ostium/cli/__init__.py +0 -0
  28. ostium/cli/main.py +1399 -0
  29. ostium/config.py +381 -0
  30. ostium/core/__init__.py +0 -0
  31. ostium/core/app.py +231 -0
  32. ostium/db/__init__.py +0 -0
  33. ostium/db/base.py +34 -0
  34. ostium/db/models.py +261 -0
  35. ostium/db/session.py +86 -0
  36. ostium/domain/__init__.py +0 -0
  37. ostium/domain/endpoints.py +313 -0
  38. ostium/engine/__init__.py +0 -0
  39. ostium/engine/core_api.py +122 -0
  40. ostium/engine/manager.py +277 -0
  41. ostium/engine/singbox/__init__.py +0 -0
  42. ostium/engine/singbox/clash_api.py +71 -0
  43. ostium/engine/singbox/config.py +485 -0
  44. ostium/engine/singbox/supervisor.py +394 -0
  45. ostium/engine/singbox/v2ray_stats.py +79 -0
  46. ostium/engine/singbox/v2ray_stats_pb2.py +46 -0
  47. ostium/engine/singbox/v2ray_stats_pb2_grpc.py +199 -0
  48. ostium/engine/singbox/validate.py +259 -0
  49. ostium/py.typed +0 -0
  50. ostium/services/__init__.py +0 -0
  51. ostium/services/audit.py +136 -0
  52. ostium/services/auth.py +342 -0
  53. ostium/services/backup.py +284 -0
  54. ostium/services/certificates.py +293 -0
  55. ostium/services/crypto.py +26 -0
  56. ostium/services/events.py +70 -0
  57. ostium/services/jobs.py +195 -0
  58. ostium/services/profiles.py +359 -0
  59. ostium/services/rules.py +133 -0
  60. ostium/services/settings.py +33 -0
  61. ostium/services/stats.py +293 -0
  62. ostium/services/subscriptions.py +141 -0
  63. ostium/services/users.py +354 -0
  64. ostium/system/__init__.py +0 -0
  65. ostium/system/proxy.py +574 -0
  66. ostium/system/windows_service.py +94 -0
  67. ostium/util/__init__.py +0 -0
  68. ostium/util/ids.py +33 -0
  69. ostium/util/redact.py +58 -0
  70. ostium/util/security.py +200 -0
  71. ostium/util/time.py +39 -0
  72. ostium/web/__init__.py +0 -0
  73. ostium/web/dist/.gitkeep +1 -0
  74. ostium/web/dist/assets/AppLayout-CnHo_1IA.css +1 -0
  75. ostium/web/dist/assets/AppLayout-TkOzyZjP.js +124 -0
  76. ostium/web/dist/assets/AuditPage-DKc-SkEr.css +1 -0
  77. ostium/web/dist/assets/AuditPage-DRMlsiOS.js +1 -0
  78. ostium/web/dist/assets/CertificatesPage-BALPvxEc.css +1 -0
  79. ostium/web/dist/assets/CertificatesPage-PoBGQlnF.js +1 -0
  80. ostium/web/dist/assets/CheckboxGroup-DaQGiz7w.js +105 -0
  81. ostium/web/dist/assets/ConnectionsPage-4L_Y9VaF.js +1 -0
  82. ostium/web/dist/assets/ConnectionsPage-BGHvE-XG.css +1 -0
  83. ostium/web/dist/assets/DiagnosticsPage-GBMdduQn.js +1 -0
  84. ostium/web/dist/assets/DiagnosticsPage-chvywnsd.css +1 -0
  85. ostium/web/dist/assets/Dropdown-CwXzstQE.js +125 -0
  86. ostium/web/dist/assets/HomeGate-3C_4sckV.css +1 -0
  87. ostium/web/dist/assets/HomeGate-D4c3bUDh.js +1 -0
  88. ostium/web/dist/assets/InboundsPage-DhR9BG0G.js +1 -0
  89. ostium/web/dist/assets/InboundsPage-Du4FhhK7.css +1 -0
  90. ostium/web/dist/assets/InputNumber-je36FpXB.js +13 -0
  91. ostium/web/dist/assets/JobsPage-BfbdymTp.css +1 -0
  92. ostium/web/dist/assets/JobsPage-CELbAvc8.js +1 -0
  93. ostium/web/dist/assets/LoginPage-B0c_xiRn.css +1 -0
  94. ostium/web/dist/assets/LoginPage-D1M5lp_t.js +110 -0
  95. ostium/web/dist/assets/LogsPage-BDLfX0KX.js +1 -0
  96. ostium/web/dist/assets/LogsPage-BLPPfggF.css +1 -0
  97. ostium/web/dist/assets/NetworkPage-B83yUZtL.css +1 -0
  98. ostium/web/dist/assets/NetworkPage-DMjbNN8w.js +1 -0
  99. ostium/web/dist/assets/NotFoundPage-4Eqar04L.js +1 -0
  100. ostium/web/dist/assets/NotFoundPage-eEit74Eg.css +1 -0
  101. ostium/web/dist/assets/OBrandMark-B6ptsQ9Y.js +1 -0
  102. ostium/web/dist/assets/OBrandMark-CVWmop63.css +1 -0
  103. ostium/web/dist/assets/OCopyButton-B-oqlfqz.css +1 -0
  104. ostium/web/dist/assets/OCopyButton-B0BdYJ8p.js +1 -0
  105. ostium/web/dist/assets/OEmptyState-C8WDHXJd.css +1 -0
  106. ostium/web/dist/assets/OEmptyState-CuNuiClw.js +1 -0
  107. ostium/web/dist/assets/OErrorState-C3LiX1aU.js +1 -0
  108. ostium/web/dist/assets/OErrorState-C3iaLxYh.css +1 -0
  109. ostium/web/dist/assets/OLazyChart-BxXcJLKv.css +1 -0
  110. ostium/web/dist/assets/OLazyChart-CPui9WML.js +2 -0
  111. ostium/web/dist/assets/OPageHeader-BpyY4eEc.js +1 -0
  112. ostium/web/dist/assets/OPageHeader-BsAKYTkr.css +1 -0
  113. ostium/web/dist/assets/OQrModal-BBvUIRXZ.css +1 -0
  114. ostium/web/dist/assets/OQrModal-DRzK9PFl.js +1 -0
  115. ostium/web/dist/assets/OSectionCard-BEbjsw4P.css +1 -0
  116. ostium/web/dist/assets/OSectionCard-Dwixcexc.js +1 -0
  117. ostium/web/dist/assets/OStatCard-DMMkumPu.css +1 -0
  118. ostium/web/dist/assets/OStatCard-DihjcKRH.js +1 -0
  119. ostium/web/dist/assets/OTrafficChart-BmqMasLG.js +19 -0
  120. ostium/web/dist/assets/OTrafficChart-yKTAaNwo.css +1 -0
  121. ostium/web/dist/assets/OnboardingPage-CnC3Tkn8.js +1 -0
  122. ostium/web/dist/assets/OnboardingPage-Dao3P5YR.css +1 -0
  123. ostium/web/dist/assets/ProfilesPage-BW82LDzL.js +1 -0
  124. ostium/web/dist/assets/ProfilesPage-Bf9lzS6J.css +1 -0
  125. ostium/web/dist/assets/RadioGroup-DjGmk7eo.js +153 -0
  126. ostium/web/dist/assets/RoutingPage-CHDNOmbs.css +1 -0
  127. ostium/web/dist/assets/RoutingPage-TELCLabr.js +1 -0
  128. ostium/web/dist/assets/RulesPage-DM47ToQK.js +1 -0
  129. ostium/web/dist/assets/RulesPage-DeW2Bp-B.css +1 -0
  130. ostium/web/dist/assets/Select-IxrvO84D.js +410 -0
  131. ostium/web/dist/assets/SettingsPage-01lhFrbF.css +1 -0
  132. ostium/web/dist/assets/SettingsPage-CdaYFH0Z.js +1 -0
  133. ostium/web/dist/assets/SetupPage-B5T7fne1.css +1 -0
  134. ostium/web/dist/assets/SetupPage-BWRiRx0N.js +1 -0
  135. ostium/web/dist/assets/StatsPage-C_J3rnv5.css +1 -0
  136. ostium/web/dist/assets/StatsPage-CiP7SXHY.js +1 -0
  137. ostium/web/dist/assets/SubscriptionsPage-BSecexRk.css +1 -0
  138. ostium/web/dist/assets/SubscriptionsPage-Cdq6Y3T2.js +1 -0
  139. ostium/web/dist/assets/Switch-CX9oUrD6.js +102 -0
  140. ostium/web/dist/assets/SystemPage-Deu_D2H5.css +1 -0
  141. ostium/web/dist/assets/SystemPage-DqesWfD8.js +1 -0
  142. ostium/web/dist/assets/UsersPage-CDpf--BF.js +362 -0
  143. ostium/web/dist/assets/UsersPage-DtGDcfjr.css +1 -0
  144. ostium/web/dist/assets/_plugin-vue_export-helper-CtsCrH4a.js +3 -0
  145. ostium/web/dist/assets/arrow-left-D_hTaPYD.js +1 -0
  146. ostium/web/dist/assets/arrow-up-from-line-C0pEYqml.js +1 -0
  147. ostium/web/dist/assets/calendar-clock-DiHESNVh.js +1 -0
  148. ostium/web/dist/assets/check-DGkXO7lB.js +1 -0
  149. ostium/web/dist/assets/compass-L-dwGuZp.js +1 -0
  150. ostium/web/dist/assets/composables-BsF0VAcy.js +1 -0
  151. ostium/web/dist/assets/create-mR1YnL_8.js +117 -0
  152. ostium/web/dist/assets/createLucideIcon-CWlB5tHO.js +45 -0
  153. ostium/web/dist/assets/css-HHsd4Dgi.js +1 -0
  154. ostium/web/dist/assets/database-Cq48lz38.js +1 -0
  155. ostium/web/dist/assets/download-58fxtqjE.js +1 -0
  156. ostium/web/dist/assets/earth-BmbhRGrA.js +1 -0
  157. ostium/web/dist/assets/events-iRSPNrZu.js +1 -0
  158. ostium/web/dist/assets/get-DCmvXwyo.js +1 -0
  159. ostium/web/dist/assets/index-BF0kVdj-.css +1 -0
  160. ostium/web/dist/assets/index-ByVCKlLD.js +1318 -0
  161. ostium/web/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  162. ostium/web/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  163. ostium/web/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  164. ostium/web/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  165. ostium/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  166. ostium/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  167. ostium/web/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  168. ostium/web/dist/assets/key-round-pOGf6RQ8.js +1 -0
  169. ostium/web/dist/assets/lock-keyhole-CJztbacy.js +1 -0
  170. ostium/web/dist/assets/memory-stick-DFakatVS.js +1 -0
  171. ostium/web/dist/assets/misc-v7XGxIq4.js +1 -0
  172. ostium/web/dist/assets/pause-CmQQzZLQ.js +1 -0
  173. ostium/web/dist/assets/play-Dj9D01ba.js +1 -0
  174. ostium/web/dist/assets/plus-BS9m8U59.js +1 -0
  175. ostium/web/dist/assets/replaceable-Xwldo3FD.js +77 -0
  176. ostium/web/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  177. ostium/web/dist/assets/rotate-ccw-CxnBy7vT.js +1 -0
  178. ostium/web/dist/assets/route-DY54yEWA.js +1 -0
  179. ostium/web/dist/assets/server-DmdBrRlX.js +1 -0
  180. ostium/web/dist/assets/shield-question-mark-p_oiEXDj.js +1 -0
  181. ostium/web/dist/assets/sparkles-DwBhkDLT.js +1 -0
  182. ostium/web/dist/assets/terminal-DSrQLazn.js +1 -0
  183. ostium/web/dist/assets/trash-B4t97Yg6.js +1 -0
  184. ostium/web/dist/assets/use-message-DpuN2mQG.js +1 -0
  185. ostium/web/dist/assets/useBrowserNotifications-D7TM1fAV.js +1 -0
  186. ostium/web/dist/assets/useCopy-qMe7zkUi.js +1 -0
  187. ostium/web/dist/assets/user-round-BgbIfPpl.js +1 -0
  188. ostium/web/dist/assets/zap-mwHRUjCZ.js +1 -0
  189. ostium/web/dist/icons/apple-touch-icon.png +0 -0
  190. ostium/web/dist/icons/favicon.svg +14 -0
  191. ostium/web/dist/icons/icon-192.png +0 -0
  192. ostium/web/dist/icons/icon-512.png +0 -0
  193. ostium/web/dist/icons/maskable-512.png +0 -0
  194. ostium/web/dist/index.html +25 -0
  195. ostium/web/dist/manifest.webmanifest +21 -0
  196. ostium/web/dist/sw.js +54 -0
  197. ostium/web/dist/theme-init.js +6 -0
  198. ostium_ctl-0.1.0.dist-info/METADATA +903 -0
  199. ostium_ctl-0.1.0.dist-info/RECORD +203 -0
  200. ostium_ctl-0.1.0.dist-info/WHEEL +4 -0
  201. ostium_ctl-0.1.0.dist-info/entry_points.txt +3 -0
  202. ostium_ctl-0.1.0.dist-info/licenses/LICENSE +674 -0
  203. ostium_ctl-0.1.0.dist-info/licenses/NOTICE +32 -0
ostium/__init__.py ADDED
@@ -0,0 +1,25 @@
1
+ """Ostium package metadata and public constants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import PackageNotFoundError, version
6
+
7
+ try:
8
+ __version__ = version("ostium-ctl")
9
+ except PackageNotFoundError: # pragma: no cover - source checkout without install
10
+ __version__ = "0.1.0"
11
+
12
+ APP_NAME = "Ostium"
13
+ API_PREFIX = "/api/v1"
14
+ CORE_API_VERSION = "v1"
15
+ PROFILE_FORMAT_VERSION = 1
16
+ USER_AGENT = f"Ostium/{__version__}"
17
+
18
+ __all__ = [
19
+ "API_PREFIX",
20
+ "APP_NAME",
21
+ "CORE_API_VERSION",
22
+ "PROFILE_FORMAT_VERSION",
23
+ "USER_AGENT",
24
+ "__version__",
25
+ ]
ostium/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from ostium.cli.main import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
ostium/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.0'
22
+ __version_tuple__ = version_tuple = (0, 1, 0)
23
+
24
+ __commit_id__ = commit_id = None
File without changes
ostium/acme/client.py ADDED
@@ -0,0 +1,202 @@
1
+ """Built-in ACME HTTP-01 client.
2
+
3
+ Only HTTP-01 is implemented by design (see PLAN.md §2.4). The challenge server
4
+ binds port 80 for the duration of issuance and serves exactly the requested key
5
+ authorizations, then shuts down.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import contextlib
12
+ import datetime as dt
13
+ import threading
14
+ from collections.abc import Iterator
15
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
16
+ from typing import Any
17
+
18
+ import josepy as jose
19
+ from acme import challenges, client, errors, messages
20
+ from cryptography import x509
21
+ from cryptography.hazmat.primitives import hashes, serialization
22
+ from cryptography.hazmat.primitives.asymmetric import rsa
23
+ from cryptography.x509.oid import NameOID
24
+
25
+ from ostium.config import Settings
26
+ from ostium.services.crypto import SecretStore
27
+
28
+
29
+ class AcmeError(Exception):
30
+ def __init__(self, message: str, code: str = "acme_error", status_code: int = 502) -> None:
31
+ super().__init__(message)
32
+ self.code = code
33
+ self.status_code = status_code
34
+
35
+
36
+ class _ChallengeStore:
37
+ def __init__(self) -> None:
38
+ self.tokens: dict[str, str] = {}
39
+ self.lock = threading.Lock()
40
+
41
+ def put(self, token: str, key_authorization: str) -> None:
42
+ with self.lock:
43
+ self.tokens[token] = key_authorization
44
+
45
+ def get(self, token: str) -> str | None:
46
+ with self.lock:
47
+ return self.tokens.get(token)
48
+
49
+
50
+ def _handler(store: _ChallengeStore) -> type[BaseHTTPRequestHandler]:
51
+ class Handler(BaseHTTPRequestHandler):
52
+ def do_GET(self) -> None:
53
+ prefix = "/.well-known/acme-challenge/"
54
+ if not self.path.startswith(prefix):
55
+ self.send_error(404)
56
+ return
57
+ token = self.path[len(prefix) :].split("?", 1)[0]
58
+ value = store.get(token)
59
+ if value is None:
60
+ self.send_error(404)
61
+ return
62
+ body = value.encode("ascii")
63
+ self.send_response(200)
64
+ self.send_header("Content-Type", "text/plain")
65
+ self.send_header("Content-Length", str(len(body)))
66
+ self.end_headers()
67
+ self.wfile.write(body)
68
+
69
+ def log_message(self, _format: str, *_args: Any) -> None:
70
+ return
71
+
72
+ return Handler
73
+
74
+
75
+ @contextlib.contextmanager
76
+ def challenge_server(port: int = 80) -> Iterator[_ChallengeStore]:
77
+ store = _ChallengeStore()
78
+ try:
79
+ server = ThreadingHTTPServer(("0.0.0.0", port), _handler(store))
80
+ except OSError as exc:
81
+ raise AcmeError(
82
+ f"cannot bind port {port} for the ACME HTTP-01 challenge: {exc}", "port_80_unavailable"
83
+ ) from exc
84
+ server.daemon_threads = True
85
+ thread = threading.Thread(target=server.serve_forever, name="acme-http01", daemon=True)
86
+ thread.start()
87
+ try:
88
+ yield store
89
+ finally:
90
+ server.shutdown()
91
+ server.server_close()
92
+ thread.join(timeout=5)
93
+
94
+
95
+ class AcmeIssuer:
96
+ def __init__(self, settings: Settings, store: SecretStore) -> None:
97
+ self.settings = settings
98
+ self.store = store
99
+
100
+ def _directory_url(self) -> str:
101
+ if self.settings.acme_staging:
102
+ return "https://acme-staging-v02.api.letsencrypt.org/directory"
103
+ return self.settings.acme_directory
104
+
105
+ def _load_account_key(self) -> rsa.RSAPrivateKey:
106
+ # The account key lives in a small file next to the master key; it is
107
+ # encrypted at rest with the same master key.
108
+ path = self.settings.data_dir / "acme" / "account.key.enc"
109
+ path.parent.mkdir(parents=True, exist_ok=True)
110
+ if path.is_file():
111
+ pem = self.store.decrypt(
112
+ path.read_text(encoding="utf-8"),
113
+ table="settings",
114
+ record_id="acme",
115
+ field="account_key",
116
+ )
117
+ key = serialization.load_pem_private_key(pem.encode(), password=None)
118
+ assert isinstance(key, rsa.RSAPrivateKey)
119
+ return key
120
+ key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
121
+ pem = key.private_bytes(
122
+ serialization.Encoding.PEM,
123
+ serialization.PrivateFormat.PKCS8,
124
+ serialization.NoEncryption(),
125
+ ).decode()
126
+ path.write_text(
127
+ self.store.encrypt(pem, table="settings", record_id="acme", field="account_key"),
128
+ encoding="utf-8",
129
+ )
130
+ with contextlib.suppress(OSError):
131
+ path.chmod(0o600)
132
+ return key
133
+
134
+ async def issue(self, domain: str, *, timeout_seconds: int = 180) -> tuple[str, str]:
135
+ return await asyncio.to_thread(self._issue_sync, domain, timeout_seconds)
136
+
137
+ def _issue_sync(self, domain: str, timeout_seconds: int) -> tuple[str, str]:
138
+ from ostium.services.certificates import CertificateError, validate_domain
139
+
140
+ try:
141
+ domain = validate_domain(domain)
142
+ except CertificateError as exc:
143
+ raise AcmeError(str(exc), "invalid_domain", 400) from exc
144
+ account_key = self._load_account_key()
145
+ acme_key = jose.JWKRSA(key=account_key) # type: ignore[attr-defined]
146
+ net = client.ClientNetwork(
147
+ acme_key,
148
+ user_agent="Ostium/0.1 (+https://github.com/ThzxxArt/Ostium)",
149
+ timeout=30,
150
+ )
151
+ try:
152
+ directory = messages.Directory.from_json(net.get(self._directory_url()).json())
153
+ acme_client = client.ClientV2(directory, net)
154
+ with contextlib.suppress(errors.ConflictError):
155
+ acme_client.new_account(
156
+ messages.NewRegistration.from_data(
157
+ email=self.settings.acme_email, terms_of_service_agreed=True
158
+ )
159
+ )
160
+ cert_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
161
+ csr = (
162
+ x509.CertificateSigningRequestBuilder()
163
+ .subject_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, domain)]))
164
+ .add_extension(x509.SubjectAlternativeName([x509.DNSName(domain)]), critical=False)
165
+ .sign(cert_key, hashes.SHA256())
166
+ )
167
+ order = acme_client.new_order(csr.public_bytes(serialization.Encoding.PEM))
168
+ with challenge_server(80) as store:
169
+ for authz in order.authorizations:
170
+ challenge = next(
171
+ (
172
+ item
173
+ for item in authz.body.challenges
174
+ if item.chall.typ == challenges.HTTP01.typ
175
+ ),
176
+ None,
177
+ )
178
+ if challenge is None:
179
+ raise AcmeError(
180
+ "the ACME server did not offer HTTP-01 for "
181
+ f"{authz.body.identifier.value}"
182
+ )
183
+ response, validation = challenge.response_and_validation(acme_client.net.key)
184
+ store.put(challenge.chall.token, validation)
185
+ acme_client.answer_challenge(challenge, response)
186
+ deadline = dt.datetime.now(dt.UTC) + dt.timedelta(seconds=timeout_seconds)
187
+ finalized = acme_client.poll_and_finalize(order, deadline=deadline)
188
+ fullchain = finalized.fullchain_pem
189
+ if not fullchain:
190
+ raise AcmeError("ACME did not return a certificate chain")
191
+ key_pem = cert_key.private_bytes(
192
+ serialization.Encoding.PEM,
193
+ serialization.PrivateFormat.PKCS8,
194
+ serialization.NoEncryption(),
195
+ ).decode()
196
+ return fullchain, key_pem
197
+ except AcmeError:
198
+ raise
199
+ except errors.Error as exc:
200
+ raise AcmeError(f"ACME request failed: {exc}") from exc
201
+ except Exception as exc:
202
+ raise AcmeError(f"ACME issuance failed: {exc}") from exc
ostium/api/__init__.py ADDED
File without changes