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/&/\&/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=/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 if [ "${NUTHATCH_IDEMPOTENCY_MODE:-}" = verify ]; then pod_curl() { destination="$1" shift command curl --insecure \ --resolve "nuthatch-@@LANE@@:8080:$destination" "$@" } printf first-epoch >/tmp/epoch-one first_size="$(wc -c /tmp/epoch-first.json grep -F '"result":"succeeded"' /tmp/epoch-first.json grep -F '"idempotency_window_seconds":2' \ /tmp/epoch-first.json pod_curl "$NUTHATCH_IDEMPOTENCY_IP_B" \ --fail --silent --show-error \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header "X-Declared-Size: $first_size" \ --header "X-Content-SHA256: $first_digest" \ --header "X-CSRF-Token: $csrf" \ --data-binary @/tmp/epoch-one \ "$mutation_url" >/tmp/epoch-replay.json grep -F '"result":"succeeded"' /tmp/epoch-replay.json grep -F '"idempotency_window_seconds":2' \ /tmp/epoch-replay.json printf conflict >/tmp/conflict conflict_size="$(wc -c /tmp/epoch-conflict.json grep -F '"result":"conflict"' /tmp/epoch-conflict.json sleep 3 printf second-epoch-a >/tmp/epoch-two-a printf second-epoch-b >/tmp/epoch-two-b second_a_size="$(wc -c /tmp/epoch-two-a.json & epoch_two_a_pid=$! pod_curl "$NUTHATCH_IDEMPOTENCY_IP_B" \ --fail --silent --show-error \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header "X-Declared-Size: $second_b_size" \ --header "X-Content-SHA256: $second_b_digest" \ --header "X-CSRF-Token: $csrf" \ --data-binary @/tmp/epoch-two-b \ "$mutation_url" >/tmp/epoch-two-b.json & epoch_two_b_pid=$! wait "$epoch_two_a_pid" wait "$epoch_two_b_pid" test "$( grep -lF '"result":"succeeded"' \ /tmp/epoch-two-a.json /tmp/epoch-two-b.json | wc -l | tr -d ' ' )" = 1 test "$( grep -lF '"result":"conflict"' \ /tmp/epoch-two-a.json /tmp/epoch-two-b.json | wc -l | tr -d ' ' )" = 1 if grep -F '"result":"succeeded"' \ /tmp/epoch-two-a.json >/dev/null; then epoch_two_body=/tmp/epoch-two-a epoch_two_size="$second_a_size" epoch_two_digest="$second_a_digest" epoch_two_ip="$NUTHATCH_IDEMPOTENCY_IP_B" else epoch_two_body=/tmp/epoch-two-b epoch_two_size="$second_b_size" epoch_two_digest="$second_b_digest" epoch_two_ip="$NUTHATCH_IDEMPOTENCY_IP_A" fi pod_curl "$epoch_two_ip" \ --fail --silent --show-error \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header "X-Declared-Size: $epoch_two_size" \ --header "X-Content-SHA256: $epoch_two_digest" \ --header "X-CSRF-Token: $csrf" \ --data-binary @"$epoch_two_body" \ "$mutation_url" >/tmp/epoch-two-replay.json grep -F '"result":"succeeded"' /tmp/epoch-two-replay.json grep -F '"idempotency_window_seconds":2' \ /tmp/epoch-two-replay.json pod_curl "$NUTHATCH_IDEMPOTENCY_IP_A" \ --fail --silent --show-error \ --cookie "$cookies" \ "$base/download?root=personal&path=idempotency.txt" \ >/tmp/epoch-download cmp "$epoch_two_body" /tmp/epoch-download dd if=/dev/zero of=/tmp/active-body bs=1048576 count=1 \ status=none active_digest="$( sha256sum /tmp/active-body | cut -d' ' -f1 )" active_operation=73000000-0000-4000-8000-000000000002 active_url="$base/upload?root=personal&path=active-window.bin&operation_id=$active_operation" ( dd if=/tmp/active-body bs=65536 count=1 status=none sleep 5 dd if=/tmp/active-body bs=65536 skip=1 count=15 status=none ) | pod_curl "$NUTHATCH_IDEMPOTENCY_IP_A" \ --fail --silent --show-error \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header 'X-Declared-Size: 1048576' \ --header "X-Content-SHA256: $active_digest" \ --header "X-CSRF-Token: $csrf" \ --request POST \ --upload-file - \ "$active_url" >/tmp/active-first.json & active_pid=$! sleep 3 pod_curl "$NUTHATCH_IDEMPOTENCY_IP_B" \ --fail --silent --show-error \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header 'X-Declared-Size: 1048576' \ --header "X-Content-SHA256: $active_digest" \ --header "X-CSRF-Token: $csrf" \ --data-binary @/tmp/epoch-one \ "$active_url" >/tmp/active-replay.json grep -F '"result":"pending"' /tmp/active-replay.json lock_status="$( pod_curl "$NUTHATCH_IDEMPOTENCY_IP_B" \ --silent --show-error \ --output /tmp/active-lock.json \ --write-out '%{http_code}' \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header "X-Declared-Size: $first_size" \ --header "X-Content-SHA256: $first_digest" \ --header "X-CSRF-Token: $csrf" \ --data-binary @/tmp/epoch-one \ "$base/upload?root=personal&path=active-window.bin&operation_id=73000000-0000-4000-8000-000000000003" )" test "$lock_status" = 409 grep -F '"error":"mutation_rejected"' /tmp/active-lock.json wait "$active_pid" grep -F '"result":"succeeded"' /tmp/active-first.json printf '%s\n' \ '{"epoch":1,"action":"Init"}' \ '{"epoch":1,"action":"FirstRequest","observation":{"result":"pending","disposition":"first"}}' \ '{"epoch":1,"action":"PrepareEffect"}' \ '{"epoch":1,"action":"DispatchEffect"}' \ '{"epoch":1,"action":"ExternalEffectApplied"}' \ '{"epoch":1,"action":"FinalizeSuccess"}' \ '{"epoch":1,"action":"ReplayRequest","observation":{"result":"succeeded","disposition":"replay"}}' \ '{"epoch":1,"action":"ConflictRequest","observation":{"result":"conflict","disposition":"conflict"}}' \ '{"after_epoch":1,"action":"TraceCut","reason":"terminal_identity_expired"}' \ '{"epoch":2,"action":"Init"}' \ '{"epoch":2,"action":"FirstRequest","observation":{"result":"pending","disposition":"first"}}' \ '{"epoch":2,"action":"PrepareEffect"}' \ '{"epoch":2,"action":"DispatchEffect"}' \ '{"epoch":2,"action":"ExternalEffectApplied"}' \ '{"epoch":2,"action":"FinalizeSuccess"}' \ '{"epoch":2,"action":"ReplayRequest","observation":{"result":"succeeded","disposition":"replay"}}' printf 'Cross-process idempotency epoch probes passed\n' exit 0 fi if [ "${NUTHATCH_STREAMING_MODE:-}" = large ]; then large_bytes="${NUTHATCH_STREAMING_LARGE_BYTES:?}" printf 'Preparing %s-byte streaming object\n' "$large_bytes" large_digest="$( dd if=/dev/zero bs=1048576 \ count="$((large_bytes / 1048576))" status=none | sha256sum | cut -d' ' -f1 )" printf 'Uploading constrained-memory streaming object\n' status="$( dd if=/dev/zero bs=1048576 \ count="$((large_bytes / 1048576))" status=none | curl --silent --show-error \ --output /tmp/large-upload.json \ --write-out '%{http_code}' \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header "X-Declared-Size: $large_bytes" \ --header "X-Content-SHA256: $large_digest" \ --header "X-CSRF-Token: $csrf" \ --request POST \ --upload-file - \ "$base/upload?root=personal&path=streaming-large.bin&operation_id=70000000-0000-4000-8000-000000000001" )" printf 'Large upload status=%s\n' "$status" test "$status" = 200 grep -F '"result":"succeeded"' /tmp/large-upload.json printf 'Downloading constrained-memory streaming object\n' download_digest="$( curl --fail --silent --show-error \ --cookie "$cookies" \ "$base/download?root=personal&path=streaming-large.bin" | sha256sum | cut -d' ' -f1 )" printf 'Large download digest expected=%s actual=%s\n' \ "$large_digest" "$download_digest" test "$download_digest" = "$large_digest" printf x >/tmp/tiny tiny_digest="$(sha256sum /tmp/tiny | cut -d' ' -f1)" for attempt in first replay; do status="$( curl --silent --show-error \ --output "/tmp/prior-desired-$attempt.json" \ --write-out '%{http_code}' \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header "X-Declared-Size: $large_bytes" \ --header "X-Content-SHA256: $large_digest" \ --header "X-CSRF-Token: $csrf" \ --data-binary @/tmp/tiny \ "$base/upload?root=personal&path=streaming-large.bin&operation_id=70000000-0000-4000-8000-000000000007" )" test "$status" = 200 grep -F '"result":"succeeded"' \ "/tmp/prior-desired-$attempt.json" done status="$( curl --silent --show-error \ --output /tmp/size-mismatch.json \ --write-out '%{http_code}' \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header 'X-Declared-Size: 2' \ --header "X-Content-SHA256: $tiny_digest" \ --header "X-CSRF-Token: $csrf" \ --data-binary @/tmp/tiny \ "$base/upload?root=personal&path=size-mismatch.bin&operation_id=70000000-0000-4000-8000-000000000002" )" printf 'Size mismatch status=%s body=' "$status" cat /tmp/size-mismatch.json printf '\n' test "$status" = 400 grep -F '"error":"size_mismatch"' /tmp/size-mismatch.json status="$( curl --silent --show-error \ --output /tmp/identity-mismatch.json \ --write-out '%{http_code}' \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header 'X-Declared-Size: 1' \ --header "X-Content-SHA256: $(printf '%064d' 0)" \ --header "X-CSRF-Token: $csrf" \ --data-binary @/tmp/tiny \ "$base/upload?root=personal&path=identity-mismatch.bin&operation_id=70000000-0000-4000-8000-000000000003" )" printf 'Identity mismatch status=%s body=' "$status" cat /tmp/identity-mismatch.json printf '\n' test "$status" = 400 grep -F '"error":"content_identity_mismatch"' \ /tmp/identity-mismatch.json status="$( set +o pipefail dd if=/dev/zero bs=1048576 count=160 status=none | curl --silent --show-error \ --max-time 5 \ --limit-rate 64k \ --output /tmp/too-large.json \ --write-out '%{http_code}' \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header "X-Declared-Size: $NUTHATCH_STREAMING_OVER_LIMIT_BYTES" \ --header "X-Content-SHA256: $tiny_digest" \ --header "X-CSRF-Token: $csrf" \ --request POST \ --upload-file - \ "$base/upload?root=personal&path=too-large.bin&operation_id=70000000-0000-4000-8000-000000000004" )" printf 'Over-limit status=%s body=' "$status" cat /tmp/too-large.json printf '\n' test "$status" = 413 grep -F '"error":"object_too_large"' /tmp/too-large.json if dd if=/dev/zero bs=1048576 \ count="$((large_bytes / 1048576))" status=none | curl --silent --show-error \ --max-time 1 \ --limit-rate 64k \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header "X-Declared-Size: $large_bytes" \ --header "X-Content-SHA256: $large_digest" \ --header "X-CSRF-Token: $csrf" \ --request POST \ --upload-file - \ "$base/upload?root=personal&path=aborted-upload.bin&operation_id=70000000-0000-4000-8000-000000000005" \ >/tmp/aborted-upload.json; then exit 1 fi aborted_upload_status="$( curl --silent --output /dev/null \ --write-out '%{http_code}' \ --cookie "$cookies" \ "$base/download?root=personal&path=aborted-upload.bin" )" printf 'Aborted upload lookup status=%s\n' \ "$aborted_upload_status" test "$aborted_upload_status" = 404 for retry in $(seq 1 20); do status="$( curl --silent --show-error \ --output /tmp/aborted-upload-replay.json \ --write-out '%{http_code}' \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header "X-Declared-Size: $large_bytes" \ --header "X-Content-SHA256: $large_digest" \ --header "X-CSRF-Token: $csrf" \ --data-binary @/tmp/tiny \ "$base/upload?root=personal&path=aborted-upload.bin&operation_id=70000000-0000-4000-8000-000000000005" )" test "$status" = 200 if grep -F '"result":"failed"' \ /tmp/aborted-upload-replay.json >/dev/null; then break fi sleep 0.25 done grep -F '"result":"failed"' /tmp/aborted-upload-replay.json if dd if=/dev/zero bs=1048576 \ count="$((large_bytes / 1048576))" status=none | curl --silent --show-error \ --max-time 1 \ --limit-rate 64k \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header "X-Declared-Size: $large_bytes" \ --header "X-Content-SHA256: $(printf '%064d' 0)" \ --header "X-CSRF-Token: $csrf" \ --request POST \ --upload-file - \ "$base/upload?root=personal&path=streaming-large.bin&operation_id=70000000-0000-4000-8000-000000000006" \ >/tmp/aborted-overwrite.json; then exit 1 fi preserved_digest="$( curl --fail --silent --show-error \ --cookie "$cookies" \ "$base/download?root=personal&path=streaming-large.bin" | sha256sum | cut -d' ' -f1 )" test "$preserved_digest" = "$large_digest" if curl --silent --show-error \ --max-time 1 \ --limit-rate 64k \ --cookie "$cookies" \ "$base/download?root=personal&path=streaming-large.bin" \ >/tmp/aborted-download; then exit 1 fi aborted_bytes="$(wc -c /tmp/tiny test "$( curl --silent --output /tmp/prestart-upload.json \ --write-out '%{http_code}' \ --cookie /tmp/cookies-b \ --header 'Content-Type: application/octet-stream' \ --header 'X-Declared-Size: 1' \ --header "X-Content-SHA256: $upload_digest" \ --header "X-CSRF-Token: $csrf_b" \ --data-binary @/tmp/tiny \ "$base/upload?root=personal&path=prestart.bin&operation_id=71000000-0000-4000-8000-000000000002" )" = 429 curl --silent --show-error --cookie "$cookies" \ "$base/download?root=personal&path=streaming-large.bin" \ >/tmp/parallel-a & first_pid=$! curl --silent --show-error --cookie "$cookies" \ "$base/download?root=personal&path=streaming-large.bin" \ >/tmp/parallel-b & second_pid=$! first_ok=true second_ok=true wait "$first_pid" || first_ok=false wait "$second_pid" || second_ok=false test "$first_ok" = false test "$second_ok" = false parallel_bytes="$( wc -c /tmp/parallel-a /tmp/parallel-b | awk 'END {print $1}' )" printf 'Parallel downloads moved %s bytes with burst %s\n' \ "$parallel_bytes" \ "$NUTHATCH_STREAMING_THROTTLE_BURST_BYTES" test "$parallel_bytes" -gt 0 joint_limit="$( expr "$NUTHATCH_STREAMING_THROTTLE_BURST_BYTES" - 65536 + 4096 )" test "$parallel_bytes" -le "$joint_limit" prestart_status="$( curl --silent --output /tmp/prestart-download.json \ --write-out '%{http_code}' \ --cookie "$cookies" \ "$base/download?root=personal&path=streaming-large.bin" )" printf 'Post-exhaustion download status=%s\n' "$prestart_status" test "$prestart_status" = 429 printf 'Shared incremental throttle probes passed\n' exit 0 fi if [ "${NUTHATCH_STREAMING_MODE:-}" = refill ]; then dd if=/dev/zero of=/tmp/refill-upload bs=65536 \ count=1 status=none refill_digest="$( sha256sum /tmp/refill-upload | cut -d' ' -f1 )" for suffix in initial refilled; do if [ "$suffix" = refilled ]; then sleep 2 refill_operation=72000000-0000-4000-8000-000000000002 else refill_operation=72000000-0000-4000-8000-000000000001 fi status="$( curl --silent --show-error \ --output "/tmp/refill-$suffix.json" \ --write-out '%{http_code}' \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header 'X-Declared-Size: 65536' \ --header "X-Content-SHA256: $refill_digest" \ --header "X-CSRF-Token: $csrf" \ --data-binary @/tmp/refill-upload \ "$base/upload?root=personal&path=refill-$suffix.bin&operation_id=$refill_operation" )" test "$status" = 200 grep -F '"result":"succeeded"' "/tmp/refill-$suffix.json" if [ "$suffix" = initial ]; then test "$( curl --silent --output /tmp/refill-exhausted.json \ --write-out '%{http_code}' \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header 'X-Declared-Size: 65536' \ --header "X-Content-SHA256: $refill_digest" \ --header "X-CSRF-Token: $csrf" \ --data-binary @/tmp/refill-upload \ "$base/upload?root=personal&path=refill-exhausted.bin&operation_id=72000000-0000-4000-8000-000000000003" )" = 429 fi done printf 'Token-bucket exhaustion and refill probes passed\n' exit 0 fi if [ "${NUTHATCH_STREAMING_MODE:-}" = unavailable ]; then printf x >/tmp/tiny tiny_digest="$(sha256sum /tmp/tiny | cut -d' ' -f1)" test "$( curl --silent --output /tmp/unavailable-upload.json \ --write-out '%{http_code}' \ --cookie "$cookies" \ --header 'Content-Type: application/octet-stream' \ --header 'X-Declared-Size: 1' \ --header "X-Content-SHA256: $tiny_digest" \ --header "X-CSRF-Token: $csrf" \ --data-binary @/tmp/tiny \ "$base/upload?root=personal&path=unavailable.bin&operation_id=72000000-0000-4000-8000-000000000001" )" = 503 grep -F '"error":"throttle_unavailable"' \ /tmp/unavailable-upload.json test "$( curl --silent --output /tmp/unavailable-download.json \ --write-out '%{http_code}' \ --cookie "$cookies" \ "$base/download?root=personal&path=streaming-large.bin" )" = 503 grep -F '"error":"throttle_unavailable"' \ /tmp/unavailable-download.json printf 'Unavailable coordination probes passed\n' exit 0 fi exercise_root() { root="$1" suffix="$2" first="first-$root" second="second-$root" printf '%s' "$first" >/tmp/body size="$(wc -c /tmp/download test "$(cat /tmp/download)" = "$first" printf '%s' "$second" >/tmp/body size="$(wc -c /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/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_STREAMING_MODE valueFrom: configMapKeyRef: name: @@PROFILE_NAME@@ key: NUTHATCH_STREAMING_MODE optional: true - name: NUTHATCH_STREAMING_LARGE_BYTES valueFrom: configMapKeyRef: name: @@PROFILE_NAME@@ key: NUTHATCH_STREAMING_LARGE_BYTES optional: true - name: NUTHATCH_STREAMING_OVER_LIMIT_BYTES valueFrom: configMapKeyRef: name: @@PROFILE_NAME@@ key: NUTHATCH_STREAMING_OVER_LIMIT_BYTES optional: true - name: NUTHATCH_STREAMING_THROTTLE_BURST_BYTES valueFrom: configMapKeyRef: name: @@PROFILE_NAME@@ key: NUTHATCH_STREAMING_THROTTLE_BURST_BYTES optional: true - name: NUTHATCH_IDEMPOTENCY_MODE valueFrom: configMapKeyRef: name: @@PROFILE_NAME@@ key: NUTHATCH_IDEMPOTENCY_MODE optional: true - name: NUTHATCH_IDEMPOTENCY_IP_A valueFrom: configMapKeyRef: name: @@PROFILE_NAME@@ key: NUTHATCH_IDEMPOTENCY_IP_A optional: true - name: NUTHATCH_IDEMPOTENCY_IP_B valueFrom: configMapKeyRef: name: @@PROFILE_NAME@@ key: NUTHATCH_IDEMPOTENCY_IP_B optional: true - 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}