Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/actions/atlas-diff/scrub-baseline.sh
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,17 @@ while IFS= read -r templates_dir; do
# current-side render — unchanged Secrets vanish from the diff, rotated
# ones show a marker change. Running the map replay first would hash
# already-redacted shapes instead and break that alignment.
#
# Idempotency guard: baselines rendered by a post-Secret-redaction
# ATLAS already carry REDACTED:sha256: markers — re-hashing those
# produces sha256("REDACTED:sha256:<hash>") and every Secret shows as
# changed (#70). Values already in marker form are left untouched, so
# replaying over an already-redacted tree is a no-op.
secret_vals="$(yq eval -N '
[ select(.kind == "Secret" and .apiVersion == "v1")
| (.data[]?, .stringData[]?)
| select(tag != "!!null")
| select((tostring | test("^REDACTED:sha256:")) | not)
| tostring | @base64 ]
| .[]' "$yf" 2>/dev/null | sort -u)" || secret_vals=""
if [ -n "$secret_vals" ]; then
Expand All @@ -122,6 +129,7 @@ while IFS= read -r templates_dir; do
(select(.kind == "Secret" and .apiVersion == "v1")
| (.data[]?, .stringData[]?)
| select(tag != "!!null")
| select((tostring | test("^REDACTED:sha256:")) | not)
) |= ($m[(. | tostring)] // "REDACTED:sha256:unmapped")
' "$yf" > "$tmp_out" 2>/dev/null; then
mv "$tmp_out" "$yf"
Expand Down
70 changes: 70 additions & 0 deletions tests/bats/redaction/redaction-replay.bats
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,73 @@ YAML
run yq '.data.accessKeyId' "$release_dir/secret.yaml"
[ "$output" = "REDACTED" ]
}

# --- Idempotency: already-redacted baselines must pass through (issue #70) ---

@test "scrubber: already-redacted Secret markers are left untouched" {
local scratch="${BATS_TEST_TMPDIR}/scrub-idempotent"
local baseline="${scratch}/baseline"
local mapdir="${scratch}/maps"
local release_dir="${baseline}/cluster1/dep/rel/chart/templates"
mkdir -p "$release_dir" "${mapdir}/cluster1/dep"

# Baseline as a post-Secret-redaction ATLAS renders it: markers already
# in place, plus one raw value (the class the structural pass is for).
cat > "$release_dir/secret.yaml" <<'YAML'
apiVersion: v1
kind: Secret
metadata:
name: already-done
stringData:
fromSops: REDACTED:sha256:9fab3a1ed1c3
unmapped: REDACTED:sha256:unmapped
stillRaw: leaked-cleartext
YAML

cat > "${mapdir}/cluster1/dep/rel.json" <<'JSON'
{"never-matches":"REDACTED"}
JSON

run bash "${SCRUB_SCRIPT}" "$baseline" "$mapdir"
[ "$status" -eq 0 ]

# Existing markers unchanged — NOT re-hashed into sha256("REDACTED:...").
run yq '.stringData.fromSops' "$release_dir/secret.yaml"
[ "$output" = "REDACTED:sha256:9fab3a1ed1c3" ]
run yq '.stringData.unmapped' "$release_dir/secret.yaml"
[ "$output" = "REDACTED:sha256:unmapped" ]
# The raw value still gets its structural marker.
run yq '.stringData.stillRaw' "$release_dir/secret.yaml"
[[ "$output" == REDACTED:sha256:* ]]
[ "$output" != "REDACTED:sha256:unmapped" ]
}

@test "scrubber: running twice produces identical output (no-op replay)" {
local scratch="${BATS_TEST_TMPDIR}/scrub-twice"
local baseline="${scratch}/baseline"
local mapdir="${scratch}/maps"
local release_dir="${baseline}/cluster1/dep/rel/chart/templates"
mkdir -p "$release_dir" "${mapdir}/cluster1/dep"

cat > "$release_dir/secret.yaml" <<'YAML'
apiVersion: v1
kind: Secret
metadata:
name: fresh
stringData:
token: s3cretvalue
YAML

cat > "${mapdir}/cluster1/dep/rel.json" <<'JSON'
{"s3cretvalue":"REDACTED"}
JSON

run bash "${SCRUB_SCRIPT}" "$baseline" "$mapdir"
[ "$status" -eq 0 ]
local first_pass
first_pass="$(cat "$release_dir/secret.yaml")"

run bash "${SCRUB_SCRIPT}" "$baseline" "$mapdir"
[ "$status" -eq 0 ]
[ "$(cat "$release_dir/secret.yaml")" = "$first_pass" ]
}