tba-concrete-svid 0.4.0.dev2__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.
- tba_concrete_svid-0.4.0.dev2/PKG-INFO +14 -0
- tba_concrete_svid-0.4.0.dev2/pyproject.toml +14 -0
- tba_concrete_svid-0.4.0.dev2/src/tba_concrete_svid/__init__.py +13 -0
- tba_concrete_svid-0.4.0.dev2/src/tba_concrete_svid/jwt_svid.py +34 -0
- tba_concrete_svid-0.4.0.dev2/src/tba_concrete_svid/py.typed +1 -0
- tba_concrete_svid-0.4.0.dev2/src/tba_concrete_svid/selectors.py +19 -0
- tba_concrete_svid-0.4.0.dev2/src/tba_concrete_svid/spiffe_ids.py +13 -0
- tba_concrete_svid-0.4.0.dev2/src/tba_concrete_svid/x509_svid.py +23 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tba-concrete-svid
|
|
3
|
+
Version: 0.4.0.dev2
|
|
4
|
+
Summary: Deterministic SPIFFE ID and SVID structures.
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.10,<3.15
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: tba-concrete-jose (==0.4.0.dev2)
|
|
14
|
+
Requires-Dist: tba-contracts-identity (==0.4.0.dev2)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tba-concrete-svid"
|
|
3
|
+
version = "0.4.0.dev2"
|
|
4
|
+
description = "Deterministic SPIFFE ID and SVID structures."
|
|
5
|
+
requires-python = ">=3.10,<3.15"
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
dependencies = ["tba-contracts-identity==0.4.0.dev2", "tba-concrete-jose==0.4.0.dev2"]
|
|
8
|
+
|
|
9
|
+
[build-system]
|
|
10
|
+
requires = ["poetry-core>=1.0.0"]
|
|
11
|
+
build-backend = "poetry.core.masonry.api"
|
|
12
|
+
|
|
13
|
+
[tool.poetry]
|
|
14
|
+
packages = [{ include = "tba_concrete_svid", from = "src" }]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .jwt_svid import JwtSvidClaimSetPayload, parse_jwt_svid_claims
|
|
2
|
+
from .selectors import normalize_selectors
|
|
3
|
+
from .spiffe_ids import normalize_spiffe_id
|
|
4
|
+
from .x509_svid import X509SvidStructure, validate_x509_svid_structure
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"JwtSvidClaimSetPayload",
|
|
8
|
+
"X509SvidStructure",
|
|
9
|
+
"normalize_selectors",
|
|
10
|
+
"normalize_spiffe_id",
|
|
11
|
+
"parse_jwt_svid_claims",
|
|
12
|
+
"validate_x509_svid_structure",
|
|
13
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Mapping
|
|
3
|
+
|
|
4
|
+
from .spiffe_ids import normalize_spiffe_id
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True, slots=True)
|
|
8
|
+
class JwtSvidClaimSetPayload:
|
|
9
|
+
subject: str
|
|
10
|
+
audience: tuple[str, ...]
|
|
11
|
+
expiration: int
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse_jwt_svid_claims(claims: Mapping[str, object]) -> JwtSvidClaimSetPayload:
|
|
15
|
+
subject, audience, expiration = (
|
|
16
|
+
claims.get("sub"),
|
|
17
|
+
claims.get("aud"),
|
|
18
|
+
claims.get("exp"),
|
|
19
|
+
)
|
|
20
|
+
if not isinstance(subject, str):
|
|
21
|
+
raise ValueError("JWT-SVID requires a SPIFFE ID subject")
|
|
22
|
+
normalize_spiffe_id(subject)
|
|
23
|
+
if isinstance(audience, str):
|
|
24
|
+
audience = (audience,)
|
|
25
|
+
elif isinstance(audience, list) and all(isinstance(item, str) for item in audience):
|
|
26
|
+
audience = tuple(audience)
|
|
27
|
+
if not isinstance(audience, tuple) or not audience or not all(audience):
|
|
28
|
+
raise ValueError("JWT-SVID requires a non-empty audience")
|
|
29
|
+
if not isinstance(expiration, int) or isinstance(expiration, bool):
|
|
30
|
+
raise ValueError("JWT-SVID requires NumericDate exp")
|
|
31
|
+
return JwtSvidClaimSetPayload(subject, audience, expiration)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__all__ = ["JwtSvidClaimSetPayload", "parse_jwt_svid_claims"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
|
|
3
|
+
from tba_contracts_identity.workloads import WorkloadSelector
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def normalize_selectors(
|
|
7
|
+
selectors: Iterable[WorkloadSelector],
|
|
8
|
+
) -> tuple[WorkloadSelector, ...]:
|
|
9
|
+
normalized = tuple(
|
|
10
|
+
sorted(set(selectors), key=lambda item: (item.selector_type, item.value))
|
|
11
|
+
)
|
|
12
|
+
if not normalized or any(
|
|
13
|
+
not item.selector_type or not item.value for item in normalized
|
|
14
|
+
):
|
|
15
|
+
raise ValueError("workload selectors must be non-empty")
|
|
16
|
+
return normalized
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ["normalize_selectors"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from tba_contracts_identity.workloads import SpiffeId
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def normalize_spiffe_id(value: str | SpiffeId) -> SpiffeId:
|
|
5
|
+
parsed = SpiffeId.parse(value) if isinstance(value, str) else value
|
|
6
|
+
if parsed.path == "/" or "//" in parsed.path or parsed.path.endswith("/"):
|
|
7
|
+
raise ValueError(
|
|
8
|
+
"SPIFFE workload path must identify a non-root normalized workload"
|
|
9
|
+
)
|
|
10
|
+
return parsed
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = ["normalize_spiffe_id"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from .spiffe_ids import normalize_spiffe_id
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class X509SvidStructure:
|
|
8
|
+
uri_sans: tuple[str, ...]
|
|
9
|
+
dns_sans: tuple[str, ...] = ()
|
|
10
|
+
key_usage_digital_signature: bool = True
|
|
11
|
+
is_ca: bool = False
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def validate_x509_svid_structure(value: X509SvidStructure):
|
|
15
|
+
if len(value.uri_sans) != 1:
|
|
16
|
+
raise ValueError("X.509-SVID requires exactly one SPIFFE ID URI SAN")
|
|
17
|
+
spiffe_id = normalize_spiffe_id(value.uri_sans[0])
|
|
18
|
+
if value.is_ca or not value.key_usage_digital_signature:
|
|
19
|
+
raise ValueError("X.509-SVID leaf must be non-CA and permit digital signatures")
|
|
20
|
+
return spiffe_id
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__all__ = ["X509SvidStructure", "validate_x509_svid_structure"]
|