Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the kubeflow-m2m-oidc-configurator a CronJob #2667

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
6 changes: 5 additions & 1 deletion .github/workflows/kserve_m2m_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
run: kustomize build common/kubeflow-namespace/base | kubectl apply -f -

- name: Install Istio with ext auth
run: ./tests/gh-actions/install_istio_with_ext_auth.sh*
run: ./tests/gh-actions/install_istio_with_ext_auth.sh

- name: Install cert-manager
run: ./tests/gh-actions/install_cert_manager.sh
Expand Down Expand Up @@ -67,6 +67,10 @@ jobs:
nohup kubectl port-forward --namespace istio-system svc/${INGRESS_GATEWAY_SERVICE} 8080:80 &
while ! curl localhost:8080; do echo waiting for port-forwarding; sleep 1; done; echo port-forwarding ready

- name: Wait for the kubeflow-m2m-oidc-configurator Job
run: |
./tests/gh-actions/wait_for_kubeflow_m2m_oidc_configurator.sh

- name: Run kserve tests with m2m token from SA default/default
run: |
export KSERVE_INGRESS_HOST_PORT=localhost:8080
Expand Down
11 changes: 8 additions & 3 deletions .github/workflows/notebook_controller_m2m_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
run: kustomize build common/kubeflow-namespace/base | kubectl apply -f -

- name: Install Istio with ext auth
run: ./tests/gh-actions/install_istio_with_ext_auth.sh*
run: ./tests/gh-actions/install_istio_with_ext_auth.sh

- name: Install kubeflow-istio-resources
run: kustomize build common/istio-1-22/kubeflow-istio-resources/base | kubectl apply -f -
Expand All @@ -47,7 +47,8 @@ jobs:
run: |
kustomize build apps/jupyter/jupyter-web-app/upstream/overlays/istio/ | kubectl apply -f -
kustomize build apps/jupyter/notebook-controller/upstream/overlays/kubeflow/ | kubectl apply -f -
kubectl wait --for=condition=Ready pods --all --all-namespaces --timeout 300s
kubectl wait --for=condition=Ready pods --all --all-namespaces --timeout=300s \
--field-selector=status.phase!=Succeeded

- name: Create KF Profile
run: kustomize build common/user-namespace/base | kubectl apply -f -
Expand All @@ -58,6 +59,10 @@ jobs:
nohup kubectl port-forward --namespace istio-system svc/${INGRESS_GATEWAY_SERVICE} 8080:80 &
while ! curl localhost:8080; do echo waiting for port-forwarding; sleep 1; done; echo port-forwarding ready

- name: Wait for the kubeflow-m2m-oidc-configurator Job
run: |
./tests/gh-actions/wait_for_kubeflow_m2m_oidc_configurator.sh

- name: List notebooks over API with authorized SA Token
run: |
KF_PROFILE=kubeflow-user-example-com
Expand Down Expand Up @@ -86,4 +91,4 @@ jobs:
if test $STATUS_CODE -ne 403; then
echo "Error, this call should fail to list notebooks in namespace ${KF_PROFILE}."
exit 1
fi
fi
6 changes: 5 additions & 1 deletion .github/workflows/pipeline_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ jobs:
nohup kubectl port-forward --namespace istio-system svc/${ingress_gateway_service} 8080:80 &
while ! curl localhost:8080; do echo waiting for port-forwarding; sleep 1; done; echo port-forwarding ready

- name: Wait for the kubeflow-m2m-oidc-configurator Job
run: |
./tests/gh-actions/wait_for_kubeflow_m2m_oidc_configurator.sh

- name: List and deploy test pipeline with authorized ServiceAccount Token
run: |
pip3 install kfp==2.4.0
Expand Down Expand Up @@ -116,4 +120,4 @@ jobs:
' "${TOKEN}" "${KF_PROFILE}"

echo "Test succeeded. Token from unauthorized ServiceAccount cannot list \
piplines in $KF_PROFILE namespace."
piplines in $KF_PROFILE namespace."
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: kubeflow-m2m-oidc-configurator
namespace: istio-system
spec:
schedule: '*/5 * * * *'
concurrencyPolicy: Forbid
jobTemplate:
spec:
backoffLimit: 3
ttlSecondsAfterFinished: 600
template:
metadata:
labels: {}
spec:
restartPolicy: OnFailure
serviceAccountName: kubeflow-m2m-oidc-configurator
containers:
- image: docker.io/curlimages/curl
name: kubeflow-m2m-oidc-configurator
command:
- /script.sh
envFrom:
- configMapRef:
name: kubeflow-m2m-oidc-configurator-envs
volumeMounts:
- mountPath: /script.sh
name: script
subPath: script.sh
resources: {}
volumes:
- name: script
configMap:
name: kubeflow-m2m-oidc-configurator-script
defaultMode: 0777
items:
- key: script.sh
path: script.sh
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure that script.sh is idempotent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, well it doesn't verify if the JWKS is present and after all is always performing the patch so this might be an improvement. I think the JWKS value should be also compared and only patched if different.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made changes so the script will first check for the JWKS present in RequestAuthentication and only patch if not equal to the desired JWKS.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component

