Skip to content

Commit f7f47c8

Browse files
jrhynessclaude
andcommitted
feat: simplify AuthPolicy to pass full subscription-info object
Pass the complete subscription-info object instead of extracting individual fields, simplifying the AuthPolicy and enabling consumers to access any field without controller changes. Changes: - Add subscription_info field with full auth.metadata["subscription-info"] object - Remove redundant top-level fields: - organizationId (now: subscription_info.organizationId) - costCenter (now: subscription_info.costCenter) - subscription_labels (now: subscription_info.labels) - Update TelemetryPolicy to access nested fields - Add test coverage verifying subscription_info returns full object Benefits: - Cleaner policy: single object vs multiple extracted fields - Extensibility: new subscription fields automatically available - Consistency: field name matches source (subscription-info → subscription_info) Consumers should update expressions: Before: auth.identity.organizationId After: auth.identity.subscription_info.organizationId Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 7e02117 commit f7f47c8

File tree

4 files changed

+51
-15
lines changed

4 files changed

+51
-15
lines changed

deployment/base/observability/gateway-telemetry-policy.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ spec:
1212
user: auth.identity.userid
1313
# Subscription metadata for usage attribution and billing
1414
subscription: auth.identity.selected_subscription
15-
organization_id: auth.identity.organizationId
16-
cost_center: auth.identity.costCenter
15+
organization_id: auth.identity.subscription_info.organizationId
16+
cost_center: auth.identity.subscription_info.costCenter
1717
targetRef:
1818
group: gateway.networking.k8s.io
1919
kind: Gateway

docs/content/configuration-and-management/maas-controller-overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ The controller-generated AuthPolicies do **not** inject identity-related HTTP he
243243

244244
All identity information remains available to **gateway-level features** through Authorino's `auth.identity` and `auth.metadata` contexts, which are consumed by:
245245

246-
- **TokenRateLimitPolicy (TRLP)**: Uses `selected_subscription_key`, `userid`, `groups`, and `subscription_labels` from `filters.identity`
246+
- **TokenRateLimitPolicy (TRLP)**: Uses `selected_subscription_key`, `userid`, `groups`, and `subscription_info` from `filters.identity` (access `subscription_info.labels` for tier-based rate limiting)
247247
- **Gateway telemetry/metrics**: Accesses identity fields with `metrics: true` enabled on `filters.identity`
248248
- **Authorization policies**: OPA/Rego rules evaluate `auth.identity` and `auth.metadata` directly
249249

maas-controller/pkg/controller/maas/maasauthpolicy_controller.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -477,14 +477,11 @@ allow {
477477
ref.Namespace, ref.Name,
478478
),
479479
},
480-
"organizationId": map[string]interface{}{
481-
"expression": `has(auth.metadata["subscription-info"].organizationId) ? auth.metadata["subscription-info"].organizationId : ""`,
482-
},
483-
"costCenter": map[string]interface{}{
484-
"expression": `has(auth.metadata["subscription-info"].costCenter) ? auth.metadata["subscription-info"].costCenter : ""`,
485-
},
486-
"subscription_labels": map[string]interface{}{
487-
"expression": `has(auth.metadata["subscription-info"].labels) ? auth.metadata["subscription-info"].labels : {}`,
480+
// Full subscription-info object from subscription-select endpoint
481+
// Contains: name, namespace, labels, organizationId, costCenter, error, message
482+
// Consumers should access nested fields (e.g., subscription_info.organizationId)
483+
"subscription_info": map[string]interface{}{
484+
"expression": `has(auth.metadata["subscription-info"]) ? auth.metadata["subscription-info"] : {}`,
488485
},
489486
// Error information (for debugging - only populated when selection fails)
490487
"subscription_error": map[string]interface{}{

maas-controller/pkg/controller/maas/maasauthpolicy_controller_test.go

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,7 +1137,7 @@ func TestMaaSAuthPolicyReconciler_NoIdentityHeadersUpstream(t *testing.T) {
11371137
"groups", // User groups
11381138
"selected_subscription_key", // Model-scoped subscription key for TRLP
11391139
"selected_subscription", // Subscription name
1140-
"subscription_labels", // Labels for rate limit tiers
1140+
"subscription_info", // Full subscription object (includes labels, organizationId, costCenter, etc.)
11411141
}
11421142

11431143
for _, field := range requiredFields {
@@ -1148,16 +1148,55 @@ func TestMaaSAuthPolicyReconciler_NoIdentityHeadersUpstream(t *testing.T) {
11481148

11491149
// Verify telemetry/observability fields
11501150
observabilityFields := []string{
1151-
"keyId", // API key tracking
1152-
"organizationId", // Cost attribution
1153-
"costCenter", // Chargeback
1151+
"keyId", // API key tracking
11541152
}
11551153

11561154
for _, field := range observabilityFields {
11571155
if _, exists := identity[field]; !exists {
11581156
t.Errorf("filters.identity should include %q for observability, but it's missing", field)
11591157
}
11601158
}
1159+
1160+
// Verify subscription_info contains full object (not just individual fields)
1161+
// This object should contain: name, namespace, labels, organizationId, costCenter
1162+
// Consumers should access nested fields like:
1163+
// - subscription_info.organizationId (for billing/cost attribution)
1164+
// - subscription_info.costCenter (for chargeback)
1165+
// - subscription_info.labels (for tier-based rate limiting)
1166+
subscriptionInfoField, exists := identity["subscription_info"]
1167+
if !exists {
1168+
t.Error("filters.identity must include 'subscription_info' field (full object from subscription-select)")
1169+
} else {
1170+
// Verify it's an expression that returns the full object
1171+
subscriptionInfoMap, ok := subscriptionInfoField.(map[string]interface{})
1172+
if !ok {
1173+
t.Errorf("subscription_info should be a map, got %T", subscriptionInfoField)
1174+
} else {
1175+
expr, hasExpr := subscriptionInfoMap["expression"]
1176+
if !hasExpr {
1177+
t.Error("subscription_info should have an 'expression' field")
1178+
} else {
1179+
exprStr, ok := expr.(string)
1180+
if !ok {
1181+
t.Errorf("subscription_info expression should be a string, got %T", expr)
1182+
} else {
1183+
// Verify it returns the full object, not just a sub-field
1184+
if !contains(exprStr, `auth.metadata["subscription-info"]`) {
1185+
t.Errorf("subscription_info expression should reference full subscription-info object, got: %s", exprStr)
1186+
}
1187+
// Verify it doesn't extract only labels (old behavior)
1188+
if contains(exprStr, ".labels") && !contains(exprStr, "?") {
1189+
t.Errorf("subscription_info expression should return full object, not just .labels, got: %s", exprStr)
1190+
}
1191+
// Note: We can't verify organizationId and costCenter exist at runtime here
1192+
// (they're optional fields that depend on MaaSSubscription configuration)
1193+
// but the full object is passed, so consumers can access them via:
1194+
// auth.identity.subscription_info.organizationId
1195+
// auth.identity.subscription_info.costCenter
1196+
}
1197+
}
1198+
}
1199+
}
11611200
})
11621201

11631202
// Test 3: Verify metrics are enabled on identity filter

0 commit comments

Comments
 (0)