nuthatch/tests/kind/product-matrix-job.yaml.in
ms2k 15eb0038d2 Replace planning-era names and squash initial schema
Use descriptive test and executable names so implementation surfaces do not depend on journal organization. Consolidate the unreleased migration history into the authoritative initial schema because no deployed database requires an upgrade path.
2026-07-19 15:21:24 +09:00

1615 lines
77 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)" = "/app"
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
if [ "${NUTHATCH_RUNTIME_MODE:-}" = coherence ]; then
csrf_b="$(login "$NUTHATCH_OIDC_USER_B" b)"
test -n "$csrf_b"
for _ in 1 2 3 4; do
curl --fail --silent --show-error \
--cookie /tmp/cookies-b \
--header 'Accept: application/json' \
"$base/api/browse?root=shared" >/tmp/shared-warm.json
curl --fail --silent --show-error \
--cookie /tmp/cookies-b \
--header 'Accept: application/json' \
"$base/api/browse?root=shared&prefix=runtime" \
>/tmp/shared-prefix-warm.json
done
printf runtime-coherence >/tmp/runtime-body
runtime_size="$(wc -c </tmp/runtime-body | tr -d ' ')"
runtime_digest="$(
sha256sum /tmp/runtime-body | cut -d' ' -f1
)"
curl --fail --silent --show-error \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $runtime_size" \
--header "X-Content-SHA256: $runtime_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/runtime-body \
"$base/api/upload?root=shared&path=runtime%2Fcoherence.txt&operation_id=74000000-0000-4000-8000-000000000001" \
| grep -F '"result":"succeeded"'
curl --fail --silent --show-error \
--cookie /tmp/cookies-b \
--header 'Accept: application/json' \
"$base/api/browse?root=shared" |
grep -F 'runtime'
curl --fail --silent --show-error \
--cookie /tmp/cookies-b \
--header 'Accept: application/json' \
"$base/api/browse?root=shared&prefix=runtime" |
grep -F 'coherence.txt'
curl --fail --silent --show-error \
--cookie "$cookies" \
--header 'Content-Type: application/json' \
--data "{\"root\":\"shared\",\"path\":\"runtime/coherence.txt\",\"operation_id\":\"74000000-0000-4000-8000-000000000002\",\"csrf\":\"$csrf\"}" \
"$base/api/delete" |
grep -F '"result":"succeeded"'
if curl --fail --silent --show-error \
--cookie /tmp/cookies-b \
--header 'Accept: application/json' \
"$base/api/browse?root=shared&prefix=runtime" |
grep -F 'coherence.txt'; then
exit 1
fi
sleep 2
curl --fail --silent --show-error \
--cookie /tmp/cookies-b \
--header 'Accept: application/json' \
"$base/api/browse?root=shared" >/dev/null
printf 'Cross-user cache coherence and TTL probes passed\n'
exit 0
fi
if [ "${NUTHATCH_RUNTIME_MODE:-}" = database-unavailable ]; then
printf 'Database fault checkpoint\n'
sleep 10
printf x >/tmp/database-body
database_digest="$(
sha256sum /tmp/database-body | cut -d' ' -f1
)"
test "$(
curl --silent --output /tmp/database-upload.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header 'X-Declared-Size: 1' \
--header "X-Content-SHA256: $database_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/database-body \
"$base/api/upload?root=personal&path=database-unavailable.txt&operation_id=75000000-0000-4000-8000-000000000001"
)" = 503
grep -F '"error":"mutation_rejected"' \
/tmp/database-upload.json
test "$(
curl --silent --output /tmp/database-delete.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/json' \
--data "{\"root\":\"personal\",\"path\":\"cross-user.txt\",\"operation_id\":\"75000000-0000-4000-8000-000000000002\",\"csrf\":\"$csrf\"}" \
"$base/api/delete"
)" = 503
grep -F '"error":"mutation_rejected"' \
/tmp/database-delete.json
printf 'Database-unavailable mutation probes passed\n'
exit 0
fi
if [ "${NUTHATCH_PRODUCT_MODE:-}" = timeout ]; then
curl --insecure --fail --silent --show-error \
--get \
--cookie "$cookies" \
--data-urlencode root=personal \
--data-urlencode q=timeout-probe \
"$base/api/browse" >/tmp/product-timeout.json
grep -F '"partial":true' /tmp/product-timeout.json
printf 'Timed search bound probe passed\n'
exit 0
fi
if [ "${NUTHATCH_PRODUCT_MODE:-}" = verify ]; then
product_upload() {
product_path="$1"
product_operation="$2"
product_body="$3"
printf '%s' "$product_body" >/tmp/product-body
product_size="$(wc -c </tmp/product-body | tr -d ' ')"
product_digest="$(
sha256sum /tmp/product-body | cut -d' ' -f1
)"
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $product_size" \
--header "X-Content-SHA256: $product_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/product-body \
"$base/api/upload?root=personal&path=$product_path&operation_id=$product_operation"
}
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
"$base/api/session" >/tmp/product-session.json
grep -F '"user_id":' /tmp/product-session.json
grep -F "\"csrf\":\"$csrf\"" /tmp/product-session.json
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
--dump-header /tmp/product-app.headers \
"$base/app" >/tmp/product-app.html
grep -i '^content-security-policy:' /tmp/product-app.headers |
grep -F "default-src 'none'" |
grep -F "script-src 'self'" |
grep -F "frame-ancestors 'none'" >/dev/null
asset="$(
sed -n 's#.*src="/assets/\([^"]*\.js\)".*#\1#p' \
/tmp/product-app.html
)"
test -n "$asset"
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
--dump-header /tmp/product-asset.headers \
"$base/assets/$asset" >/tmp/product-asset.js
grep -i '^content-type:.*javascript' \
/tmp/product-asset.headers >/dev/null
grep -i '^x-content-type-options: *nosniff' \
/tmp/product-asset.headers >/dev/null
for marker in \
'Drop a file here' \
'Shared root' \
'Retry same upload' \
'Retry same delete' \
'nuthatch-recursive-delete' \
'URLSearchParams'; do
grep -F "$marker" /tmp/product-asset.js >/dev/null
done
test "$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
"$base/app"
)" = 303
test "$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
"$base/assets/$asset"
)" = 303
product_upload product-browse/Alpha.txt \
81000000-0000-4000-8000-000000000001 alpha \
>/tmp/product-upload.json
grep -F '"result":"succeeded"' /tmp/product-upload.json
product_upload product-browse/Alpha.txt \
81000000-0000-4000-8000-000000000001 alpha \
>/tmp/product-upload-replay.json
grep -F '"result":"succeeded"' \
/tmp/product-upload-replay.json
product_upload product-browse/beta.txt \
81000000-0000-4000-8000-000000000002 beta >/dev/null
product_upload product-browse/gamma.txt \
81000000-0000-4000-8000-000000000003 gamma >/dev/null
curl --insecure --fail --silent --show-error \
--get \
--cookie "$cookies" \
--data-urlencode root=personal \
--data-urlencode prefix=product-browse \
"$base/api/browse" >/tmp/product-page-one.json
grep -F '"path":"product-browse/Alpha.txt"' \
/tmp/product-page-one.json
grep -F '"path":"product-browse/beta.txt"' \
/tmp/product-page-one.json
if grep -F '"path":"product-browse/gamma.txt"' \
/tmp/product-page-one.json; then
exit 1
fi
continuation="$(
sed -n \
's/.*"continuation":"\([^"]*\)".*/\1/p' \
/tmp/product-page-one.json
)"
test -n "$continuation"
curl --insecure --fail --silent --show-error \
--get \
--cookie "$cookies" \
--data-urlencode root=personal \
--data-urlencode prefix=product-browse \
--data-urlencode "continuation=$continuation" \
"$base/api/browse" >/tmp/product-page-two.json
grep -F '"path":"product-browse/gamma.txt"' \
/tmp/product-page-two.json
product_upload 000-Needle-A.txt \
81000000-0000-4000-8000-000000000004 one >/dev/null
product_upload 001-needle-b.txt \
81000000-0000-4000-8000-000000000005 two >/dev/null
product_upload 002-filler.txt \
81000000-0000-4000-8000-000000000006 three >/dev/null
product_upload 003-needle-after-bound.txt \
81000000-0000-4000-8000-000000000007 four >/dev/null
curl --insecure --fail --silent --show-error \
--get \
--cookie "$cookies" \
--data-urlencode root=personal \
--data-urlencode q=NEEDLE \
"$base/api/browse" >/tmp/product-search.json
grep -F '"path":"000-Needle-A.txt"' /tmp/product-search.json
grep -F '"path":"001-needle-b.txt"' /tmp/product-search.json
grep -F '"partial":true' /tmp/product-search.json
if grep -F '003-needle-after-bound.txt' \
/tmp/product-search.json; then
exit 1
fi
printf 'Product asset, pagination, and search probes passed\n'
for invalid_url in \
"$base/api/browse?root=Personal" \
"$base/api/browse?root=personal&prefix=a//b" \
"$base/api/download?root=personal&path=./file"; do
invalid_status="$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
--cookie "$cookies" \
"$invalid_url"
)"
printf 'Canonical rejection status=%s url=%s\n' \
"$invalid_status" "$invalid_url"
test "$invalid_status" = 400
done
decomposed_path="$(printf 'cafe\314\201.txt')"
decomposed_status="$(
curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' \
--get \
--cookie "$cookies" \
--data-urlencode root=personal \
--data-urlencode "path=$decomposed_path" \
"$base/api/download"
)"
printf 'NFC rejection status=%s\n' "$decomposed_status"
test "$decomposed_status" = 400
product_upload encoded%252Fname.txt \
81000000-0000-4000-8000-000000000008 encoded \
>/dev/null
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
"$base/api/download?root=personal&path=encoded%252Fname.txt" \
>/tmp/product-encoded
test "$(cat /tmp/product-encoded)" = encoded
product_upload caf%C3%A9.txt \
81000000-0000-4000-8000-000000000014 unicode \
>/dev/null
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
--dump-header /tmp/product-unicode.headers \
"$base/api/download?root=personal&path=caf%C3%A9.txt" \
>/tmp/product-unicode
test "$(cat /tmp/product-unicode)" = unicode
grep -i '^content-disposition:' /tmp/product-unicode.headers |
grep -F "filename*=UTF-8''caf%C3%A9.txt" >/dev/null
printf 'Product canonical path probes passed\n'
product_upload active.svg \
81000000-0000-4000-8000-000000000009 \
'<svg onload="alert(1)"></svg>' >/dev/null
test "$(
curl --insecure --silent --output /tmp/product-range.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Range: bytes=0-1' \
"$base/api/download?root=personal&path=active.svg"
)" = 416
grep -F '"error":"range_not_supported"' \
/tmp/product-range.json
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
--dump-header /tmp/product-download.headers \
"$base/api/download?root=personal&path=active.svg" \
>/tmp/product-download.svg
grep -i '^content-disposition: *attachment;' \
/tmp/product-download.headers |
grep -F "filename*=UTF-8''active.svg" >/dev/null
grep -i '^x-content-type-options: *nosniff' \
/tmp/product-download.headers >/dev/null
grep -i '^content-security-policy: *sandbox' \
/tmp/product-download.headers >/dev/null
grep -F '<svg onload="alert(1)"></svg>' \
/tmp/product-download.svg
printf 'Product safe download probes passed\n'
product_upload delete-tree/a.txt \
81000000-0000-4000-8000-000000000010 a >/dev/null
product_upload delete-tree/b.txt \
81000000-0000-4000-8000-000000000011 b >/dev/null
product_upload delete-tree/c.txt \
81000000-0000-4000-8000-000000000012 c >/dev/null
curl --insecure --fail --silent --show-error \
--get \
--cookie "$cookies" \
--data-urlencode root=personal \
--data-urlencode path=delete-tree \
"$base/api/delete-plan" >/tmp/product-plan-one.json
grep -F '"paths":["delete-tree/a.txt","delete-tree/b.txt"]' \
/tmp/product-plan-one.json
grep -F '"continuation":' /tmp/product-plan-one.json |
grep -v '"continuation":null' >/dev/null
delete_batch_one="$(
printf \
'{"root":"personal","csrf":"%s","items":[{"path":"delete-tree/a.txt","operation_id":"82000000-0000-4000-8000-000000000001"},{"path":"delete-tree/b.txt","operation_id":"82000000-0000-4000-8000-000000000002"}]}' \
"$csrf"
)"
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
--header 'Content-Type: application/json' \
--data "$delete_batch_one" \
"$base/api/delete-batch" >/tmp/product-batch-one.json
grep -F '"operation_id":"82000000-0000-4000-8000-000000000001"' \
/tmp/product-batch-one.json
test "$(
grep -o '"result":"succeeded"' /tmp/product-batch-one.json |
wc -l |
tr -d ' '
)" = 2
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
--header 'Content-Type: application/json' \
--data "$delete_batch_one" \
"$base/api/delete-batch" >/tmp/product-batch-replay.json
test "$(
grep -o '"result":"succeeded"' \
/tmp/product-batch-replay.json |
wc -l |
tr -d ' '
)" = 2
product_upload delete-tree/d-concurrent.txt \
81000000-0000-4000-8000-000000000013 d >/dev/null
curl --insecure --fail --silent --show-error \
--get \
--cookie "$cookies" \
--data-urlencode root=personal \
--data-urlencode path=delete-tree \
"$base/api/delete-plan" >/tmp/product-plan-restart.json
grep -F '"paths":["delete-tree/c.txt","delete-tree/d-concurrent.txt"]' \
/tmp/product-plan-restart.json
delete_batch_two="$(
printf \
'{"root":"personal","csrf":"%s","items":[{"path":"delete-tree/c.txt","operation_id":"82000000-0000-4000-8000-000000000003"},{"path":"delete-tree/d-concurrent.txt","operation_id":"82000000-0000-4000-8000-000000000004"}]}' \
"$csrf"
)"
curl --insecure --fail --silent --show-error \
--cookie "$cookies" \
--header 'Content-Type: application/json' \
--data "$delete_batch_two" \
"$base/api/delete-batch" >/tmp/product-batch-two.json
test "$(
grep -o '"result":"succeeded"' /tmp/product-batch-two.json |
wc -l |
tr -d ' '
)" = 2
curl --insecure --fail --silent --show-error \
--get \
--cookie "$cookies" \
--data-urlencode root=personal \
--data-urlencode path=delete-tree \
"$base/api/delete-plan" >/tmp/product-plan-empty.json
grep -F '"paths":[]' /tmp/product-plan-empty.json
grep -F '"continuation":null' /tmp/product-plan-empty.json
printf 'Product behavior probes passed\n'
exit 0
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-one | tr -d ' ')"
first_digest="$(sha256sum /tmp/epoch-one | cut -d' ' -f1)"
operation=73000000-0000-4000-8000-000000000001
mutation_url="$base/upload?root=personal&path=idempotency.txt&operation_id=$operation"
pod_curl "$NUTHATCH_IDEMPOTENCY_IP_A" \
--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-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/conflict | tr -d ' ')"
conflict_digest="$(sha256sum /tmp/conflict | cut -d' ' -f1)"
pod_curl "$NUTHATCH_IDEMPOTENCY_IP_B" \
--fail --silent --show-error \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $conflict_size" \
--header "X-Content-SHA256: $conflict_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/conflict \
"$mutation_url" >/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 | tr -d ' ')"
second_b_size="$(wc -c </tmp/epoch-two-b | tr -d ' ')"
second_a_digest="$(
sha256sum /tmp/epoch-two-a | cut -d' ' -f1
)"
second_b_digest="$(
sha256sum /tmp/epoch-two-b | cut -d' ' -f1
)"
pod_curl "$NUTHATCH_IDEMPOTENCY_IP_A" \
--fail --silent --show-error \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header "X-Declared-Size: $second_a_size" \
--header "X-Content-SHA256: $second_a_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/epoch-two-a \
"$mutation_url" >/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"
)"
printf 'Active key lock status=%s body=' "$lock_status"
cat /tmp/active-lock.json
printf '\n'
test "$lock_status" = 200
grep -F '"result":"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
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"
)"
printf 'Aborted upload replay status=%s body=' "$status"
cat /tmp/aborted-upload-replay.json
printf '\n'
test "$status" = 200
grep -E '"result":"(pending|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/aborted-download | tr -d ' ')"
test "$aborted_bytes" -gt 0
test "$aborted_bytes" -lt "$large_bytes"
curl --fail --silent --show-error "$base/readyz" |
grep -F '"ready":true'
printf 'Large streaming and abort probes passed\n'
exit 0
fi
if [ "${NUTHATCH_STREAMING_MODE:-}" = throttle ]; then
csrf_b="$(login "$NUTHATCH_OIDC_USER_B" b)"
dd if=/dev/zero of=/tmp/joint-upload bs=65536 \
count=1 status=none
joint_digest="$(
sha256sum /tmp/joint-upload | cut -d' ' -f1
)"
status="$(
curl --silent --show-error \
--output /tmp/joint-upload.json \
--write-out '%{http_code}' \
--cookie "$cookies" \
--header 'Content-Type: application/octet-stream' \
--header 'X-Declared-Size: 65536' \
--header "X-Content-SHA256: $joint_digest" \
--header "X-CSRF-Token: $csrf" \
--data-binary @/tmp/joint-upload \
"$base/upload?root=personal&path=joint-throttle.bin&operation_id=70000000-0000-4000-8000-000000000013"
)"
test "$status" = 200
grep -F '"result":"succeeded"' /tmp/joint-upload.json
dd if=/dev/zero of=/tmp/throttle-upload bs=1048576 \
count=1 status=none
upload_digest="$(
sha256sum /tmp/throttle-upload | cut -d' ' -f1
)"
status="$(
curl --silent --show-error \
--output /tmp/throttle-upload.json \
--write-out '%{http_code}' \
--cookie /tmp/cookies-b \
--header 'Content-Type: application/octet-stream' \
--header 'X-Declared-Size: 1048576' \
--header "X-Content-SHA256: $upload_digest" \
--header "X-CSRF-Token: $csrf_b" \
--data-binary @/tmp/throttle-upload \
"$base/upload?root=personal&path=throttled-upload.bin&operation_id=71000000-0000-4000-8000-000000000001"
)"
test "$status" = 429
grep -F '"error":"bandwidth_exceeded"' /tmp/throttle-upload.json
printf x >/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
browse_body="$(
curl --fail --silent --show-error \
--cookie "$cookies" \
--header 'Accept: application/json' \
"$base/api/browse?root=personal"
)"
printf '%s\n' "$browse_body" | grep -F '"path":'
if printf '%s\n' "$browse_body" |
grep -F 'cross-user.txt' >/dev/null; then
unavailable_path=cross-user.txt
unavailable_query=CROSS-USER
else
unavailable_path=streaming-large.bin
unavailable_query=STREAMING-LARGE
fi
curl --fail --silent --show-error \
--cookie "$cookies" \
--header 'Accept: application/json' \
"$base/api/browse?root=personal&q=$unavailable_query" |
grep -F "$unavailable_path"
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=$unavailable_path"
)" = 503
grep -F '"error":"throttle_unavailable"' \
/tmp/unavailable-download.json
printf 'Redis-unavailable product 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/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: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' \
--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_PRODUCT_MODE
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_PRODUCT_MODE
optional: true
- name: NUTHATCH_RUNTIME_MODE
valueFrom:
configMapKeyRef:
name: @@PROFILE_NAME@@
key: NUTHATCH_RUNTIME_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}