-
Notifications
You must be signed in to change notification settings - Fork 192
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add E2E tests for OIDC token validation
This adds end-to-end tests and supporting material to execute tests that validate parts of the OIDC functionality. Signed-off-by: Allain Legacy <[email protected]>
- Loading branch information
Showing
33 changed files
with
1,196 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
#!/bin/bash | ||
|
||
set -e | ||
|
||
E2E_DATA=./test/e2e | ||
TEST_DATA=./test/data | ||
WORKDIR=$(mktemp -d) | ||
|
||
trap 'rm -rf -- "${WORKDIR}"' EXIT | ||
|
||
if [ ! -d .git ]; then | ||
echo "This script must be run from the top-level directory of the repository." | ||
exit 1 | ||
fi | ||
|
||
pushd "${WORKDIR}" > /dev/null | ||
|
||
# Setup a certificate for our test CA | ||
openssl req -x509 -sha256 -days 3650 -newkey rsa:2048 -keyout ca.key -out ca.crt -noenc \ | ||
-subj "/CN=kube-rbac-proxy-signer" \ | ||
-addext "keyUsage=digitalSignature,keyEncipherment,cRLSign,keyCertSign" > /dev/null 2>&1 | ||
CA_CERT=ca.crt | ||
CA_KEY=ca.key | ||
|
||
# Setup a certificate for the test client | ||
openssl genrsa -out client.key 2048 | ||
openssl req -key client.key -new -out client.csr -subj '/CN=kube-rbac-proxy-certificates-test' | ||
openssl x509 -req -CA "${CA_CERT}" -CAkey "${CA_KEY}" -in client.csr -out client.crt -days 3650 -CAcreateserial \ | ||
-extensions client \ | ||
-extfile <( | ||
cat <<EOF | ||
[client] | ||
basicConstraints = CA:FALSE | ||
extendedKeyUsage = clientAuth | ||
EOF | ||
) 2> /dev/null | ||
|
||
# Setup a certificate for the kube-rbac-proxy front end | ||
openssl genrsa -out front-end.key | ||
openssl req -key front-end.key -new -out front-end.csr -subj '/CN=kube-rbac-proxy-front-end' | ||
openssl x509 -req -CA "${CA_CERT}" -CAkey "${CA_KEY}" -in front-end.csr -out front-end.crt -days 3650 -CAcreateserial \ | ||
-extensions server \ | ||
-extfile <( | ||
cat <<EOF | ||
[server] | ||
basicConstraints = CA:FALSE | ||
keyUsage = digitalSignature,keyEncipherment | ||
extendedKeyUsage = serverAuth | ||
subjectAltName=DNS:kube-rbac-proxy.default.svc.cluster.local | ||
EOF | ||
) 2> /dev/null | ||
|
||
# Setup a certificate for the mock-server | ||
openssl genrsa -out mock-server.key | ||
openssl req -key mock-server.key -new -out mock-server.csr -subj '/CN=kube-rbac-proxy-mock-server' | ||
openssl x509 -req -CA "${CA_CERT}" -CAkey "${CA_KEY}" -in mock-server.csr -out mock-server.crt -days 3650 -CAcreateserial \ | ||
-extensions server \ | ||
-extfile <( | ||
cat <<EOF | ||
[server] | ||
basicConstraints = CA:FALSE | ||
keyUsage = digitalSignature,keyEncipherment | ||
extendedKeyUsage = serverAuth | ||
subjectAltName=DNS:mock-server.default.svc.cluster.local | ||
EOF | ||
) 2> /dev/null | ||
|
||
# Setup a OAuth token signer | ||
openssl req -x509 -sha256 -days 3650 -newkey rsa:2048 -keyout oauth-token-signer.key -out oauth-token-signer.crt -noenc \ | ||
-subj "/CN=kube-rbac-proxy-oauth-token-signer" \ | ||
-addext "keyUsage=digitalSignature,keyEncipherment" > /dev/null 2>&1 | ||
|
||
# Clean up the serial number file | ||
rm -f ca.srl | ||
|
||
# Create the device bundles | ||
cat client.crt "${CA_CERT}" > client-bundle.pem | ||
cat mock-server.crt "${CA_CERT}" > mock-server-bundle.pem | ||
cat front-end.crt "${CA_CERT}" > front-end-bundle.pem | ||
|
||
# Create the Secret objects | ||
kubectl create secret generic -n default kube-rbac-proxy-client-certificates \ | ||
--from-file=tls.crt=client-bundle.pem \ | ||
--from-file=tls.key=client.key \ | ||
--from-file=ca.crt="${CA_CERT}" \ | ||
--dry-run=client -o yaml > client-certificate.yaml | ||
|
||
kubectl create secret generic -n default kube-rbac-proxy-ca-certificate \ | ||
--from-file=tls.crt="${CA_CERT}" \ | ||
--from-file=tls.key="${CA_KEY}" \ | ||
--dry-run=client -o yaml > ca-certificate.yaml | ||
|
||
kubectl create secret generic -n default kube-rbac-proxy-mock-server-certificate \ | ||
--from-file=tls.crt=mock-server-bundle.pem \ | ||
--from-file=tls.key=mock-server.key \ | ||
--dry-run=client -o yaml > mock-server-certificate.yaml | ||
|
||
kubectl create secret generic -n default kube-rbac-proxy-front-end-certificate \ | ||
--from-file=tls.crt=front-end-bundle.pem \ | ||
--from-file=tls.key=front-end.key \ | ||
--dry-run=client -o yaml > front-end-certificate.yaml | ||
|
||
popd >/dev/null | ||
|
||
# Distribute the certificates to the tests that require them | ||
cp "${WORKDIR}"/client-certificate.yaml "${E2E_DATA}/clientcertificates/certificate.yaml" | ||
cp "${WORKDIR}"/client-certificate.yaml "${E2E_DATA}/oidc/client-certificate.yaml" | ||
cp "${WORKDIR}"/ca-certificate.yaml "${E2E_DATA}/oidc/ca-certificate.yaml" | ||
cp "${WORKDIR}"/front-end-certificate.yaml "${E2E_DATA}/oidc/front-end-certificate.yaml" | ||
cp "${WORKDIR}"/mock-server-certificate.yaml "${E2E_DATA}/oidc/mock-server-certificate.yaml" | ||
|
||
# Distribute other certificate data | ||
cp "${WORKDIR}/oauth-token-signer.crt" "${TEST_DATA}/" | ||
cp "${WORKDIR}/oauth-token-signer.key" "${TEST_DATA}/" | ||
|
||
rm -rf -- "${WORKDIR}" | ||
|
||
exit 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
#!/bin/bash | ||
|
||
set -e | ||
|
||
TOKEN_CERT=${1:-"./test/data/oauth-token-signer.crt"} | ||
TOKEN_KEYID=${2:-"0123456789abcdef"} | ||
JWKS_FILENAME=oauth-jwks.json | ||
WORKDIR=$(mktemp -d) | ||
|
||
trap 'rm -rf -- "${WORKDIR}"' EXIT | ||
|
||
if [ ! -d .git ]; then | ||
echo "This script must be run from the top-level directory of the repository." | ||
exit 1 | ||
fi | ||
|
||
if [ "$#" -lt 1 ]; then | ||
echo "Usage: generate-jwks.sh <token-cert> [<key-id>]" | ||
exit 1 | ||
fi | ||
|
||
TOKEN_CERT=$(realpath -s "${TOKEN_CERT}") | ||
|
||
pushd "${WORKDIR}" >/dev/null | ||
|
||
# URL encode a base64 string (see rfc4648) | ||
function url_encode_base64 { | ||
local VALUE=$1 | ||
echo -n "${VALUE}" | tr -- '+/=' '-_ ' | sed -e 's/ //g' | ||
return 0 | ||
} | ||
|
||
# Extract the public key from the certificate | ||
openssl x509 -pubkey -noout -in "${TOKEN_CERT}" > token-cert.pub | ||
|
||
# Extract the various JWKS parameters (see rfc7519) | ||
X5C=$(openssl x509 -in "${TOKEN_CERT}" -outform DER | base64 -w0) | ||
X5T=$(url_encode_base64 "$(openssl x509 -in "${TOKEN_CERT}" -outform DER | openssl sha1 -binary | base64 -w0)") | ||
X5T_S256=$(url_encode_base64 "$(openssl x509 -in "${TOKEN_CERT}" -outform DER | openssl sha256 -binary | base64 -w0)") | ||
N=$(url_encode_base64 "$(openssl rsa -pubin -in token-cert.pub -noout -modulus | sed -e 's/Modulus=//' | xxd -r -p | base64 -w0)") | ||
# The exponent ends up being the same for all of our certificates so we can use a static value | ||
E="AQAB" | ||
|
||
# Build the main body | ||
JWKS=$( | ||
cat <<EOF | ||
{ | ||
"kid": "${TOKEN_KEYID}", | ||
"kty": "RSA", | ||
"alg": "RS256", | ||
"use": "sig", | ||
"n": "${N}", | ||
"e": "${E}", | ||
"x5c": [ | ||
"${X5C}" | ||
], | ||
"x5t": "${X5T}", | ||
"x5t#S256": "${X5T_S256}" | ||
} | ||
EOF | ||
) | ||
|
||
# Clean/format up the JSON and save to a file | ||
echo "${JWKS}" | jq > ${JWKS_FILENAME} | ||
|
||
popd >/dev/null | ||
|
||
# Save the file for later | ||
cp "${WORKDIR}/${JWKS_FILENAME}" ./test/data/ | ||
|
||
rm -rf -- "${WORKDIR}" | ||
|
||
exit 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
#!/bin/bash | ||
|
||
set -e | ||
|
||
KEY=${1:-"./test/data/oauth-token-signer.key"} | ||
KEYID=${2:-"0123456789abcdef"} | ||
SECRET_FILENAME="client-tokens-secret.yaml" | ||
TEST_CLIENT_ID="test-client-id" | ||
TEST_USERNAME="test-client" | ||
WORKDIR=$(mktemp -d) | ||
|
||
trap 'rm -rf -- "${WORKDIR}"' EXIT | ||
|
||
if [ ! -d .git ]; then | ||
echo "This script must be run from the top-level directory of the repository." | ||
exit 1 | ||
fi | ||
|
||
KEY=$(realpath -s "${KEY}") | ||
|
||
pushd "${WORKDIR}" > /dev/null | ||
|
||
# URL encode a base64 string (see rfc4648) | ||
function base64url_encode { | ||
local VALUE=$1 | ||
echo -n "${VALUE}" | base64 -w0 | tr -- '+/=' '-_ ' | sed -e 's/ //g' | ||
return 0 | ||
} | ||
|
||
# Generate a token with a specific key-id, issued-at, and expired values | ||
function generate_token { | ||
local KEYID=$1 | ||
local IAT=$2 | ||
local EXP=$3 | ||
local USERNAME=$4 | ||
local CLIENT_ID=$5 | ||
|
||
# Construct the token header and footer (see rfc7519) | ||
JWT_HEADER=$( | ||
cat <<EOF | ||
{ | ||
"typ":"JWT", | ||
"alg":"RS256", | ||
"kid":"${KEYID}" | ||
} | ||
EOF | ||
) | ||
|
||
# username here aligns with the username assigned to the client in the client role binding | ||
JWT_BODY=$( | ||
cat <<EOF | ||
{ | ||
"sub": "1234567890", | ||
"iss": "https://mock-server.default.svc.cluster.local:8443", | ||
"preferred_username": "${USERNAME}", | ||
"iat": ${IAT}, | ||
"exp": ${EXP}, | ||
"aud": "${CLIENT_ID}", | ||
"roles": ["metrics"] | ||
} | ||
EOF | ||
) | ||
|
||
# Base64 encode the header and footer | ||
ENCODED_HEADER=$(base64url_encode "$(echo -n "${JWT_HEADER}" | jq -c .)") | ||
ENCODED_BODY=$(base64url_encode "$(echo -n "${JWT_BODY}" | jq -c .)") | ||
|
||
# Sign the body of the token and generate the signature footer | ||
ENCODED_SIGNATURE=$(echo -n "${ENCODED_HEADER}.${ENCODED_BODY}" | | ||
openssl dgst -sha256 -sign "${KEY}" -binary | | ||
base64 -w0 | tr -- '+/=' '-_ ' | sed -e 's/ //g') | ||
|
||
# Assemble the full token | ||
JWT_TOKEN="${ENCODED_HEADER}.${ENCODED_BODY}.${ENCODED_SIGNATURE}" | ||
|
||
# Output the token to a file and then encapsulate that within a Secret. | ||
echo -n "${JWT_TOKEN}" | ||
} | ||
|
||
NOW=$(date "+%s") | ||
generate_token "${KEYID}" "${NOW}" "$((NOW + 864000))" "${TEST_USERNAME}" "${TEST_CLIENT_ID}" > token.jwt | ||
generate_token "${KEYID}" "$((NOW - 86400))" "${NOW}" "${TEST_USERNAME}" "${TEST_CLIENT_ID}" > expired-token.jwt | ||
generate_token "unknown-key-id" "${NOW}" "$((NOW + 864000))" "${TEST_USERNAME}" "${TEST_CLIENT_ID}" > unknown-keyid-token.jwt | ||
generate_token "${KEYID}" "${NOW}" "$((NOW + 864000))" "unknown-username" "${TEST_CLIENT_ID}" > unknown-user-token.jwt | ||
generate_token "${KEYID}" "${NOW}" "$((NOW + 864000))" "${TEST_USERNAME}" "unknown-audience" > unknown-audience-token.jwt | ||
|
||
kubectl create secret generic -n default kube-rbac-proxy-client-tokens \ | ||
--from-file=token.jwt=token.jwt \ | ||
--from-file=expired-token.jwt=expired-token.jwt \ | ||
--from-file=unknown-token.jwt=unknown-keyid-token.jwt \ | ||
--from-file=unknown-user-token.jwt=unknown-user-token.jwt \ | ||
--from-file=unknown-audience-token.jwt=unknown-audience-token.jwt \ | ||
--dry-run=client -oyaml > ${SECRET_FILENAME} | ||
|
||
popd >/dev/null | ||
|
||
# Distribute the file to the tests that require it. | ||
cp "${WORKDIR}/${SECRET_FILENAME}" ./test/e2e/oidc/ | ||
|
||
rm -rf -- "${WORKDIR}" | ||
|
||
exit 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
#!/bin/bash | ||
|
||
set -e | ||
|
||
if [ ! -d .git ]; then | ||
echo "This script must be run from the top-level directory of the repository." | ||
exit 1 | ||
fi | ||
|
||
EXPECTATION_FILE=./test/data/mock-server-expectations.json | ||
JWKS_FILE=./test/data/oauth-jwks.json | ||
MOCK_SERVER_CONFIGMAP=./test/e2e/oidc/mock-server-configmap.yaml | ||
|
||
# Insert the JWKS fragment into the 2nd response for the /certs API endpoint | ||
mv "${EXPECTATION_FILE}" "${EXPECTATION_FILE}.orig" | ||
jq ".[1].httpResponse.body.keys.[0] = input" "${EXPECTATION_FILE}.orig" "${JWKS_FILE}" > "${EXPECTATION_FILE}" | ||
rm -f "${EXPECTATION_FILE}.orig" | ||
|
||
# Create a configmap with the new expectations file | ||
kubectl create configmap -n default mock-server-config \ | ||
--from-file=expectations.json="${EXPECTATION_FILE}" \ | ||
--dry-run=client -o yaml > "${MOCK_SERVER_CONFIGMAP}" | ||
|
||
exit 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#!/bin/bash | ||
|
||
set -e | ||
|
||
KEYID=${1:-"0123456789abcdef"} | ||
|
||
if [ ! -d .git ]; then | ||
echo "This script must be run from the top-level directory of the repository." | ||
exit 1 | ||
fi | ||
|
||
if [ "${1}" == "-f" ]; then | ||
# Only re-generate the certificates if the "-f" was specified since there is no need to do so unless they have | ||
# expired or their configuration attributes have been modified in some way. | ||
./scripts/generate-certificates.sh | ||
fi | ||
|
||
# Generate the test tokens used by the client for various test scenarios | ||
./scripts/generate-tokens.sh ./test/data/oauth-token-signer.key "${KEYID}" | ||
|
||
# Generate a JWKS profile to represent the public key to be used to verify the generated tokens | ||
./scripts/generate-jwks.sh ./test/data/oauth-token-signer.crt "${KEYID}" | ||
|
||
# Inject the JWKS profile into the mock-server response configuration | ||
./scripts/update-mock-server-expectations.sh | ||
|
||
exit 0 |
Oops, something went wrong.