runspec-webops-core 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/.gitignore +5 -0
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/CHANGELOG.md +15 -0
- runspec_webops_core-0.3.2/PKG-INFO +56 -0
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/README.md +1 -1
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/pyproject.toml +2 -1
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/tls.py +10 -4
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/tests/test_tls.py +27 -0
- runspec_webops_core-0.3.0/PKG-INFO +0 -13
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/__init__.py +0 -0
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/dns.py +0 -0
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/errors.py +0 -0
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/http.py +0 -0
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/openapi.py +0 -0
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/proxy.py +0 -0
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/tests/__init__.py +0 -0
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/tests/test_dns.py +0 -0
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/tests/test_http.py +0 -0
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/tests/test_openapi.py +0 -0
- {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/tests/test_proxy.py +0 -0
|
@@ -25,6 +25,8 @@ coverage.xml
|
|
|
25
25
|
# Node
|
|
26
26
|
node_modules/
|
|
27
27
|
dist/
|
|
28
|
+
# runspec manifest output (written at npm pack time)
|
|
29
|
+
runspec-files.json
|
|
28
30
|
*.js.map
|
|
29
31
|
.npm
|
|
30
32
|
|
|
@@ -59,3 +61,6 @@ chainlit-docs/
|
|
|
59
61
|
|
|
60
62
|
# Stray committed test venv (removed from tracking)
|
|
61
63
|
.venv-test/
|
|
64
|
+
|
|
65
|
+
# Private gitleaks rules (see .gitleaks.toml)
|
|
66
|
+
.gitleaks.local.toml
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# runspec-webops-core Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.2] — 2026-11-12
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `connect_peer` (behind `cert-check`, `cert-chain` and `tls-info`) failed on
|
|
8
|
+
Python 3.13+ with `'bytes' object has no attribute 'public_bytes'`: the public
|
|
9
|
+
`SSLSocket.get_unverified_chain()` returns DER bytes, not certificate objects.
|
|
10
|
+
Both shapes are read now.
|
|
11
|
+
|
|
12
|
+
## [0.3.1] — 2026-11-05
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- The PyPI page shows the README (`pyproject.toml` didn't declare it).
|
|
17
|
+
|
|
3
18
|
## [0.3.0] — 2026-09-01
|
|
4
19
|
|
|
5
20
|
### Added — custom CA bundle for TLS verification
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: runspec-webops-core
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: Pure-Python HTTPS/TLS certificate + web-endpoint helpers — the importable core behind the runspec web runnables (no runspec dependency, no runnables)
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Provides-Extra: dev
|
|
7
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
8
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
9
|
+
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
10
|
+
Requires-Dist: ruff==0.15.20; extra == 'dev'
|
|
11
|
+
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
12
|
+
Provides-Extra: yaml
|
|
13
|
+
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# runspec-webops-core
|
|
17
|
+
|
|
18
|
+
Pure-Python HTTPS/TLS certificate + web-endpoint helpers — the importable logic
|
|
19
|
+
core behind the cross-platform web runnables in
|
|
20
|
+
[`runspec-linux`](https://pypi.org/project/runspec-linux/) and [`runspec-windows`](https://pypi.org/project/runspec-windows/).
|
|
21
|
+
|
|
22
|
+
Stdlib only (`ssl` / `socket` / `http.client`) — **no `runspec` dependency, no
|
|
23
|
+
`runspec.toml`, and no entry points**, so installing it exposes the helper
|
|
24
|
+
functions for import without surfacing any runnables (it is invisible to
|
|
25
|
+
`runspec local` / `runspec serve` discovery).
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from runspec_webops_core import connect_peer, leaf_report, probe, resolve
|
|
29
|
+
|
|
30
|
+
peer = connect_peer("example.com", 443, 10)
|
|
31
|
+
print(leaf_report(peer, warn_days=30)) # days-to-expiry, issuer, SANs, trust
|
|
32
|
+
print(probe("https://example.com", 10)) # status, timing, redirect target
|
|
33
|
+
print(resolve("example.com", want_reverse=True))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Function | What it does |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `connect_peer` / `leaf_report` / `chain_report` | TLS certificate inspection — expiry, issuer, SANs, fingerprint, full chain, trust result |
|
|
39
|
+
| `probe` / `classify` | Single HTTP(S) request — status, response time, redirect target |
|
|
40
|
+
| `fetch_headers` | Response headers + a security-header report (HSTS, CSP, …) |
|
|
41
|
+
| `trace_redirects` | Walk the redirect chain, hop by hop, with loop detection |
|
|
42
|
+
| `resolve` | Forward (A/AAAA) and reverse (PTR) DNS lookups |
|
|
43
|
+
| `load_document` / `summarize_spec` / `describe_operation` | Read an OpenAPI/Swagger spec (URL or file) and condense its shape — operations, params, request/response schemas — to bounded JSON |
|
|
44
|
+
|
|
45
|
+
OpenAPI/Swagger specs in JSON parse with the stdlib; YAML needs the optional
|
|
46
|
+
`yaml` extra (`pip install runspec-webops-core[yaml]`).
|
|
47
|
+
|
|
48
|
+
Each function returns plain data and *raises* on failure (`ConnectError`,
|
|
49
|
+
`ResolveError` — both subclasses of `WebopsCoreError`).
|
|
50
|
+
|
|
51
|
+
## Corporate wrapping
|
|
52
|
+
|
|
53
|
+
Depend on `runspec-webops-core` alone, import the helpers, and ship your own
|
|
54
|
+
private package with its own `runspec.toml` that bakes in corporate defaults
|
|
55
|
+
(proxies, allowed hosts, redaction) as plain params — only your wrapped
|
|
56
|
+
runnables surface in the venv, the generic published code stays clean.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Pure-Python HTTPS/TLS certificate + web-endpoint helpers — the importable logic
|
|
4
4
|
core behind the cross-platform web runnables in
|
|
5
|
-
[`runspec-linux`](
|
|
5
|
+
[`runspec-linux`](https://pypi.org/project/runspec-linux/) and [`runspec-windows`](https://pypi.org/project/runspec-windows/).
|
|
6
6
|
|
|
7
7
|
Stdlib only (`ssl` / `socket` / `http.client`) — **no `runspec` dependency, no
|
|
8
8
|
`runspec.toml`, and no entry points**, so installing it exposes the helper
|
|
@@ -4,9 +4,10 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "runspec-webops-core"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
requires-python = ">=3.10"
|
|
9
9
|
description = "Pure-Python HTTPS/TLS certificate + web-endpoint helpers — the importable core behind the runspec web runnables (no runspec dependency, no runnables)"
|
|
10
|
+
readme = "README.md"
|
|
10
11
|
dependencies = []
|
|
11
12
|
|
|
12
13
|
[project.optional-dependencies]
|
|
@@ -201,8 +201,8 @@ def client_ssl_context(insecure: bool = False, ca_bundle: str | None = None) ->
|
|
|
201
201
|
def _peer_chain(ssock: ssl.SSLSocket) -> list[CertInfo]:
|
|
202
202
|
"""Return the server-presented certificate chain (leaf first).
|
|
203
203
|
|
|
204
|
-
Prefers the public ``SSLSocket.get_unverified_chain()`` (Python 3.13
|
|
205
|
-
falls back to the underlying ``_sslobj`` method (3.10–3.12), and finally to
|
|
204
|
+
Prefers the public ``SSLSocket.get_unverified_chain()`` (Python 3.13+,
|
|
205
|
+
which returns DER bytes), falls back to the underlying ``_sslobj`` method (3.10–3.12), and finally to
|
|
206
206
|
the leaf-only certificate via ``getpeercert`` when neither is available.
|
|
207
207
|
"""
|
|
208
208
|
getter = getattr(ssock, "get_unverified_chain", None)
|
|
@@ -218,8 +218,14 @@ def _peer_chain(ssock: ssl.SSLSocket) -> list[CertInfo]:
|
|
|
218
218
|
if raw:
|
|
219
219
|
out: list[CertInfo] = []
|
|
220
220
|
for cert in raw:
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
if isinstance(cert, (bytes, bytearray)):
|
|
222
|
+
# Python 3.13+ public API: DER-encoded bytes.
|
|
223
|
+
der = bytes(cert)
|
|
224
|
+
out.append(cert_info_from_ssl(_decode_der(der), der))
|
|
225
|
+
else:
|
|
226
|
+
# 3.10–3.12 ``_sslobj`` method: ``_ssl.Certificate`` objects.
|
|
227
|
+
der = ssl.PEM_cert_to_DER_cert(cert.public_bytes())
|
|
228
|
+
out.append(cert_info_from_ssl(cert.get_info(), der))
|
|
223
229
|
return out
|
|
224
230
|
|
|
225
231
|
# Fallback: only the leaf is recoverable on this interpreter.
|
|
@@ -132,3 +132,30 @@ def test_chain_report_marks_root() -> None:
|
|
|
132
132
|
rep = chain_report(peer, now=NOW)
|
|
133
133
|
assert [link["self_signed"] for link in rep] == [False, True]
|
|
134
134
|
assert rep[0]["days_remaining"] == 30
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _a_der_cert() -> bytes:
|
|
138
|
+
"""Any real DER certificate: the first system CA (no network needed)."""
|
|
139
|
+
certs = ssl.create_default_context().get_ca_certs(binary_form=True)
|
|
140
|
+
if not certs:
|
|
141
|
+
pytest.skip("no system CA certificates to use as a sample")
|
|
142
|
+
return certs[0]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_peer_chain_reads_der_bytes_from_public_api() -> None:
|
|
146
|
+
# Python 3.13+ ``SSLSocket.get_unverified_chain()`` returns DER bytes, not
|
|
147
|
+
# ``_ssl.Certificate`` objects; this used to fail with
|
|
148
|
+
# "'bytes' object has no attribute 'public_bytes'".
|
|
149
|
+
from runspec_webops_core.tls import _peer_chain
|
|
150
|
+
|
|
151
|
+
der = _a_der_cert()
|
|
152
|
+
|
|
153
|
+
class FakeSocket:
|
|
154
|
+
def get_unverified_chain(self) -> list[bytes]:
|
|
155
|
+
return [der, der]
|
|
156
|
+
|
|
157
|
+
chain = _peer_chain(FakeSocket()) # type: ignore[arg-type]
|
|
158
|
+
assert len(chain) == 2
|
|
159
|
+
assert chain[0].subject
|
|
160
|
+
assert chain[0].valid_to
|
|
161
|
+
assert chain[0].fingerprint_sha256
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: runspec-webops-core
|
|
3
|
-
Version: 0.3.0
|
|
4
|
-
Summary: Pure-Python HTTPS/TLS certificate + web-endpoint helpers — the importable core behind the runspec web runnables (no runspec dependency, no runnables)
|
|
5
|
-
Requires-Python: >=3.10
|
|
6
|
-
Provides-Extra: dev
|
|
7
|
-
Requires-Dist: mypy; extra == 'dev'
|
|
8
|
-
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
9
|
-
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
10
|
-
Requires-Dist: ruff==0.15.20; extra == 'dev'
|
|
11
|
-
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
12
|
-
Provides-Extra: yaml
|
|
13
|
-
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|