sasy-common 0.1.0__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 (25) hide show
  1. sasy_common-0.1.0/.gitignore +7 -0
  2. sasy_common-0.1.0/PKG-INFO +195 -0
  3. sasy_common-0.1.0/README.md +175 -0
  4. sasy_common-0.1.0/observability_common/__init__.py +112 -0
  5. sasy_common-0.1.0/observability_common/auth.py +681 -0
  6. sasy_common-0.1.0/observability_common/auth_config.py +152 -0
  7. sasy_common-0.1.0/observability_common/auth_hooks.py +942 -0
  8. sasy_common-0.1.0/observability_common/certificates.py +418 -0
  9. sasy_common-0.1.0/observability_common/grpc_interceptors.py +649 -0
  10. sasy_common-0.1.0/observability_common/proto/__init__.py +24 -0
  11. sasy_common-0.1.0/observability_common/proto/credential_server_pb2.py +46 -0
  12. sasy_common-0.1.0/observability_common/proto/credential_server_pb2.pyi +45 -0
  13. sasy_common-0.1.0/observability_common/proto/credential_server_pb2_grpc.py +140 -0
  14. sasy_common-0.1.0/observability_common/proto/observability_pb2.py +102 -0
  15. sasy_common-0.1.0/observability_common/proto/observability_pb2.pyi +345 -0
  16. sasy_common-0.1.0/observability_common/proto/observability_pb2_grpc.py +480 -0
  17. sasy_common-0.1.0/observability_common/proto/policy_engine_pb2.py +68 -0
  18. sasy_common-0.1.0/observability_common/proto/policy_engine_pb2.pyi +157 -0
  19. sasy_common-0.1.0/observability_common/proto/policy_engine_pb2_grpc.py +189 -0
  20. sasy_common-0.1.0/observability_common/proto/reference_monitor_pb2.py +51 -0
  21. sasy_common-0.1.0/observability_common/proto/reference_monitor_pb2.pyi +68 -0
  22. sasy_common-0.1.0/observability_common/proto/reference_monitor_pb2_grpc.py +142 -0
  23. sasy_common-0.1.0/observability_common/py.typed +0 -0
  24. sasy_common-0.1.0/observability_common/tls.py +158 -0
  25. sasy_common-0.1.0/pyproject.toml +33 -0
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .mypy_cache/
4
+ .ruff_cache/
5
+ *.egg-info/
6
+ .venv/
7
+ *~
@@ -0,0 +1,195 @@
1
+ Metadata-Version: 2.4
2
+ Name: sasy-common
3
+ Version: 0.1.0
4
+ Summary: Common utilities for observability platform: auth, TLS, credentials, and proto stubs
5
+ Project-URL: Homepage, https://github.com/nilspalumbo/observability
6
+ Author: Nils Palumbo
7
+ License: MIT
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Requires-Python: >=3.11
11
+ Requires-Dist: cryptography>=44.0.0
12
+ Requires-Dist: grpcio>=1.76.0
13
+ Requires-Dist: httpx>=0.28.1
14
+ Requires-Dist: protobuf>=5.29.0
15
+ Requires-Dist: pydantic-settings>=2.0.0
16
+ Requires-Dist: pydantic>=2.0.0
17
+ Requires-Dist: pyjwt[crypto]>=2.8.0
18
+ Requires-Dist: pyyaml>=6.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Observability Common
22
+
23
+ Shared utilities for the observability platform, providing authentication, TLS configuration, and protocol buffer stubs used by all services.
24
+
25
+ ## Design
26
+
27
+ This package contains shared code including that for authentication, TLS handling, and gRPC communication. It includes:
28
+
29
+ - **Server-side auth providers**: Validate incoming requests (JWT, mTLS, API keys)
30
+ - **Client-side auth hooks**: Add credentials to outgoing requests
31
+ - **TLS configuration**: Consistent TLS/mTLS setup across services
32
+ - **gRPC interceptors**: Authentication enforcement for gRPC services
33
+ - **Protocol buffer stubs**: Generated Python code for all service protos
34
+
35
+ ## Components
36
+
37
+ ### Server-Side Authentication (`auth.py`)
38
+
39
+ Auth providers validate incoming requests and extract entity/role information:
40
+
41
+ ```python
42
+ from observability_common import JWTAuthProvider, MTLSAuthProvider, APIKeyAuthProvider
43
+
44
+ # JWT validation with JWKS endpoint
45
+ jwt_provider = JWTAuthProvider(
46
+ jwks_url="https://keycloak:8443/realms/myapp/protocol/openid-connect/certs",
47
+ issuer="https://keycloak:8443/realms/myapp",
48
+ entity_claim="preferred_username",
49
+ roles_claim="roles",
50
+ )
51
+
52
+ # mTLS - extract entity from certificate
53
+ mtls_provider = MTLSAuthProvider(entity_source="cn")
54
+
55
+ # API key authentication
56
+ apikey_provider = APIKeyAuthProvider(
57
+ metadata_key="x-api-key",
58
+ static_keys={"dev-key": "developer", "prod-key": "service"},
59
+ )
60
+
61
+ # Chain multiple providers (try in order)
62
+ from observability_common import ChainAuthProvider
63
+ chain = ChainAuthProvider([jwt_provider, apikey_provider, mtls_provider])
64
+
65
+ # Validate a request
66
+ result = provider.authenticate(gRPC_context)
67
+ if result.authenticated:
68
+ print(f"Entity: {result.entity}, Roles: {result.roles}")
69
+ ```
70
+
71
+ ### Client-Side Authentication (`auth_hooks.py`)
72
+
73
+ Auth hooks add credentials to outgoing gRPC requests:
74
+
75
+ ```python
76
+ from observability_common import (
77
+ NoAuthHook,
78
+ APIKeyAuthHook,
79
+ OIDCAuthHook,
80
+ KeycloakAuthHook,
81
+ BrowserAuthHook,
82
+ DeviceAuthHook,
83
+ )
84
+
85
+ # No authentication
86
+ hook = NoAuthHook()
87
+
88
+ # Static API key
89
+ hook = APIKeyAuthHook(api_key="my-secret-key")
90
+
91
+ # OAuth2 client credentials
92
+ hook = OIDCAuthHook(
93
+ token_url="https://keycloak:8443/realms/myapp/protocol/openid-connect/token",
94
+ client_id="my-service",
95
+ client_secret="secret",
96
+ scope="openid profile",
97
+ )
98
+
99
+ # Browser-based OAuth2 with PKCE
100
+ hook = BrowserAuthHook(
101
+ authorization_url="https://keycloak:8443/realms/myapp/protocol/openid-connect/auth",
102
+ token_url="https://keycloak:8443/realms/myapp/protocol/openid-connect/token",
103
+ client_id="my-cli",
104
+ redirect_port=8400,
105
+ )
106
+
107
+ # Device authorization grant
108
+ hook = DeviceAuthHook(
109
+ device_authorization_url="https://keycloak:8443/realms/myapp/protocol/openid-connect/auth/device",
110
+ token_url="https://keycloak:8443/realms/myapp/protocol/openid-connect/token",
111
+ client_id="my-device",
112
+ )
113
+
114
+ # Get metadata for gRPC calls
115
+ metadata = hook.get_metadata() # Returns [("authorization", "Bearer ...")]
116
+ ```
117
+
118
+ ### TLS Configuration (`tls.py`)
119
+
120
+ Consistent TLS setup for clients and servers:
121
+
122
+ ```python
123
+ from observability_common import TLSConfig, ServerTLSConfig, get_shared_tls_config
124
+
125
+ # Client TLS config
126
+ client_tls = TLSConfig(
127
+ ca_path="certs/ca.crt",
128
+ cert_path="certs/client.crt", # For mTLS
129
+ key_path="certs/client.key",
130
+ )
131
+ credentials = client_tls.to_grpc_credentials()
132
+
133
+ # Server TLS config
134
+ server_tls = ServerTLSConfig(
135
+ cert_path="certs/server.crt",
136
+ key_path="certs/server.key",
137
+ ca_path="certs/ca.crt", # For mTLS client verification
138
+ require_client_auth=True,
139
+ )
140
+ credentials = server_tls.to_grpc_credentials()
141
+
142
+ # Shared config from environment
143
+ tls = get_shared_tls_config() # Uses TLS_CA_PATH, TLS_CERT_PATH, TLS_KEY_PATH
144
+ ```
145
+
146
+ ### gRPC Interceptors (`grpc_interceptors.py`)
147
+
148
+ Enforce authentication and authorization for gRPC services:
149
+
150
+ ```python
151
+ from observability_common import AuthInterceptor, require_role, get_auth_context
152
+
153
+ # Create interceptor with auth provider
154
+ interceptor = AuthInterceptor(auth_provider)
155
+
156
+ # Use in gRPC server
157
+ server = grpc.server(
158
+ futures.ThreadPoolExecutor(),
159
+ interceptors=[interceptor],
160
+ )
161
+
162
+ # In service methods, check roles
163
+ @require_role("admin")
164
+ def AdminOnlyMethod(self, request, context):
165
+ auth = get_auth_context(context)
166
+ print(f"Called by: {auth.entity}")
167
+ ```
168
+
169
+ ### Authorization Config (`auth_config.yaml`)
170
+
171
+ Central entity-to-role mapping loaded from YAML:
172
+
173
+ ```python
174
+ from observability_common import load_auth_config, get_auth_config
175
+
176
+ # Load from file
177
+ config = load_auth_config("config/auth_config.yaml")
178
+
179
+ # Get roles for an entity
180
+ roles = config.get_roles_for_entity("my-service") # ["admin", "reader"]
181
+ ```
182
+
183
+ ## Protocol Buffer Stubs
184
+
185
+ Generated stubs for all platform services are in `observability_common/proto/`:
186
+
187
+ - `observability_pb2.py` - Observability server messages
188
+ - `credential_server_pb2.py` - Credential server messages
189
+ - `policy_engine_pb2.py` - Policy engine messages
190
+ - `reference_monitor_pb2.py` - Reference monitor messages
191
+
192
+ Regenerate with:
193
+ ```bash
194
+ make proto # From repo root
195
+ ```
@@ -0,0 +1,175 @@
1
+ # Observability Common
2
+
3
+ Shared utilities for the observability platform, providing authentication, TLS configuration, and protocol buffer stubs used by all services.
4
+
5
+ ## Design
6
+
7
+ This package contains shared code including that for authentication, TLS handling, and gRPC communication. It includes:
8
+
9
+ - **Server-side auth providers**: Validate incoming requests (JWT, mTLS, API keys)
10
+ - **Client-side auth hooks**: Add credentials to outgoing requests
11
+ - **TLS configuration**: Consistent TLS/mTLS setup across services
12
+ - **gRPC interceptors**: Authentication enforcement for gRPC services
13
+ - **Protocol buffer stubs**: Generated Python code for all service protos
14
+
15
+ ## Components
16
+
17
+ ### Server-Side Authentication (`auth.py`)
18
+
19
+ Auth providers validate incoming requests and extract entity/role information:
20
+
21
+ ```python
22
+ from observability_common import JWTAuthProvider, MTLSAuthProvider, APIKeyAuthProvider
23
+
24
+ # JWT validation with JWKS endpoint
25
+ jwt_provider = JWTAuthProvider(
26
+ jwks_url="https://keycloak:8443/realms/myapp/protocol/openid-connect/certs",
27
+ issuer="https://keycloak:8443/realms/myapp",
28
+ entity_claim="preferred_username",
29
+ roles_claim="roles",
30
+ )
31
+
32
+ # mTLS - extract entity from certificate
33
+ mtls_provider = MTLSAuthProvider(entity_source="cn")
34
+
35
+ # API key authentication
36
+ apikey_provider = APIKeyAuthProvider(
37
+ metadata_key="x-api-key",
38
+ static_keys={"dev-key": "developer", "prod-key": "service"},
39
+ )
40
+
41
+ # Chain multiple providers (try in order)
42
+ from observability_common import ChainAuthProvider
43
+ chain = ChainAuthProvider([jwt_provider, apikey_provider, mtls_provider])
44
+
45
+ # Validate a request
46
+ result = provider.authenticate(gRPC_context)
47
+ if result.authenticated:
48
+ print(f"Entity: {result.entity}, Roles: {result.roles}")
49
+ ```
50
+
51
+ ### Client-Side Authentication (`auth_hooks.py`)
52
+
53
+ Auth hooks add credentials to outgoing gRPC requests:
54
+
55
+ ```python
56
+ from observability_common import (
57
+ NoAuthHook,
58
+ APIKeyAuthHook,
59
+ OIDCAuthHook,
60
+ KeycloakAuthHook,
61
+ BrowserAuthHook,
62
+ DeviceAuthHook,
63
+ )
64
+
65
+ # No authentication
66
+ hook = NoAuthHook()
67
+
68
+ # Static API key
69
+ hook = APIKeyAuthHook(api_key="my-secret-key")
70
+
71
+ # OAuth2 client credentials
72
+ hook = OIDCAuthHook(
73
+ token_url="https://keycloak:8443/realms/myapp/protocol/openid-connect/token",
74
+ client_id="my-service",
75
+ client_secret="secret",
76
+ scope="openid profile",
77
+ )
78
+
79
+ # Browser-based OAuth2 with PKCE
80
+ hook = BrowserAuthHook(
81
+ authorization_url="https://keycloak:8443/realms/myapp/protocol/openid-connect/auth",
82
+ token_url="https://keycloak:8443/realms/myapp/protocol/openid-connect/token",
83
+ client_id="my-cli",
84
+ redirect_port=8400,
85
+ )
86
+
87
+ # Device authorization grant
88
+ hook = DeviceAuthHook(
89
+ device_authorization_url="https://keycloak:8443/realms/myapp/protocol/openid-connect/auth/device",
90
+ token_url="https://keycloak:8443/realms/myapp/protocol/openid-connect/token",
91
+ client_id="my-device",
92
+ )
93
+
94
+ # Get metadata for gRPC calls
95
+ metadata = hook.get_metadata() # Returns [("authorization", "Bearer ...")]
96
+ ```
97
+
98
+ ### TLS Configuration (`tls.py`)
99
+
100
+ Consistent TLS setup for clients and servers:
101
+
102
+ ```python
103
+ from observability_common import TLSConfig, ServerTLSConfig, get_shared_tls_config
104
+
105
+ # Client TLS config
106
+ client_tls = TLSConfig(
107
+ ca_path="certs/ca.crt",
108
+ cert_path="certs/client.crt", # For mTLS
109
+ key_path="certs/client.key",
110
+ )
111
+ credentials = client_tls.to_grpc_credentials()
112
+
113
+ # Server TLS config
114
+ server_tls = ServerTLSConfig(
115
+ cert_path="certs/server.crt",
116
+ key_path="certs/server.key",
117
+ ca_path="certs/ca.crt", # For mTLS client verification
118
+ require_client_auth=True,
119
+ )
120
+ credentials = server_tls.to_grpc_credentials()
121
+
122
+ # Shared config from environment
123
+ tls = get_shared_tls_config() # Uses TLS_CA_PATH, TLS_CERT_PATH, TLS_KEY_PATH
124
+ ```
125
+
126
+ ### gRPC Interceptors (`grpc_interceptors.py`)
127
+
128
+ Enforce authentication and authorization for gRPC services:
129
+
130
+ ```python
131
+ from observability_common import AuthInterceptor, require_role, get_auth_context
132
+
133
+ # Create interceptor with auth provider
134
+ interceptor = AuthInterceptor(auth_provider)
135
+
136
+ # Use in gRPC server
137
+ server = grpc.server(
138
+ futures.ThreadPoolExecutor(),
139
+ interceptors=[interceptor],
140
+ )
141
+
142
+ # In service methods, check roles
143
+ @require_role("admin")
144
+ def AdminOnlyMethod(self, request, context):
145
+ auth = get_auth_context(context)
146
+ print(f"Called by: {auth.entity}")
147
+ ```
148
+
149
+ ### Authorization Config (`auth_config.yaml`)
150
+
151
+ Central entity-to-role mapping loaded from YAML:
152
+
153
+ ```python
154
+ from observability_common import load_auth_config, get_auth_config
155
+
156
+ # Load from file
157
+ config = load_auth_config("config/auth_config.yaml")
158
+
159
+ # Get roles for an entity
160
+ roles = config.get_roles_for_entity("my-service") # ["admin", "reader"]
161
+ ```
162
+
163
+ ## Protocol Buffer Stubs
164
+
165
+ Generated stubs for all platform services are in `observability_common/proto/`:
166
+
167
+ - `observability_pb2.py` - Observability server messages
168
+ - `credential_server_pb2.py` - Credential server messages
169
+ - `policy_engine_pb2.py` - Policy engine messages
170
+ - `reference_monitor_pb2.py` - Reference monitor messages
171
+
172
+ Regenerate with:
173
+ ```bash
174
+ make proto # From repo root
175
+ ```
@@ -0,0 +1,112 @@
1
+ """Observability Common - Shared utilities for the observability platform."""
2
+
3
+ from observability_common.auth import (
4
+ AuthProvider,
5
+ AuthResult,
6
+ AuthChain as ChainAuthProvider,
7
+ JWTAuthProvider,
8
+ MTLSAuthProvider,
9
+ APIKeyAuthProvider,
10
+ PassthroughAuthProvider,
11
+ load_auth_provider,
12
+ )
13
+ from observability_common.auth_hooks import (
14
+ AuthHook,
15
+ NoAuthHook,
16
+ StaticTokenAuthHook,
17
+ APIKeyAuthHook,
18
+ CallableAuthHook,
19
+ EnvironmentAuthHook,
20
+ OIDCAuthHook,
21
+ KeycloakAuthHook,
22
+ DeviceAuthHook,
23
+ BrowserAuthHook,
24
+ keycloak_auth_hook,
25
+ okta_auth_hook,
26
+ azure_ad_auth_hook,
27
+ device_auth_hook_keycloak,
28
+ device_auth_hook_azure,
29
+ browser_auth_hook_keycloak,
30
+ browser_auth_hook_azure,
31
+ browser_auth_hook_google,
32
+ )
33
+ from observability_common.tls import (
34
+ TLSConfig,
35
+ ServerTLSConfig,
36
+ TLSConfigWithFallback,
37
+ get_shared_tls_config,
38
+ )
39
+ from observability_common.grpc_interceptors import (
40
+ AuthInterceptor,
41
+ AsyncAuthInterceptor,
42
+ create_mtls_interceptor,
43
+ create_spiffe_interceptor,
44
+ create_service_interceptor,
45
+ get_auth_context,
46
+ require_role,
47
+ require_any_role,
48
+ disable_rbac,
49
+ enable_rbac,
50
+ )
51
+ from observability_common.auth_config import (
52
+ AuthorizationConfig,
53
+ EntityConfig,
54
+ load_auth_config,
55
+ get_auth_config,
56
+ set_auth_config,
57
+ DEFAULT_CONFIG,
58
+ )
59
+
60
+ __all__ = [
61
+ # Server-side auth providers
62
+ "AuthProvider",
63
+ "AuthResult",
64
+ "ChainAuthProvider",
65
+ "JWTAuthProvider",
66
+ "MTLSAuthProvider",
67
+ "APIKeyAuthProvider",
68
+ "PassthroughAuthProvider",
69
+ "load_auth_provider",
70
+ # Client-side auth hooks
71
+ "AuthHook",
72
+ "NoAuthHook",
73
+ "StaticTokenAuthHook",
74
+ "APIKeyAuthHook",
75
+ "CallableAuthHook",
76
+ "EnvironmentAuthHook",
77
+ "OIDCAuthHook",
78
+ "KeycloakAuthHook",
79
+ "DeviceAuthHook",
80
+ "BrowserAuthHook",
81
+ "keycloak_auth_hook",
82
+ "okta_auth_hook",
83
+ "azure_ad_auth_hook",
84
+ "device_auth_hook_keycloak",
85
+ "device_auth_hook_azure",
86
+ "browser_auth_hook_keycloak",
87
+ "browser_auth_hook_azure",
88
+ "browser_auth_hook_google",
89
+ # TLS
90
+ "TLSConfig",
91
+ "ServerTLSConfig",
92
+ "TLSConfigWithFallback",
93
+ "get_shared_tls_config",
94
+ # gRPC Interceptors
95
+ "AuthInterceptor",
96
+ "AsyncAuthInterceptor",
97
+ "create_mtls_interceptor",
98
+ "create_spiffe_interceptor",
99
+ "create_service_interceptor",
100
+ "get_auth_context",
101
+ "require_role",
102
+ "require_any_role",
103
+ "disable_rbac",
104
+ "enable_rbac",
105
+ # Authorization Config
106
+ "AuthorizationConfig",
107
+ "EntityConfig",
108
+ "load_auth_config",
109
+ "get_auth_config",
110
+ "set_auth_config",
111
+ "DEFAULT_CONFIG",
112
+ ]