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

1605 lines
76 KiB
YAML
Raw Normal View History

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"
)"
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/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
curl --fail --silent --show-error \
--cookie "$cookies" \
--header 'Accept: application/json' \
"$base/api/browse?root=personal" |
grep -F 'cross-user.txt'
curl --fail --silent --show-error \
--cookie "$cookies" \
--header 'Accept: application/json' \
"$base/api/browse?root=personal&q=CROSS-USER" |
grep -F 'cross-user.txt'
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=cross-user.txt"
)" = 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}