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