2026-07-18 16:16:24 +09:00
|
|
|
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
|
2026-07-18 18:08:41 +09:00
|
|
|
dnsPolicy: None
|
|
|
|
|
dnsConfig:
|
|
|
|
|
nameservers: [10.96.0.53]
|
|
|
|
|
searches:
|
|
|
|
|
- nuthatch-testbed.svc.cluster.local
|
|
|
|
|
- svc.cluster.local
|
|
|
|
|
- cluster.local
|
|
|
|
|
options:
|
|
|
|
|
- {name: ndots, value: "5"}
|
2026-07-18 16:16:24 +09:00
|
|
|
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:
|
|
|
|
|
- |
|
2026-07-18 18:08:41 +09:00
|
|
|
base="https://nuthatch-@@LANE@@:8080"
|
|
|
|
|
|
|
|
|
|
curl() {
|
|
|
|
|
command curl --insecure "$@"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
header_value() {
|
|
|
|
|
awk -v expected="$2" '
|
|
|
|
|
BEGIN { IGNORECASE=1 }
|
|
|
|
|
$1 == expected ":" {
|
|
|
|
|
sub(/^[^:]*:[[:space:]]*/, "")
|
|
|
|
|
sub(/\r$/, "")
|
|
|
|
|
print
|
|
|
|
|
exit
|
|
|
|
|
}
|
|
|
|
|
' "$1"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
json_field() {
|
|
|
|
|
tr -d '\n' < "$1" |
|
|
|
|
|
sed -n 's/.*"'"$2"'"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
oidc_callback_for() {
|
|
|
|
|
user="$1"
|
|
|
|
|
suffix="$2"
|
|
|
|
|
authorization_url="$3"
|
|
|
|
|
printf 'Completing %s authorization %s\n' \
|
|
|
|
|
"$NUTHATCH_OIDC_PROVIDER" "$suffix" >&2
|
|
|
|
|
case "$NUTHATCH_OIDC_PROVIDER" in
|
|
|
|
|
keycloak)
|
|
|
|
|
curl --fail --silent --show-error \
|
|
|
|
|
--cookie-jar "/tmp/idp-$suffix" \
|
|
|
|
|
--dump-header "/tmp/idp-start-$suffix.headers" \
|
|
|
|
|
"$authorization_url" >"/tmp/idp-$suffix.html"
|
|
|
|
|
action="$(
|
|
|
|
|
sed -n 's/.*action="\([^"]*\)".*/\1/p' "/tmp/idp-$suffix.html" |
|
|
|
|
|
head -n 1 |
|
|
|
|
|
sed 's/&/\&/g'
|
|
|
|
|
)"
|
|
|
|
|
test -n "$action"
|
|
|
|
|
curl --silent --show-error \
|
|
|
|
|
--cookie "/tmp/idp-$suffix" \
|
|
|
|
|
--cookie-jar "/tmp/idp-$suffix" \
|
|
|
|
|
--dump-header "/tmp/idp-finish-$suffix.headers" \
|
|
|
|
|
--data-urlencode "username=$user" \
|
|
|
|
|
--data-urlencode "password=$NUTHATCH_OIDC_USER_PASSWORD" \
|
|
|
|
|
--data-urlencode credentialId= \
|
|
|
|
|
"$action" >"/tmp/idp-finish-$suffix.body"
|
|
|
|
|
header_value "/tmp/idp-finish-$suffix.headers" Location
|
|
|
|
|
;;
|
|
|
|
|
zitadel)
|
|
|
|
|
curl --silent --show-error \
|
|
|
|
|
--dump-header "/tmp/idp-start-$suffix.headers" \
|
|
|
|
|
"$authorization_url" >"/tmp/idp-start-$suffix.body"
|
|
|
|
|
login_location="$(
|
|
|
|
|
header_value "/tmp/idp-start-$suffix.headers" Location
|
|
|
|
|
)"
|
|
|
|
|
auth_request="${login_location##*authRequest=}"
|
|
|
|
|
auth_request="${auth_request%%&*}"
|
|
|
|
|
test -n "$auth_request"
|
|
|
|
|
curl --fail --silent --show-error \
|
|
|
|
|
--header "Authorization: Bearer $NUTHATCH_OIDC_LOGIN_PAT" \
|
|
|
|
|
--header 'Content-Type: application/json' \
|
|
|
|
|
--data "{\"checks\":{\"user\":{\"userId\":\"$user\"},\"password\":{\"password\":\"$NUTHATCH_OIDC_USER_PASSWORD\"}},\"lifetime\":\"300s\"}" \
|
|
|
|
|
"$NUTHATCH_OIDC_ISSUER/v2/sessions" \
|
|
|
|
|
>"/tmp/session-$suffix.json"
|
|
|
|
|
session_id="$(json_field "/tmp/session-$suffix.json" sessionId)"
|
|
|
|
|
session_token="$(json_field "/tmp/session-$suffix.json" sessionToken)"
|
|
|
|
|
test -n "$session_id"
|
|
|
|
|
test -n "$session_token"
|
|
|
|
|
curl --fail --silent --show-error \
|
|
|
|
|
--header "Authorization: Bearer $NUTHATCH_OIDC_LOGIN_PAT" \
|
|
|
|
|
--header 'Content-Type: application/json' \
|
|
|
|
|
--data "{\"session\":{\"sessionId\":\"$session_id\",\"sessionToken\":\"$session_token\"}}" \
|
|
|
|
|
"$NUTHATCH_OIDC_ISSUER/v2/oidc/auth_requests/$auth_request" \
|
|
|
|
|
>"/tmp/finalize-$suffix.json"
|
|
|
|
|
json_field "/tmp/finalize-$suffix.json" callbackUrl |
|
|
|
|
|
sed 's/\\u0026/\&/g; s#\\/#/#g'
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
exit 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
login() {
|
|
|
|
|
user="$1"
|
|
|
|
|
suffix="$2"
|
|
|
|
|
cookies="/tmp/cookies-$suffix"
|
|
|
|
|
printf 'Starting login %s\n' "$suffix" >&2
|
|
|
|
|
curl --insecure --silent --show-error \
|
|
|
|
|
--cookie-jar "$cookies" \
|
|
|
|
|
--dump-header "/tmp/login-$suffix.headers" \
|
|
|
|
|
"$base/login" >"/tmp/login-$suffix.body"
|
|
|
|
|
test "$(header_value "/tmp/login-$suffix.headers" Status 2>/dev/null || true)" = ""
|
|
|
|
|
authorization_url="$(
|
|
|
|
|
header_value "/tmp/login-$suffix.headers" Location
|
|
|
|
|
)"
|
|
|
|
|
test -n "$authorization_url"
|
|
|
|
|
grep -i '^set-cookie: nuthatch_oidc=' "/tmp/login-$suffix.headers" |
|
|
|
|
|
grep -i 'secure' |
|
|
|
|
|
grep -i 'httponly' |
|
|
|
|
|
grep -i 'samesite=lax' |
|
|
|
|
|
grep -i 'path=/oidc/callback' >/dev/null
|
|
|
|
|
state="$(
|
|
|
|
|
printf '%s' "$authorization_url" |
|
|
|
|
|
sed -n 's/.*[?&]state=\([^&]*\).*/\1/p'
|
|
|
|
|
)"
|
|
|
|
|
test -n "$state"
|
|
|
|
|
if [ "$suffix" = a ]; then
|
|
|
|
|
wrong_redirect_url="$(
|
|
|
|
|
printf '%s' "$authorization_url" |
|
|
|
|
|
sed 's#redirect_uri=[^&]*#redirect_uri=https%3A%2F%2Finvalid.example%2Fcallback#'
|
|
|
|
|
)"
|
|
|
|
|
wrong_redirect_status="$(
|
|
|
|
|
curl --silent --output /dev/null \
|
|
|
|
|
--write-out '%{http_code}' \
|
|
|
|
|
"$wrong_redirect_url"
|
|
|
|
|
)"
|
|
|
|
|
test "$wrong_redirect_status" -ge 400
|
|
|
|
|
printf 'Redirect URI rejection passed\n' >&2
|
|
|
|
|
fi
|
|
|
|
|
if curl --insecure --fail --silent \
|
|
|
|
|
--cookie "$cookies" \
|
|
|
|
|
"$base/oidc/callback?code=unused&state=invalid-$state" >/dev/null; then
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
if [ "$suffix" = a ]; then
|
|
|
|
|
transaction="$(
|
|
|
|
|
awk -F '\t' '$6 == "nuthatch_oidc" { print $7 }' "$cookies"
|
|
|
|
|
)"
|
|
|
|
|
test "${#transaction}" -gt 1
|
|
|
|
|
tampered_transaction="x${transaction#?}"
|
|
|
|
|
test "$(
|
|
|
|
|
curl --insecure --silent --output /dev/null \
|
|
|
|
|
--write-out '%{http_code}' \
|
|
|
|
|
--header 'Cookie: nuthatch_oidc=malformed' \
|
|
|
|
|
"$base/oidc/callback?code=unused&state=$state"
|
|
|
|
|
)" = 400
|
|
|
|
|
test "$(
|
|
|
|
|
curl --insecure --silent --output /dev/null \
|
|
|
|
|
--write-out '%{http_code}' \
|
|
|
|
|
--header "Cookie: nuthatch_oidc=$tampered_transaction" \
|
|
|
|
|
"$base/oidc/callback?code=unused&state=$state"
|
|
|
|
|
)" = 400
|
|
|
|
|
fi
|
|
|
|
|
callback="$(
|
|
|
|
|
oidc_callback_for "$user" "$suffix" "$authorization_url"
|
|
|
|
|
)"
|
|
|
|
|
test -n "$callback"
|
|
|
|
|
printf 'Authorization callback received %s\n' "$suffix" >&2
|
|
|
|
|
callback_status="$(
|
|
|
|
|
curl --insecure --silent --show-error \
|
|
|
|
|
--output "/tmp/callback-$suffix.body" \
|
|
|
|
|
--write-out '%{http_code}' \
|
|
|
|
|
--cookie "$cookies" \
|
|
|
|
|
--cookie-jar "$cookies" \
|
|
|
|
|
--dump-header "/tmp/callback-$suffix.headers" \
|
|
|
|
|
"$callback"
|
|
|
|
|
)"
|
|
|
|
|
printf 'Application callback status=%s %s\n' \
|
|
|
|
|
"$callback_status" "$suffix" >&2
|
|
|
|
|
if [ "$callback_status" != 303 ]; then
|
|
|
|
|
sed -n '1,8p' "/tmp/callback-$suffix.body" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
2026-07-19 01:12:29 +09:00
|
|
|
test "$(header_value "/tmp/callback-$suffix.headers" Location)" = "/app"
|
2026-07-18 18:08:41 +09:00
|
|
|
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)"
|
2026-07-18 16:16:24 +09:00
|
|
|
test -n "$csrf"
|
2026-07-18 18:08:41 +09:00
|
|
|
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
|
2026-07-18 16:16:24 +09:00
|
|
|
|
2026-07-19 03:41:01 +09:00
|
|
|
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
|
|
|
|
|
|
2026-07-19 01:12:29 +09:00
|
|
|
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
|
|
|
|
|
|
2026-07-18 21:25:50 +09:00
|
|
|
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
|
|
|
|
|
|
2026-07-18 20:48:26 +09:00
|
|
|
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
|
2026-07-19 04:21:27 +09:00
|
|
|
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
|
2026-07-18 20:48:26 +09:00
|
|
|
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
|
2026-07-19 04:21:27 +09:00
|
|
|
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
|
2026-07-19 03:41:01 +09:00
|
|
|
curl --fail --silent --show-error \
|
|
|
|
|
--cookie "$cookies" \
|
|
|
|
|
--header 'Accept: application/json' \
|
2026-07-19 04:21:27 +09:00
|
|
|
"$base/api/browse?root=personal&q=$unavailable_query" |
|
|
|
|
|
grep -F "$unavailable_path"
|
2026-07-18 20:48:26 +09:00
|
|
|
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" \
|
2026-07-19 04:21:27 +09:00
|
|
|
"$base/download?root=personal&path=$unavailable_path"
|
2026-07-18 20:48:26 +09:00
|
|
|
)" = 503
|
|
|
|
|
grep -F '"error":"throttle_unavailable"' \
|
|
|
|
|
/tmp/unavailable-download.json
|
2026-07-19 03:41:01 +09:00
|
|
|
printf 'Redis-unavailable product probes passed\n'
|
2026-07-18 20:48:26 +09:00
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
2026-07-18 16:16:24 +09:00
|
|
|
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)"
|
2026-07-18 18:08:41 +09:00
|
|
|
upload_status="$(
|
|
|
|
|
curl --silent --show-error \
|
|
|
|
|
--output /tmp/upload.json \
|
|
|
|
|
--write-out '%{http_code}' \
|
|
|
|
|
--cookie "$cookies" \
|
2026-07-18 16:16:24 +09:00
|
|
|
--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 \
|
2026-07-18 18:08:41 +09:00
|
|
|
"$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
|
2026-07-18 16:16:24 +09:00
|
|
|
curl --fail --silent --show-error \
|
2026-07-18 18:08:41 +09:00
|
|
|
--cookie "$cookies" \
|
2026-07-18 16:16:24 +09:00
|
|
|
--header 'Accept: application/json' \
|
|
|
|
|
"$base/?root=$root" |
|
|
|
|
|
grep -F "smoke-$suffix.txt"
|
|
|
|
|
curl --fail --silent --show-error \
|
2026-07-18 18:08:41 +09:00
|
|
|
--cookie "$cookies" \
|
2026-07-18 16:16:24 +09:00
|
|
|
"$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 \
|
2026-07-18 18:08:41 +09:00
|
|
|
--cookie "$cookies" \
|
2026-07-18 16:16:24 +09:00
|
|
|
--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 \
|
2026-07-18 18:08:41 +09:00
|
|
|
--cookie "$cookies" \
|
2026-07-18 16:16:24 +09:00
|
|
|
"$base/download?root=$root&path=smoke-$suffix.txt" >/tmp/download
|
|
|
|
|
test "$(cat /tmp/download)" = "$second"
|
|
|
|
|
|
|
|
|
|
curl --fail --silent --show-error \
|
2026-07-18 18:08:41 +09:00
|
|
|
--cookie "$cookies" \
|
2026-07-18 16:16:24 +09:00
|
|
|
--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 \
|
2026-07-18 18:08:41 +09:00
|
|
|
--cookie "$cookies" \
|
2026-07-18 16:16:24 +09:00
|
|
|
"$base/download?root=$root&path=smoke-$suffix.txt" >/dev/null; then
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exercise_root personal 01
|
|
|
|
|
exercise_root shared 02
|
2026-07-18 18:08:41 +09:00
|
|
|
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" \
|
2026-07-19 03:41:01 +09:00
|
|
|
--header 'X-CSRF-Token: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' \
|
2026-07-18 18:08:41 +09:00
|
|
|
--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
|
2026-07-18 20:48:26 +09:00
|
|
|
- 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
|
2026-07-18 21:25:50 +09:00
|
|
|
- name: NUTHATCH_IDEMPOTENCY_MODE
|
|
|
|
|
valueFrom:
|
|
|
|
|
configMapKeyRef:
|
|
|
|
|
name: @@PROFILE_NAME@@
|
|
|
|
|
key: NUTHATCH_IDEMPOTENCY_MODE
|
|
|
|
|
optional: true
|
2026-07-19 01:12:29 +09:00
|
|
|
- name: NUTHATCH_PRODUCT_MODE
|
|
|
|
|
valueFrom:
|
|
|
|
|
configMapKeyRef:
|
|
|
|
|
name: @@PROFILE_NAME@@
|
|
|
|
|
key: NUTHATCH_PRODUCT_MODE
|
|
|
|
|
optional: true
|
2026-07-19 03:41:01 +09:00
|
|
|
- name: NUTHATCH_RUNTIME_MODE
|
|
|
|
|
valueFrom:
|
|
|
|
|
configMapKeyRef:
|
|
|
|
|
name: @@PROFILE_NAME@@
|
|
|
|
|
key: NUTHATCH_RUNTIME_MODE
|
|
|
|
|
optional: true
|
2026-07-18 21:25:50 +09:00
|
|
|
- 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
|
2026-07-18 18:08:41 +09:00
|
|
|
- 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
|
2026-07-18 16:16:24 +09:00
|
|
|
resources:
|
|
|
|
|
requests: {cpu: 50m, memory: 32Mi}
|
|
|
|
|
limits: {cpu: "1", memory: 128Mi}
|