diff --git a/charts/spire/charts/spiffe-oidc-discovery-provider/files/test/jwt-decode.sh b/charts/spire/charts/spiffe-oidc-discovery-provider/files/test/jwt-decode.sh new file mode 100755 index 0000000..aedb696 --- /dev/null +++ b/charts/spire/charts/spiffe-oidc-discovery-provider/files/test/jwt-decode.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash + +set -x + +# https://gist.github.com/stokito/f2d7ea0b300f14638a9063559384ec89/ +# Decode a JWT from stdin and verify it's signature with the JWT issuer public key +# Only RS256 keys are supported for signature check +# +# HOW TO USE: +# $ export JWTTOKEN="eyF......g" +# $ ./jwt-decode.sh https://example.com/keys "${JWTTOKEN}" +# if signature check failed then error code will be non-zero + +URL=$1 + +JWT=$2 + +if [ -z "$(command -v jq)" ]; then + echo "This script will NOT work on your machine." + echo "Please install jq first: https://stedolan.github.io/jq/download/" + exit 1 +fi + +base64_padding() { + local len=$(( ${#1} % 4 )) + local padded_b64='' + if [ ${len} = 2 ]; then + padded_b64="${1}==" + elif [ ${len} = 3 ]; then + padded_b64="${1}=" + else + padded_b64="${1}" + fi + echo -n "$padded_b64" +} + +base64url_to_b64() { + base64_padding "${1}" | tr -- '-_' '+/' +} + +b2hex() { echo -n "$1"==== | fold -w 4 | sed '$ d' | tr -d '\n' |base64 -d | xxd -p | tr -d \\n; } + +mint_rsa_key() { + JWK=$1 + + # Extract the modulus and exponent from the JWK, converting from URL-safe Base64 to standard Base64 + MODULUS=$(echo "$JWK" | jq -r '.n' | tr '_-' '/+') + EXPONENT=$(echo "$JWK" | jq -r '.e' | tr '_-' '/+') + + modulus=$(b2hex "$MODULUS") + exponent=$(b2hex "$EXPONENT") + + asnconf=$(mktemp) + + asnconf="asn1=SEQUENCE:pubkeyinfo\n[pubkeyinfo]\nalgorithm=SEQUENCE:rsa_alg\npubkey=BITWRAP,SEQUENCE:rsapubkey\n[rsa_alg]\nalgorithm=OID:rsaEncryption\nparameter=NULL\n[rsapubkey]\nn=INTEGER:0x$modulus\ne=INTEGER:0x$exponent" + + derfile=$(mktemp) + echo >&2 "derfile: $derfile" + echo -e "$asnconf" | openssl asn1parse -genconf /dev/stdin -noout -out "$derfile" + + openssl rsa -in "$derfile" -inform DER -pubin +} + +# read the JWT from stdin and split by comma into three variables +IFS='.' read -r JWT_HEADER_B64URL JWT_PAYLOAD_B64URL JWT_SIGNATURE_B64URL <<< "${JWT}" + +JWT_HEADER_B64=$(base64url_to_b64 "${JWT_HEADER_B64URL}") +JWT_PAYLOAD_B64=$(base64url_to_b64 "${JWT_PAYLOAD_B64URL}") +JWT_SIGNATURE_B64=$(base64url_to_b64 "${JWT_SIGNATURE_B64URL}") + +JWT_HEADER=$(echo "${JWT_HEADER_B64}" | base64 -d) +JWT_PAYLOAD=$(echo "${JWT_PAYLOAD_B64}" | base64 -d) + +echo "JWT Header:" +echo "${JWT_HEADER}" | jq +echo "JWT Payload:" +echo "${JWT_PAYLOAD}" | jq +echo "JWT Signature (Base 64 padded):" +echo "${JWT_SIGNATURE_B64}" + +JWT_ALG=$(echo "$JWT_HEADER" | jq -r .alg) +JWT_KID=$(echo "$JWT_HEADER" | jq -r .kid) +#JWT_TYP=$(echo "$JWT_HEADER" | jq -r .typ) +#JWT_ISS=$(echo "$JWT_PAYLOAD" | jq -r .iss) +JWT_SUB=$(echo "$JWT_PAYLOAD" | jq -r .sub) +JWT_EMAIL=$(echo "$JWT_PAYLOAD" | jq -r .email) +JWT_IAT=$(echo "$JWT_PAYLOAD" | jq -r .iat) +echo "alg: $JWT_ALG kid: $JWT_KID" +echo "sub: $JWT_SUB email: $JWT_EMAIL iat: $JWT_IAT" + +echo "URL: ${URL}" +JWK_SET=$(curl -k -s "${URL}") +echo >&2 "JWK_SET: $JWK_SET" +JWK=$(echo "$JWK_SET" | jq -c -r --arg KID "$JWT_KID" '.keys[] | select(.kid==$KID)') +echo >&2 "JWK: $JWK" + +PUB_KEY_FILE=$(mktemp) +mint_rsa_key "$JWK" > "$PUB_KEY_FILE" + +# verify signature +if [ "${JWT_ALG}" = "RS256" ]; then + #SIG_FILE="/tmp/$JWT_SUB-$JWT_IAT.sig.dat" + SIG_FILE=$(mktemp) + echo -n "$JWT_SIGNATURE_B64" | base64 -d > "${SIG_FILE}" + JWT_BODY=$(echo -n "$JWT_HEADER_B64URL.$JWT_PAYLOAD_B64URL") + echo -n "$JWT_BODY" | openssl dgst -sha256 -verify "${PUB_KEY_FILE}" -signature "${SIG_FILE}" + JWT_SIG_VERIFIED=$? + rm "${SIG_FILE}" + if [ ${JWT_SIG_VERIFIED} -ne 0 ]; then + >&2 echo "Bad Signature" + exit ${JWT_SIG_VERIFIED}; + fi +else + >&2 echo "Error 3: Unsupported signature algorithm $JWT_ALG" + exit 3 +fi diff --git a/charts/spire/charts/spiffe-oidc-discovery-provider/templates/tests/test-keys.yaml b/charts/spire/charts/spiffe-oidc-discovery-provider/templates/tests/test-keys.yaml new file mode 100644 index 0000000..e2bbc43 --- /dev/null +++ b/charts/spire/charts/spiffe-oidc-discovery-provider/templates/tests/test-keys.yaml @@ -0,0 +1,87 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "spiffe-oidc-discovery-provider.fullname" . }}-test-keys" + namespace: {{ include "spiffe-oidc-discovery-provider.namespace" . }} + labels: + {{- include "spiffe-oidc-discovery-provider.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 4 }} + {{- end }} + serviceAccountName: {{ include "spiffe-oidc-discovery-provider.serviceAccountName" . }} + initContainers: + - name: static-busybox + image: {{ template "spire-lib.image" (dict "image" .Values.tests.busybox.image "global" .Values.global) }} + command: + - sh + - -c + - | + cp /bin/busybox /data/busybox + chmod +x /data/busybox + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + volumeMounts: + - name: data-volume + mountPath: /data + - name: gettoken + image: {{ template "spire-lib.image" (dict "appVersion" $.Chart.AppVersion "image" .Values.tools.kubectl.image "global" .Values.global "KubeVersion" .Capabilities.KubeVersion.Version "image" .Values.tests.agent.image) }} + command: + - /data/busybox + - sh + - -c + - | + while true; do + /opt/spire/bin/spire-agent api fetch jwt -audience foo -format json -socketPath /spire-agent/spire-agent.sock -timeout 5s > /data/token.svid + [ $? -eq 0 ] && break + sleep 1 + done + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + volumeMounts: + - name: data-volume + mountPath: /data + - name: spire-api + mountPath: /spire-agent + readOnly: true + containers: + - name: verify-keys + image: {{ template "spire-lib.image" (dict "image" .Values.tests.toolkit.image "global" .Values.global) }} + command: + - bash + env: + - name: TMPDIR + value: /data + args: + - -c + - | + URL=http://{{ include "spiffe-oidc-discovery-provider.fullname" . }}.{{ include "spiffe-oidc-discovery-provider.namespace" . }}.svc.{{ include "spire-lib.cluster-domain" . }}:{{ .Values.service.port }} + curl -k -s -f "${URL}"/keys + JWT=$(cat /data/token.svid | jq -r '.[].svids[0].svid' | xargs) + cat <<'EOF' >> /data/jwt-decode.sh + {{- (.Files.Get "files/test/jwt-decode.sh") | nindent 10 }} + EOF + bash /data/jwt-decode.sh "${URL}"/keys "${JWT}" + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + volumeMounts: + - mountPath: /data + name: data-volume + restartPolicy: Never + volumes: + - csi: + driver: csi.spiffe.io + readOnly: true + name: spire-api + - name: data-volume + emptyDir: {} +--- diff --git a/charts/spire/charts/spiffe-oidc-discovery-provider/values.yaml b/charts/spire/charts/spiffe-oidc-discovery-provider/values.yaml index 5430819..e758cda 100644 --- a/charts/spire/charts/spiffe-oidc-discovery-provider/values.yaml +++ b/charts/spire/charts/spiffe-oidc-discovery-provider/values.yaml @@ -225,6 +225,44 @@ tests: # -- Overrides the image tag tag: latest@sha256:96ab1600d945b4a99c8610b5c8b31e346da63dc20573a26bb0777dd0190db5d4 + toolkit: + image: + # -- The OCI registry to pull the tests image from + registry: cgr.dev + # -- The repository within the registry + repository: chainguard/slim-toolkit-debug + # -- The tests image pull policy + pullPolicy: IfNotPresent + # -- This value is deprecated in favor of tag. (Will be removed in a future release) + version: "" + # -- Overrides the image tag + tag: latest@sha256:d717d0a2c88518f8e36d9cfe1571639a40617e8c4291e34876d46bdeefb1ab5a + + busybox: + image: + # -- The OCI registry to pull the image from + registry: "" + # -- The repository within the registry + repository: busybox + # -- The image pull policy + pullPolicy: IfNotPresent + # -- This value is deprecated in favor of tag. (Will be removed in a future release) + version: "" + # -- Overrides the image tag + tag: uclibc@sha256:3e516f71d8801b0ce6c3f8f8e4f11093ec04e168177a90f1da4498014ee06b6b + agent: + image: + # -- The OCI registry to pull the image from + registry: ghcr.io + # -- The repository within the registry + repository: spiffe/spire-agent + # -- The image pull policy + pullPolicy: IfNotPresent + # -- This value is deprecated in favor of tag. (Will be removed in a future release) + version: "" + # -- Overrides the image tag + tag: "" + tools: kubectl: image: