162 lines
7.2 KiB
Bash
Executable File
162 lines
7.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Contract tests for production handoff controls. Network calls are replaced by
|
|
# a narrow aws stub so we test fail-closed command construction without cloud credentials.
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$SCRIPT_DIR/../scripts/bash/casan-paths.sh"
|
|
SHIP="$CASAN_HARNESS_ROOT/scripts/bash/audit-ship-s3.sh"
|
|
PREFLIGHT="$CASAN_HARNESS_ROOT/scripts/bash/production-preflight.sh"
|
|
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
|
|
PASS=0; FAIL=0
|
|
pass() { echo "PASS: $1"; PASS=$((PASS+1)); }
|
|
fail() { echo "FAIL: $1"; FAIL=$((FAIL+1)); }
|
|
rc() { set +e; "$@" >/dev/null 2>&1; local code=$?; set -e; echo "$code"; }
|
|
|
|
mkdir -p "$WORK/bin"
|
|
cat > "$WORK/bin/aws" <<'AWS'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
printf '%s\n' "$*" >> "${AWS_LOG:?}"
|
|
if [[ "$1 $2" == "s3api get-object-lock-configuration" ]]; then
|
|
printf '%s\n' '{"ObjectLockConfiguration":{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Days":365}}}}'
|
|
fi
|
|
if [[ "$1 $2" == "s3api head-object" ]]; then
|
|
exit 1
|
|
fi
|
|
AWS
|
|
chmod +x "$WORK/bin/aws"
|
|
printf '%064d\n' 0 > "$WORK/head.txt"
|
|
|
|
AWS_LOG="$WORK/aws.log" PATH="$WORK/bin:$PATH" CASAN_PROFILE=prod \
|
|
CASAN_S3_BUCKET=casan-production-audit CASAN_S3_REGION=ap-northeast-1 \
|
|
CASAN_S3_RETENTION_DAYS=365 CASAN_S3_KMS_KEY_ID=alias/casan-audit bash "$SHIP" "$WORK/head.txt" >/dev/null 2>&1 \
|
|
&& grep -q -- '--object-lock-mode COMPLIANCE' "$WORK/aws.log" \
|
|
&& grep -q -- '--server-side-encryption aws:kms' "$WORK/aws.log" \
|
|
&& grep -q -- '--ssekms-key-id alias/casan-audit' "$WORK/aws.log" \
|
|
&& pass "S3 anchor uses COMPLIANCE Object Lock and SSE-KMS" || fail "S3 anchor missing immutable controls"
|
|
|
|
[[ "$(rc env PATH="$WORK/bin:$PATH" CASAN_PROFILE=dev CASAN_S3_BUCKET=casan-production-audit CASAN_S3_REGION=ap-northeast-1 CASAN_S3_KMS_KEY_ID=alias/casan-audit bash "$SHIP" "$WORK/head.txt")" -ne 0 ]] \
|
|
&& pass "non-production profile is refused" || fail "non-production profile accepted"
|
|
|
|
cat > "$WORK/bin/aws" <<'AWS'
|
|
#!/usr/bin/env bash
|
|
if [[ "$1 $2" == "s3api get-object-lock-configuration" ]]; then
|
|
printf '%s\n' '{"ObjectLockConfiguration":{"ObjectLockEnabled":"Disabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Days":365}}}}'
|
|
fi
|
|
AWS
|
|
chmod +x "$WORK/bin/aws"
|
|
[[ "$(rc env PATH="$WORK/bin:$PATH" CASAN_PROFILE=prod CASAN_S3_BUCKET=casan-production-audit CASAN_S3_REGION=ap-northeast-1 CASAN_S3_KMS_KEY_ID=alias/casan-audit bash "$SHIP" "$WORK/head.txt")" -ne 0 ]] \
|
|
&& pass "bucket without Object Lock is refused" || fail "Object-Lock-free bucket accepted"
|
|
|
|
printf 'not-a-digest\n' > "$WORK/bad-head.txt"
|
|
[[ "$(rc env PATH="$WORK/bin:$PATH" CASAN_PROFILE=prod CASAN_S3_BUCKET=casan-production-audit CASAN_S3_REGION=ap-northeast-1 CASAN_S3_KMS_KEY_ID=alias/casan-audit bash "$SHIP" "$WORK/bad-head.txt")" -ne 0 ]] \
|
|
&& pass "malformed audit head is refused" || fail "malformed audit head accepted"
|
|
|
|
echo "===== Production configuration boundary ====="
|
|
mkdir -p "$WORK/tls" "$WORK/state/logs/audit" "$WORK/output"
|
|
openssl req -x509 -newkey rsa:2048 -nodes -days 60 \
|
|
-subj '/CN=control.casan.internal' -addext 'subjectAltName=DNS:control.casan.internal' \
|
|
-keyout "$WORK/tls/tls.key" -out "$WORK/tls/tls.crt" >/dev/null 2>&1
|
|
openssl genrsa -out "$WORK/idp-private.pem" 2048 >/dev/null 2>&1
|
|
openssl rsa -in "$WORK/idp-private.pem" -pubout -out "$WORK/idp-public.pem" >/dev/null 2>&1
|
|
printf 'test-ca\n' > "$WORK/vault-ca.pem"
|
|
printf '%064d\n' 0 > "$WORK/state/logs/audit/audit-head.txt"
|
|
|
|
cat > "$WORK/oauth.env" <<'EOF'
|
|
OAUTH2_PROXY_PROVIDER=oidc
|
|
OAUTH2_PROXY_OIDC_ISSUER_URL=https://id.casan.internal/realms/casan
|
|
OAUTH2_PROXY_CLIENT_ID=casan-control-plane
|
|
OAUTH2_PROXY_CLIENT_SECRET=secret-manager-injected
|
|
OAUTH2_PROXY_COOKIE_SECRET=base64-cookie-secret-value
|
|
OAUTH2_PROXY_COOKIE_SECURE=true
|
|
OAUTH2_PROXY_REDIRECT_URL=https://control.casan.internal/oauth2/callback
|
|
OAUTH2_PROXY_OIDC_GROUPS_CLAIM=groups
|
|
OAUTH2_PROXY_SET_XAUTHREQUEST=true
|
|
OAUTH2_PROXY_PASS_ACCESS_TOKEN=true
|
|
OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER=true
|
|
EOF
|
|
cat > "$WORK/vault.env" <<EOF
|
|
VAULT_ADDR=https://vault.casan.internal
|
|
VAULT_TOKEN=short-lived-workload-token
|
|
VAULT_CACERT=$WORK/vault-ca.pem
|
|
EOF
|
|
|
|
write_runtime() {
|
|
local audience="$1" signing="$2" anchor="$3"
|
|
cat > "$WORK/runtime.env" <<EOF
|
|
CASAN_PROFILE=prod
|
|
CASAN_CP_AUTH_MODE=jwt
|
|
CASAN_CP_JWT_ISSUER=https://id.casan.internal/realms/casan
|
|
CASAN_CP_JWT_AUDIENCE=$audience
|
|
CASAN_CP_JWT_PUBLIC_KEY_FILE=/run/casan-idp/idp-public.pem
|
|
CASAN_CP_JWT_CLOCK_SKEW_SECONDS=60
|
|
CASAN_SIGNING_PROVIDER=$signing
|
|
CASAN_IMMUTABLE_ANCHOR_PROVIDER=$anchor
|
|
EOF
|
|
}
|
|
write_prod_env() {
|
|
local public_key="$1"
|
|
cat > "$WORK/casan-prod.env" <<EOF
|
|
CASAN_PUBLIC_FQDN=control.casan.internal
|
|
CASAN_CP_HTTPS_PORT=443
|
|
CASAN_CP_TLS_DIR=$WORK/tls
|
|
CASAN_CP_OAUTH_ENV=$WORK/oauth.env
|
|
CASAN_CP_RUNTIME_ENV=$WORK/runtime.env
|
|
CASAN_CP_VAULT_ENV=$WORK/vault.env
|
|
CASAN_CP_STATE_DIR=$WORK/state
|
|
CASAN_CP_OUTPUT_DIR=$WORK/output
|
|
CASAN_CP_IDP_PUBLIC_KEY=$public_key
|
|
CASAN_CP_API_IMAGE=registry.casan.internal/api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
CASAN_CP_UI_IMAGE=registry.casan.internal/ui@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
|
CASAN_CP_OAUTH2_PROXY_IMAGE=registry.casan.internal/oauth@sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
|
|
CASAN_S3_BUCKET=casan-production-audit
|
|
CASAN_S3_PREFIX=audit-anchors
|
|
CASAN_S3_REGION=ap-northeast-1
|
|
CASAN_S3_RETENTION_DAYS=365
|
|
CASAN_S3_KMS_KEY_ID=alias/casan-audit
|
|
EOF
|
|
}
|
|
|
|
cat > "$WORK/bin/curl" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
printf '%s\n' '{"data":{"ttl":300,"policies":["casan-audit-sign"]}}'
|
|
EOF
|
|
cat > "$WORK/bin/docker" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
exit 0
|
|
EOF
|
|
cat > "$WORK/bin/aws" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
if [[ "$1 $2" == "s3api get-object-lock-configuration" ]]; then
|
|
printf '%s\n' '{"ObjectLockConfiguration":{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Days":365}}}}'
|
|
elif [[ "$1 $2" == "s3api head-object" ]]; then
|
|
exit 1
|
|
fi
|
|
exit 0
|
|
EOF
|
|
chmod +x "$WORK/bin/curl" "$WORK/bin/docker" "$WORK/bin/aws"
|
|
|
|
write_runtime casan-control-plane vault_kms s3_object_lock
|
|
write_prod_env "$WORK/idp-public.pem"
|
|
[[ "$(rc env PATH="$WORK/bin:$PATH" bash "$PREFLIGHT" "$WORK/casan-prod.env")" -eq 0 ]] \
|
|
&& pass "complete JWT/Vault/Object-Lock production contract passes with local service stubs" \
|
|
|| fail "valid production configuration rejected"
|
|
|
|
write_runtime wrong-audience vault_kms s3_object_lock
|
|
[[ "$(rc env PATH="$WORK/bin:$PATH" bash "$PREFLIGHT" "$WORK/casan-prod.env")" -ne 0 ]] \
|
|
&& pass "OIDC audience mismatch is refused" || fail "OIDC audience mismatch accepted"
|
|
|
|
write_runtime casan-control-plane local_openssl local_hash_chain
|
|
[[ "$(rc env PATH="$WORK/bin:$PATH" bash "$PREFLIGHT" "$WORK/casan-prod.env")" -ne 0 ]] \
|
|
&& pass "production local signing and local ledger configuration is refused" \
|
|
|| fail "production local trust fallback accepted"
|
|
|
|
write_runtime casan-control-plane vault_kms s3_object_lock
|
|
write_prod_env "$WORK/missing-idp-public.pem"
|
|
[[ "$(rc env PATH="$WORK/bin:$PATH" bash "$PREFLIGHT" "$WORK/casan-prod.env")" -ne 0 ]] \
|
|
&& pass "missing IdP verification key is refused" || fail "missing IdP key accepted"
|
|
|
|
echo "===== PRODUCTION HANDOFF SUMMARY: PASS=$PASS FAIL=$FAIL ====="
|
|
[[ "$FAIL" -eq 0 ]]
|