diff --git a/.github/workflows/helm-kustomize-comparison.yml b/.github/workflows/helm-kustomize-comparison.yml
index f88bd46959..1d4256138c 100644
--- a/.github/workflows/helm-kustomize-comparison.yml
+++ b/.github/workflows/helm-kustomize-comparison.yml
@@ -8,6 +8,8 @@ on:
- common/kubeflow-namespace/**
- common/kubeflow-roles/**
- scripts/synchronize-cert-manager-manifests.sh
+ - common/oauth2-proxy/**
+ - scripts/synchronize-oauth2-proxy-manifests.sh
- tests/kustomize_install.sh
- tests/helm_kustomize_compare.py
- tests/helm_kustomize_compare.sh
diff --git a/common/oauth2-proxy/base/deployment.yaml b/common/oauth2-proxy/base/deployment.yaml
index 0b5b2b2865..72a7c63d42 100644
--- a/common/oauth2-proxy/base/deployment.yaml
+++ b/common/oauth2-proxy/base/deployment.yaml
@@ -14,6 +14,7 @@ spec:
labels:
app.kubernetes.io/name: oauth2-proxy
spec:
+ serviceAccountName: oauth2-proxy
volumes:
- name: oauth2-proxy-config
configMap:
diff --git a/common/oauth2-proxy/helm/Chart.yaml b/common/oauth2-proxy/helm/Chart.yaml
new file mode 100644
index 0000000000..86d0cbc916
--- /dev/null
+++ b/common/oauth2-proxy/helm/Chart.yaml
@@ -0,0 +1,22 @@
+apiVersion: v2
+name: oauth2-proxy
+description: A Helm chart for Kubeflow oauth2-proxy authentication and authorization resources
+version: 0.1.0
+appVersion: 7.15.2
+
+keywords:
+- authentication
+- authorization
+- oauth2
+- kubeflow
+- oauth2-proxy
+
+home: https://github.com/kubeflow/community-distribution
+
+sources:
+- https://github.com/kubeflow/community-distribution/tree/master/common/oauth2-proxy
+- https://github.com/oauth2-proxy/oauth2-proxy
+
+annotations:
+ category: Authentication
+ licenses: Apache-2.0
diff --git a/common/oauth2-proxy/helm/README.md b/common/oauth2-proxy/helm/README.md
new file mode 100644
index 0000000000..174b0cbea7
--- /dev/null
+++ b/common/oauth2-proxy/helm/README.md
@@ -0,0 +1,39 @@
+# OAuth2-Proxy Helm Chart
+
+This chart renders the current Kubeflow oauth2-proxy Kustomize resources with
+Helm. It is intentionally static for the first chart slice so rendered output
+stays aligned with `common/oauth2-proxy`.
+
+## Installation
+
+Install foundation, cert-manager, and Istio first. The `kubeflow-namespaces`
+foundation chart creates `Namespace/oauth2-proxy`; this chart stores Helm
+release metadata in that same workload namespace.
+
+## Namespace names
+
+Namespace names are fixed to match the Kustomize baseline and `kubeflow-namespaces` foundation chart. oauth2-proxy workloads use `oauth2-proxy`, Istio authentication and authorization resources use `istio-system`, and gateway references use `kubeflow`. These names are not configurable.
+
+```bash
+helm install oauth2-proxy ./common/oauth2-proxy/helm \
+ --namespace oauth2-proxy \
+ --values ./common/oauth2-proxy/helm/ci/values-m2m-dex-only.yaml
+```
+
+## Kustomize Mapping
+
+- `ci/values-m2m-dex-only.yaml`: `common/oauth2-proxy/overlays/m2m-dex-only`
+
+Kubernetes in Docker and Amazon EKS machine-to-machine values are deferred until
+Helm integration tests or documented cluster-specific scenarios cover them.
+
+Direct enterprise IdP mode, Cloudflare cache policies, and an upstream
+oauth2-proxy Helm dependency wrapper are deferred until the parity chart is
+stable.
+
+## Comparison
+
+```bash
+helm lint common/oauth2-proxy/helm
+./tests/helm_kustomize_compare.sh oauth2-proxy m2m-dex-only
+```
diff --git a/common/oauth2-proxy/helm/ci/values-m2m-dex-only.yaml b/common/oauth2-proxy/helm/ci/values-m2m-dex-only.yaml
new file mode 100644
index 0000000000..e3902a63cf
--- /dev/null
+++ b/common/oauth2-proxy/helm/ci/values-m2m-dex-only.yaml
@@ -0,0 +1,15 @@
+credentials:
+ clientSecret: pUBnBOY80SnXgjibTYM9ZWNzY2xreNGQok
+ cookieSecret: 7d16fee92f8d11b8940b081b3f8b8acb
+
+istioExternalAuthorization:
+ enabled: true
+
+m2m:
+ enabled: false
+
+clusterJwksProxy:
+ enabled: false
+
+parameters:
+ allowSelfSignedIssuer: "true"
diff --git a/common/oauth2-proxy/helm/files/kubeflow-logo.svg b/common/oauth2-proxy/helm/files/kubeflow-logo.svg
new file mode 100644
index 0000000000..19a3174ae0
--- /dev/null
+++ b/common/oauth2-proxy/helm/files/kubeflow-logo.svg
@@ -0,0 +1,15 @@
+
+
diff --git a/common/oauth2-proxy/helm/files/oauth2_proxy.cfg b/common/oauth2-proxy/helm/files/oauth2_proxy.cfg
new file mode 100644
index 0000000000..946768aa60
--- /dev/null
+++ b/common/oauth2-proxy/helm/files/oauth2_proxy.cfg
@@ -0,0 +1,79 @@
+provider = "oidc"
+oidc_issuer_url = "http://dex.auth.svc.cluster.local:5556/dex"
+scope = "profile email groups openid"
+email_domains = [ "*" ]
+
+# serve a static HTTP 200 upstream on for authentication success
+# we are using oauth2-proxy as an ExtAuthz to "check" each request, not pass it on
+upstreams = [ "static://200" ]
+
+# skip authentication for these paths
+skip_auth_routes = [
+ "^/dex/",
+]
+
+# requests to paths matching these regex patterns will receive a 401 Unauthorized response
+# when not authenticated, instead of being redirected to the login page with a 302,
+# this prevents background requests being redirected to the login page,
+# and the accumulation of CSRF cookies
+api_routes = [
+ # Generic
+ # NOTE: included because most background requests contain these paths
+ "/api/",
+ "/apis/",
+
+ # Kubeflow Pipelines
+ # NOTE: included because KFP UI makes MANY background requests to these paths but because they are
+ # not `application/json` requests, oauth2-proxy will redirect them to the login page
+ "^/ml_metadata",
+]
+
+# OIDC Discovery has to be skipped and login url has to be provided directly
+# in order to enable relative auth redirect. Using OIDC Discovery would set
+# the redirect location to http://dex.auth.svc.cluster.local:5556 in the example
+# installation. This address is usually not available through the Web Browser.
+# If you have a setup where dex has it's url as other than the in-cluster
+# service, this is optional.
+skip_oidc_discovery = true
+login_url = "/dex/auth"
+redeem_url = "http://dex.auth.svc.cluster.local:5556/dex/token"
+oidc_jwks_url = "http://dex.auth.svc.cluster.local:5556/dex/keys"
+
+# if `false`, a sign-in page is displayed before starting the login flow
+# prevents background requests starting their own login flow on token expiry,
+# which can lead to many CSRF cookies, potentially exceeding the cookie limit
+skip_provider_button = false
+
+# style the sign-in page
+provider_display_name = "Dex"
+custom_sign_in_logo = "/custom-theme/kubeflow-logo.svg"
+banner = "-"
+footer = "-"
+
+# oauth2-proxy sends "force" by default, which causes dex to always prompt for login
+# https://github.com/dexidp/dex/pull/3086
+prompt = "none"
+
+# set Authorization Bearer response header. This is needed in order to
+# forward the Authorization Bearer token to Istio and enable authorization
+# based on JWT.
+set_authorization_header = true
+
+# set X-Auth-Request-User, X-Auth-Request-Groups, X-Auth-Request-Email and
+# X-Auth-Request-Preferred-Username. This is optional for Kubeflow but you
+# may have other services that use standard auth headers.
+set_xauthrequest = true
+
+cookie_name = "oauth2_proxy_kubeflow"
+
+# Dex default cookie expiration is 24h.
+# If set to 168h (default oauth2-proxy), Istio will not be able to use the JWT after 24h,
+# but oauth2-proxy will still consider the cookie valid.
+# It's possible to configure the JWT Refresh Token to enable longer login session.
+cookie_expire = "24h"
+cookie_refresh = 0
+
+code_challenge_method = "S256"
+
+redirect_url = "/oauth2/callback"
+relative_redirect_url = true
\ No newline at end of file
diff --git a/common/oauth2-proxy/helm/templates/_helpers.tpl b/common/oauth2-proxy/helm/templates/_helpers.tpl
new file mode 100644
index 0000000000..16ac5a6415
--- /dev/null
+++ b/common/oauth2-proxy/helm/templates/_helpers.tpl
@@ -0,0 +1,18 @@
+{{/*
+Render a JWT rule for Istio RequestAuthentication.
+*/}}
+{{- define "oauth2-proxy.m2mJwtRule" -}}
+- forwardOriginalToken: true
+ fromHeaders:
+ - name: Authorization
+ prefix: "Bearer "
+ issuer: {{ .issuer }}
+{{- if .jwksUri }}
+ jwksUri: {{ .jwksUri }}
+{{- end }}
+ outputClaimToHeaders:
+ - claim: sub
+ header: kubeflow-userid
+ - claim: groups
+ header: kubeflow-groups
+{{- end -}}
diff --git a/common/oauth2-proxy/helm/templates/cluster-jwks-proxy.yaml b/common/oauth2-proxy/helm/templates/cluster-jwks-proxy.yaml
new file mode 100644
index 0000000000..8d52d47d0b
--- /dev/null
+++ b/common/oauth2-proxy/helm/templates/cluster-jwks-proxy.yaml
@@ -0,0 +1,85 @@
+{{- if .Values.clusterJwksProxy.enabled }}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ labels:
+ app.kubernetes.io/name: cluster-jwks-proxy
+ name: cluster-jwks-proxy
+ namespace: istio-system
+---
+apiVersion: v1
+kind: Service
+metadata:
+ labels:
+ app.kubernetes.io/name: cluster-jwks-proxy
+ name: cluster-jwks-proxy
+ namespace: istio-system
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http
+ selector:
+ app.kubernetes.io/name: cluster-jwks-proxy
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ labels:
+ app.kubernetes.io/name: cluster-jwks-proxy
+ name: cluster-jwks-proxy
+ namespace: istio-system
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: cluster-jwks-proxy
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: cluster-jwks-proxy
+ spec:
+ containers:
+ - args:
+ - proxy
+ - --address=0.0.0.0
+ - --port=8080
+ - --accept-hosts=.*
+ - --accept-paths=^(?:/openid/v1/jwks)|(?:/.well-known/openid-configuration)$
+ - --reject-methods=^(POST|PUT|PATCH|DELETE|HEAD|OPTIONS|CONNECT|TRACE)$
+ image: {{ required "clusterJwksProxy.image is required when clusterJwksProxy.enabled is true" .Values.clusterJwksProxy.image }}
+ livenessProbe:
+ httpGet:
+ path: /openid/v1/jwks
+ port: http
+ initialDelaySeconds: 15
+ periodSeconds: 15
+ timeoutSeconds: 5
+ name: kubectl-proxy
+ ports:
+ - containerPort: 8080
+ name: http
+ readinessProbe:
+ httpGet:
+ path: /openid/v1/jwks
+ port: http
+ initialDelaySeconds: 15
+ periodSeconds: 15
+ timeoutSeconds: 5
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ runAsNonRoot: true
+ seccompProfile:
+ type: RuntimeDefault
+ startupProbe:
+ httpGet:
+ path: /openid/v1/jwks
+ port: http
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ timeoutSeconds: 2
+ serviceAccountName: cluster-jwks-proxy
+{{- end }}
diff --git a/common/oauth2-proxy/helm/templates/istio-external-authorization.yaml b/common/oauth2-proxy/helm/templates/istio-external-authorization.yaml
new file mode 100644
index 0000000000..63f3d96283
--- /dev/null
+++ b/common/oauth2-proxy/helm/templates/istio-external-authorization.yaml
@@ -0,0 +1,68 @@
+{{- if .Values.istioExternalAuthorization.enabled }}
+apiVersion: security.istio.io/v1beta1
+kind: AuthorizationPolicy
+metadata:
+ name: istio-ingressgateway-oauth2-proxy
+ namespace: istio-system
+spec:
+ action: CUSTOM
+ provider:
+ name: oauth2-proxy
+ rules:
+ - to:
+ - operation:
+ notPaths:
+ - /dex/*
+ - /dex/**
+ - /oauth2/*
+ when:
+ - key: request.headers[authorization]
+ notValues:
+ - '*'
+ selector:
+ matchLabels:
+ app: istio-ingressgateway
+---
+apiVersion: security.istio.io/v1beta1
+kind: AuthorizationPolicy
+metadata:
+ name: istio-ingressgateway-require-jwt
+ namespace: istio-system
+spec:
+ action: DENY
+ rules:
+ - from:
+ - source:
+ notRequestPrincipals:
+ - '*'
+ to:
+ - operation:
+ notPaths:
+ - /dex/*
+ - /dex/**
+ - /oauth2/*
+ selector:
+ matchLabels:
+ app: istio-ingressgateway
+---
+apiVersion: security.istio.io/v1beta1
+kind: RequestAuthentication
+metadata:
+ name: dex-jwt
+ namespace: istio-system
+spec:
+ jwtRules:
+ - forwardOriginalToken: true
+ fromHeaders:
+ - name: Authorization
+ prefix: 'Bearer '
+ issuer: http://dex.auth.svc.cluster.local:5556/dex
+ outputClaimToHeaders:
+ - claim: email
+ header: kubeflow-userid
+ - claim: groups
+ header: kubeflow-groups
+ selector:
+ matchLabels:
+ app: istio-ingressgateway
+{{- end }}
diff --git a/common/oauth2-proxy/helm/templates/m2m.yaml b/common/oauth2-proxy/helm/templates/m2m.yaml
new file mode 100644
index 0000000000..28f1791024
--- /dev/null
+++ b/common/oauth2-proxy/helm/templates/m2m.yaml
@@ -0,0 +1,16 @@
+{{- if .Values.m2m.enabled }}
+apiVersion: security.istio.io/v1beta1
+kind: RequestAuthentication
+metadata:
+ name: m2m-token-issuer
+ namespace: istio-system
+spec:
+ jwtRules:
+{{ include "oauth2-proxy.m2mJwtRule" (dict "issuer" .Values.m2m.issuer "jwksUri" .Values.m2m.jwksUri) | nindent 2 }}
+{{- range .Values.m2m.additionalJwtRules }}
+{{ include "oauth2-proxy.m2mJwtRule" (dict "issuer" .issuer "jwksUri" .jwksUri) | nindent 2 }}
+{{- end }}
+ selector:
+ matchLabels:
+ app: istio-ingressgateway
+{{- end }}
diff --git a/common/oauth2-proxy/helm/templates/networkpolicies.yaml b/common/oauth2-proxy/helm/templates/networkpolicies.yaml
new file mode 100644
index 0000000000..2ed13cbd39
--- /dev/null
+++ b/common/oauth2-proxy/helm/templates/networkpolicies.yaml
@@ -0,0 +1,40 @@
+{{- if .Values.networkPolicies.enabled }}
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: allow-istio-ingressgateway
+ namespace: oauth2-proxy
+spec:
+ ingress:
+ - from:
+ - namespaceSelector:
+ matchExpressions:
+ - key: kubernetes.io/metadata.name
+ operator: In
+ values:
+ - istio-system
+ podSelector:
+ matchLabels:
+ app: istio-ingressgateway
+ podSelector:
+ matchExpressions:
+ - key: app.kubernetes.io/name
+ operator: In
+ values:
+ - oauth2-proxy
+ policyTypes:
+ - Ingress
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: default-allow-same-namespace-oauth2-proxy
+ namespace: oauth2-proxy
+spec:
+ ingress:
+ - from:
+ - podSelector: {}
+ podSelector: {}
+ policyTypes:
+ - Ingress
+{{- end }}
diff --git a/common/oauth2-proxy/helm/templates/oauth2-proxy.yaml b/common/oauth2-proxy/helm/templates/oauth2-proxy.yaml
new file mode 100644
index 0000000000..a299d808b7
--- /dev/null
+++ b/common/oauth2-proxy/helm/templates/oauth2-proxy.yaml
@@ -0,0 +1,185 @@
+{{- if .Values.oauth2Proxy.enabled }}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: oauth2-proxy
+ namespace: oauth2-proxy
+---
+apiVersion: v1
+data:
+ oauth2_proxy.cfg: |-
+{{ .Files.Get "files/oauth2_proxy.cfg" | indent 4 }}
+kind: ConfigMap
+metadata:
+ name: oauth2-proxy
+ namespace: oauth2-proxy
+---
+apiVersion: v1
+data:
+ ALLOW_SELF_SIGNED_ISSUER: {{ .Values.parameters.allowSelfSignedIssuer | quote }}
+ ENABLE_M2M_TOKENS: {{ .Values.parameters.enableM2MTokens | quote }}
+ EXTRA_JWT_ISSUERS: {{ .Values.parameters.extraJwtIssuers | quote }}
+ FORCE_HTTPS: {{ .Values.parameters.forceHttps | quote }}
+kind: ConfigMap
+metadata:
+ name: oauth2-proxy-parameters
+ namespace: oauth2-proxy
+---
+apiVersion: v1
+data:
+ kubeflow-logo.svg: |
+{{ .Files.Get "files/kubeflow-logo.svg" | indent 4 }}
+kind: ConfigMap
+metadata:
+ name: oauth2-proxy-theme
+ namespace: oauth2-proxy
+---
+apiVersion: v1
+data:
+ client-id: {{ .Values.credentials.clientID | b64enc }}
+ client-secret: {{ .Values.credentials.clientSecret | b64enc }}
+ cookie-secret: {{ .Values.credentials.cookieSecret | b64enc }}
+kind: Secret
+metadata:
+ name: oauth2-proxy
+ namespace: oauth2-proxy
+type: Opaque
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: oauth2-proxy
+ namespace: oauth2-proxy
+spec:
+ ports:
+ - name: http
+ port: 80
+ targetPort: http
+ publishNotReadyAddresses: true
+ selector:
+ app.kubernetes.io/name: oauth2-proxy
+ type: ClusterIP
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ labels:
+ app: oauth2-proxy
+ name: oauth2-proxy
+ namespace: oauth2-proxy
+spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: oauth2-proxy
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: oauth2-proxy
+ spec:
+ serviceAccountName: oauth2-proxy
+ containers:
+ - args:
+ - --http-address=0.0.0.0:4180
+ - --config=/etc/oauth2_proxy/oauth2_proxy.cfg
+ env:
+ - name: OAUTH2_PROXY_CLIENT_ID
+ valueFrom:
+ secretKeyRef:
+ key: client-id
+ name: oauth2-proxy
+ - name: OAUTH2_PROXY_CLIENT_SECRET
+ valueFrom:
+ secretKeyRef:
+ key: client-secret
+ name: oauth2-proxy
+ - name: OAUTH2_PROXY_COOKIE_SECRET
+ valueFrom:
+ secretKeyRef:
+ key: cookie-secret
+ name: oauth2-proxy
+ - name: OAUTH2_PROXY_COOKIE_SECURE
+ valueFrom:
+ configMapKeyRef:
+ key: FORCE_HTTPS
+ name: oauth2-proxy-parameters
+ - name: OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY
+ valueFrom:
+ configMapKeyRef:
+ key: ALLOW_SELF_SIGNED_ISSUER
+ name: oauth2-proxy-parameters
+ - name: OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS
+ valueFrom:
+ configMapKeyRef:
+ key: ENABLE_M2M_TOKENS
+ name: oauth2-proxy-parameters
+ - name: OAUTH2_PROXY_EXTRA_JWT_ISSUERS
+ valueFrom:
+ configMapKeyRef:
+ key: EXTRA_JWT_ISSUERS
+ name: oauth2-proxy-parameters
+ image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.2
+ livenessProbe:
+ httpGet:
+ path: /ping
+ port: http
+ scheme: HTTP
+ name: oauth2-proxy
+ ports:
+ - containerPort: 4180
+ name: http
+ protocol: TCP
+ - containerPort: 44180
+ name: metrics
+ protocol: TCP
+ readinessProbe:
+ httpGet:
+ path: /ready
+ port: http
+ scheme: HTTP
+ resources: {}
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ runAsNonRoot: true
+ seccompProfile:
+ type: RuntimeDefault
+ volumeMounts:
+ - mountPath: /etc/oauth2_proxy/oauth2_proxy.cfg
+ name: oauth2-proxy-config
+ readOnly: true
+ subPath: oauth2_proxy.cfg
+ - mountPath: /custom-theme/kubeflow-logo.svg
+ name: oauth2-proxy-theme
+ readOnly: true
+ subPath: kubeflow-logo.svg
+ volumes:
+ - configMap:
+ name: oauth2-proxy
+ name: oauth2-proxy-config
+ - configMap:
+ name: oauth2-proxy-theme
+ name: oauth2-proxy-theme
+---
+apiVersion: networking.istio.io/v1alpha3
+kind: VirtualService
+metadata:
+ name: oauth2-proxy
+ namespace: oauth2-proxy
+spec:
+ gateways:
+ - kubeflow/kubeflow-gateway
+ hosts:
+ - '*'
+ http:
+ - match:
+ - uri:
+ prefix: /oauth2/
+ route:
+ - destination:
+ host: oauth2-proxy.oauth2-proxy.svc.cluster.local
+ port:
+ number: 80
+{{- end }}
diff --git a/common/oauth2-proxy/helm/values.yaml b/common/oauth2-proxy/helm/values.yaml
new file mode 100644
index 0000000000..b0e9503a56
--- /dev/null
+++ b/common/oauth2-proxy/helm/values.yaml
@@ -0,0 +1,46 @@
+# Default values for the Kubeflow oauth2-proxy chart.
+
+credentials:
+ # -- OAuth client ID used by oauth2-proxy.
+ clientID: kubeflow-oidc-authservice
+ # -- OAuth client secret used by oauth2-proxy.
+ clientSecret: REPLACE_ME_CLIENT_SECRET
+ # -- Cookie secret used by oauth2-proxy.
+ cookieSecret: REPLACE_ME_COOKIE_SECRET
+
+parameters:
+ # -- Configure oauth2-proxy cookie secure behavior.
+ forceHttps: "false"
+ # -- Allow self-signed issuer certificates.
+ allowSelfSignedIssuer: "true"
+ # -- Allow machine-to-machine bearer tokens by setting OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS.
+ enableM2MTokens: "true"
+ # -- Extra JWT issuers in oauth2-proxy --extra-jwt-issuers format.
+ extraJwtIssuers: ""
+
+oauth2Proxy:
+ # -- Render oauth2-proxy workload resources.
+ enabled: true
+
+networkPolicies:
+ # -- Render oauth2-proxy namespace NetworkPolicies.
+ enabled: true
+
+istioExternalAuthorization:
+ # -- Render Istio AuthorizationPolicy and RequestAuthentication resources for oauth2-proxy external authorization.
+ enabled: false
+
+m2m:
+ # -- Render machine-to-machine RequestAuthentication.
+ enabled: false
+ # -- Primary JWT issuer.
+ issuer: PATCH_ME
+ # -- Optional JWKS URI for the primary JWT issuer.
+ jwksUri: ""
+ # -- Additional JWT rules.
+ additionalJwtRules: []
+
+clusterJwksProxy:
+ # -- Render the cluster JWKS proxy used by kind-like clusters.
+ enabled: false
+ image: ""
diff --git a/scripts/synchronize-oauth2-proxy-manifests.sh b/scripts/synchronize-oauth2-proxy-manifests.sh
index 7b76ec2f1c..9bf4b6b7ef 100755
--- a/scripts/synchronize-oauth2-proxy-manifests.sh
+++ b/scripts/synchronize-oauth2-proxy-manifests.sh
@@ -7,18 +7,30 @@ COMPONENT_NAME="oauth2-proxy"
REPOSITORY_NAME="oauth2-proxy/oauth2-proxy"
COMMIT="v7.15.2"
BRANCH_NAME=${BRANCH_NAME:=synchronize-${COMPONENT_NAME}-manifests-${COMMIT?}}
-MANIFESTS_DIRECTORY=$(dirname $SCRIPT_DIRECTORY)
-DESTINATION_DIRECTORY=$MANIFESTS_DIRECTORY/common/${COMPONENT_NAME}
+MANIFESTS_DIRECTORY="$(dirname "$SCRIPT_DIRECTORY")"
+DESTINATION_DIRECTORY="$MANIFESTS_DIRECTORY/common/${COMPONENT_NAME}"
+CHART_DIRECTORY="$DESTINATION_DIRECTORY/helm"
create_branch "$BRANCH_NAME"
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i "" "s|quay.io/oauth2-proxy/oauth2-proxy:.*|quay.io/oauth2-proxy/oauth2-proxy:${COMMIT}|g" \
- $DESTINATION_DIRECTORY/base/deployment.yaml
+ "$DESTINATION_DIRECTORY/base/deployment.yaml"
+ sed -i "" "s|^appVersion: .*|appVersion: ${COMMIT#v}|g" \
+ "$CHART_DIRECTORY/Chart.yaml"
+ sed -i "" "s|quay.io/oauth2-proxy/oauth2-proxy:.*|quay.io/oauth2-proxy/oauth2-proxy:${COMMIT}|g" \
+ "$CHART_DIRECTORY/templates/oauth2-proxy.yaml"
else
sed -i "s|quay.io/oauth2-proxy/oauth2-proxy:.*|quay.io/oauth2-proxy/oauth2-proxy:${COMMIT}|g" \
- $DESTINATION_DIRECTORY/base/deployment.yaml
+ "$DESTINATION_DIRECTORY/base/deployment.yaml"
+ sed -i "s|^appVersion: .*|appVersion: ${COMMIT#v}|g" \
+ "$CHART_DIRECTORY/Chart.yaml"
+ sed -i "s|quay.io/oauth2-proxy/oauth2-proxy:.*|quay.io/oauth2-proxy/oauth2-proxy:${COMMIT}|g" \
+ "$CHART_DIRECTORY/templates/oauth2-proxy.yaml"
fi
SOURCE_TEXT="\[.*\](https://github.com/${REPOSITORY_NAME}/releases/tag/v.*)"
DESTINATION_TEXT="\[${COMMIT#v}\](https://github.com/${REPOSITORY_NAME}/releases/tag/${COMMIT})"
update_readme "$MANIFESTS_DIRECTORY" "$SOURCE_TEXT" "$DESTINATION_TEXT"
-commit_changes "$MANIFESTS_DIRECTORY" "Update ${REPOSITORY_NAME} manifests from ${COMMIT}" "$MANIFESTS_DIRECTORY"
+commit_changes "$MANIFESTS_DIRECTORY" "Update ${REPOSITORY_NAME} manifests from ${COMMIT}" \
+ "README.md" \
+ "scripts/synchronize-oauth2-proxy-manifests.sh" \
+ "common/${COMPONENT_NAME}"
echo "Synchronization completed successfully."
diff --git a/tests/helm_kustomize_compare.py b/tests/helm_kustomize_compare.py
index 035d816526..5ffb54ce1b 100755
--- a/tests/helm_kustomize_compare.py
+++ b/tests/helm_kustomize_compare.py
@@ -6,6 +6,8 @@
from typing import Dict, List, Tuple, Any
import re
+KUSTOMIZE_HASH_SUFFIX = re.compile(r"-(?=[a-z0-9]{10}$)[a-z0-9]{10}$")
+
CERT_MANAGER_KUBEFLOW_RESOURCES = {
("ClusterIssuer", "kubeflow-self-signing-issuer"),
("NetworkPolicy", "cert-manager-webhook"),
@@ -108,8 +110,8 @@ def normalize_kustomize_refs(obj: Any, path: str = "") -> Any:
current_path = f"{path}.{key}" if path else key
# Normalize secret/configmap references in common locations
- if key == "name" and isinstance(value, str):
- if any(
+ if isinstance(value, str):
+ if key == "name" and any(
ref_pattern in path
for ref_pattern in [
"secretKeyRef",
@@ -119,11 +121,11 @@ def normalize_kustomize_refs(obj: Any, path: str = "") -> Any:
"volumes",
]
):
- value = re.sub(r"-[a-z0-9]{10}$", "", value)
- elif "volumes" in path and key == "secretName":
- value = re.sub(r"-[a-z0-9]{10}$", "", value)
- elif "volumes" in path and "configMap" in path:
- value = re.sub(r"-[a-z0-9]{10}$", "", value)
+ value = KUSTOMIZE_HASH_SUFFIX.sub("", value)
+ elif key == "secretName" and "volumes" in path:
+ value = KUSTOMIZE_HASH_SUFFIX.sub("", value)
+ elif key == "name" and "volumes" in path and "configMap" in path:
+ value = KUSTOMIZE_HASH_SUFFIX.sub("", value)
normalized[key] = normalize_kustomize_refs(value, current_path)
return normalized
@@ -133,7 +135,9 @@ def normalize_kustomize_refs(obj: Any, path: str = "") -> Any:
return obj
-def normalize_manifest(manifest: Dict, component: str = "katib") -> Dict:
+def normalize_manifest(
+ manifest: Dict, component: str = "katib", normalize_kustomize_names: bool = True
+) -> Dict:
"""Normalize manifest by removing/standardizing certain fields."""
normalized = manifest.copy()
@@ -143,8 +147,9 @@ def normalize_manifest(manifest: Dict, component: str = "katib") -> Dict:
if component == "cert-manager":
preserve_cert_manager_kubeflow_labels(manifest, normalized)
- # Normalize Kustomize hash references
- normalized = normalize_kustomize_refs(normalized)
+ # Normalize Kustomize hash references only for Kustomize output.
+ if normalize_kustomize_names:
+ normalized = normalize_kustomize_refs(normalized)
# Handle ConfigMap data normalization (only for Katib)
if (
@@ -166,11 +171,15 @@ def normalize_manifest(manifest: Dict, component: str = "katib") -> Dict:
normalized_data[key] = value
normalized["data"] = normalized_data
- if "metadata" in normalized and "name" in normalized["metadata"]:
+ if (
+ normalize_kustomize_names
+ and "metadata" in normalized
+ and "name" in normalized["metadata"]
+ ):
kind = normalized.get("kind", "")
if kind in ["Secret", "ConfigMap"]:
name = normalized["metadata"]["name"]
- normalized["metadata"]["name"] = re.sub(r"-[a-z0-9]{10}$", "", name)
+ normalized["metadata"]["name"] = KUSTOMIZE_HASH_SUFFIX.sub("", name)
if "metadata" in normalized:
metadata = normalized["metadata"]
@@ -223,7 +232,7 @@ def should_compare_manifest(manifest: Dict, component: str, scenario: str) -> bo
"""Select the resource subset owned by a comparison scenario."""
kind = manifest.get("kind", "")
- if component == "cert-manager" and manifest.get("kind") == "Namespace":
+ if component in ["cert-manager", "oauth2-proxy"] and kind == "Namespace":
return False
if component == "kubeflow-namespaces" and scenario == "base":
@@ -242,10 +251,13 @@ def get_resource_key(manifest: Dict, component: str = "katib") -> str:
namespace = manifest.get("metadata", {}).get("namespace", "")
if kind in ["Secret", "ConfigMap"]:
- name = re.sub(r"-[a-z0-9]{10}$", "", name)
+ name = KUSTOMIZE_HASH_SUFFIX.sub("", name)
- # Include namespace in key for components with same-named namespaced resources.
- if component in ["katib", "cert-manager", "kubeflow-namespaces"] and namespace:
+ # Include namespace in key for components that render repeated names across namespaces.
+ if (
+ component in ["katib", "cert-manager", "kubeflow-namespaces", "oauth2-proxy"]
+ and namespace
+ ):
return f"{kind}/{namespace}/{name}"
else:
return f"{kind}/{name}"
@@ -297,6 +309,8 @@ def get_expected_helm_extras(component: str, scenario: str) -> set:
return set() # No extra resources in Helm for Model Registry
elif component == "kserve-models-web-application":
return set()
+ elif component == "cert-manager":
+ return set()
else:
return set()
@@ -318,14 +332,20 @@ def compare_manifests(
for manifest in kustomize_manifests:
if not should_compare_manifest(manifest, component, scenario):
continue
- normalized = normalize_manifest(manifest, component)
+ normalized = normalize_manifest(
+ manifest, component, normalize_kustomize_names=True
+ )
key = get_resource_key(normalized, component)
kustomize_resources[key] = normalized
for manifest in helm_manifests:
if not should_compare_manifest(manifest, component, scenario):
continue
- normalized = normalize_manifest(manifest, component)
+ normalized = normalize_manifest(
+ manifest,
+ component,
+ normalize_kustomize_names=(component != "oauth2-proxy"),
+ )
key = get_resource_key(normalized, component)
helm_resources[key] = normalized
@@ -377,7 +397,7 @@ def compare_manifests(
"Usage: python compare.py [namespace] [--verbose]"
)
print(
- "Components: katib, hub, kserve-models-web-application, cert-manager, kubeflow-namespaces, kubeflow-platform"
+ "Components: katib, hub, kserve-models-web-application, cert-manager, kubeflow-namespaces, kubeflow-platform, oauth2-proxy"
)
sys.exit(1)
@@ -396,10 +416,11 @@ def compare_manifests(
"cert-manager",
"kubeflow-namespaces",
"kubeflow-platform",
+ "oauth2-proxy",
]:
print(f"ERROR: Unknown component: {component}")
print(
- "Supported components: katib, hub, kserve-models-web-application, cert-manager, kubeflow-namespaces, kubeflow-platform"
+ "Supported components: katib, hub, kserve-models-web-application, cert-manager, kubeflow-namespaces, kubeflow-platform, oauth2-proxy"
)
sys.exit(1)
diff --git a/tests/helm_kustomize_compare.sh b/tests/helm_kustomize_compare.sh
index 44406e1385..3a40575694 100755
--- a/tests/helm_kustomize_compare.sh
+++ b/tests/helm_kustomize_compare.sh
@@ -4,14 +4,14 @@
set -euo pipefail
COMPONENT=${1:-""}
-SCENARIO=${2:-"base"}
+SCENARIO=${2:-""}
SCRIPT_DIRECTORY="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
ROOT_DIRECTORY="$(dirname "$SCRIPT_DIRECTORY")"
if [[ -z "$COMPONENT" ]]; then
echo "ERROR: Component is required"
echo "Usage: $0 "
- echo "Components: katib, hub, kserve-models-web-application, cert-manager, kubeflow-namespaces, kubeflow-platform"
+ echo "Components: katib, hub, kserve-models-web-application, cert-manager, kubeflow-namespaces, kubeflow-platform, oauth2-proxy"
exit 1
fi
@@ -199,13 +199,38 @@ case "$COMPONENT" in
)
;;
+ "oauth2-proxy")
+ CHART_DIRECTORY="$ROOT_DIRECTORY/common/oauth2-proxy/helm"
+ MANIFESTS_DIRECTORY="$ROOT_DIRECTORY/common/oauth2-proxy"
+
+ declare -A KUSTOMIZE_PATHS=(
+ ["m2m-dex-only"]="$MANIFESTS_DIRECTORY/overlays/m2m-dex-only"
+ )
+
+ declare -A HELM_VALUES=(
+ ["m2m-dex-only"]="$CHART_DIRECTORY/ci/values-m2m-dex-only.yaml"
+ )
+
+ declare -A NAMESPACES=(
+ ["m2m-dex-only"]="oauth2-proxy"
+ )
+ ;;
+
*)
echo "ERROR: Unknown component: $COMPONENT"
- echo "Supported components: katib, hub, kserve-models-web-application, cert-manager, kubeflow-namespaces, kubeflow-platform"
+ echo "Supported components: katib, hub, kserve-models-web-application, cert-manager, kubeflow-namespaces, kubeflow-platform, oauth2-proxy"
exit 1
;;
esac
+if [[ -z "$SCENARIO" ]]; then
+ if [[ "$COMPONENT" == "oauth2-proxy" ]]; then
+ SCENARIO="m2m-dex-only"
+ else
+ SCENARIO="base"
+ fi
+fi
+
if [[ ! "${KUSTOMIZE_PATHS[$SCENARIO]:-}" ]]; then
echo "ERROR: Unknown scenario '$SCENARIO' for component '$COMPONENT'"
echo "Supported scenarios for $COMPONENT:"
@@ -291,6 +316,11 @@ else
--namespace "$NAMESPACE" \
--include-crds \
--values "$HELM_VALUES_ARGUMENTS" > "$HELM_OUTPUT"
+ elif [[ "$COMPONENT" == "oauth2-proxy" ]]; then
+ helm template oauth2-proxy . \
+ --namespace "$NAMESPACE" \
+ --include-crds \
+ --values "$HELM_VALUES_ARGUMENTS" > "$HELM_OUTPUT"
else
helm template hub . \
--namespace "$NAMESPACE" \
diff --git a/tests/helm_kustomize_compare_all.sh b/tests/helm_kustomize_compare_all.sh
index d886baf348..f96f5eb291 100755
--- a/tests/helm_kustomize_compare_all.sh
+++ b/tests/helm_kustomize_compare_all.sh
@@ -15,6 +15,7 @@ declare -A COMPONENT_SCENARIOS=(
["cert-manager"]="base kubeflow existing-cert-manager"
["kubeflow-namespaces"]="base platform-namespaces"
["kubeflow-platform"]="base"
+ ["oauth2-proxy"]="m2m-dex-only"
)
prepare_component() {
@@ -63,7 +64,7 @@ if [[ "$COMPONENT" == "all" ]]; then
declare -a passed_components=()
declare -a failed_components=()
- for component in katib hub kserve-models-web-application cert-manager kubeflow-namespaces kubeflow-platform; do
+ for component in katib hub kserve-models-web-application cert-manager kubeflow-namespaces kubeflow-platform oauth2-proxy; do
if test_component "$component"; then
passed_components+=("$component")
else
@@ -94,6 +95,7 @@ elif [[ "$COMPONENT" == "help" ]] || [[ "$COMPONENT" == "--help" ]] || [[ "$COMP
echo " cert-manager Test cert-manager wrapper scenarios"
echo " kubeflow-namespaces Test Kubeflow namespace foundation chart"
echo " kubeflow-platform Test Kubeflow platform foundation chart"
+ echo " oauth2-proxy Test oauth2-proxy wrapper scenarios"
echo ""
echo "Examples:"
echo " $0 # Test all components"
@@ -113,7 +115,7 @@ elif [[ "${COMPONENT_SCENARIOS[$COMPONENT]:-}" ]]; then
else
echo "ERROR: Unknown component: $COMPONENT"
- echo "Supported components: katib, hub, kserve-models-web-application, cert-manager, kubeflow-namespaces, kubeflow-platform, all"
+ echo "Supported components: katib, hub, kserve-models-web-application, cert-manager, kubeflow-namespaces, kubeflow-platform, oauth2-proxy, all"
echo "Use '$0 help' for more information."
exit 1
fi