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.
Files changed (28) hide show
  1. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/PKG-INFO +4 -4
  2. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/__init__.py +1 -1
  3. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/integrations/litestar.py +85 -19
  4. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/pyproject.toml +3 -3
  5. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/.gitignore +0 -0
  6. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/LICENSE +0 -0
  7. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/README.md +0 -0
  8. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/authorization_details.py +0 -0
  9. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/delegation.py +0 -0
  10. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/dpop.py +0 -0
  11. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/events.py +0 -0
  12. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/integrations/__init__.py +0 -0
  13. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/introspection.py +0 -0
  14. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/jwks.py +0 -0
  15. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/jwt.py +0 -0
  16. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/lifecycle.py +0 -0
  17. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/migrations/0001_postgresql.sql +0 -0
  18. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/models.py +0 -0
  19. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/mtls.py +0 -0
  20. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/provider.py +0 -0
  21. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/py.typed +0 -0
  22. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/rate_limit.py +0 -0
  23. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/redis_store.py +0 -0
  24. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/spiffe.py +0 -0
  25. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/spiffe_snapshot.py +0 -0
  26. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/sqlalchemy.py +0 -0
  27. {authweave_workload-7.3.2 → authweave_workload-7.3.3}/authweave_workload/stores.py +0 -0
  28. {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.2
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.2
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.2; extra == 'all'
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.2; extra == 'litestar'
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
@@ -34,7 +34,7 @@ from authweave_workload.stores import (
34
34
  WorkloadStore,
35
35
  )
36
36
 
37
- __version__ = "7.3.2"
37
+ __version__ = "7.3.3"
38
38
 
39
39
  __all__ = (
40
40
  "PAYMENT_AUTHORIZATION_TYPE",
@@ -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
- _ENVOY_SHA256_HEX_LENGTH = 64
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 EnvoyTLSHeaderEvidence:
306
- """Project sanitized Envoy headers only from allowlisted proxy connections."""
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 = "envoy"
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 = "Envoy TLS evidence requires proxy addresses and trust anchors"
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=_parse_envoy_thumbprint(values[b"x-auth-client-cert-sha256"]),
350
- certificate_not_before=_parse_time(values[b"x-auth-client-cert-not-before"]),
351
- certificate_not_after=_parse_time(values[b"x-auth-client-cert-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 _parse_time(value: bytes) -> datetime:
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) != _ENVOY_SHA256_HEX_LENGTH:
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.2"
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.2"]
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.2"]
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]