resources:
- job.configure-kubernetes-oidc-issuer-jwks-in-requestauthentication.yaml
- cronjob.kubeflow-m2m-oidc-configurator.yaml
- rbac.yaml

configMapGenerator:
- name: configure-self-signed-kubernetes-oidc-issuer-script
- name: kubeflow-m2m-oidc-configurator-script
namespace: istio-system
files:
- script.sh=script.sh

- name: configure-self-signed-kubernetes-oidc-issuer-envs
- name: kubeflow-m2m-oidc-configurator-envs
namespace: istio-system
literals:
- ISTIO_ROOT_NAMESPACE=istio-system
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: self-signed-kubernetes-oidc-issuer-configurator
name: kubeflow-m2m-oidc-configurator
namespace: istio-system

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: self-signed-kubernetes-oidc-issuer-configurator
name: kubeflow-m2m-oidc-configurator
namespace: istio-system
rules:
- apiGroups:
Expand All @@ -23,13 +23,13 @@ rules:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: self-signed-kubernetes-oidc-issuer-configurator
name: kubeflow-m2m-oidc-configurator
namespace: istio-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: self-signed-kubernetes-oidc-issuer-configurator
name: kubeflow-m2m-oidc-configurator
subjects:
- kind: ServiceAccount
name: self-signed-kubernetes-oidc-issuer-configurator
name: kubeflow-m2m-oidc-configurator
namespace: istio-system
Original file line number Diff line number Diff line change
Expand Up @@ -12,60 +12,132 @@ ${ISTIO_ROOT_NAMESPACE}\
/requestauthentications/\
${REQUEST_AUTHENTICATION_NAME}"

echo "Wait until resource RequestAuthentication ${REQUEST_AUTHENTICATION_NAME} in namespace ${ISTIO_ROOT_NAMESPACE} is ready."
while true; do
response="$(
curl -s -o /dev/null \
--url "${RESOURCE_URL}" \
-w "%{http_code}" \
--header "Authorization: Bearer $(cat /run/secrets/kubernetes.io/serviceaccount/token)" \
--insecure
)"
if [ "${response}" = "200" ]; then
break
fi
echo "Resource RequestAuthentication ${REQUEST_AUTHENTICATION_NAME} in namespace ${ISTIO_ROOT_NAMESPACE} is not ready yet."
sleep 5
done
echo "Resource RequestAuthentication ${REQUEST_AUTHENTICATION_NAME} in namespace ${ISTIO_ROOT_NAMESPACE} is ready."
wait_for_resource_ready() {
while true; do
response="$(
curl -s -o /dev/null \
--url "${RESOURCE_URL}" \
-w "%{http_code}" \
--header "Authorization: Bearer $(cat /run/secrets/kubernetes.io/serviceaccount/token)" \
--insecure
)"
if [ "${response}" = "200" ]; then
break
fi
sleep 5
done
}

# Get Issuer URL configured in RequestAuthentication.
ISSUER_URL="$(
get_request_authentication_obj() {
curl -s --request GET \
--url "${RESOURCE_URL}" \
--header "Authorization: Bearer $(cat /run/secrets/kubernetes.io/serviceaccount/token)" \
--insecure |
awk -F'"' '/"issuer":/ { print $4 }'
)"
echo "ISSUER_URL: ${ISSUER_URL}."
--insecure
}

get_issuer_url_from_obj() {
obj="${1}"
echo "${obj}" | awk -F'"' '/"issuer":/ { print $4 }'
}

# GET URI to the JWKS.
JWKS_URI="$(
get_current_escaped_jwks_from_obj() {
obj="${1}"
echo "${obj}" | awk -F'"' '/"jwks":/' | sed -n 's/^.*"jwks": "\(.*\)".*$/\1/p'
}

get_jwks_uri() {
issuer_url="${1}"
curl -s --request GET \
--url "${ISSUER_URL}/.well-known/openid-configuration" \
--header "Authorization: Bearer $(cat /run/secrets/kubernetes.io/serviceaccount/token)" \
--insecure |
grep -o '"jwks_uri":"https:\/\/[^"]\+"' |
sed 's/"jwks_uri":"\(.*\)"/\1/'
)"
echo "JWKS_URI: ${JWKS_URI}."

# Get content of the JWKS.
JWKS="$(
--url "${issuer_url}/.well-known/openid-configuration" \
--header "Authorization: Bearer $(cat /run/secrets/kubernetes.io/serviceaccount/token)" \
--insecure |
grep -o '"jwks_uri":"https:\/\/[^"]\+"' |
sed 's/"jwks_uri":"\(.*\)"/\1/'
}

get_jwks_from_uri() {
jwks_uri="${1}"
curl -s --request GET \
--url "${JWKS_URI}" \
--url "${jwks_uri}" \
--header "Authorization: Bearer $(cat /run/secrets/kubernetes.io/serviceaccount/token)" \
--insecure
}

# Format JWKS in a way that can be accepted in resource patch.
parse_escaped_jwks() {
jwks="${1}"
echo "${jwks}" | sed 's/"/\\"/g'
}

