Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ generate-configuration:
EXAMPLES := \
examples/istiostacks/minimal.yaml:: \
examples/istiostacks/standard.yaml:: \
examples/istiostacks/with-aws-lbc.yaml::
examples/istiostacks/with-aws-lbc.yaml:: \
examples/istiostacks/with-extensionproviders.yaml::

# Render all examples (parallel execution, output shown per-job when complete)
render\:all:
Expand Down
92 changes: 92 additions & 0 deletions apis/istiostacks/definition.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,86 @@ spec:
items:
type: object
x-kubernetes-preserve-unknown-fields: true
extensionProviders:
description: |
Istio MeshConfig extensionProviders registered on istiod. Each entry maps 1:1
onto a meshConfig.extensionProviders[] element so AuthorizationPolicy.CUSTOM
rules in any namespace can reference the registered provider name. Operator-
managed list — consumer stacks (oauth2-proxy bridges, etc.) declare intent in
their own spec, and the operator adds the matching entry here. Currently
scoped to envoyExtAuthzHttp and envoyExtAuthzGrpc; broader MeshConfig knobs
are out of scope for this surface. Exactly one of envoyExtAuthzHttp /
envoyExtAuthzGrpc must be set per entry; render fails otherwise.
type: array
items:
type: object
required:
- name
properties:
name:
description: Unique provider name. AuthorizationPolicy.CUSTOM rules reference this string. Convention is <stack-namespace>-oauth2-proxy (e.g., observe-oauth2-proxy).
type: string
envoyExtAuthzHttp:
description: HTTP ext_authz provider. Mirrors Istio MeshConfig.ExtensionProvider.EnvoyExternalAuthorizationHttpProvider.
type: object
required:
- service
- port
properties:
service:
description: FQDN of the ext_authz service (e.g., oauth2-proxy.observe.svc.cluster.local).
type: string
port:
description: Port of the ext_authz service.
type: integer
pathPrefix:
description: Path prefix for the ext_authz HTTP check (e.g., /oauth2/auth).
type: string
timeout:
description: ext_authz call timeout as a duration string (e.g., 1s, 250ms).
type: string
includeRequestHeadersInCheck:
description: Request headers to forward to the ext_authz service.
type: array
items:
type: string
headersToUpstreamOnAllow:
description: Response headers from the ext_authz service to forward upstream when the request is allowed.
type: array
items:
type: string
headersToDownstreamOnAllow:
description: Response headers from the ext_authz service to forward downstream when the request is allowed.
type: array
items:
type: string
headersToDownstreamOnDeny:
description: Response headers from the ext_authz service to forward downstream when the request is denied.
type: array
items:
type: string
statusOnError:
description: HTTP status code returned to the downstream client when the ext_authz call fails (default 403).
type: string
envoyExtAuthzGrpc:
description: gRPC ext_authz provider. Mirrors Istio MeshConfig.ExtensionProvider.EnvoyExternalAuthorizationGrpcProvider.
type: object
required:
- service
- port
properties:
service:
description: FQDN of the ext_authz gRPC service.
type: string
port:
description: Port of the ext_authz gRPC service.
type: integer
timeout:
description: ext_authz call timeout.
type: string
failOpen:
description: When true, allow requests if the ext_authz service is unreachable. Defaults to false (deny on error), matching Istio.
type: boolean
awsLoadBalancerController:
description: AWS Load Balancer Controller integration. This stack does NOT install the controller — install separately via aws-lbc-stack. When enabled, NLB annotations are propagated to the Gateway's auto-created Service via spec.infrastructure.annotations on the Gateway resource. The LBC then provisions an NLB in TCP passthrough mode.
type: object
Expand Down Expand Up @@ -199,5 +279,17 @@ spec:
properties:
ready:
type: boolean
extensionProviders:
description: Observed state of the registered MeshConfig extensionProviders. Consumers gate AuthorizationPolicy.CUSTOM MRs on this readiness to avoid race-on-first-apply.
type: object
properties:
configured:
description: Names of extensionProviders that made it into the rendered istiod helm values.
type: array
items:
type: string
ready:
description: True when istiod is Ready and the rendered providers match the spec list.
type: boolean
required:
- spec
34 changes: 34 additions & 0 deletions examples/istiostacks/with-extensionproviders.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apiVersion: hops.ops.com.ai/v1alpha1
kind: IstioStack
metadata:
name: istio
namespace: default
spec:
clusterName: my-cluster

