authweave-workload 7.3.2__tar.gz → 7.3.3__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.
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/PKG-INFO +4 -4
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/__init__.py +1 -1
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/integrations/litestar.py +85 -19
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/pyproject.toml +3 -3
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/.gitignore +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/LICENSE +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/README.md +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/authorization_details.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/delegation.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/dpop.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/events.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/integrations/__init__.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/introspection.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/jwks.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/jwt.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/lifecycle.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/migrations/0001_postgresql.sql +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/models.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/mtls.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/provider.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/py.typed +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/rate_limit.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/redis_store.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/spiffe.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/spiffe_snapshot.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/sqlalchemy.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/stores.py +0 -0
- {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/token_exchange.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: authweave-workload
|
|
3
|
-
Version: 7.3.
|
|
3
|
+
Version: 7.3.3
|
|
4
4
|
Summary: Framework-neutral sender-constrained workload authentication
|
|
5
5
|
Project-URL: homepage, https://github.com/ZYLVEXT/litestar-auth
|
|
6
6
|
Project-URL: documentation, https://zylvext.github.io/litestar-auth/
|
|
@@ -20,12 +20,12 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
20
20
|
Classifier: Topic :: Security
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries
|
|
22
22
|
Requires-Python: <3.15.0,>=3.12.0
|
|
23
|
-
Requires-Dist: authweave-core==7.3.
|
|
23
|
+
Requires-Dist: authweave-core==7.3.3
|
|
24
24
|
Provides-Extra: all
|
|
25
25
|
Requires-Dist: anyio<5.0,>=4.14.2; extra == 'all'
|
|
26
26
|
Requires-Dist: cryptography<51.0,>=50.0.0; extra == 'all'
|
|
27
27
|
Requires-Dist: httpx<1.0,>=0.28.1; extra == 'all'
|
|
28
|
-
Requires-Dist: litestar-auth==7.3.
|
|
28
|
+
Requires-Dist: litestar-auth==7.3.3; extra == 'all'
|
|
29
29
|
Requires-Dist: litestar<3.0,>=2.24.0; extra == 'all'
|
|
30
30
|
Requires-Dist: pyjwt<3.0,>=2.13.0; extra == 'all'
|
|
31
31
|
Requires-Dist: redis<9.0,>=8.1.0; extra == 'all'
|
|
@@ -47,7 +47,7 @@ Requires-Dist: cryptography<51.0,>=50.0.0; extra == 'jwt'
|
|
|
47
47
|
Requires-Dist: httpx<1.0,>=0.28.1; extra == 'jwt'
|
|
48
48
|
Requires-Dist: pyjwt<3.0,>=2.13.0; extra == 'jwt'
|
|
49
49
|
Provides-Extra: litestar
|
|
50
|
-
Requires-Dist: litestar-auth==7.3.
|
|
50
|
+
Requires-Dist: litestar-auth==7.3.3; extra == 'litestar'
|
|
51
51
|
Requires-Dist: litestar<3.0,>=2.24.0; extra == 'litestar'
|
|
52
52
|
Requires-Dist: sqlalchemy<3.0,>=2.0.51; extra == 'litestar'
|
|
53
53
|
Provides-Extra: mtls
|
{authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/integrations/litestar.py
RENAMED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import base64
|
|
6
|
+
import re
|
|
6
7
|
from collections.abc import Awaitable, Callable
|
|
7
8
|
from dataclasses import dataclass
|
|
8
|
-
from datetime import datetime
|
|
9
|
+
from datetime import UTC, datetime
|
|
9
10
|
from importlib import import_module
|
|
10
|
-
from typing import TYPE_CHECKING, Any, Never, cast
|
|
11
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Never, cast
|
|
11
12
|
|
|
12
13
|
from authweave_core import AuthenticationContext, ReplayStore, SpiffePeerEvidence, TlsPeerEvidence
|
|
13
14
|
from litestar.exceptions import NotAuthorizedException, PermissionDeniedException
|
|
@@ -40,8 +41,30 @@ type SecurityEventCallback = Callable[[SecurityEvent], Awaitable[None] | None]
|
|
|
40
41
|
type TlsPeerEvidenceFactory = Callable[[Scope], TlsPeerEvidence | None]
|
|
41
42
|
type SpiffePeerEvidenceFactory = Callable[[Scope], SpiffePeerEvidence | None]
|
|
42
43
|
type MachineGuard = Callable[[ASGIConnection[Any, Any, Any, Any], BaseRouteHandler], None]
|
|
44
|
+
type _CertificateTimeParser = Callable[[bytes], datetime]
|
|
43
45
|
UNIX_SOCKET_PROXY = "unix"
|
|
44
|
-
|
|
46
|
+
_SHA256_HEX_LENGTH = 64
|
|
47
|
+
|
|
48
|
+
_CLOUDFLARE_CERTIFICATE_TIME_PATTERN = re.compile(
|
|
49
|
+
rb"(?P<month>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) "
|
|
50
|
+
rb"(?P<day> [1-9]|[12][0-9]|3[01]) "
|
|
51
|
+
rb"(?P<hour>[0-2][0-9]):(?P<minute>[0-5][0-9]):(?P<second>[0-5][0-9]) "
|
|
52
|
+
rb"(?P<year>[0-9]{4}) GMT"
|
|
53
|
+
)
|
|
54
|
+
_CLOUDFLARE_MONTHS = {
|
|
55
|
+
b"Jan": 1,
|
|
56
|
+
b"Feb": 2,
|
|
57
|
+
b"Mar": 3,
|
|
58
|
+
b"Apr": 4,
|
|
59
|
+
b"May": 5,
|
|
60
|
+
b"Jun": 6,
|
|
61
|
+
b"Jul": 7,
|
|
62
|
+
b"Aug": 8,
|
|
63
|
+
b"Sep": 9,
|
|
64
|
+
b"Oct": 10,
|
|
65
|
+
b"Nov": 11,
|
|
66
|
+
b"Dec": 12,
|
|
67
|
+
}
|
|
45
68
|
|
|
46
69
|
_TLS_HEADER_NAMES = (
|
|
47
70
|
b"x-auth-tls-verified",
|
|
@@ -301,14 +324,38 @@ class WorkloadAuthExtension:
|
|
|
301
324
|
)
|
|
302
325
|
|
|
303
326
|
|
|
327
|
+
def _parse_time(value: bytes) -> datetime:
|
|
328
|
+
parsed = datetime.fromisoformat(value.decode("ascii"))
|
|
329
|
+
if parsed.utcoffset() is None:
|
|
330
|
+
raise ValueError
|
|
331
|
+
return parsed
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _parse_cloudflare_time(value: bytes) -> datetime:
|
|
335
|
+
match = _CLOUDFLARE_CERTIFICATE_TIME_PATTERN.fullmatch(value)
|
|
336
|
+
if match is None:
|
|
337
|
+
raise ValueError
|
|
338
|
+
return datetime(
|
|
339
|
+
int(match["year"]),
|
|
340
|
+
_CLOUDFLARE_MONTHS[match["month"]],
|
|
341
|
+
int(match["day"]),
|
|
342
|
+
int(match["hour"]),
|
|
343
|
+
int(match["minute"]),
|
|
344
|
+
int(match["second"]),
|
|
345
|
+
tzinfo=UTC,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
|
|
304
349
|
@dataclass(frozen=True, slots=True)
|
|
305
|
-
class
|
|
306
|
-
"""
|
|
350
|
+
class _TLSHeaderEvidence:
|
|
351
|
+
"""Shared fail-closed projection for provider-specific TLS header contracts."""
|
|
307
352
|
|
|
308
353
|
proxy_addresses: frozenset[str]
|
|
309
354
|
trust_anchors: frozenset[str]
|
|
310
355
|
revocation_checked_at: Callable[[], datetime]
|
|
311
|
-
termination_boundary: str
|
|
356
|
+
termination_boundary: str
|
|
357
|
+
_provider_name: ClassVar[str]
|
|
358
|
+
_certificate_time_parser: ClassVar[_CertificateTimeParser]
|
|
312
359
|
|
|
313
360
|
def __post_init__(self) -> None:
|
|
314
361
|
"""Require an explicit proxy and trust-anchor allowlist.
|
|
@@ -317,7 +364,7 @@ class EnvoyTLSHeaderEvidence:
|
|
|
317
364
|
ValueError: If either allowlist is empty.
|
|
318
365
|
"""
|
|
319
366
|
if not self.proxy_addresses or not self.trust_anchors or not callable(self.revocation_checked_at):
|
|
320
|
-
msg = "
|
|
367
|
+
msg = f"{self._provider_name} TLS evidence requires proxy addresses and trust anchors"
|
|
321
368
|
raise ValueError(msg)
|
|
322
369
|
|
|
323
370
|
def __call__(self, scope: Scope) -> TlsPeerEvidence | None:
|
|
@@ -346,9 +393,9 @@ class EnvoyTLSHeaderEvidence:
|
|
|
346
393
|
_require_trust_anchor(trust_anchor, self.trust_anchors)
|
|
347
394
|
return TlsPeerEvidence(
|
|
348
395
|
tls_version=values[b"x-auth-tls-version"].decode("ascii"),
|
|
349
|
-
certificate_thumbprint=
|
|
350
|
-
certificate_not_before=
|
|
351
|
-
certificate_not_after=
|
|
396
|
+
certificate_thumbprint=_parse_sha256_thumbprint(values[b"x-auth-client-cert-sha256"]),
|
|
397
|
+
certificate_not_before=self._certificate_time_parser(values[b"x-auth-client-cert-not-before"]),
|
|
398
|
+
certificate_not_after=self._certificate_time_parser(values[b"x-auth-client-cert-not-after"]),
|
|
352
399
|
revocation_checked_at=self.revocation_checked_at(),
|
|
353
400
|
trust_anchor=trust_anchor,
|
|
354
401
|
termination_boundary=self.termination_boundary,
|
|
@@ -357,6 +404,31 @@ class EnvoyTLSHeaderEvidence:
|
|
|
357
404
|
raise NotAuthorizedException(detail="TLS client evidence is invalid.") from exc
|
|
358
405
|
|
|
359
406
|
|
|
407
|
+
@dataclass(frozen=True, slots=True)
|
|
408
|
+
class EnvoyTLSHeaderEvidence(_TLSHeaderEvidence):
|
|
409
|
+
"""Project sanitized Envoy headers only from allowlisted proxy connections."""
|
|
410
|
+
|
|
411
|
+
termination_boundary: str = "envoy"
|
|
412
|
+
_provider_name: ClassVar[str] = "Envoy"
|
|
413
|
+
_certificate_time_parser: ClassVar[_CertificateTimeParser] = staticmethod(_parse_time)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
@dataclass(frozen=True, slots=True)
|
|
417
|
+
class CloudflareTLSHeaderEvidence(_TLSHeaderEvidence):
|
|
418
|
+
"""Project sanitized Cloudflare mTLS headers from allowlisted origin connections.
|
|
419
|
+
|
|
420
|
+
Cloudflare certificate validity fields use its documented OpenSSL-style
|
|
421
|
+
``Mon DD HH:MM:SS YYYY GMT`` representation. The edge must emit
|
|
422
|
+
``X-Auth-TLS-Verified: SUCCESS`` only when ``cert_verified`` is true and
|
|
423
|
+
``cert_revoked`` is false, overwrite every ``X-Auth-*`` evidence header,
|
|
424
|
+
and prevent direct traffic from reaching the origin.
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
termination_boundary: str = "cloudflare"
|
|
428
|
+
_provider_name: ClassVar[str] = "Cloudflare"
|
|
429
|
+
_certificate_time_parser: ClassVar[_CertificateTimeParser] = staticmethod(_parse_cloudflare_time)
|
|
430
|
+
|
|
431
|
+
|
|
360
432
|
@dataclass(frozen=True, slots=True)
|
|
361
433
|
class MeshSPIFFEHeaderEvidence:
|
|
362
434
|
"""Project SPIFFE peer identity from allowlisted mesh identity headers.
|
|
@@ -527,16 +599,9 @@ def _spiffe_headers(scope: Scope) -> dict[bytes, bytes]:
|
|
|
527
599
|
return relevant
|
|
528
600
|
|
|
529
601
|
|
|
530
|
-
def
|
|
531
|
-
parsed = datetime.fromisoformat(value.decode("ascii"))
|
|
532
|
-
if parsed.utcoffset() is None:
|
|
533
|
-
raise ValueError
|
|
534
|
-
return parsed
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
def _parse_envoy_thumbprint(value: bytes) -> str:
|
|
602
|
+
def _parse_sha256_thumbprint(value: bytes) -> str:
|
|
538
603
|
encoded = value.decode("ascii")
|
|
539
|
-
if len(encoded) !=
|
|
604
|
+
if len(encoded) != _SHA256_HEX_LENGTH:
|
|
540
605
|
raise ValueError
|
|
541
606
|
digest = bytes.fromhex(encoded)
|
|
542
607
|
return base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
|
|
@@ -658,6 +723,7 @@ def _machine_context(connection: ASGIConnection[Any, Any, Any, Any]) -> Authenti
|
|
|
658
723
|
|
|
659
724
|
__all__ = (
|
|
660
725
|
"UNIX_SOCKET_PROXY",
|
|
726
|
+
"CloudflareTLSHeaderEvidence",
|
|
661
727
|
"DPoPBoundIntrospectionProviderConfig",
|
|
662
728
|
"DPoPBoundJWTProviderConfig",
|
|
663
729
|
"DirectMTLSProviderConfig",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "authweave-workload"
|
|
3
|
-
version = "7.3.
|
|
3
|
+
version = "7.3.3"
|
|
4
4
|
description = "Framework-neutral sender-constrained workload authentication"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -23,7 +23,7 @@ classifiers = [
|
|
|
23
23
|
"Programming Language :: Python :: 3.14",
|
|
24
24
|
]
|
|
25
25
|
requires-python = "<3.15.0,>=3.12.0"
|
|
26
|
-
dependencies = ["authweave-core==7.3.
|
|
26
|
+
dependencies = ["authweave-core==7.3.3"]
|
|
27
27
|
|
|
28
28
|
[project.urls]
|
|
29
29
|
homepage = "https://github.com/ZYLVEXT/litestar-auth"
|
|
@@ -40,7 +40,7 @@ spiffe = ["authweave-workload[mtls]", "spiffe>=0.3.0,<0.4.0"]
|
|
|
40
40
|
introspection = ["authweave-workload[jwt]"]
|
|
41
41
|
token-exchange = ["authweave-workload[jwt]"]
|
|
42
42
|
redis = ["redis>=8.1.0,<9.0"]
|
|
43
|
-
litestar = ["authweave-workload[sqlalchemy]", "litestar>=2.24.0,<3.0", "litestar-auth==7.3.
|
|
43
|
+
litestar = ["authweave-workload[sqlalchemy]", "litestar>=2.24.0,<3.0", "litestar-auth==7.3.3"]
|
|
44
44
|
all = ["authweave-workload[jwt,dpop,spiffe,introspection,token-exchange,redis,litestar]"]
|
|
45
45
|
|
|
46
46
|
[build-system]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/authorization_details.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/integrations/__init__.py
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|