Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1d3eb8d
Add default TypeAuthorizer routing for all controlplane services
mhotan Mar 19, 2026
e40ef25
Move TypeExternal config example to services.authorizer section
mhotan Mar 19, 2026
df456a7
Update TypeExternal config example to new grpcConfig shape
mhotan Mar 19, 2026
1ada39e
Document all externalClient config parameters in values.yaml
mhotan Mar 20, 2026
0a39b9b
Fix authorizerEndpoint to use dns:/// scheme for gRPC name resolution
katrogan Mar 24, 2026
623b38e
Expose native gRPC port for authorizer service
katrogan Mar 24, 2026
4032112
Expose grpc-native port and fix authorizerEndpoint
katrogan Mar 24, 2026
03927d8
Route authorizerEndpoint through nginx like all other intra-service c…
katrogan Mar 24, 2026
224d9bb
Merge origin/main into mike/selfhosted-authz-routing
mhotan Mar 25, 2026
4561c84
Merge branch 'mike/selfhosted-authz-routing' into katrina/ext-authz-wip
mhotan Mar 25, 2026
e7856d8
Default all services to TypeAuthorizer routing with documented overrides
mhotan Mar 25, 2026
204b224
Merge branch 'mike/selfhosted-authz-routing' into katrina/ext-authz-wip
mhotan Mar 25, 2026
2f2a8cd
Route authorizerEndpoint directly to authorizer service
mhotan Mar 25, 2026
3e747e9
Merge branch 'mike/selfhosted-authz-routing' into katrina/ext-authz-wip
mhotan Mar 25, 2026
97fd81c
Replace authorizerEndpoint with authorizerClient gRPC config
mhotan Mar 25, 2026
c59f81c
Merge branch 'mike/selfhosted-authz-routing' into katrina/ext-authz-wip
mhotan Mar 25, 2026
ec13436
Remove global.AUTHZ_TYPE — authorizer config is now values-driven
mhotan Mar 25, 2026
d4325f2
Merge branch 'mike/selfhosted-authz-routing' into katrina/ext-authz-wip
mhotan Mar 25, 2026
a120b88
Gate union-authz sidecar on authorizer type instead of separate flag
mhotan Mar 25, 2026
2c8053c
Merge branch 'mike/selfhosted-authz-routing' into katrina/ext-authz-wip
mhotan Mar 25, 2026
fec0036
Merge branch 'mike/selfhosted-authz-routing' into katrina/ext-authz-wip
mhotan Mar 25, 2026
f16f8f6
Gate union-authz sidecar on authorizer type instead of separate flag
mhotan Mar 25, 2026
4f99fdb
Merge branch 'mike/selfhosted-authz-routing' into katrina/ext-authz-wip
mhotan Mar 25, 2026
fa49a80
Merge branch 'katrina/ext-authz-wip' of github.com:unionai/helm-chart…
mhotan Mar 25, 2026
bd8fdf4
Add authorizer dashboard panels and alerting rules
mhotan Mar 26, 2026
a55a996
Regenerate helm test snapshots
mhotan Mar 26, 2026
b54e328
Merge remote-tracking branch 'origin/main' into mike/selfhosted-authz…
mhotan Mar 26, 2026
3840dfd
Provide UserClouds client defaults in authorizer config
mhotan Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 263 additions & 0 deletions charts/controlplane/dashboards/union-controlplane-overview.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)."
}
]
},
Expand Down
10 changes: 0 additions & 10 deletions charts/controlplane/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
2 changes: 1 addition & 1 deletion charts/controlplane/templates/authz/configmap.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- if eq .Values.global.AUTHZ_TYPE "union" -}}
{{- if eq ((index .Values "services" "authorizer" "configMap" "authorizer" "type") | default "") "UserClouds" -}}
apiVersion: v1
kind: ConfigMap
metadata:
Expand Down
2 changes: 1 addition & 1 deletion charts/controlplane/templates/authz/deployment.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- if eq .Values.global.AUTHZ_TYPE "union" -}}
{{- if eq ((index .Values "services" "authorizer" "configMap" "authorizer" "type") | default "") "UserClouds" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
Expand Down
2 changes: 1 addition & 1 deletion charts/controlplane/templates/authz/hpa.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- if and (eq .Values.global.AUTHZ_TYPE "union") .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:
Expand Down
2 changes: 1 addition & 1 deletion charts/controlplane/templates/authz/networkpolicy.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- if and (eq .Values.global.AUTHZ_TYPE "union") .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:
Expand Down
2 changes: 1 addition & 1 deletion charts/controlplane/templates/authz/pdb.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- if and (eq .Values.global.AUTHZ_TYPE "union") .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:
Expand Down
2 changes: 1 addition & 1 deletion charts/controlplane/templates/authz/rbac.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- if and (eq .Values.global.AUTHZ_TYPE "union") .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:
Expand Down
2 changes: 1 addition & 1 deletion charts/controlplane/templates/authz/service.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- if eq .Values.global.AUTHZ_TYPE "union" -}}
{{- if eq ((index .Values "services" "authorizer" "configMap" "authorizer" "type") | default "") "UserClouds" -}}
apiVersion: v1
kind: Service
metadata:
Expand Down
2 changes: 1 addition & 1 deletion charts/controlplane/templates/authz/serviceaccount.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- if and (eq .Values.global.AUTHZ_TYPE "union") .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:
Expand Down
40 changes: 40 additions & 0 deletions charts/controlplane/templates/monitoring/prometheusrule.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 }}
Expand Down
Loading
Loading