From 1d3eb8d1ee7ceaad081f4ed738463aee166a0777 Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Wed, 18 Mar 2026 17:31:25 -0700 Subject: [PATCH 01/17] Add default TypeAuthorizer routing for all controlplane services All non-authorizer services now route Authorize() calls to the in-cluster authorizer service by default. The authorizer service itself defaults to Noop (no enforcement). To enable external authorization, override the authorizer service config with TypeExternal pointing to a customer's gRPC authorization server. Co-Authored-By: Claude Opus 4.6 --- charts/controlplane/values.yaml | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index 301fe6ed..79ca05e3 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -218,10 +218,22 @@ ingress: protectedIngressAnnotationsGrpc: configMap: + # Authorization — all non-authorizer services route Authorize() calls to the + # in-cluster authorizer service. The authorizer service defaults to Noop (no + # enforcement). To enable external authorization, configure TypeExternal on + # the authorizer service: + # + # services.authorizer.configMap.authorizer: + # type: "External" + # externalClient: + # endpoint: "http://your-authz-server:50051" + # timeout: "5s" + # forwardToken: true + # failOpen: false + # plaintextGrpc: true authorizer: - type: "Noop" - internalCommunicationConfig: - enabled: false + type: "Authorizer" + authorizerEndpoint: 'http://{{ .Values.services.authorizer.fullnameOverride | default "authorizer" }}.{{ .Release.Namespace }}.svc.cluster.local:80' cache: identity: enabled: false @@ -327,6 +339,10 @@ services: - --config - /etc/config/*.yaml configMap: + # The authorizer service itself defaults to Noop (no enforcement). + # Override with TypeExternal to proxy to a customer's authz server. + authorizer: + type: "Noop" sharedService: connectPort: 8081 metrics: @@ -1027,9 +1043,8 @@ flyte: endpoint: 'dns:///{{ .Values.global.UNION_HOST }}' insecure: false authorizer: - type: "Noop" - internalCommunicationConfig: - enabled: false + type: "Authorizer" + authorizerEndpoint: 'http://authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' cloudEvents: enable: false connection: @@ -1073,9 +1088,8 @@ flyte: enabled: true urlPattern: '{{ printf "_SERVICE_.%s.svc.cluster.local:80" .Release.Namespace }}' authorizer: - type: "Noop" - internalCommunicationConfig: - enabled: false + type: "Authorizer" + authorizerEndpoint: 'http://authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' cacheservice: storage-prefix: cached_outputs metrics-scope: flyte From e40ef25414f7bb036364cc7629f8887b62ce889d Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Wed, 18 Mar 2026 17:51:49 -0700 Subject: [PATCH 02/17] Move TypeExternal config example to services.authorizer section Relocates the external authorization configuration comment from configMap.authorizer (where non-authorizer services are configured) to services.authorizer.configMap.authorizer (where users actually configure the authorizer service). Addresses PR review feedback. Co-Authored-By: Claude Opus 4.6 --- charts/controlplane/values.yaml | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index 79ca05e3..49453609 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -219,18 +219,7 @@ ingress: configMap: # Authorization — all non-authorizer services route Authorize() calls to the - # in-cluster authorizer service. The authorizer service defaults to Noop (no - # enforcement). To enable external authorization, configure TypeExternal on - # the authorizer service: - # - # services.authorizer.configMap.authorizer: - # type: "External" - # externalClient: - # endpoint: "http://your-authz-server:50051" - # timeout: "5s" - # forwardToken: true - # failOpen: false - # plaintextGrpc: true + # in-cluster authorizer service (see services.authorizer below). authorizer: type: "Authorizer" authorizerEndpoint: 'http://{{ .Values.services.authorizer.fullnameOverride | default "authorizer" }}.{{ .Release.Namespace }}.svc.cluster.local:80' @@ -339,8 +328,17 @@ services: - --config - /etc/config/*.yaml configMap: - # The authorizer service itself defaults to Noop (no enforcement). - # Override with TypeExternal to proxy to a customer's authz server. + # The authorizer service defaults to Noop (no enforcement). + # To enable external authorization, set type to "External" and configure + # externalClient with your gRPC authorization server: + # + # type: "External" + # externalClient: + # endpoint: "http://your-authz-server:50051" + # timeout: "5s" + # forwardToken: true + # failOpen: false + # plaintextGrpc: true authorizer: type: "Noop" sharedService: From df456a7daacdf11165763d33683bc3e83d7129d1 Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Thu, 19 Mar 2026 03:08:18 -0700 Subject: [PATCH 03/17] Update TypeExternal config example to new grpcConfig shape The external authorization client now uses apimachinery/grpc.Config (grpcConfig) instead of flat endpoint/timeout/plaintextGrpc fields. Co-Authored-By: Claude Opus 4.6 --- charts/controlplane/values.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index 49453609..a62b6288 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -334,11 +334,10 @@ services: # # type: "External" # externalClient: - # endpoint: "http://your-authz-server:50051" - # timeout: "5s" - # forwardToken: true + # grpcConfig: + # host: "dns:///your-authz-server:50051" + # insecure: true # failOpen: false - # plaintextGrpc: true authorizer: type: "Noop" sharedService: From 1ada39e9f8a6c105e4cba895bb63af2cd2a7bc29 Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Thu, 19 Mar 2026 18:14:02 -0700 Subject: [PATCH 04/17] Document all externalClient config parameters in values.yaml Co-Authored-By: Claude Opus 4.6 --- charts/controlplane/values.yaml | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index a62b6288..037e97cc 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -330,13 +330,39 @@ services: configMap: # The authorizer service defaults to Noop (no enforcement). # To enable external authorization, set type to "External" and configure - # externalClient with your gRPC authorization server: + # externalClient with your gRPC authorization server. # + # Example: # type: "External" # externalClient: # grpcConfig: - # host: "dns:///your-authz-server:50051" + # # gRPC target for the external authorization server. + # # Uses standard gRPC name resolution (dns:///, unix:///, etc). + # host: "dns:///your-authz-server.namespace.svc.cluster.local:50051" + # + # # Connect without TLS (default: false). # insecure: true + # + # # Skip server certificate verification — do NOT use in production (default: false). + # # insecureSkipVerify: false + # + # # Timeout per gRPC retry attempt (default: "5s"). + # # perRetryTimeout: "5s" + # + # # Max gRPC retries. 0 = no retries, fail fast (default: 0). + # # maxRetries: 0 + # + # # Max delay for gRPC backoff between retries. + # # maxBackoffDelay: "10s" + # + # # Incoming gRPC metadata keys to forward to the external server. + # # Default: ["authorization", "flyte-authorization"] + # # forwardHeaders: + # # - authorization + # # - flyte-authorization + # + # # If true, allow requests when the external server is unreachable. + # # If false (default), deny on error. # failOpen: false authorizer: type: "Noop" From 0a39b9b197efbadfd21df917a8b67237200bb06e Mon Sep 17 00:00:00 2001 From: Katrina Rogan Date: Mon, 23 Mar 2026 18:56:59 -0700 Subject: [PATCH 05/17] Fix authorizerEndpoint to use dns:/// scheme for gRPC name resolution The Go code's TypeAuthorizer handler passes the endpoint through url.Parse and unionGrpc.NewConnection, which requires gRPC-style target format (dns:///) rather than http:// URLs. --- charts/controlplane/values.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index 037e97cc..d9a39dba 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -222,7 +222,7 @@ configMap: # in-cluster authorizer service (see services.authorizer below). authorizer: type: "Authorizer" - authorizerEndpoint: 'http://{{ .Values.services.authorizer.fullnameOverride | default "authorizer" }}.{{ .Release.Namespace }}.svc.cluster.local:80' + authorizerEndpoint: 'dns:///{{ .Values.services.authorizer.fullnameOverride | default "authorizer" }}.{{ .Release.Namespace }}.svc.cluster.local:80' cache: identity: enabled: false @@ -1067,7 +1067,7 @@ flyte: insecure: false authorizer: type: "Authorizer" - authorizerEndpoint: 'http://authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' + authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' cloudEvents: enable: false connection: @@ -1112,7 +1112,7 @@ flyte: urlPattern: '{{ printf "_SERVICE_.%s.svc.cluster.local:80" .Release.Namespace }}' authorizer: type: "Authorizer" - authorizerEndpoint: 'http://authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' + authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' cacheservice: storage-prefix: cached_outputs metrics-scope: flyte From 623b38e54bbe511dc3204123deeb75985b28d95a Mon Sep 17 00:00:00 2001 From: Katrina Rogan Date: Mon, 23 Mar 2026 20:19:15 -0700 Subject: [PATCH 06/17] Expose native gRPC port for authorizer service Add grpcNativePort to expose the raw gRPC container port (8080) alongside the connect-targeted grpc port (80). Update authorizerEndpoint to use this port so TypeAuthorizer clients can reach the authorizer via plaintext gRPC. --- charts/controlplane/templates/service.yaml | 6 ++++++ charts/controlplane/values.yaml | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/charts/controlplane/templates/service.yaml b/charts/controlplane/templates/service.yaml index 300f4ab6..b693576e 100644 --- a/charts/controlplane/templates/service.yaml +++ b/charts/controlplane/templates/service.yaml @@ -19,6 +19,12 @@ spec: port: {{ $svc.grpcport | default 8080 }} protocol: TCP targetPort: {{ if $shared.connectPort }}connect{{ else }}grpc{{ end }} + {{- if $svc.grpcNativePort }} + - name: grpc-native + port: {{ $svc.grpcNativePort }} + protocol: TCP + targetPort: grpc + {{- end }} {{- if $svc.connectport }} - name: connect port: {{ $svc.connectport }} diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index d9a39dba..28082220 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -222,7 +222,7 @@ configMap: # in-cluster authorizer service (see services.authorizer below). authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///{{ .Values.services.authorizer.fullnameOverride | default "authorizer" }}.{{ .Release.Namespace }}.svc.cluster.local:80' + authorizerEndpoint: 'dns:///{{ .Values.services.authorizer.fullnameOverride | default "authorizer" }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.services.authorizer.service.grpcNativePort | default 8080 }}' cache: identity: enabled: false @@ -320,6 +320,8 @@ services: triggerProcessorsWait: 10 authorizer: fullnameOverride: "authorizer" + service: + grpcNativePort: 8080 sharedService: connectPort: 8081 args: @@ -1067,7 +1069,7 @@ flyte: insecure: false authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' + authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.services.authorizer.service.grpcNativePort | default 8080 }}' cloudEvents: enable: false connection: @@ -1112,7 +1114,7 @@ flyte: urlPattern: '{{ printf "_SERVICE_.%s.svc.cluster.local:80" .Release.Namespace }}' authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' + authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.services.authorizer.service.grpcNativePort | default 8080 }}' cacheservice: storage-prefix: cached_outputs metrics-scope: flyte From 40321123ce1d9f9bac6c328a535b15ac278fca69 Mon Sep 17 00:00:00 2001 From: Katrina Rogan Date: Tue, 24 Mar 2026 11:12:42 -0700 Subject: [PATCH 07/17] Expose grpc-native port and fix authorizerEndpoint When a service has sharedService.connectPort, the grpc named service port targets the connect server (8081) instead of the raw gRPC server (8080). Add a grpc-native port (from sharedService.grpcNativePort, default 8080) so internal gRPC clients can reach the native gRPC server. Move grpcNativePort from service to sharedService so it survives Terraform values merges (Terraform overrides services.authorizer but preserves sharedService). Hardcode port 8080 in flyte subchart adminServer/cacheserviceServer authorizerEndpoint because the subchart tpl context cannot access parent .Values.services. --- charts/controlplane/templates/service.yaml | 4 ++-- charts/controlplane/values.yaml | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/charts/controlplane/templates/service.yaml b/charts/controlplane/templates/service.yaml index b693576e..85d62203 100644 --- a/charts/controlplane/templates/service.yaml +++ b/charts/controlplane/templates/service.yaml @@ -19,9 +19,9 @@ spec: port: {{ $svc.grpcport | default 8080 }} protocol: TCP targetPort: {{ if $shared.connectPort }}connect{{ else }}grpc{{ end }} - {{- if $svc.grpcNativePort }} + {{- if $shared.connectPort }} - name: grpc-native - port: {{ $svc.grpcNativePort }} + port: {{ $shared.grpcNativePort | default 8080 }} protocol: TCP targetPort: grpc {{- end }} diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index 28082220..d0f6124c 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -222,7 +222,7 @@ configMap: # in-cluster authorizer service (see services.authorizer below). authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///{{ .Values.services.authorizer.fullnameOverride | default "authorizer" }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.services.authorizer.service.grpcNativePort | default 8080 }}' + authorizerEndpoint: 'dns:///{{ .Values.services.authorizer.fullnameOverride | default "authorizer" }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.services.authorizer.sharedService.grpcNativePort | default 8080 }}' cache: identity: enabled: false @@ -320,10 +320,9 @@ services: triggerProcessorsWait: 10 authorizer: fullnameOverride: "authorizer" - service: - grpcNativePort: 8080 sharedService: connectPort: 8081 + grpcNativePort: 8080 args: - authorizer - serve @@ -1069,7 +1068,9 @@ flyte: insecure: false authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.services.authorizer.service.grpcNativePort | default 8080 }}' + # Hardcoded: flyte subchart tpl context cannot access .Values.services, + # so we can't reference services.authorizer.sharedService.grpcNativePort here. + authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:8080' cloudEvents: enable: false connection: @@ -1114,7 +1115,9 @@ flyte: urlPattern: '{{ printf "_SERVICE_.%s.svc.cluster.local:80" .Release.Namespace }}' authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.services.authorizer.service.grpcNativePort | default 8080 }}' + # Hardcoded: flyte subchart tpl context cannot access .Values.services, + # so we can't reference services.authorizer.sharedService.grpcNativePort here. + authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:8080' cacheservice: storage-prefix: cached_outputs metrics-scope: flyte From 03927d847a270837707767a182b5ba097a99779c Mon Sep 17 00:00:00 2001 From: Katrina Rogan Date: Tue, 24 Mar 2026 14:06:47 -0700 Subject: [PATCH 08/17] Route authorizerEndpoint through nginx like all other intra-service calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of connecting directly to the authorizer service on a custom grpc-native port, route through controlplane-nginx-controller — the same path used by all other service-to-service gRPC calls. --- charts/controlplane/values.yaml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index d0f6124c..b49bcaf6 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -222,7 +222,7 @@ configMap: # in-cluster authorizer service (see services.authorizer below). authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///{{ .Values.services.authorizer.fullnameOverride | default "authorizer" }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.services.authorizer.sharedService.grpcNativePort | default 8080 }}' + authorizerEndpoint: 'dns:///controlplane-nginx-controller.{{ .Release.Namespace }}.svc.cluster.local' cache: identity: enabled: false @@ -1068,9 +1068,7 @@ flyte: insecure: false authorizer: type: "Authorizer" - # Hardcoded: flyte subchart tpl context cannot access .Values.services, - # so we can't reference services.authorizer.sharedService.grpcNativePort here. - authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:8080' + authorizerEndpoint: 'dns:///controlplane-nginx-controller.{{ .Release.Namespace }}.svc.cluster.local' cloudEvents: enable: false connection: @@ -1115,9 +1113,7 @@ flyte: urlPattern: '{{ printf "_SERVICE_.%s.svc.cluster.local:80" .Release.Namespace }}' authorizer: type: "Authorizer" - # Hardcoded: flyte subchart tpl context cannot access .Values.services, - # so we can't reference services.authorizer.sharedService.grpcNativePort here. - authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:8080' + authorizerEndpoint: 'dns:///controlplane-nginx-controller.{{ .Release.Namespace }}.svc.cluster.local' cacheservice: storage-prefix: cached_outputs metrics-scope: flyte From e7856d8c478f8c5cfb87cb36ce135290a1dcb969 Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Wed, 25 Mar 2026 16:45:02 +1100 Subject: [PATCH 09/17] Default all services to TypeAuthorizer routing with documented overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All non-authorizer services now default to TypeAuthorizer, routing authorization calls through the in-cluster authorizer service via nginx. The authorizer service itself defaults to Noop. Documents how to configure the authorizer backend for: - Union Cloud (UserClouds) — with full config example - Selfhosted (External) — customer-provided gRPC authz server Also documents UserClouds override patterns for flyteadmin and cacheservice configs. Co-Authored-By: Claude Opus 4.6 (1M context) --- charts/controlplane/values.yaml | 91 ++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 30 deletions(-) diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index 9fd9de13..bdf9cbf9 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -251,8 +251,10 @@ ingress: nginx.ingress.kubernetes.io/auth-cache-key: "$http_authorization$http_flyte_authorization$http_cookie" configMap: - # Authorization — all non-authorizer services route Authorize() calls to the - # in-cluster authorizer service (see services.authorizer below). + # Authorization — all non-authorizer services use TypeAuthorizer to route + # Authorize() calls to the in-cluster authorizer service via the nginx + # ingress controller. The authorizer service itself decides the backend + # (Noop, UserClouds, or External) — see services.authorizer.configMap below. authorizer: type: "Authorizer" authorizerEndpoint: 'dns:///controlplane-nginx-controller.{{ .Release.Namespace }}.svc.cluster.local' @@ -368,42 +370,60 @@ services: - --config - /etc/config/*.yaml configMap: - # The authorizer service defaults to Noop (no enforcement). - # To enable external authorization, set type to "External" and configure - # externalClient with your gRPC authorization server. + # The authorizer service's own backend. All other services route to this + # service via TypeAuthorizer; this config controls what the authorizer + # service does with those requests. # - # Example: - # type: "External" - # externalClient: - # grpcConfig: - # # gRPC target for the external authorization server. - # # Uses standard gRPC name resolution (dns:///, unix:///, etc). - # host: "dns:///your-authz-server.namespace.svc.cluster.local:50051" + # Supported types: + # - "Noop" — no enforcement (default) + # - "UserClouds" — Union Cloud's authorization backend + # - "External" — customer-provided gRPC authorization server (selfhosted) # - # # Connect without TLS (default: false). - # insecure: true + # --- Union Cloud (UserClouds) --- + # For Union Cloud deployments, set type to "UserClouds": + # authorizer: + # type: "UserClouds" + # userCloudsClient: + # tenantUrl: 'http://{{ .Release.Name }}-union-authz.{{ .Release.Namespace }}.svc.cluster.local:8080' + # tenantID: '623771e7-ddd6-4575-bedb-7c970ec75b87' + # clientID: '{{ .Values.union.authz.clientID }}' + # clientSecretName: 'union/client_secret' + # enableLogging: true # - # # Skip server certificate verification — do NOT use in production (default: false). - # # insecureSkipVerify: false + # --- External Authorization (selfhosted) --- + # For selfhosted deployments with a customer-provided authz server: + # authorizer: + # type: "External" + # externalClient: + # grpcConfig: + # # gRPC target for the external authorization server. + # # Uses standard gRPC name resolution (dns:///, unix:///, etc). + # host: "dns:///your-authz-server.namespace.svc.cluster.local:50051" # - # # Timeout per gRPC retry attempt (default: "5s"). - # # perRetryTimeout: "5s" + # # Connect without TLS (default: false). + # insecure: true # - # # Max gRPC retries. 0 = no retries, fail fast (default: 0). - # # maxRetries: 0 + # # Skip server certificate verification — do NOT use in production (default: false). + # # insecureSkipVerify: false # - # # Max delay for gRPC backoff between retries. - # # maxBackoffDelay: "10s" + # # Timeout per gRPC retry attempt (default: "5s"). + # # perRetryTimeout: "5s" # - # # Incoming gRPC metadata keys to forward to the external server. - # # Default: ["authorization", "flyte-authorization"] - # # forwardHeaders: - # # - authorization - # # - flyte-authorization + # # Max gRPC retries. 0 = no retries, fail fast (default: 0). + # # maxRetries: 0 # - # # If true, allow requests when the external server is unreachable. - # # If false (default), deny on error. - # failOpen: false + # # Max delay for gRPC backoff between retries. + # # maxBackoffDelay: "10s" + # + # # Incoming gRPC metadata keys to forward to the external server. + # # Default: ["authorization", "flyte-authorization"] + # # forwardHeaders: + # # - authorization + # # - flyte-authorization + # + # # If true, allow requests when the external server is unreachable. + # # If false (default), deny on error. + # failOpen: false authorizer: type: "Noop" sharedService: @@ -1128,6 +1148,15 @@ flyte: admin: endpoint: 'dns:///{{ .Values.global.UNION_HOST }}' insecure: false + # flyteadmin routes authorization to the in-cluster authorizer service. + # For Union Cloud with UserClouds, override with: + # type: '{{ if eq .Values.global.AUTHZ_TYPE "union" }}UserClouds{{ else }}Noop{{ end }}' + # userCloudsClient: + # tenantUrl: 'http://{{ .Release.Name }}-union-authz.{{ .Release.Namespace }}.svc.cluster.local:8080' + # tenantID: '623771e7-ddd6-4575-bedb-7c970ec75b87' + # clientID: "union-authz-client" + # clientSecretName: 'union/client_secret' + # enableLogging: true authorizer: type: "Authorizer" authorizerEndpoint: 'dns:///controlplane-nginx-controller.{{ .Release.Namespace }}.svc.cluster.local' @@ -1173,6 +1202,8 @@ flyte: internalConnectionConfig: enabled: true urlPattern: '{{ printf "_SERVICE_.%s.svc.cluster.local:80" .Release.Namespace }}' + # cacheservice routes authorization to the in-cluster authorizer service. + # For Union Cloud with UserClouds, override with the same pattern as flyteadmin above. authorizer: type: "Authorizer" authorizerEndpoint: 'dns:///controlplane-nginx-controller.{{ .Release.Namespace }}.svc.cluster.local' From 2f2a8cdde45249ab8410c60e015eb6199ffc9539 Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Wed, 25 Mar 2026 17:10:24 +1100 Subject: [PATCH 10/17] Route authorizerEndpoint directly to authorizer service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change authorizerEndpoint from nginx ingress controller to the authorizer service directly. The nginx path applies /me auth subrequests to all protected-grpc routes including internal AuthorizerService calls, causing 401s on service-to-service Authorize() calls that don't carry browser cookies. Direct routing bypasses this — internal services talk to the authorizer over plain gRPC without nginx auth interference. Co-Authored-By: Claude Opus 4.6 (1M context) --- charts/controlplane/values.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index bdf9cbf9..4edff957 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -252,12 +252,12 @@ ingress: configMap: # Authorization — all non-authorizer services use TypeAuthorizer to route - # Authorize() calls to the in-cluster authorizer service via the nginx - # ingress controller. The authorizer service itself decides the backend - # (Noop, UserClouds, or External) — see services.authorizer.configMap below. + # Authorize() calls directly to the in-cluster authorizer service. + # The authorizer service itself decides the backend (Noop, UserClouds, + # or External) — see services.authorizer.configMap below. authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///controlplane-nginx-controller.{{ .Release.Namespace }}.svc.cluster.local' + authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' cache: identity: enabled: false @@ -1159,7 +1159,7 @@ flyte: # enableLogging: true authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///controlplane-nginx-controller.{{ .Release.Namespace }}.svc.cluster.local' + authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' cloudEvents: enable: false connection: @@ -1206,7 +1206,7 @@ flyte: # For Union Cloud with UserClouds, override with the same pattern as flyteadmin above. authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///controlplane-nginx-controller.{{ .Release.Namespace }}.svc.cluster.local' + authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' cacheservice: storage-prefix: cached_outputs metrics-scope: flyte From 97fd81cd7d3f950d9f542164a79242943278b974 Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Wed, 25 Mar 2026 17:28:27 +1100 Subject: [PATCH 11/17] Replace authorizerEndpoint with authorizerClient gRPC config Migrate from bare `authorizerEndpoint` string to structured `authorizerClient` with `grpcConfig` (host, insecure, etc.) and `forwardHeaders`. This matches the ExternalClient config pattern and allows transport settings to be configured via values. Sets `insecure: true` for direct plaintext gRPC to the authorizer service, and explicitly lists forwarded metadata headers. Co-Authored-By: Claude Opus 4.6 (1M context) --- charts/controlplane/values.yaml | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index 4edff957..9c210a6d 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -257,7 +257,13 @@ configMap: # or External) — see services.authorizer.configMap below. authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' + authorizerClient: + grpcConfig: + host: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' + insecure: true + forwardHeaders: + - authorization + - flyte-authorization cache: identity: enabled: false @@ -1159,7 +1165,13 @@ flyte: # enableLogging: true authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' + authorizerClient: + grpcConfig: + host: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' + insecure: true + forwardHeaders: + - authorization + - flyte-authorization cloudEvents: enable: false connection: @@ -1206,7 +1218,13 @@ flyte: # For Union Cloud with UserClouds, override with the same pattern as flyteadmin above. authorizer: type: "Authorizer" - authorizerEndpoint: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' + authorizerClient: + grpcConfig: + host: 'dns:///authorizer.{{ .Release.Namespace }}.svc.cluster.local:80' + insecure: true + forwardHeaders: + - authorization + - flyte-authorization cacheservice: storage-prefix: cached_outputs metrics-scope: flyte From ec13436d25a65a2b96765ebd14257d21592b9028 Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Wed, 25 Mar 2026 19:28:32 +1100 Subject: [PATCH 12/17] =?UTF-8?q?Remove=20global.AUTHZ=5FTYPE=20=E2=80=94?= =?UTF-8?q?=20authorizer=20config=20is=20now=20values-driven?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the `global.AUTHZ_TYPE` variable and all derived logic: - Remove the forced Noop override in _helpers.tpl that stomped on any authorizer type set via values overlays - Replace AUTHZ_TYPE guards in templates/authz/* with union.authz.enabled (controls UserClouds sidecar pods only) - Remove AUTHZ_TYPE from global defaults Authorization mode is now configured solely through services.authorizer.configMap.authorizer.type and the corresponding client config (authorizerClient, externalClient, userCloudsClient). No global flag needed. Co-Authored-By: Claude Opus 4.6 (1M context) --- charts/controlplane/templates/_helpers.tpl | 10 ---------- .../controlplane/templates/authz/configmap.yaml | 2 +- .../controlplane/templates/authz/deployment.yaml | 2 +- charts/controlplane/templates/authz/hpa.yaml | 2 +- .../templates/authz/networkpolicy.yaml | 2 +- charts/controlplane/templates/authz/pdb.yaml | 2 +- charts/controlplane/templates/authz/rbac.yaml | 2 +- charts/controlplane/templates/authz/service.yaml | 2 +- .../templates/authz/serviceaccount.yaml | 2 +- charts/controlplane/values.yaml | 15 +++++---------- 10 files changed, 13 insertions(+), 28 deletions(-) diff --git a/charts/controlplane/templates/_helpers.tpl b/charts/controlplane/templates/_helpers.tpl index 6e2ca633..489070b9 100644 --- a/charts/controlplane/templates/_helpers.tpl +++ b/charts/controlplane/templates/_helpers.tpl @@ -361,16 +361,6 @@ IfNotPresent {{- end }} {{- $merged := (include "unionai.deepMerge" (dict "dest" $global "source" $svc) | fromYaml) }} -{{- /* When AUTHZ_TYPE is not "union", fall back to Noop authorizer. */}} -{{- /* Note: union.auth (service-to-service OAuth2 token acquisition) is intentionally NOT */}} -{{- /* disabled here. It controls whether services attach bearer tokens when calling other */}} -{{- /* services through nginx, which is needed in any deployment with auth enabled — */}} -{{- /* regardless of whether Union's RBAC authorizer is active. */}} -{{- if ne .Values.global.AUTHZ_TYPE "union" }} - {{- if hasKey $merged "authorizer" }} - {{- $_ := set $merged "authorizer" (dict "type" "Noop") }} - {{- end }} -{{- end }} {{- $rendered := tpl ($merged | toYaml) . }} {{- $rendered }} {{- end }} diff --git a/charts/controlplane/templates/authz/configmap.yaml b/charts/controlplane/templates/authz/configmap.yaml index 9b2b13fe..74d6e527 100644 --- a/charts/controlplane/templates/authz/configmap.yaml +++ b/charts/controlplane/templates/authz/configmap.yaml @@ -1,4 +1,4 @@ -{{- if eq .Values.global.AUTHZ_TYPE "union" -}} +{{- if .Values.union.authz.enabled -}} apiVersion: v1 kind: ConfigMap metadata: diff --git a/charts/controlplane/templates/authz/deployment.yaml b/charts/controlplane/templates/authz/deployment.yaml index 98e09e15..819e7bda 100644 --- a/charts/controlplane/templates/authz/deployment.yaml +++ b/charts/controlplane/templates/authz/deployment.yaml @@ -1,4 +1,4 @@ -{{- if eq .Values.global.AUTHZ_TYPE "union" -}} +{{- if .Values.union.authz.enabled -}} apiVersion: apps/v1 kind: Deployment metadata: diff --git a/charts/controlplane/templates/authz/hpa.yaml b/charts/controlplane/templates/authz/hpa.yaml index bbe5f7db..38236a2a 100644 --- a/charts/controlplane/templates/authz/hpa.yaml +++ b/charts/controlplane/templates/authz/hpa.yaml @@ -1,4 +1,4 @@ -{{- if and (eq .Values.global.AUTHZ_TYPE "union") .Values.union.authz.autoscaling.enabled }} +{{- if and (.Values.union.authz.enabled) .Values.union.authz.autoscaling.enabled }} apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: diff --git a/charts/controlplane/templates/authz/networkpolicy.yaml b/charts/controlplane/templates/authz/networkpolicy.yaml index 64526758..333f1166 100644 --- a/charts/controlplane/templates/authz/networkpolicy.yaml +++ b/charts/controlplane/templates/authz/networkpolicy.yaml @@ -1,4 +1,4 @@ -{{- if and (eq .Values.global.AUTHZ_TYPE "union") .Values.union.authz.networkPolicy.enabled }} +{{- if and (.Values.union.authz.enabled) .Values.union.authz.networkPolicy.enabled }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: diff --git a/charts/controlplane/templates/authz/pdb.yaml b/charts/controlplane/templates/authz/pdb.yaml index dfe0fe6b..732c7568 100644 --- a/charts/controlplane/templates/authz/pdb.yaml +++ b/charts/controlplane/templates/authz/pdb.yaml @@ -1,4 +1,4 @@ -{{- if and (eq .Values.global.AUTHZ_TYPE "union") .Values.union.authz.pdb.enabled }} +{{- if and (.Values.union.authz.enabled) .Values.union.authz.pdb.enabled }} apiVersion: policy/v1 kind: PodDisruptionBudget metadata: diff --git a/charts/controlplane/templates/authz/rbac.yaml b/charts/controlplane/templates/authz/rbac.yaml index f0f8b3e4..51a8e2ab 100644 --- a/charts/controlplane/templates/authz/rbac.yaml +++ b/charts/controlplane/templates/authz/rbac.yaml @@ -1,4 +1,4 @@ -{{- if and (eq .Values.global.AUTHZ_TYPE "union") .Values.union.authz.serviceAccount.create -}} +{{- if and (.Values.union.authz.enabled) .Values.union.authz.serviceAccount.create -}} apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: diff --git a/charts/controlplane/templates/authz/service.yaml b/charts/controlplane/templates/authz/service.yaml index 2393fb49..51f12e49 100644 --- a/charts/controlplane/templates/authz/service.yaml +++ b/charts/controlplane/templates/authz/service.yaml @@ -1,4 +1,4 @@ -{{- if eq .Values.global.AUTHZ_TYPE "union" -}} +{{- if .Values.union.authz.enabled -}} apiVersion: v1 kind: Service metadata: diff --git a/charts/controlplane/templates/authz/serviceaccount.yaml b/charts/controlplane/templates/authz/serviceaccount.yaml index 3f9cfceb..90beb8f5 100644 --- a/charts/controlplane/templates/authz/serviceaccount.yaml +++ b/charts/controlplane/templates/authz/serviceaccount.yaml @@ -1,4 +1,4 @@ -{{- if and (eq .Values.global.AUTHZ_TYPE "union") .Values.union.authz.serviceAccount.create -}} +{{- if and (.Values.union.authz.enabled) .Values.union.authz.serviceAccount.create -}} apiVersion: v1 kind: ServiceAccount metadata: diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index 9c210a6d..bb5c6e2f 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -51,12 +51,6 @@ global: # Contact Union for controlplane access and distribution. IMAGE_REPOSITORY_PREFIX: "643379628101.dkr.ecr.us-east-1.amazonaws.com/union-cp" - # Authorization type for RBAC. Set to "union" to enable UserClouds-based authorization. - # Controls union-authz pod deployment and all four authorizer blocks (union services, - # flyteadmin, cacheservice). Leave empty or set to "noop" to disable (default). - # Visible to subcharts (flyte-core) via global scope. - AUTHZ_TYPE: "" - # OAuth2 client ID for service-to-service authentication (client_credentials flow). # Services use this to acquire tokens for internal calls through nginx. # Example: "0oa3xyz4abc5def6g7h8" @@ -1156,7 +1150,7 @@ flyte: insecure: false # flyteadmin routes authorization to the in-cluster authorizer service. # For Union Cloud with UserClouds, override with: - # type: '{{ if eq .Values.global.AUTHZ_TYPE "union" }}UserClouds{{ else }}Noop{{ end }}' + # type: "UserClouds" # userCloudsClient: # tenantUrl: 'http://{{ .Release.Name }}-union-authz.{{ .Release.Namespace }}.svc.cluster.local:8080' # tenantID: '623771e7-ddd6-4575-bedb-7c970ec75b87' @@ -1417,13 +1411,14 @@ scylla-operator: # union.authz # ---------------------------------------------------------------------------- # Deploys the union-authz (userclouds-lite) service for RBAC authorization. -# Activation is controlled by global.AUTHZ_TYPE (see global section above). -# Set global.AUTHZ_TYPE: "union" in an env overlay to activate the full RBAC -# stack: union-authz pods, all four authorizer blocks, and service-to-service auth. +# Set enabled: true in an env overlay to activate the union-authz pods. +# Authorization type is configured separately via services.authorizer.configMap. # Database settings reference the global controlplane DB by default. # Override database.host/name/user/password for a dedicated database. union: authz: + enabled: false + # OAuth2 client ID used by union CP services to authenticate with union-authz. # This is a fixed constant — userclouds-lite bootstraps its own DB on first start # using whatever value is set here, so no external provisioning is required. From a120b88a029d41295a87fc900af1719308f00f55 Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Wed, 25 Mar 2026 19:32:03 +1100 Subject: [PATCH 13/17] Gate union-authz sidecar on authorizer type instead of separate flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace union.authz.enabled with a check on services.authorizer.configMap.authorizer.type == "UserClouds". The union-authz (userclouds-lite) pods now auto-deploy when the authorizer backend is set to UserClouds — no separate enable flag. Co-Authored-By: Claude Opus 4.6 (1M context) --- charts/controlplane/templates/authz/configmap.yaml | 2 +- charts/controlplane/templates/authz/deployment.yaml | 2 +- charts/controlplane/templates/authz/hpa.yaml | 2 +- charts/controlplane/templates/authz/networkpolicy.yaml | 2 +- charts/controlplane/templates/authz/pdb.yaml | 2 +- charts/controlplane/templates/authz/rbac.yaml | 2 +- charts/controlplane/templates/authz/service.yaml | 2 +- charts/controlplane/templates/authz/serviceaccount.yaml | 2 +- charts/controlplane/values.yaml | 6 ++---- 9 files changed, 10 insertions(+), 12 deletions(-) diff --git a/charts/controlplane/templates/authz/configmap.yaml b/charts/controlplane/templates/authz/configmap.yaml index 74d6e527..1c07b481 100644 --- a/charts/controlplane/templates/authz/configmap.yaml +++ b/charts/controlplane/templates/authz/configmap.yaml @@ -1,4 +1,4 @@ -{{- if .Values.union.authz.enabled -}} +{{- if eq (dig "services" "authorizer" "configMap" "authorizer" "type" "" .Values) "UserClouds" -}} apiVersion: v1 kind: ConfigMap metadata: diff --git a/charts/controlplane/templates/authz/deployment.yaml b/charts/controlplane/templates/authz/deployment.yaml index 819e7bda..cc110c87 100644 --- a/charts/controlplane/templates/authz/deployment.yaml +++ b/charts/controlplane/templates/authz/deployment.yaml @@ -1,4 +1,4 @@ -{{- if .Values.union.authz.enabled -}} +{{- if eq (dig "services" "authorizer" "configMap" "authorizer" "type" "" .Values) "UserClouds" -}} apiVersion: apps/v1 kind: Deployment metadata: diff --git a/charts/controlplane/templates/authz/hpa.yaml b/charts/controlplane/templates/authz/hpa.yaml index 38236a2a..0605ddc4 100644 --- a/charts/controlplane/templates/authz/hpa.yaml +++ b/charts/controlplane/templates/authz/hpa.yaml @@ -1,4 +1,4 @@ -{{- if and (.Values.union.authz.enabled) .Values.union.authz.autoscaling.enabled }} +{{- if and (eq (dig "services" "authorizer" "configMap" "authorizer" "type" "" .Values) "UserClouds") .Values.union.authz.autoscaling.enabled }} apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: diff --git a/charts/controlplane/templates/authz/networkpolicy.yaml b/charts/controlplane/templates/authz/networkpolicy.yaml index 333f1166..d21043c6 100644 --- a/charts/controlplane/templates/authz/networkpolicy.yaml +++ b/charts/controlplane/templates/authz/networkpolicy.yaml @@ -1,4 +1,4 @@ -{{- if and (.Values.union.authz.enabled) .Values.union.authz.networkPolicy.enabled }} +{{- if and (eq (dig "services" "authorizer" "configMap" "authorizer" "type" "" .Values) "UserClouds") .Values.union.authz.networkPolicy.enabled }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: diff --git a/charts/controlplane/templates/authz/pdb.yaml b/charts/controlplane/templates/authz/pdb.yaml index 732c7568..adf46590 100644 --- a/charts/controlplane/templates/authz/pdb.yaml +++ b/charts/controlplane/templates/authz/pdb.yaml @@ -1,4 +1,4 @@ -{{- if and (.Values.union.authz.enabled) .Values.union.authz.pdb.enabled }} +{{- if and (eq (dig "services" "authorizer" "configMap" "authorizer" "type" "" .Values) "UserClouds") .Values.union.authz.pdb.enabled }} apiVersion: policy/v1 kind: PodDisruptionBudget metadata: diff --git a/charts/controlplane/templates/authz/rbac.yaml b/charts/controlplane/templates/authz/rbac.yaml index 51a8e2ab..56a758aa 100644 --- a/charts/controlplane/templates/authz/rbac.yaml +++ b/charts/controlplane/templates/authz/rbac.yaml @@ -1,4 +1,4 @@ -{{- if and (.Values.union.authz.enabled) .Values.union.authz.serviceAccount.create -}} +{{- if and (eq (dig "services" "authorizer" "configMap" "authorizer" "type" "" .Values) "UserClouds") .Values.union.authz.serviceAccount.create -}} apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: diff --git a/charts/controlplane/templates/authz/service.yaml b/charts/controlplane/templates/authz/service.yaml index 51f12e49..3778e086 100644 --- a/charts/controlplane/templates/authz/service.yaml +++ b/charts/controlplane/templates/authz/service.yaml @@ -1,4 +1,4 @@ -{{- if .Values.union.authz.enabled -}} +{{- if eq (dig "services" "authorizer" "configMap" "authorizer" "type" "" .Values) "UserClouds" -}} apiVersion: v1 kind: Service metadata: diff --git a/charts/controlplane/templates/authz/serviceaccount.yaml b/charts/controlplane/templates/authz/serviceaccount.yaml index 90beb8f5..957a7a68 100644 --- a/charts/controlplane/templates/authz/serviceaccount.yaml +++ b/charts/controlplane/templates/authz/serviceaccount.yaml @@ -1,4 +1,4 @@ -{{- if and (.Values.union.authz.enabled) .Values.union.authz.serviceAccount.create -}} +{{- if and (eq (dig "services" "authorizer" "configMap" "authorizer" "type" "" .Values) "UserClouds") .Values.union.authz.serviceAccount.create -}} apiVersion: v1 kind: ServiceAccount metadata: diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index bb5c6e2f..1e530bfa 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -1411,14 +1411,12 @@ scylla-operator: # union.authz # ---------------------------------------------------------------------------- # Deploys the union-authz (userclouds-lite) service for RBAC authorization. -# Set enabled: true in an env overlay to activate the union-authz pods. -# Authorization type is configured separately via services.authorizer.configMap. +# Automatically activated when services.authorizer.configMap.authorizer.type +# is set to "UserClouds". No separate enable flag needed. # Database settings reference the global controlplane DB by default. # Override database.host/name/user/password for a dedicated database. union: authz: - enabled: false - # OAuth2 client ID used by union CP services to authenticate with union-authz. # This is a fixed constant — userclouds-lite bootstraps its own DB on first start # using whatever value is set here, so no external provisioning is required. From f16f8f61ed332822ddbe57b1424ebb27f807e7ff Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Wed, 25 Mar 2026 19:32:03 +1100 Subject: [PATCH 14/17] Gate union-authz sidecar on authorizer type instead of separate flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace union.authz.enabled with a check on services.authorizer.configMap.authorizer.type == "UserClouds". The union-authz (userclouds-lite) pods now auto-deploy when the authorizer backend is set to UserClouds — no separate enable flag. Co-Authored-By: Claude Opus 4.6 (1M context) --- charts/controlplane/templates/authz/configmap.yaml | 2 +- charts/controlplane/templates/authz/deployment.yaml | 2 +- charts/controlplane/templates/authz/hpa.yaml | 2 +- charts/controlplane/templates/authz/networkpolicy.yaml | 2 +- charts/controlplane/templates/authz/pdb.yaml | 2 +- charts/controlplane/templates/authz/rbac.yaml | 2 +- charts/controlplane/templates/authz/service.yaml | 2 +- charts/controlplane/templates/authz/serviceaccount.yaml | 2 +- charts/controlplane/values.yaml | 6 ++---- 9 files changed, 10 insertions(+), 12 deletions(-) diff --git a/charts/controlplane/templates/authz/configmap.yaml b/charts/controlplane/templates/authz/configmap.yaml index 74d6e527..37fdda2f 100644 --- a/charts/controlplane/templates/authz/configmap.yaml +++ b/charts/controlplane/templates/authz/configmap.yaml @@ -1,4 +1,4 @@ -{{- if .Values.union.authz.enabled -}} +{{- if eq ((index .Values "services" "authorizer" "configMap" "authorizer" "type") | default "") "UserClouds" -}} apiVersion: v1 kind: ConfigMap metadata: diff --git a/charts/controlplane/templates/authz/deployment.yaml b/charts/controlplane/templates/authz/deployment.yaml index 819e7bda..b04fa8f0 100644 --- a/charts/controlplane/templates/authz/deployment.yaml +++ b/charts/controlplane/templates/authz/deployment.yaml @@ -1,4 +1,4 @@ -{{- if .Values.union.authz.enabled -}} +{{- if eq ((index .Values "services" "authorizer" "configMap" "authorizer" "type") | default "") "UserClouds" -}} apiVersion: apps/v1 kind: Deployment metadata: diff --git a/charts/controlplane/templates/authz/hpa.yaml b/charts/controlplane/templates/authz/hpa.yaml index 38236a2a..9f8de177 100644 --- a/charts/controlplane/templates/authz/hpa.yaml +++ b/charts/controlplane/templates/authz/hpa.yaml @@ -1,4 +1,4 @@ -{{- if and (.Values.union.authz.enabled) .Values.union.authz.autoscaling.enabled }} +{{- if and (eq ((index .Values "services" "authorizer" "configMap" "authorizer" "type") | default "") "UserClouds") .Values.union.authz.autoscaling.enabled }} apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: diff --git a/charts/controlplane/templates/authz/networkpolicy.yaml b/charts/controlplane/templates/authz/networkpolicy.yaml index 333f1166..3105c0c6 100644 --- a/charts/controlplane/templates/authz/networkpolicy.yaml +++ b/charts/controlplane/templates/authz/networkpolicy.yaml @@ -1,4 +1,4 @@ -{{- if and (.Values.union.authz.enabled) .Values.union.authz.networkPolicy.enabled }} +{{- if and (eq ((index .Values "services" "authorizer" "configMap" "authorizer" "type") | default "") "UserClouds") .Values.union.authz.networkPolicy.enabled }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: diff --git a/charts/controlplane/templates/authz/pdb.yaml b/charts/controlplane/templates/authz/pdb.yaml index 732c7568..42e1a421 100644 --- a/charts/controlplane/templates/authz/pdb.yaml +++ b/charts/controlplane/templates/authz/pdb.yaml @@ -1,4 +1,4 @@ -{{- if and (.Values.union.authz.enabled) .Values.union.authz.pdb.enabled }} +{{- if and (eq ((index .Values "services" "authorizer" "configMap" "authorizer" "type") | default "") "UserClouds") .Values.union.authz.pdb.enabled }} apiVersion: policy/v1 kind: PodDisruptionBudget metadata: diff --git a/charts/controlplane/templates/authz/rbac.yaml b/charts/controlplane/templates/authz/rbac.yaml index 51a8e2ab..d681d292 100644 --- a/charts/controlplane/templates/authz/rbac.yaml +++ b/charts/controlplane/templates/authz/rbac.yaml @@ -1,4 +1,4 @@ -{{- if and (.Values.union.authz.enabled) .Values.union.authz.serviceAccount.create -}} +{{- if and (eq ((index .Values "services" "authorizer" "configMap" "authorizer" "type") | default "") "UserClouds") .Values.union.authz.serviceAccount.create -}} apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: diff --git a/charts/controlplane/templates/authz/service.yaml b/charts/controlplane/templates/authz/service.yaml index 51f12e49..79cfa05f 100644 --- a/charts/controlplane/templates/authz/service.yaml +++ b/charts/controlplane/templates/authz/service.yaml @@ -1,4 +1,4 @@ -{{- if .Values.union.authz.enabled -}} +{{- if eq ((index .Values "services" "authorizer" "configMap" "authorizer" "type") | default "") "UserClouds" -}} apiVersion: v1 kind: Service metadata: diff --git a/charts/controlplane/templates/authz/serviceaccount.yaml b/charts/controlplane/templates/authz/serviceaccount.yaml index 90beb8f5..2b3fab43 100644 --- a/charts/controlplane/templates/authz/serviceaccount.yaml +++ b/charts/controlplane/templates/authz/serviceaccount.yaml @@ -1,4 +1,4 @@ -{{- if and (.Values.union.authz.enabled) .Values.union.authz.serviceAccount.create -}} +{{- if and (eq ((index .Values "services" "authorizer" "configMap" "authorizer" "type") | default "") "UserClouds") .Values.union.authz.serviceAccount.create -}} apiVersion: v1 kind: ServiceAccount metadata: diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index bb5c6e2f..1e530bfa 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -1411,14 +1411,12 @@ scylla-operator: # union.authz # ---------------------------------------------------------------------------- # Deploys the union-authz (userclouds-lite) service for RBAC authorization. -# Set enabled: true in an env overlay to activate the union-authz pods. -# Authorization type is configured separately via services.authorizer.configMap. +# Automatically activated when services.authorizer.configMap.authorizer.type +# is set to "UserClouds". No separate enable flag needed. # Database settings reference the global controlplane DB by default. # Override database.host/name/user/password for a dedicated database. union: authz: - enabled: false - # OAuth2 client ID used by union CP services to authenticate with union-authz. # This is a fixed constant — userclouds-lite bootstraps its own DB on first start # using whatever value is set here, so no external provisioning is required. From bd8fdf4aae259e65188e45037340eaf08dfc5716 Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Thu, 26 Mar 2026 22:02:57 +1100 Subject: [PATCH 15/17] Add authorizer dashboard panels and alerting rules Dashboard (union-controlplane-overview.json): - Authorizer Mode stat panel showing active backend type - External Backend Latency (p50/p95/p99) - External Errors by gRPC Code for failure mode disambiguation - Fail-Open Activations (security-critical bypass events) - Decisions by Action (stacked allowed/denied by action type) - Error Attribution by error_source PrometheusRule alerts (gated by monitoring.alerting.enabled): - UnionCPAuthorizerExternalErrors: external backend errors >0.1/s - UnionCPAuthorizerFailOpenActive: authorization bypass detected - UnionCPAuthorizerHighDenyRate: >50% deny rate Recording rule: - union:cp:authz:external_error_rate Co-Authored-By: Claude Opus 4.6 (1M context) --- .../union-controlplane-overview.json | 263 ++++++++++++++++++ .../templates/monitoring/prometheusrule.yaml | 40 +++ 2 files changed, 303 insertions(+) diff --git a/charts/controlplane/dashboards/union-controlplane-overview.json b/charts/controlplane/dashboards/union-controlplane-overview.json index f9d7f9d5..2aab235a 100644 --- a/charts/controlplane/dashboards/union-controlplane-overview.json +++ b/charts/controlplane/dashboards/union-controlplane-overview.json @@ -2784,6 +2784,269 @@ } ], "description": "Percentage of authorization decisions that denied access. Spikes indicate policy changes or auth issues. [Metrics pending: requires cloud service instrumentation to be deployed]" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "mappings": [ + { + "type": "value", + "options": { + "noop": { "text": "Noop", "index": 0 }, + "userclouds": { "text": "UserClouds", "index": 1 }, + "external": { "text": "External", "index": 2 }, + "authorizer": { "text": "Authorizer", "index": 3 } + } + } + ] + } + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 0, + "y": 23 + }, + "id": 760, + "options": { + "colorMode": "background", + "graphMode": "none", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^type$/" + }, + "textMode": "value" + }, + "title": "Authorizer Mode", + "type": "stat", + "targets": [ + { + "expr": "authorizer:cloudauthorizer:authz_type_info{namespace=\"$namespace\"} == 1", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "description": "Currently active authorizer backend type (Noop, UserClouds, External, Authorizer)." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ms" + } + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 4, + "y": 23 + }, + "id": 761, + "title": "External Backend Latency", + "type": "timeseries", + "targets": [ + { + "expr": "histogram_quantile(0.50, sum by (le) (rate(authorizer:cloudauthorizer:external:authorize_duration_bucket{namespace=\"$namespace\"}[$__rate_interval])))", + "legendFormat": "p50", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, sum by (le) (rate(authorizer:cloudauthorizer:external:authorize_duration_bucket{namespace=\"$namespace\"}[$__rate_interval])))", + "legendFormat": "p95", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.99, sum by (le) (rate(authorizer:cloudauthorizer:external:authorize_duration_bucket{namespace=\"$namespace\"}[$__rate_interval])))", + "legendFormat": "p99", + "refId": "C" + } + ], + "description": "Latency of calls to the external authorization backend (p50/p95/p99)." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 23 + }, + "id": 762, + "title": "External Errors by gRPC Code", + "type": "timeseries", + "targets": [ + { + "expr": "sum by (grpc_code) (rate(authorizer:cloudauthorizer:external:errors{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "{{ grpc_code }}", + "refId": "A" + } + ], + "description": "Error rate from the external authorization backend, broken down by gRPC status code." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 23 + }, + "id": 763, + "title": "Fail-Open Activations", + "type": "timeseries", + "targets": [ + { + "expr": "rate(authorizer:cloudauthorizer:external:fail_open_activated{namespace=\"$namespace\"}[$__rate_interval])", + "legendFormat": "Fail-Open", + "refId": "A" + } + ], + "description": "Rate of fail-open activations. Non-zero means the external backend is unreachable and requests are being allowed without authorization." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never", + "stacking": { + "mode": "normal" + } + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 31 + }, + "id": 764, + "title": "Decisions by Action", + "type": "timeseries", + "targets": [ + { + "expr": "sum by (action) (rate(authorizer:cloudauthorizer:authz_allowed{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "allowed: {{ action }}", + "refId": "A" + }, + { + "expr": "sum by (action) (rate(authorizer:cloudauthorizer:authz_denied{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "denied: {{ action }}", + "refId": "B" + } + ], + "description": "Authorization decisions broken down by action (e.g. read, write, execute). Stacked to show total volume." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 31 + }, + "id": 765, + "title": "Error Attribution", + "type": "timeseries", + "targets": [ + { + "expr": "sum by (error_source) (rate(authorizer:cloudauthorizer:authorize_errors_total{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "{{ error_source }}", + "refId": "A" + } + ], + "description": "Authorization errors attributed by source (e.g. identity resolution, backend, policy evaluation)." } ] }, diff --git a/charts/controlplane/templates/monitoring/prometheusrule.yaml b/charts/controlplane/templates/monitoring/prometheusrule.yaml index a6578619..66302a0a 100644 --- a/charts/controlplane/templates/monitoring/prometheusrule.yaml +++ b/charts/controlplane/templates/monitoring/prometheusrule.yaml @@ -45,6 +45,12 @@ spec: expr: | sum(rate(nginx_ingress_controller_request_duration_seconds_count{namespace="{{ .Release.Namespace }}", status=~"5.."}[5m])) + - record: union:cp:authz:external_error_rate + expr: | + sum(rate(authorizer:cloudauthorizer:external:errors{namespace="{{ .Release.Namespace }}"}[5m])) + / + (sum(rate(authorizer:cloudauthorizer:external:authorize_duration_count{namespace="{{ .Release.Namespace }}"}[5m])) > 0 or vector(1)) + {{- if .Values.monitoring.alerting.enabled }} # --- Operational alerts (opt-in) --- # Basic health checks only. For SLO-based alerts, see monitoring.slos. @@ -81,6 +87,40 @@ spec: severity: critical annotations: summary: "Handler panic detected in CP service" + + - alert: UnionCPAuthorizerExternalErrors + expr: | + sum(rate(authorizer:cloudauthorizer:external:errors{namespace="{{ .Release.Namespace }}"}[5m])) > 0.1 + for: 5m + labels: + severity: warning + annotations: + summary: "External authorization backend returning errors" + description: "The external authz backend is returning errors at >0.1/s for 5+ minutes. Check external backend health." + + - alert: UnionCPAuthorizerFailOpenActive + expr: | + sum(rate(authorizer:cloudauthorizer:external:fail_open_activated{namespace="{{ .Release.Namespace }}"}[5m])) > 0 + for: 1m + labels: + severity: critical + annotations: + summary: "Authorizer fail-open mode is actively bypassing authorization" + description: "The external authz backend is unreachable and fail-open is allowing requests without authorization checks." + + - alert: UnionCPAuthorizerHighDenyRate + expr: | + ( + sum(rate(authorizer:cloudauthorizer:authz_denied{namespace="{{ .Release.Namespace }}"}[5m])) + / + (sum(rate(authorizer:cloudauthorizer:authz_allowed{namespace="{{ .Release.Namespace }}"}[5m])) + sum(rate(authorizer:cloudauthorizer:authz_denied{namespace="{{ .Release.Namespace }}"}[5m]))) + ) > 0.5 + for: 10m + labels: + severity: warning + annotations: + summary: "Authorization deny rate exceeds 50%" + description: "More than half of authorization decisions are being denied. Possible policy misconfiguration." {{- end }} {{- if .Values.monitoring.slos.enabled }} From a55a996d42716344384f82f3d0c65013cc0700b9 Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Thu, 26 Mar 2026 22:37:53 +1100 Subject: [PATCH 16/17] Regenerate helm test snapshots Update expected snapshots for: - TypeAuthorizer routing defaults with authorizerClient config - grpc-native port on services with connectPort - Removed global.AUTHZ_TYPE and Noop override - union.authz gated on authorizer type instead of enabled flag - Authorizer dashboard panels and alerting rules Co-Authored-By: Claude Opus 4.6 (1M context) --- .../controlplane.aws.billing-enable.yaml | 374 +++++++++- tests/generated/controlplane.aws.yaml | 374 +++++++++- tests/generated/controlplane.userclouds.yaml | 688 +++++++++--------- 3 files changed, 1045 insertions(+), 391 deletions(-) diff --git a/tests/generated/controlplane.aws.billing-enable.yaml b/tests/generated/controlplane.aws.billing-enable.yaml index 8628c26e..7f2674a3 100644 --- a/tests/generated/controlplane.aws.billing-enable.yaml +++ b/tests/generated/controlplane.aws.billing-enable.yaml @@ -419,15 +419,14 @@ data: - profile - openid authorizer: - internalCommunicationConfig: - enabled: false - type: 'Noop' - userCloudsClient: - clientID: union-authz-client - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cloudEvents: enable: false connection: @@ -547,15 +546,14 @@ data: level: null server.yaml: | authorizer: - internalCommunicationConfig: - enabled: false - type: 'Noop' - userCloudsClient: - clientID: union-authz-client - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache-server: grpcPort: 8089 grpcServerReflection: true @@ -605,6 +603,13 @@ metadata: data: config.yaml: | authorizer: + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true type: Noop cache: identity: @@ -653,7 +658,14 @@ metadata: data: config.yaml: | authorizer: - type: Noop + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -716,7 +728,14 @@ metadata: data: config.yaml: | authorizer: - type: Noop + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -767,7 +786,14 @@ metadata: data: config.yaml: | authorizer: - type: Noop + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -837,7 +863,14 @@ metadata: data: config.yaml: | authorizer: - type: Noop + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -894,7 +927,14 @@ metadata: data: config.yaml: | authorizer: - type: Noop + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -951,7 +991,14 @@ metadata: data: config.yaml: | authorizer: - type: Noop + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer billing: enable: true cache: @@ -3900,6 +3947,269 @@ data: } ], "description": "Percentage of authorization decisions that denied access. Spikes indicate policy changes or auth issues. [Metrics pending: requires cloud service instrumentation to be deployed]" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "mappings": [ + { + "type": "value", + "options": { + "noop": { "text": "Noop", "index": 0 }, + "userclouds": { "text": "UserClouds", "index": 1 }, + "external": { "text": "External", "index": 2 }, + "authorizer": { "text": "Authorizer", "index": 3 } + } + } + ] + } + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 0, + "y": 23 + }, + "id": 760, + "options": { + "colorMode": "background", + "graphMode": "none", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^type$/" + }, + "textMode": "value" + }, + "title": "Authorizer Mode", + "type": "stat", + "targets": [ + { + "expr": "authorizer:cloudauthorizer:authz_type_info{namespace=\"$namespace\"} == 1", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "description": "Currently active authorizer backend type (Noop, UserClouds, External, Authorizer)." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ms" + } + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 4, + "y": 23 + }, + "id": 761, + "title": "External Backend Latency", + "type": "timeseries", + "targets": [ + { + "expr": "histogram_quantile(0.50, sum by (le) (rate(authorizer:cloudauthorizer:external:authorize_duration_bucket{namespace=\"$namespace\"}[$__rate_interval])))", + "legendFormat": "p50", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, sum by (le) (rate(authorizer:cloudauthorizer:external:authorize_duration_bucket{namespace=\"$namespace\"}[$__rate_interval])))", + "legendFormat": "p95", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.99, sum by (le) (rate(authorizer:cloudauthorizer:external:authorize_duration_bucket{namespace=\"$namespace\"}[$__rate_interval])))", + "legendFormat": "p99", + "refId": "C" + } + ], + "description": "Latency of calls to the external authorization backend (p50/p95/p99)." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 23 + }, + "id": 762, + "title": "External Errors by gRPC Code", + "type": "timeseries", + "targets": [ + { + "expr": "sum by (grpc_code) (rate(authorizer:cloudauthorizer:external:errors{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "{{ grpc_code }}", + "refId": "A" + } + ], + "description": "Error rate from the external authorization backend, broken down by gRPC status code." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 23 + }, + "id": 763, + "title": "Fail-Open Activations", + "type": "timeseries", + "targets": [ + { + "expr": "rate(authorizer:cloudauthorizer:external:fail_open_activated{namespace=\"$namespace\"}[$__rate_interval])", + "legendFormat": "Fail-Open", + "refId": "A" + } + ], + "description": "Rate of fail-open activations. Non-zero means the external backend is unreachable and requests are being allowed without authorization." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never", + "stacking": { + "mode": "normal" + } + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 31 + }, + "id": 764, + "title": "Decisions by Action", + "type": "timeseries", + "targets": [ + { + "expr": "sum by (action) (rate(authorizer:cloudauthorizer:authz_allowed{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "allowed: {{ action }}", + "refId": "A" + }, + { + "expr": "sum by (action) (rate(authorizer:cloudauthorizer:authz_denied{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "denied: {{ action }}", + "refId": "B" + } + ], + "description": "Authorization decisions broken down by action (e.g. read, write, execute). Stacked to show total volume." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 31 + }, + "id": 765, + "title": "Error Attribution", + "type": "timeseries", + "targets": [ + { + "expr": "sum by (error_source) (rate(authorizer:cloudauthorizer:authorize_errors_total{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "{{ error_source }}", + "refId": "A" + } + ], + "description": "Authorization errors attributed by source (e.g. identity resolution, backend, policy evaluation)." } ] }, @@ -5337,6 +5647,10 @@ spec: port: 80 protocol: TCP targetPort: connect + - name: grpc-native + port: 8080 + protocol: TCP + targetPort: grpc - name: connect port: 83 protocol: TCP @@ -5372,6 +5686,10 @@ spec: port: 80 protocol: TCP targetPort: connect + - name: grpc-native + port: 8080 + protocol: TCP + targetPort: grpc - name: connect port: 83 protocol: TCP @@ -5547,6 +5865,10 @@ spec: port: 80 protocol: TCP targetPort: connect + - name: grpc-native + port: 8080 + protocol: TCP + targetPort: grpc - name: connect port: 83 protocol: TCP @@ -5587,7 +5909,7 @@ spec: template: metadata: annotations: - configChecksum: "b509f2650de48d5f4e931d1494a4871db2c0fb3db30ce957593c84712e6db7c" + configChecksum: "dfd6624df8ba9f5d5ca6fd6c5bb8c04f6caaa038a79043a6d545384a00ff261" kubectl.kubernetes.io/default-container: flyteadmin labels: app.kubernetes.io/name: flyteadmin @@ -5966,7 +6288,7 @@ spec: template: metadata: annotations: - configChecksum: "fb5bd9900a4b2f1bed4038dfdbd4184d7a8aac5bb50ba8061d39f2d888dd363" + configChecksum: "4faf73330109ffb030ace3e963be59139b834fc0b88a14740de4f6bbe0f8d84" labels: app.kubernetes.io/name: cacheservice app.kubernetes.io/instance: release-name diff --git a/tests/generated/controlplane.aws.yaml b/tests/generated/controlplane.aws.yaml index b2b03e19..fc620d5a 100644 --- a/tests/generated/controlplane.aws.yaml +++ b/tests/generated/controlplane.aws.yaml @@ -419,15 +419,14 @@ data: - profile - openid authorizer: - internalCommunicationConfig: - enabled: false - type: 'Noop' - userCloudsClient: - clientID: union-authz-client - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cloudEvents: enable: false connection: @@ -547,15 +546,14 @@ data: level: null server.yaml: | authorizer: - internalCommunicationConfig: - enabled: false - type: 'Noop' - userCloudsClient: - clientID: union-authz-client - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache-server: grpcPort: 8089 grpcServerReflection: true @@ -605,6 +603,13 @@ metadata: data: config.yaml: | authorizer: + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true type: Noop cache: identity: @@ -653,7 +658,14 @@ metadata: data: config.yaml: | authorizer: - type: Noop + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -716,7 +728,14 @@ metadata: data: config.yaml: | authorizer: - type: Noop + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -767,7 +786,14 @@ metadata: data: config.yaml: | authorizer: - type: Noop + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -837,7 +863,14 @@ metadata: data: config.yaml: | authorizer: - type: Noop + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -894,7 +927,14 @@ metadata: data: config.yaml: | authorizer: - type: Noop + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -951,7 +991,14 @@ metadata: data: config.yaml: | authorizer: - type: Noop + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer billing: enable: false cache: @@ -3900,6 +3947,269 @@ data: } ], "description": "Percentage of authorization decisions that denied access. Spikes indicate policy changes or auth issues. [Metrics pending: requires cloud service instrumentation to be deployed]" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "mappings": [ + { + "type": "value", + "options": { + "noop": { "text": "Noop", "index": 0 }, + "userclouds": { "text": "UserClouds", "index": 1 }, + "external": { "text": "External", "index": 2 }, + "authorizer": { "text": "Authorizer", "index": 3 } + } + } + ] + } + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 0, + "y": 23 + }, + "id": 760, + "options": { + "colorMode": "background", + "graphMode": "none", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^type$/" + }, + "textMode": "value" + }, + "title": "Authorizer Mode", + "type": "stat", + "targets": [ + { + "expr": "authorizer:cloudauthorizer:authz_type_info{namespace=\"$namespace\"} == 1", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "description": "Currently active authorizer backend type (Noop, UserClouds, External, Authorizer)." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ms" + } + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 4, + "y": 23 + }, + "id": 761, + "title": "External Backend Latency", + "type": "timeseries", + "targets": [ + { + "expr": "histogram_quantile(0.50, sum by (le) (rate(authorizer:cloudauthorizer:external:authorize_duration_bucket{namespace=\"$namespace\"}[$__rate_interval])))", + "legendFormat": "p50", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, sum by (le) (rate(authorizer:cloudauthorizer:external:authorize_duration_bucket{namespace=\"$namespace\"}[$__rate_interval])))", + "legendFormat": "p95", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.99, sum by (le) (rate(authorizer:cloudauthorizer:external:authorize_duration_bucket{namespace=\"$namespace\"}[$__rate_interval])))", + "legendFormat": "p99", + "refId": "C" + } + ], + "description": "Latency of calls to the external authorization backend (p50/p95/p99)." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 23 + }, + "id": 762, + "title": "External Errors by gRPC Code", + "type": "timeseries", + "targets": [ + { + "expr": "sum by (grpc_code) (rate(authorizer:cloudauthorizer:external:errors{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "{{ grpc_code }}", + "refId": "A" + } + ], + "description": "Error rate from the external authorization backend, broken down by gRPC status code." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 23 + }, + "id": 763, + "title": "Fail-Open Activations", + "type": "timeseries", + "targets": [ + { + "expr": "rate(authorizer:cloudauthorizer:external:fail_open_activated{namespace=\"$namespace\"}[$__rate_interval])", + "legendFormat": "Fail-Open", + "refId": "A" + } + ], + "description": "Rate of fail-open activations. Non-zero means the external backend is unreachable and requests are being allowed without authorization." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never", + "stacking": { + "mode": "normal" + } + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 31 + }, + "id": 764, + "title": "Decisions by Action", + "type": "timeseries", + "targets": [ + { + "expr": "sum by (action) (rate(authorizer:cloudauthorizer:authz_allowed{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "allowed: {{ action }}", + "refId": "A" + }, + { + "expr": "sum by (action) (rate(authorizer:cloudauthorizer:authz_denied{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "denied: {{ action }}", + "refId": "B" + } + ], + "description": "Authorization decisions broken down by action (e.g. read, write, execute). Stacked to show total volume." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 31 + }, + "id": 765, + "title": "Error Attribution", + "type": "timeseries", + "targets": [ + { + "expr": "sum by (error_source) (rate(authorizer:cloudauthorizer:authorize_errors_total{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "{{ error_source }}", + "refId": "A" + } + ], + "description": "Authorization errors attributed by source (e.g. identity resolution, backend, policy evaluation)." } ] }, @@ -5337,6 +5647,10 @@ spec: port: 80 protocol: TCP targetPort: connect + - name: grpc-native + port: 8080 + protocol: TCP + targetPort: grpc - name: connect port: 83 protocol: TCP @@ -5372,6 +5686,10 @@ spec: port: 80 protocol: TCP targetPort: connect + - name: grpc-native + port: 8080 + protocol: TCP + targetPort: grpc - name: connect port: 83 protocol: TCP @@ -5547,6 +5865,10 @@ spec: port: 80 protocol: TCP targetPort: connect + - name: grpc-native + port: 8080 + protocol: TCP + targetPort: grpc - name: connect port: 83 protocol: TCP @@ -5587,7 +5909,7 @@ spec: template: metadata: annotations: - configChecksum: "b509f2650de48d5f4e931d1494a4871db2c0fb3db30ce957593c84712e6db7c" + configChecksum: "dfd6624df8ba9f5d5ca6fd6c5bb8c04f6caaa038a79043a6d545384a00ff261" kubectl.kubernetes.io/default-container: flyteadmin labels: app.kubernetes.io/name: flyteadmin @@ -5966,7 +6288,7 @@ spec: template: metadata: annotations: - configChecksum: "fb5bd9900a4b2f1bed4038dfdbd4184d7a8aac5bb50ba8061d39f2d888dd363" + configChecksum: "4faf73330109ffb030ace3e963be59139b834fc0b88a14740de4f6bbe0f8d84" labels: app.kubernetes.io/name: cacheservice app.kubernetes.io/instance: release-name diff --git a/tests/generated/controlplane.userclouds.yaml b/tests/generated/controlplane.userclouds.yaml index 8c959a2c..73e165ac 100644 --- a/tests/generated/controlplane.userclouds.yaml +++ b/tests/generated/controlplane.userclouds.yaml @@ -31,24 +31,6 @@ spec: app.kubernetes.io/name: webhook-server app.kubernetes.io/instance: webhook-server --- -# Source: controlplane/templates/authz/pdb.yaml -apiVersion: policy/v1 -kind: PodDisruptionBudget -metadata: - name: release-name-union-authz - labels: - helm.sh/chart: controlplane-2026.3.10 - app.kubernetes.io/name: union-authz - app.kubernetes.io/instance: release-name - app.kubernetes.io/version: "2026.3.7" - app.kubernetes.io/managed-by: Helm -spec: - minAvailable: 2 - selector: - matchLabels: - app.kubernetes.io/name: union-authz - app.kubernetes.io/instance: release-name ---- # Source: controlplane/templates/console/pdb.yaml apiVersion: policy/v1 kind: PodDisruptionBudget @@ -222,18 +204,6 @@ metadata: app.kubernetes.io/name: webhook-server app.kubernetes.io/instance: webhook-server --- -# Source: controlplane/templates/authz/serviceaccount.yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: release-name-union-authz - labels: - helm.sh/chart: controlplane-2026.3.10 - app.kubernetes.io/name: union-authz - app.kubernetes.io/instance: release-name - app.kubernetes.io/version: "2026.3.7" - app.kubernetes.io/managed-by: Helm ---- # Source: controlplane/templates/cacheservice/rbac.yaml apiVersion: v1 kind: ServiceAccount @@ -449,15 +419,14 @@ data: - profile - openid authorizer: - internalCommunicationConfig: - enabled: false - type: 'UserClouds' - userCloudsClient: - clientID: union-authz-client - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cloudEvents: enable: false connection: @@ -551,52 +520,6 @@ data: BASE_URL: /console CONFIG_DIR: /etc/flyte/config --- -# Source: controlplane/templates/authz/configmap.yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: release-name-union-authz-config - labels: - helm.sh/chart: controlplane-2026.3.10 - app.kubernetes.io/name: union-authz - app.kubernetes.io/instance: release-name - app.kubernetes.io/version: "2026.3.7" - app.kubernetes.io/managed-by: Helm -data: - config.yaml: | - database: - host: "" - port: 5432 - name: "userclouds" - user: "" - password: "file:///etc/db/pass.txt" - sslMode: "require" - mode: "normal" - - auth: - issuer: "http://release-name-union-authz.union.svc.cluster.local:8080" - signingKey: "kube://secrets/userclouds-signing-key?key=signing_key" - apps: - - credentials: - - clientId: 'union-authz-client' - clientSecret: kube://secrets/?key=client_secret - id: union-controlplane - name: union-controlplane - - cache: - enabled: true - type: "memory" - ttl: "60m" - memory: - maxEntries: 100000 - shards: 128 - depShards: 128 - - services: - checkAttributeEndpoint: "http://localhost:8080" - idpEndpoint: "http://localhost:8080" - authzEndpoint: "http://localhost:8080" ---- # Source: controlplane/templates/cacheservice/configmap.yaml apiVersion: v1 kind: ConfigMap @@ -623,15 +546,14 @@ data: level: null server.yaml: | authorizer: - internalCommunicationConfig: - enabled: false - type: 'UserClouds' - userCloudsClient: - clientID: union-authz-client - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache-server: grpcPort: 8089 grpcServerReflection: true @@ -681,13 +603,14 @@ metadata: data: config.yaml: | authorizer: - type: UserClouds - userCloudsClient: - clientID: 'union-authz-client' - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Noop cache: identity: enabled: false @@ -735,13 +658,14 @@ metadata: data: config.yaml: | authorizer: - type: UserClouds - userCloudsClient: - clientID: 'union-authz-client' - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -804,13 +728,14 @@ metadata: data: config.yaml: | authorizer: - type: UserClouds - userCloudsClient: - clientID: 'union-authz-client' - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -861,13 +786,14 @@ metadata: data: config.yaml: | authorizer: - type: UserClouds - userCloudsClient: - clientID: 'union-authz-client' - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -937,13 +863,14 @@ metadata: data: config.yaml: | authorizer: - type: UserClouds - userCloudsClient: - clientID: 'union-authz-client' - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -1000,13 +927,14 @@ metadata: data: config.yaml: | authorizer: - type: UserClouds - userCloudsClient: - clientID: 'union-authz-client' - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer cache: identity: enabled: false @@ -1063,13 +991,14 @@ metadata: data: config.yaml: | authorizer: - type: UserClouds - userCloudsClient: - clientID: 'union-authz-client' - clientSecretName: union/client_secret - enableLogging: true - tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 - tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 + authorizerClient: + forwardHeaders: + - authorization + - flyte-authorization + grpcConfig: + host: dns:///authorizer.union.svc.cluster.local:80 + insecure: true + type: Authorizer billing: enable: false cache: @@ -4018,6 +3947,269 @@ data: } ], "description": "Percentage of authorization decisions that denied access. Spikes indicate policy changes or auth issues. [Metrics pending: requires cloud service instrumentation to be deployed]" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "mappings": [ + { + "type": "value", + "options": { + "noop": { "text": "Noop", "index": 0 }, + "userclouds": { "text": "UserClouds", "index": 1 }, + "external": { "text": "External", "index": 2 }, + "authorizer": { "text": "Authorizer", "index": 3 } + } + } + ] + } + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 0, + "y": 23 + }, + "id": 760, + "options": { + "colorMode": "background", + "graphMode": "none", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^type$/" + }, + "textMode": "value" + }, + "title": "Authorizer Mode", + "type": "stat", + "targets": [ + { + "expr": "authorizer:cloudauthorizer:authz_type_info{namespace=\"$namespace\"} == 1", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "description": "Currently active authorizer backend type (Noop, UserClouds, External, Authorizer)." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ms" + } + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 4, + "y": 23 + }, + "id": 761, + "title": "External Backend Latency", + "type": "timeseries", + "targets": [ + { + "expr": "histogram_quantile(0.50, sum by (le) (rate(authorizer:cloudauthorizer:external:authorize_duration_bucket{namespace=\"$namespace\"}[$__rate_interval])))", + "legendFormat": "p50", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, sum by (le) (rate(authorizer:cloudauthorizer:external:authorize_duration_bucket{namespace=\"$namespace\"}[$__rate_interval])))", + "legendFormat": "p95", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.99, sum by (le) (rate(authorizer:cloudauthorizer:external:authorize_duration_bucket{namespace=\"$namespace\"}[$__rate_interval])))", + "legendFormat": "p99", + "refId": "C" + } + ], + "description": "Latency of calls to the external authorization backend (p50/p95/p99)." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 23 + }, + "id": 762, + "title": "External Errors by gRPC Code", + "type": "timeseries", + "targets": [ + { + "expr": "sum by (grpc_code) (rate(authorizer:cloudauthorizer:external:errors{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "{{ grpc_code }}", + "refId": "A" + } + ], + "description": "Error rate from the external authorization backend, broken down by gRPC status code." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 23 + }, + "id": 763, + "title": "Fail-Open Activations", + "type": "timeseries", + "targets": [ + { + "expr": "rate(authorizer:cloudauthorizer:external:fail_open_activated{namespace=\"$namespace\"}[$__rate_interval])", + "legendFormat": "Fail-Open", + "refId": "A" + } + ], + "description": "Rate of fail-open activations. Non-zero means the external backend is unreachable and requests are being allowed without authorization." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never", + "stacking": { + "mode": "normal" + } + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 31 + }, + "id": 764, + "title": "Decisions by Action", + "type": "timeseries", + "targets": [ + { + "expr": "sum by (action) (rate(authorizer:cloudauthorizer:authz_allowed{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "allowed: {{ action }}", + "refId": "A" + }, + { + "expr": "sum by (action) (rate(authorizer:cloudauthorizer:authz_denied{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "denied: {{ action }}", + "refId": "B" + } + ], + "description": "Authorization decisions broken down by action (e.g. read, write, execute). Stacked to show total volume." + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 1, + "showPoints": "never" + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 31 + }, + "id": 765, + "title": "Error Attribution", + "type": "timeseries", + "targets": [ + { + "expr": "sum by (error_source) (rate(authorizer:cloudauthorizer:authorize_errors_total{namespace=\"$namespace\"}[$__rate_interval]))", + "legendFormat": "{{ error_source }}", + "refId": "A" + } + ], + "description": "Authorization errors attributed by source (e.g. identity resolution, backend, policy evaluation)." } ] }, @@ -5250,22 +5442,6 @@ subjects: name: scylla-operator namespace: scylla-operator --- -# Source: controlplane/templates/authz/rbac.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: release-name-union-authz-secrets-manager - labels: - helm.sh/chart: controlplane-2026.3.10 - app.kubernetes.io/name: union-authz - app.kubernetes.io/instance: release-name - app.kubernetes.io/version: "2026.3.7" - app.kubernetes.io/managed-by: Helm -rules: - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list", "watch", "create", "update", "delete"] ---- # Source: controlplane/templates/flyte-core-app.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: Role @@ -5297,26 +5473,6 @@ rules: verbs: - '*' --- -# Source: controlplane/templates/authz/rbac.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: release-name-union-authz-secrets-manager - labels: - helm.sh/chart: controlplane-2026.3.10 - app.kubernetes.io/name: union-authz - app.kubernetes.io/instance: release-name - app.kubernetes.io/version: "2026.3.7" - app.kubernetes.io/managed-by: Helm -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: release-name-union-authz-secrets-manager -subjects: - - kind: ServiceAccount - name: release-name-union-authz - namespace: union ---- # Source: controlplane/templates/flyte-core-app.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding @@ -5414,28 +5570,6 @@ spec: app.kubernetes.io/name: webhook-server app.kubernetes.io/instance: webhook-server --- -# Source: controlplane/templates/authz/service.yaml -apiVersion: v1 -kind: Service -metadata: - name: release-name-union-authz - labels: - helm.sh/chart: controlplane-2026.3.10 - app.kubernetes.io/name: union-authz - app.kubernetes.io/instance: release-name - app.kubernetes.io/version: "2026.3.7" - app.kubernetes.io/managed-by: Helm -spec: - type: ClusterIP - ports: - - port: 8080 - targetPort: http - protocol: TCP - name: http - selector: - app.kubernetes.io/name: union-authz - app.kubernetes.io/instance: release-name ---- # Source: controlplane/templates/cacheservice/service.yaml apiVersion: v1 kind: Service @@ -5513,6 +5647,10 @@ spec: port: 80 protocol: TCP targetPort: connect + - name: grpc-native + port: 8080 + protocol: TCP + targetPort: grpc - name: connect port: 83 protocol: TCP @@ -5548,6 +5686,10 @@ spec: port: 80 protocol: TCP targetPort: connect + - name: grpc-native + port: 8080 + protocol: TCP + targetPort: grpc - name: connect port: 83 protocol: TCP @@ -5723,6 +5865,10 @@ spec: port: 80 protocol: TCP targetPort: connect + - name: grpc-native + port: 8080 + protocol: TCP + targetPort: grpc - name: connect port: 83 protocol: TCP @@ -5763,7 +5909,7 @@ spec: template: metadata: annotations: - configChecksum: "4150b5c1afad3c32533edd9d6effda17a60a694dbb986cce396960d517fda47" + configChecksum: "dfd6624df8ba9f5d5ca6fd6c5bb8c04f6caaa038a79043a6d545384a00ff261" kubectl.kubernetes.io/default-container: flyteadmin labels: app.kubernetes.io/name: flyteadmin @@ -6122,116 +6268,6 @@ spec: topologyKey: kubernetes.io/hostname weight: 1 --- -# Source: controlplane/templates/authz/deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: release-name-union-authz - labels: - helm.sh/chart: controlplane-2026.3.10 - app.kubernetes.io/name: union-authz - app.kubernetes.io/instance: release-name - app.kubernetes.io/version: "2026.3.7" - app.kubernetes.io/managed-by: Helm -spec: - strategy: - rollingUpdate: - maxSurge: 1 - maxUnavailable: 0 - type: RollingUpdate - selector: - matchLabels: - app.kubernetes.io/name: union-authz - app.kubernetes.io/instance: release-name - template: - metadata: - annotations: - checksum/config: af438a6ec6210e9b743a843f136d5cc25d942a7f66b308a24068b4b3bbe7f425 - labels: - app.kubernetes.io/name: union-authz - app.kubernetes.io/instance: release-name - spec: - serviceAccountName: release-name-union-authz - terminationGracePeriodSeconds: 45 - securityContext: - fsGroup: 1000 - runAsGroup: 1000 - runAsNonRoot: true - runAsUser: 1000 - containers: - - name: userclouds-lite - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - image: 643379628101.dkr.ecr.us-east-1.amazonaws.com/union-cp/services:2026.3.7 - imagePullPolicy: IfNotPresent - command: - - userclouds-lite - args: - - serve - - all - - --config=/etc/userclouds/config.yaml - - --addr=:8080 - - --static=/usr/share/userclouds/static - ports: - - name: http - containerPort: 8080 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: http - initialDelaySeconds: 10 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 3 - readinessProbe: - httpGet: - path: /readyz - port: http - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 3 - failureThreshold: 3 - resources: - limits: - cpu: "1" - memory: 512Mi - requests: - cpu: 250m - memory: 256Mi - lifecycle: - preStop: - exec: - command: ["sleep", "5"] - volumeMounts: - - name: config - mountPath: /etc/userclouds - readOnly: true - - name: db-pass - mountPath: /etc/db - - name: tmp - mountPath: /tmp - volumes: - - name: config - configMap: - name: release-name-union-authz-config - - name: db-pass - secret: - secretName: - - name: tmp - emptyDir: {} - affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchLabels: - app.kubernetes.io/name: union-authz - topologyKey: kubernetes.io/hostname ---- # Source: controlplane/templates/cacheservice/deployment.yaml apiVersion: apps/v1 kind: Deployment @@ -6252,7 +6288,7 @@ spec: template: metadata: annotations: - configChecksum: "51558f4520a9d7ed5a0ad0f0dcad38250772b05e41f793c421be04bce90149e" + configChecksum: "4faf73330109ffb030ace3e963be59139b834fc0b88a14740de4f6bbe0f8d84" labels: app.kubernetes.io/name: cacheservice app.kubernetes.io/instance: release-name @@ -7275,32 +7311,6 @@ spec: type: Utilization type: Resource --- -# Source: controlplane/templates/authz/hpa.yaml -apiVersion: autoscaling/v2 -kind: HorizontalPodAutoscaler -metadata: - name: release-name-union-authz - labels: - helm.sh/chart: controlplane-2026.3.10 - app.kubernetes.io/name: union-authz - app.kubernetes.io/instance: release-name - app.kubernetes.io/version: "2026.3.7" - app.kubernetes.io/managed-by: Helm -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: release-name-union-authz - minReplicas: 3 - maxReplicas: 10 - metrics: - - type: Resource - resource: - name: cpu - target: - type: Utilization - averageUtilization: 80 ---- # Source: controlplane/templates/console/hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler From 3840dfd365e841dfa65dbb9ebcc3cabce8fb3b4f Mon Sep 17 00:00:00 2001 From: Michael Hotan Date: Fri, 27 Mar 2026 07:57:01 +1100 Subject: [PATCH 17/17] Provide UserClouds client defaults in authorizer config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the UserClouds client config from a commented-out example into actual defaults under services.authorizer.configMap.authorizer. When type is "Noop" (default), the userCloudsClient config is present but unused. Switching to type "UserClouds" activates it without needing additional config — just change the type field. This makes Union Cloud RBAC a one-line change from the default config. Co-Authored-By: Claude Opus 4.6 (1M context) --- charts/controlplane/values.yaml | 60 +++++-------------- .../controlplane.aws.billing-enable.yaml | 6 ++ tests/generated/controlplane.aws.yaml | 6 ++ tests/generated/controlplane.userclouds.yaml | 6 ++ 4 files changed, 34 insertions(+), 44 deletions(-) diff --git a/charts/controlplane/values.yaml b/charts/controlplane/values.yaml index d92c4629..24e02989 100644 --- a/charts/controlplane/values.yaml +++ b/charts/controlplane/values.yaml @@ -380,53 +380,25 @@ services: # - "UserClouds" — Union Cloud's authorization backend # - "External" — customer-provided gRPC authorization server (selfhosted) # - # --- Union Cloud (UserClouds) --- - # For Union Cloud deployments, set type to "UserClouds": - # authorizer: - # type: "UserClouds" - # userCloudsClient: - # tenantUrl: 'http://{{ .Release.Name }}-union-authz.{{ .Release.Namespace }}.svc.cluster.local:8080' - # tenantID: '623771e7-ddd6-4575-bedb-7c970ec75b87' - # clientID: '{{ .Values.union.authz.clientID }}' - # clientSecretName: 'union/client_secret' - # enableLogging: true + # To enable Union Cloud RBAC, set type to "UserClouds" below. + # The userCloudsClient defaults are pre-configured — just change the type. # - # --- External Authorization (selfhosted) --- - # For selfhosted deployments with a customer-provided authz server: - # authorizer: - # type: "External" - # externalClient: - # grpcConfig: - # # gRPC target for the external authorization server. - # # Uses standard gRPC name resolution (dns:///, unix:///, etc). - # host: "dns:///your-authz-server.namespace.svc.cluster.local:50051" - # - # # Connect without TLS (default: false). - # insecure: true - # - # # Skip server certificate verification — do NOT use in production (default: false). - # # insecureSkipVerify: false - # - # # Timeout per gRPC retry attempt (default: "5s"). - # # perRetryTimeout: "5s" - # - # # Max gRPC retries. 0 = no retries, fail fast (default: 0). - # # maxRetries: 0 - # - # # Max delay for gRPC backoff between retries. - # # maxBackoffDelay: "10s" - # - # # Incoming gRPC metadata keys to forward to the external server. - # # Default: ["authorization", "flyte-authorization"] - # # forwardHeaders: - # # - authorization - # # - flyte-authorization - # - # # If true, allow requests when the external server is unreachable. - # # If false (default), deny on error. - # failOpen: false + # To enable external authorization (selfhosted), set type to "External" + # and configure externalClient: + # externalClient: + # grpcConfig: + # host: "dns:///your-authz-server.namespace.svc.cluster.local:50051" + # insecure: true + # # forwardHeaders: ["authorization", "flyte-authorization"] + # # failOpen: false authorizer: type: "Noop" + userCloudsClient: + tenantUrl: 'http://{{ .Release.Name }}-union-authz.{{ .Release.Namespace }}.svc.cluster.local:8080' + tenantID: '623771e7-ddd6-4575-bedb-7c970ec75b87' + clientID: '{{ .Values.union.authz.clientID }}' + clientSecretName: 'union/client_secret' + enableLogging: true sharedService: connectPort: 8081 metrics: diff --git a/tests/generated/controlplane.aws.billing-enable.yaml b/tests/generated/controlplane.aws.billing-enable.yaml index 079ac340..141e0ef3 100644 --- a/tests/generated/controlplane.aws.billing-enable.yaml +++ b/tests/generated/controlplane.aws.billing-enable.yaml @@ -611,6 +611,12 @@ data: host: dns:///authorizer.union.svc.cluster.local:80 insecure: true type: Noop + userCloudsClient: + clientID: 'union-authz-client' + clientSecretName: union/client_secret + enableLogging: true + tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 + tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 cache: identity: enabled: false diff --git a/tests/generated/controlplane.aws.yaml b/tests/generated/controlplane.aws.yaml index e48dbe87..00ef2194 100644 --- a/tests/generated/controlplane.aws.yaml +++ b/tests/generated/controlplane.aws.yaml @@ -611,6 +611,12 @@ data: host: dns:///authorizer.union.svc.cluster.local:80 insecure: true type: Noop + userCloudsClient: + clientID: 'union-authz-client' + clientSecretName: union/client_secret + enableLogging: true + tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 + tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 cache: identity: enabled: false diff --git a/tests/generated/controlplane.userclouds.yaml b/tests/generated/controlplane.userclouds.yaml index eeb291f5..a51a5bb5 100644 --- a/tests/generated/controlplane.userclouds.yaml +++ b/tests/generated/controlplane.userclouds.yaml @@ -611,6 +611,12 @@ data: host: dns:///authorizer.union.svc.cluster.local:80 insecure: true type: Noop + userCloudsClient: + clientID: 'union-authz-client' + clientSecretName: union/client_secret + enableLogging: true + tenantID: 623771e7-ddd6-4575-bedb-7c970ec75b87 + tenantUrl: http://release-name-union-authz.union.svc.cluster.local:8080 cache: identity: enabled: false