# MeshConfig extensionProviders registered on istiod so consumer namespaces
# can attach AuthorizationPolicy.CUSTOM rules that delegate to ext_authz
# services running in their own namespace. Operator-managed list — one entry
# per oauth2-proxy bridge, named <stack-namespace>-oauth2-proxy.
#
# See specs/platform-public-exposure for the architectural pattern.
extensionProviders:
- name: observe-oauth2-proxy
envoyExtAuthzHttp:
service: oauth2-proxy.observe.svc.cluster.local
port: 4180
pathPrefix: /oauth2/auth
timeout: 1s
includeRequestHeadersInCheck:
- authorization
- cookie
headersToUpstreamOnAllow:
- authorization
- path
- x-auth-request-user
- x-auth-request-email
- x-auth-request-access-token
headersToDownstreamOnDeny:
- content-type
- set-cookie
statusOnError: "403"
28 changes: 28 additions & 0 deletions functions/render/000-state-init.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,33 @@
{{- $cni := $spec.cni | default dict }}
{{- $ztunnel := $spec.ztunnel | default dict }}

# ==============================================================================
# MeshConfig extensionProviders
#
# Operator-managed list. Each entry mirrors a meshConfig.extensionProviders[]
# element. Exactly one of envoyExtAuthzHttp / envoyExtAuthzGrpc must be set.
# Names must be unique — duplicates would silently overwrite in MeshConfig.
# ==============================================================================
{{- $extensionProviders := $spec.extensionProviders | default list }}
{{- $seenNames := dict }}
{{- range $idx, $ep := $extensionProviders }}
{{- if not $ep.name }}
{{- fail (printf "spec.extensionProviders[%d].name is required" $idx) }}
{{- end }}
{{- if hasKey $seenNames $ep.name }}
{{- fail (printf "spec.extensionProviders: duplicate name %q (entries must be uniquely named)" $ep.name) }}
{{- end }}
{{- $seenNames = set $seenNames $ep.name true }}
{{- $hasHttp := and (hasKey $ep "envoyExtAuthzHttp") (gt (len ($ep.envoyExtAuthzHttp | default dict)) 0) }}
{{- $hasGrpc := and (hasKey $ep "envoyExtAuthzGrpc") (gt (len ($ep.envoyExtAuthzGrpc | default dict)) 0) }}
{{- if and $hasHttp $hasGrpc }}
{{- fail (printf "spec.extensionProviders[%s]: set exactly one of envoyExtAuthzHttp or envoyExtAuthzGrpc, not both" $ep.name) }}
{{- end }}
{{- if not (or $hasHttp $hasGrpc) }}
{{- fail (printf "spec.extensionProviders[%s]: set exactly one of envoyExtAuthzHttp or envoyExtAuthzGrpc" $ep.name) }}
{{- end }}
{{- end }}

# ==============================================================================
# Ingress Gateway
# ==============================================================================
Expand Down Expand Up @@ -135,6 +162,7 @@
"enabled" $lbcEnabled
"releaseRef" (dict "name" $lbcReleaseRefName)
)
"extensionProviders" $extensionProviders
"observed" (dict)
"status" (dict)
}}
10 changes: 10 additions & 0 deletions functions/render/010-state-status.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
{{- $allReady = and $allReady (get $checkReady "gateway") }}
{{- end }}

{{- $configuredProviders := list }}
{{- range $ep := $state.extensionProviders }}
{{- $configuredProviders = append $configuredProviders $ep.name }}
{{- end }}
{{- $providersReady := and (get $checkReady "istiod") (gt (len $configuredProviders) 0) }}

{{- $state = set $state "observed" (dict
"controlPlaneNamespace" (dict "ready" (get $checkReady "control-plane-namespace"))
"ingressNamespace" (dict "ready" (get $checkReady "ingress-namespace"))
Expand All @@ -32,6 +38,10 @@
"cni" (dict "ready" (get $checkReady "istio-cni"))
"ztunnel" (dict "ready" (get $checkReady "ztunnel"))
"gateway" (dict "ready" (get $checkReady "gateway"))
"extensionProviders" (dict
"configured" $configuredProviders
"ready" $providersReady
)
) }}

{{- $state = set $state "status" (dict
Expand Down
4 changes: 4 additions & 0 deletions functions/render/210-istiod.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ spec:
profile: ambient
meshConfig:
accessLogFile: /dev/stdout
{{- if gt (len $state.extensionProviders) 0 }}
extensionProviders:
{{- toYaml $state.extensionProviders | nindent 10 }}
{{- end }}
pilot:
env:
PILOT_ENABLE_METADATA_EXCHANGE: "true"
Expand Down
3 changes: 3 additions & 0 deletions functions/render/999-status.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ status:
ready: {{ $state.observed.ztunnel.ready }}
gateway:
ready: {{ $state.observed.gateway.ready }}
extensionProviders:
configured: {{ $state.observed.extensionProviders.configured | toJson }}
ready: {{ $state.observed.extensionProviders.ready }}
Loading
Loading