are_jwks_equal() {
jwks1="${1}"
jwks2="${2}"
test "$(echo "${jwks1}" | base64 -w0)" = "$(echo "${jwks2}" | base64 -w0)"
}

patch_request_authentication_with_escaped_jwks() {
jwks_escaped="${1}"
curl -s --request PATCH \
--url "${RESOURCE_URL}" \
--header "Content-Type: application/json-patch+json" \
--header "Authorization: Bearer $(cat /run/secrets/kubernetes.io/serviceaccount/token)" \
-d '[{ "op": "add", "path": "/spec/jwtRules/0/jwks", "value": "'"${jwks_escaped}"'" }]' \
--insecure
)"
echo "JWKS: ${JWKS}."
echo
}

# Format JWKS in a way that can be accepted in resource patch.
JWKS_ESCAPED="$(echo "${JWKS}" | sed 's/"/\\"/g')"

# Patch the RequestAuthentication with JWKS.
curl -s --request PATCH \
--url "${RESOURCE_URL}" \
--header "Content-Type: application/json-patch+json" \
--header "Authorization: Bearer $(cat /run/secrets/kubernetes.io/serviceaccount/token)" \
-d '[{ "op": "add", "path": "/spec/jwtRules/0/jwks", "value": "'"${JWKS_ESCAPED}"'" }]' \
--insecure
patch_request_authentication_with_jwks_if_required() {
echo "Getting RequestAuthentication object."
REQUEST_AUTHENTICATION_OBJ="$(get_request_authentication_obj)"

ISSUER_URL="$(get_issuer_url_from_obj "${REQUEST_AUTHENTICATION_OBJ}")"
echo "Issuer Url in RequestAuthentication: ${ISSUER_URL}"

CURRENT_JWKS_ESCAPED="$(get_current_escaped_jwks_from_obj "${REQUEST_AUTHENTICATION_OBJ}")"
printf "Current Jwks (escaped):\n%s\n" "${CURRENT_JWKS_ESCAPED}"

JWKS_URI="$(get_jwks_uri "${ISSUER_URL}")"
echo "Jwks Uri from Well Known OpenID Configuration: ${JWKS_URI}"

JWKS="$(get_jwks_from_uri "${JWKS_URI}")"
JWKS_ESCAPED="$(parse_escaped_jwks "${JWKS}")"
printf "JWKS from Well Known OpenID Configuration (escaped): \n%s\n" "${JWKS_ESCAPED}"

if are_jwks_equal "${JWKS_ESCAPED}" "${CURRENT_JWKS_ESCAPED}"; then
echo "JWKS in RequestAuthentication ${REQUEST_AUTHENTICATION_NAME} is configured correctly."
else
echo "JWKS in RequestAuthentication ${REQUEST_AUTHENTICATION_NAME} needs to be configured."
patch_request_authentication_with_escaped_jwks "${JWKS_ESCAPED}"
fi
}

verify_jwks_in_request_authentication() {
REQUEST_AUTHENTICATION_OBJ="$(get_request_authentication_obj)"
ISSUER_URL="$(get_issuer_url_from_obj "${REQUEST_AUTHENTICATION_OBJ}")"
CURRENT_JWKS_ESCAPED="$(get_current_escaped_jwks_from_obj "${REQUEST_AUTHENTICATION_OBJ}")"
JWKS_URI="$(get_jwks_uri "${ISSUER_URL}")"
JWKS="$(get_jwks_from_uri "${JWKS_URI}")"
JWKS_ESCAPED="$(parse_escaped_jwks "${JWKS}")"
if ! are_jwks_equal "${JWKS_ESCAPED}" "${CURRENT_JWKS_ESCAPED}"; then
echo "JWKS not properly configured, exit with error code 1"
exit 1
fi
}

main() {
echo "Wait until resource RequestAuthentication ${REQUEST_AUTHENTICATION_NAME} in namespace ${ISTIO_ROOT_NAMESPACE} is ready."
wait_for_resource_ready
echo "Resource RequestAuthentication ${REQUEST_AUTHENTICATION_NAME} in namespace ${ISTIO_ROOT_NAMESPACE} is ready."

echo "Patch RequestAuthentication with JWKS if required."
patch_request_authentication_with_jwks_if_required

echo "Wait 5 seconds before verifying RequestAuthentication JWKS configuration."
sleep 5

echo "Verify if RequestAuthentication is properly configured with JWKS..."
verify_jwks_in_request_authentication
echo "RequestAuthentication is properly configured with JWKS."
}

main
3 changes: 2 additions & 1 deletion tests/gh-actions/install_istio_with_ext_auth.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ kustomize build istio-install/overlays/oauth2-proxy | kubectl apply -f -
cd -

echo "Waiting for all Istio Pods to become ready..."
kubectl wait --for=condition=Ready pods --all -n istio-system --timeout 300s
kubectl wait --for=condition=Ready pods --all -n istio-system --timeout=300s \
--field-selector=status.phase!=Succeeded

echo "Installing oauth2-proxy..."
cd common/oidc-client
Expand Down
Loading
Loading