nuthatch/tests/kind/wp05-job.yaml.in

578 lines
25 KiB
YAML

apiVersion: batch/v1
kind: Job
metadata:
name: @@JOB_NAME@@
namespace: nuthatch-testbed
spec:
activeDeadlineSeconds: 300
backoffLimit: 0
ttlSecondsAfterFinished: 300
template:
spec:
automountServiceAccountToken: false
enableServiceLinks: false
dnsPolicy: None
dnsConfig:
nameservers: [10.96.0.53]
searches:
- nuthatch-testbed.svc.cluster.local
- svc.cluster.local
- cluster.local
options:
- {name: ndots, value: "5"}
restartPolicy: Never
initContainers:
- name: credential-negative
image: @@AWS_CLI_IMAGE@@
imagePullPolicy: Never
command: [/bin/sh, -ec]
args:
- |
export AWS_ACCESS_KEY_ID="$NUTHATCH_S3_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="$NUTHATCH_S3_SECRET_KEY"
export AWS_DEFAULT_REGION="$NUTHATCH_S3_REGION"
printf denied-write >/tmp/denied-write
expect_denied() {
name="$1"
shift
if "$@" >/tmp/"$name".out 2>/tmp/"$name".err; then
exit 1
fi
grep -E 'AccessDenied|Forbidden|403' /tmp/"$name".err
}
expect_denied head \
aws --endpoint-url "$NUTHATCH_S3_ENDPOINT" s3api head-bucket \
--bucket nuthatch-fixture
expect_denied list \
aws --endpoint-url "$NUTHATCH_S3_ENDPOINT" s3api list-objects-v2 \
--bucket nuthatch-fixture
expect_denied get \
aws --endpoint-url "$NUTHATCH_S3_ENDPOINT" s3api get-object \
--bucket nuthatch-fixture --key scope-canary /tmp/denied-read
expect_denied put \
aws --endpoint-url "$NUTHATCH_S3_ENDPOINT" s3api put-object \
--bucket nuthatch-fixture --key denied-write --body /tmp/denied-write
envFrom:
- configMapRef: {name: @@PROFILE_NAME@@}
resources:
requests: {cpu: 25m, memory: 64Mi}
limits: {cpu: "1", memory: 256Mi}
containers:
- name: smoke
image: @@CURL_IMAGE@@
imagePullPolicy: Never
command: [/bin/sh, -ec]
args:
- |
base="https://nuthatch-@@LANE@@:8080"
curl() {
command curl --insecure "$@"
}
header_value() {
awk -v expected="$2" '
BEGIN { IGNORECASE=1 }
$1 == expected ":" {
sub(/^[^:]*:[[:space:]]*/, "")
sub(/\r$/, "")
print
exit
}
' "$1"
}
json_field() {
tr -d '\n' < "$1" |
sed -n 's/.*"'"$2"'"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p'
}
oidc_callback_for() {
user="$1"
suffix="$2"
authorization_url="$3"
printf 'Completing %s authorization %s\n' \
"$NUTHATCH_OIDC_PROVIDER" "$suffix" >&2
case "$NUTHATCH_OIDC_PROVIDER" in
keycloak)
curl --fail --silent --show-error \
--cookie-jar "/tmp/idp-$suffix" \
--dump-header "/tmp/idp-start-$suffix.headers" \
"$authorization_url" >"/tmp/idp-$suffix.html"
action="$(
sed -n 's/.*action="\([^"]*\)".*/\1/p' "/tmp/idp-$suffix.html" |
head -n 1 |
sed 's/&amp;/\&/g'
)"
test -n "$action"
curl --silent --show-error \
--cookie "/tmp/idp-$suffix" \
--cookie-jar "/tmp/idp-$suffix" \
--dump-header "/tmp/idp-finish-$suffix.headers" \
--data-urlencode "username=$user" \
--data-urlencode "password=$NUTHATCH_OIDC_USER_PASSWORD" \
--data-urlencode credentialId= \
"$action" >"/tmp/idp-finish-$suffix.body"
header_value "/tmp/idp-finish-$suffix.headers" Location
;;
zitadel)
curl --silent --show-error \
--dump-header "/tmp/idp-start-$suffix.headers" \
"$authorization_url" >"/tmp/idp-start-$suffix.body"
login_location="$(
header_value "/tmp/idp-start-$suffix.headers" Location
)"
auth_request="${login_location##*authRequest=}"
auth_request="${auth_request%%&*}"
test -n "$auth_request"
curl --fail --silent --show-error \
--header "Authorization: Bearer $NUTHATCH_OIDC_LOGIN_PAT" \
--header 'Content-Type: application/json' \
--data "{\"checks\":{\"user\":{\"userId\":\"$user\"},\"password\":{\"password\":\"$NUTHATCH_OIDC_USER_PASSWORD\"}},\"lifetime\":\"300s\"}" \
"$NUTHATCH_OIDC_ISSUER/v2/sessions" \
>"/tmp/session-$suffix.json"
session_id="$(json_field "/tmp/session-$suffix.json" sessionId)"
session_token="$(json_field "/tmp/session-$suffix.json" sessionToken)"
test -n "$session_id"
test -n "$session_token"
curl --fail --silent --show-error \
--header "Authorization: Bearer $NUTHATCH_OIDC_LOGIN_PAT" \
--header 'Content-Type: application/json' \
--data "{\"session\":{\"sessionId\":\"$session_id\",\"sessionToken\":\"$session_token\"}}" \
"$NUTHATCH_OIDC_ISSUER/v2/oidc/auth_requests/$auth_request" \
>"/tmp/finalize-$suffix.json"
json_field "/tmp/finalize-$suffix.json" callbackUrl |
sed 's/\\u0026/\&/g; s#\\/#/#g'
;;
*)
exit 1
;;
esac
}
login() {
user="$1"
suffix="$2"
cookies="/tmp/cookies-$suffix"
printf 'Starting login %s\n' "$suffix" >&2
curl --insecure --silent --show-error \
--cookie-jar "$cookies" \
--dump-header "/tmp/login-$suffix.headers" \
"$base/login" >"/tmp/login-$suffix.body"
test "$(header_value "/tmp/login-$suffix.headers" Status 2>/dev/null || true)" = ""
authorization_url="$(
header_value "/tmp/login-$suffix.headers" Location
)"
test -n "$authorization_url"
grep -i '^set-cookie: nuthatch_oidc=' "/tmp/login-$suffix.headers" |
grep -i 'secure' |
grep -i 'httponly' |
grep -i 'samesite=lax' |
grep -i 'path=/oidc/callback' >/dev/null
state="$(
printf '%s' "$authorization_url" |
sed -n 's/.*[?&]state=\([^&]*\).*/\1/p'
)"
test -n "$state"
if [ "$suffix" = a ]; then
wrong_redirect_url="$(
printf '%s' "$authorization_url" |
sed 's#redirect_uri=[^&]*#redirect_uri=https%3A%2F%2Finvalid.example%2Fcallback#'
)"
wrong_redirect_status="$(
curl --silent --output /dev/null \
--write-out '%{http_code}' \
"$wrong_redirect_url"
)"
test "$wrong_redirect_status" -ge 400
printf 'Redirect URI rejection passed\n' >&2
fi
if curl --insecure --fail --silent \
--cookie "$cookies" \
"$base/oidc/callback?code=unused&state=invalid-$state" >/dev/null; then
exit 1
fi
if [ "$suffix" = a ]; then
transaction="$(
awk -F '\t' '$6 == "nuthatch_oidc" { print $7 }' "$cookies"
)"
test "${#transaction}" -gt 1
tampered_transaction="x${transaction#?}"
test "$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
--header 'Cookie: nuthatch_oidc=malformed' \
"$base/oidc/callback?code=unused&state=$state"
)" = 400
test "$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
--header "Cookie: nuthatch_oidc=$tampered_transaction" \
"$base/oidc/callback?code=unused&state=$state"
)" = 400
fi
callback="$(
oidc_callback_for "$user" "$suffix" "$authorization_url"
)"
test -n "$callback"
printf 'Authorization callback received %s\n' "$suffix" >&2
callback_status="$(
curl --insecure --silent --show-error \
--output "/tmp/callback-$suffix.body" \
--write-out '%{http_code}' \
--cookie "$cookies" \
--cookie-jar "$cookies" \
--dump-header "/tmp/callback-$suffix.headers" \
"$callback"
)"
printf 'Application callback status=%s %s\n' \
"$callback_status" "$suffix" >&2
if [ "$callback_status" != 303 ]; then
sed -n '1,8p' "/tmp/callback-$suffix.body" >&2
exit 1
fi
test "$(header_value "/tmp/callback-$suffix.headers" Location)" = "/"
printf 'Application callback accepted %s\n' "$suffix" >&2
sed -n \
's/^\([Ss]et-[Cc]ookie: [^=]*\)=.*/\1=<redacted>/p' \
"/tmp/callback-$suffix.headers" >&2
session_cookie="$(
grep -i '^set-cookie: nuthatch_session=' "/tmp/callback-$suffix.headers"
)"
csrf_cookie="$(
grep -i '^set-cookie: nuthatch_csrf=' "/tmp/callback-$suffix.headers"
)"
transaction_cookie="$(
grep -i '^set-cookie: nuthatch_oidc=' "/tmp/callback-$suffix.headers"
)"
printf '%s' "$session_cookie" |
grep -i secure |
grep -i httponly |
grep -i samesite=lax |
grep -i 'path=/;' >/dev/null
printf '%s' "$csrf_cookie" |
grep -i secure |
grep -i samesite=lax |
grep -iv httponly |
grep -i 'path=/;' >/dev/null
printf '%s' "$transaction_cookie" |
grep -i 'max-age=0' |
grep -i 'path=/oidc/callback;' >/dev/null
if printf '%s\n%s\n%s\n' \
"$session_cookie" "$csrf_cookie" "$transaction_cookie" |
grep -qi 'domain='; then
exit 1
fi
printf 'Cookie attributes passed %s\n' "$suffix" >&2
awk -F '\t' '
NF == 7 {
printf "Stored cookie %s value-length=%d\n", $6, length($7) > "/dev/stderr"
}
$6 == "nuthatch_csrf" { print $7 }
' "$cookies"
}
curl --insecure --fail --silent --show-error "$base/healthz" |
grep -F '"ok":true'
curl --insecure --fail --silent --show-error "$base/readyz" |
grep -F '"ready":true'
curl --insecure --fail --silent --show-error "$base/nuthatch.js" |
grep -F 'nuthatch'
{
curl --insecure --fail --silent --show-error "$base/nuthatch.js"
curl --insecure --silent --show-error "$base/login"
} > /tmp/browser-visible
if grep -Ei \
'NUTHATCH_S3|AWS_ACCESS_KEY|AWS_SECRET_ACCESS_KEY|client_secret' \
/tmp/browser-visible; then
exit 1
fi
test "$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
--request POST \
"$base/login"
)" = 405
test "$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
"$base/"
)" = 303
test "$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
"$base/download?root=personal&path=unauthenticated"
)" = 303
unauthenticated_digest="$(
printf x | sha256sum | cut -d' ' -f1
)"
test "$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
--header 'X-Declared-Size: 1' \
--header "X-Content-SHA256: $unauthenticated_digest" \
--header 'X-CSRF-Token: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' \
--data x \
"$base/upload?root=personal&path=unauthenticated&operation_id=40000000-0000-4000-8000-000000000001"
)" = 303
test "$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
--header 'Content-Type: application/json' \
--data '{"root":"personal","path":"unauthenticated","operation_id":"40000000-0000-4000-8000-000000000002","csrf":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' \
"$base/delete"
)" = 303
test "$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
--header 'Content-Type: application/json' \
--data '{"root":"personal","path":"unauthenticated","csrf":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' \
"$base/delete-prefix"
)" = 303
test "$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
--header 'X-CSRF-Token: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' \
--request POST \
"$base/logout"
)" = 303
printf 'Unauthenticated boundary probes passed\n'
csrf="$(login "$NUTHATCH_OIDC_USER_A" a)"
test -n "$csrf"
cookies=/tmp/cookies-a
printf 'Login a completed csrf-length=%d\n' "${#csrf}"
authenticated_status="$(
curl --insecure --silent --output /tmp/authenticated.body \
--write-out '%{http_code}' \
--cookie "$cookies" \
"$base/"
)"
printf 'Authenticated browse status=%s\n' "$authenticated_status"
test "$authenticated_status" = 200
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
--header 'Accept: application/json' \
"$base/" >/tmp/authenticated.json
if grep -Ei \
'NUTHATCH_S3|AWS_ACCESS_KEY|AWS_SECRET_ACCESS_KEY|client_secret' \
/tmp/authenticated.body /tmp/authenticated.json; then
exit 1
fi
exercise_root() {
root="$1"
suffix="$2"
first="first-$root"
second="second-$root"
printf '%s' "$first" >/tmp/body
size="$(wc -c </tmp/body | tr -d ' ')"
digest="$(sha256sum /tmp/body | cut -d' ' -f1)"
upload_status="$(
curl --silent --show-error \
--output /tmp/upload.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $size" \
--header "X-Content-SHA256: $digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/body \
"$base/upload?root=$root&path=smoke-$suffix.txt&operation_id=00000000-0000-4000-8000-0000000000${suffix}"
)"
printf 'Upload %s status=%s\n' "$root" "$upload_status"
test "$upload_status" = 200
grep -F '"result":"succeeded"' /tmp/upload.json
curl --fail --silent --show-error \
--cookie "$cookies" \
--header 'Accept: application/json' \
"$base/?root=$root" |
grep -F "smoke-$suffix.txt"
curl --fail --silent --show-error \
--cookie "$cookies" \
"$base/download?root=$root&path=smoke-$suffix.txt" >/tmp/download
test "$(cat /tmp/download)" = "$first"
printf '%s' "$second" >/tmp/body
size="$(wc -c </tmp/body | tr -d ' ')"
digest="$(sha256sum /tmp/body | cut -d' ' -f1)"
curl --fail --silent --show-error \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $size" \
--header "X-Content-SHA256: $digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/body \
"$base/upload?root=$root&path=smoke-$suffix.txt&operation_id=10000000-0000-4000-8000-0000000000${suffix}" |
grep -F '"result":"succeeded"'
curl --fail --silent --show-error \
--cookie "$cookies" \
"$base/download?root=$root&path=smoke-$suffix.txt" >/tmp/download
test "$(cat /tmp/download)" = "$second"
curl --fail --silent --show-error \
--cookie "$cookies" \
--header 'Content-Type: application/json' \
--data "{\"root\":\"$root\",\"path\":\"smoke-$suffix.txt\",\"operation_id\":\"20000000-0000-4000-8000-0000000000${suffix}\",\"csrf\":\"$csrf\"}" \
"$base/delete" |
grep -F '"result":"succeeded"'
if curl --fail --silent \
--cookie "$cookies" \
"$base/download?root=$root&path=smoke-$suffix.txt" >/dev/null; then
exit 1
fi
}
exercise_root personal 01
exercise_root shared 02
printf private-a >/tmp/body
size="$(wc -c </tmp/body | tr -d ' ')"
digest="$(sha256sum /tmp/body | cut -d' ' -f1)"
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $size" \
--header "X-Content-SHA256: $digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/body \
"$base/upload?root=personal&path=cross-user.txt&operation_id=30000000-0000-4000-8000-000000000001" |
grep -F '"result":"succeeded"'
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $size" \
--header "X-Content-SHA256: $digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/body \
"$base/upload?root=shared&path=cross-user.txt&operation_id=30000000-0000-4000-8000-000000000002" |
grep -F '"result":"succeeded"'
csrf_b="$(login "$NUTHATCH_OIDC_USER_B" b)"
test -n "$csrf_b"
curl --insecure --silent --show-error \
--cookie-jar /tmp/cookies-pkce \
--dump-header /tmp/login-pkce.headers \
"$base/login" >/tmp/login-pkce.body
pkce_authorization_url="$(
header_value /tmp/login-pkce.headers Location
)"
pkce_callback="$(
oidc_callback_for \
"$NUTHATCH_OIDC_USER_A" \
pkce \
"$pkce_authorization_url"
)"
pkce_code="$(
printf '%s' "$pkce_callback" |
sed -n 's/.*[?&]code=\([^&]*\).*/\1/p'
)"
test -n "$pkce_code"
curl --fail --silent --show-error \
"$NUTHATCH_OIDC_ISSUER/.well-known/openid-configuration" \
>/tmp/discovery.json
token_endpoint="$(json_field /tmp/discovery.json token_endpoint)"
test -n "$token_endpoint"
pkce_status="$(
curl --silent --output /tmp/pkce-error.json \
--write-out '%{http_code}' \
--user "$NUTHATCH_OIDC_CLIENT_ID:$NUTHATCH_OIDC_CLIENT_SECRET" \
--data-urlencode grant_type=authorization_code \
--data-urlencode "code=$pkce_code" \
--data-urlencode "redirect_uri=$NUTHATCH_OIDC_REDIRECT_URI" \
--data-urlencode code_verifier=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
"$token_endpoint"
)"
test "$pkce_status" -ge 400
if curl --insecure --fail --silent --show-error \
--cookie /tmp/cookies-b \
--header 'Accept: application/json' \
"$base/?root=personal" |
grep -F cross-user.txt; then
exit 1
fi
curl --insecure --fail --silent --show-error \
--cookie /tmp/cookies-b \
--header 'Accept: application/json' \
"$base/?root=shared" |
grep -F cross-user.txt
if curl --insecure --fail --silent \
--cookie "$cookies" \
--header 'X-CSRF-Token: invalid' \
--request POST \
"$base/logout" >/dev/null; then
exit 1
fi
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
--cookie-jar "$cookies" \
--dump-header /tmp/logout.headers \
--header "X-CSRF-Token: $csrf" \
--request POST \
"$base/logout" >/dev/null
grep -i '^set-cookie: nuthatch_session=' /tmp/logout.headers |
grep -i 'max-age=0' >/dev/null
grep -i '^set-cookie: nuthatch_csrf=' /tmp/logout.headers |
grep -i 'max-age=0' >/dev/null
grep -i '^set-cookie: nuthatch_oidc=' /tmp/logout.headers |
grep -i 'max-age=0' |
grep -i 'path=/oidc/callback' >/dev/null
test "$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
--cookie "$cookies" \
"$base/"
)" = 303
printf 'OIDC app smoke passed\n'
env:
- name: NUTHATCH_OIDC_PROVIDER
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_OIDC_PROVIDER
- name: NUTHATCH_OIDC_ISSUER
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_OIDC_ISSUER
- name: NUTHATCH_OIDC_USER_A
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_OIDC_USER_A
- name: NUTHATCH_OIDC_USER_B
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_OIDC_USER_B
- name: NUTHATCH_OIDC_USER_PASSWORD
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_OIDC_USER_PASSWORD
- name: NUTHATCH_OIDC_CLIENT_ID
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_OIDC_CLIENT_ID
- name: NUTHATCH_OIDC_REDIRECT_URI
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_OIDC_REDIRECT_URI
- name: NUTHATCH_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: nuthatch-@@LANE@@
key: NUTHATCH_OIDC_CLIENT_SECRET
- name: NUTHATCH_OIDC_LOGIN_PAT
valueFrom:
secretKeyRef:
name: nuthatch-oidc-automation-@@LANE@@
key: NUTHATCH_OIDC_LOGIN_PAT
resources:
requests: {cpu: 50m, memory: 32Mi}
limits: {cpu: "1", memory: 128Mi}