feat: twin-rule write targets — dual-write existing, v2-only for new (additive v2, EGB-712)
This commit is contained in:
parent
2866e5f4b1
commit
2f36fe1898
2 changed files with 73 additions and 19 deletions
47
secrets
47
secrets
|
|
@ -539,21 +539,6 @@ _store_format() {
|
||||||
echo 1
|
echo 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# The on-disk blob suffix for an external entry, format-aware. v2 unifies
|
|
||||||
# the legacy `gradle-properties` suffix to `properties` (matching the JSON
|
|
||||||
# manifest `type`); `file` is unchanged in both formats. The slug + this
|
|
||||||
# suffix + `.age` is the external blob name. This is the single source of
|
|
||||||
# truth for the suffix — push, pull, verify all route through it so a v1
|
|
||||||
# and a v2 store can never disagree on where a blob lives.
|
|
||||||
_external_blob_suffix() {
|
|
||||||
local mtype="$1"
|
|
||||||
if [ "$mtype" = "gradle-properties" ] && [ "$(_store_format)" = "2" ]; then
|
|
||||||
echo "properties"
|
|
||||||
else
|
|
||||||
echo "$mtype"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Resolve the on-disk path of an external blob for READING. Tries the v2 suffix
|
# Resolve the on-disk path of an external blob for READING. Tries the v2 suffix
|
||||||
# (.properties.age) first, then falls back to the v1 (.gradle-properties.age) for
|
# (.properties.age) first, then falls back to the v1 (.gradle-properties.age) for
|
||||||
# `properties` externals, so an upgraded client finds the blob whichever format
|
# `properties` externals, so an upgraded client finds the blob whichever format
|
||||||
|
|
@ -579,6 +564,26 @@ _resolve_external_blob_read() {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The on-disk path(s) to WRITE for an external blob, one per line. For a
|
||||||
|
# `properties` external this is the v2 suffix (.properties.age) ALWAYS, plus the
|
||||||
|
# v1 suffix (.gradle-properties.age) WHEN a v1 twin already exists in the store
|
||||||
|
# (dual-write keeps old clients fresh; a brand-new external is v2-only — the
|
||||||
|
# intended forcing function, additive v2 / EGB-712). `file` externals have a
|
||||||
|
# single suffix in both formats. Independent of the store marker.
|
||||||
|
_external_blob_write_targets() {
|
||||||
|
local project="$1" slug="$2" mtype="$3"
|
||||||
|
local base="$SECRETS_DIR/$project/external/$slug"
|
||||||
|
case "$mtype" in
|
||||||
|
file)
|
||||||
|
echo "$base.file.age" ;;
|
||||||
|
properties|gradle-properties)
|
||||||
|
echo "$base.properties.age"
|
||||||
|
[ -f "$base.gradle-properties.age" ] && echo "$base.gradle-properties.age" ;;
|
||||||
|
*)
|
||||||
|
echo "$base.$mtype.age" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
# Merge managed key=value lines (from $2) into target file $1, preserving
|
# Merge managed key=value lines (from $2) into target file $1, preserving
|
||||||
# all unrelated lines/comments/order. Updates a managed key in place (first
|
# all unrelated lines/comments/order. Updates a managed key in place (first
|
||||||
# occurrence), collapses duplicates, appends new keys. Atomic + mode-safe.
|
# occurrence), collapses duplicates, appends new keys. Atomic + mode-safe.
|
||||||
|
|
@ -677,7 +682,11 @@ push_external_files() {
|
||||||
# EGB-652: whole-file sync — encrypt the file verbatim (binary-safe).
|
# EGB-652: whole-file sync — encrypt the file verbatim (binary-safe).
|
||||||
mkdir -p "$SECRETS_DIR/$project/external"
|
mkdir -p "$SECRETS_DIR/$project/external"
|
||||||
local fslug; fslug=$(_secrets_files_slug "$mpath")
|
local fslug; fslug=$(_secrets_files_slug "$mpath")
|
||||||
age -r "$pubkey" -o "$SECRETS_DIR/$project/external/$fslug.$(_external_blob_suffix file).age" "$expanded"
|
local wt
|
||||||
|
while IFS= read -r wt; do
|
||||||
|
[ -n "$wt" ] || continue
|
||||||
|
age -r "$pubkey" -o "$wt" "$expanded"
|
||||||
|
done < <(_external_blob_write_targets "$project" "$fslug" file)
|
||||||
info "Encrypted file $mpath"
|
info "Encrypted file $mpath"
|
||||||
pushed=$((pushed + 1))
|
pushed=$((pushed + 1))
|
||||||
continue
|
continue
|
||||||
|
|
@ -706,7 +715,11 @@ push_external_files() {
|
||||||
fi
|
fi
|
||||||
mkdir -p "$SECRETS_DIR/$project/external"
|
mkdir -p "$SECRETS_DIR/$project/external"
|
||||||
local slug; slug=$(_secrets_files_slug "$mpath")
|
local slug; slug=$(_secrets_files_slug "$mpath")
|
||||||
age -r "$pubkey" -o "$SECRETS_DIR/$project/external/$slug.$(_external_blob_suffix "$mtype").age" "$tmp"
|
local wt
|
||||||
|
while IFS= read -r wt; do
|
||||||
|
[ -n "$wt" ] || continue
|
||||||
|
age -r "$pubkey" -o "$wt" "$tmp"
|
||||||
|
done < <(_external_blob_write_targets "$project" "$slug" "$mtype")
|
||||||
rm -f "$tmp"
|
rm -f "$tmp"
|
||||||
info "Extracted $found key(s) from $mpath"
|
info "Extracted $found key(s) from $mpath"
|
||||||
pushed=$((pushed + 1))
|
pushed=$((pushed + 1))
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,19 @@ make_v1_store() {
|
||||||
init_with_remote
|
init_with_remote
|
||||||
rm -f "$SECRETS_DIR/.secrets-format"
|
rm -f "$SECRETS_DIR/.secrets-format"
|
||||||
}
|
}
|
||||||
|
# Simulate an old (v1) client's properties blob: copy the pushed v2
|
||||||
|
# .properties.age to its v1 .gradle-properties.age twin (KEEPS both present).
|
||||||
|
m_fake_v1_twin() {
|
||||||
|
local proj="$1" v2
|
||||||
|
v2=$(ls "$SECRETS_DIR/$proj/external/"*.properties.age)
|
||||||
|
cp "$v2" "${v2%.properties.age}.gradle-properties.age"
|
||||||
|
}
|
||||||
|
# Like m_fake_v1_twin but RENAMES (leaves ONLY the v1 blob) — for copy-forward fixtures.
|
||||||
|
m_make_v1_only() {
|
||||||
|
local proj="$1" v2
|
||||||
|
v2=$(ls "$SECRETS_DIR/$proj/external/"*.properties.age)
|
||||||
|
mv "$v2" "${v2%.properties.age}.gradle-properties.age"
|
||||||
|
}
|
||||||
m_gradle_src() { mkdir -p "$HOME/.gradle"; printf '%s' "$1" > "$HOME/.gradle/gradle.properties"; }
|
m_gradle_src() { mkdir -p "$HOME/.gradle"; printf '%s' "$1" > "$HOME/.gradle/gradle.properties"; }
|
||||||
m_file_src() { mkdir -p "$HOME/keystores"; printf 'KS\x00\x01\x02\xffDATA\n' > "$HOME/keystores/upload.keystore"; }
|
m_file_src() { mkdir -p "$HOME/keystores"; printf 'KS\x00\x01\x02\xffDATA\n' > "$HOME/keystores/upload.keystore"; }
|
||||||
|
|
||||||
|
|
@ -49,13 +62,41 @@ m_file_src() { mkdir -p "$HOME/keystores"; printf 'KS\x00\x01\x02\xffDATA\n' >
|
||||||
[ "$status" -ne 0 ]
|
[ "$status" -ne 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "push on a v1 store still writes .gradle-properties.age (back-compat)" {
|
@test "push on a v1 store writes the v2 suffix for a fresh external (additive v2)" {
|
||||||
make_v1_store
|
make_v1_store
|
||||||
m_gradle_src $'beaconClerkPkTest=pk_test_abc\n'
|
m_gradle_src $'beaconClerkPkTest=pk_test_abc\n'
|
||||||
create_project_dir v1push
|
create_project_dir v1push
|
||||||
printf 'gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest\n' > .secrets-files
|
printf 'gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest\n' > .secrets-files
|
||||||
"$SECRETS_BIN" push v1push >/dev/null 2>&1
|
"$SECRETS_BIN" push v1push >/dev/null 2>&1
|
||||||
run bash -c "ls $SECRETS_DIR/v1push/external/*.gradle-properties.age"
|
run bash -c "ls $SECRETS_DIR/v1push/external/*.properties.age"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "push writes the v2 suffix for a fresh external even on a v1 store" {
|
||||||
|
make_v1_store
|
||||||
|
m_gradle_src $'beaconClerkPkTest=pk_test_abc\n'
|
||||||
|
create_project_dir freshv1
|
||||||
|
printf 'gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest\n' > .secrets-files
|
||||||
|
"$SECRETS_BIN" push freshv1 >/dev/null 2>&1
|
||||||
|
run bash -c "ls $SECRETS_DIR/freshv1/external/*.properties.age"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run bash -c "ls $SECRETS_DIR/freshv1/external/*.gradle-properties.age 2>/dev/null"
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "push dual-writes the v1 twin so old clients stay fresh" {
|
||||||
|
make_v1_store
|
||||||
|
m_gradle_src $'beaconClerkPkTest=pk_test_old\n'
|
||||||
|
create_project_dir dualwrite
|
||||||
|
printf 'gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest\n' > .secrets-files
|
||||||
|
"$SECRETS_BIN" push dualwrite >/dev/null 2>&1
|
||||||
|
m_fake_v1_twin dualwrite
|
||||||
|
m_gradle_src $'beaconClerkPkTest=pk_test_new\n'
|
||||||
|
"$SECRETS_BIN" push dualwrite >/dev/null 2>&1
|
||||||
|
rm -f "$SECRETS_DIR/dualwrite/external/"*.properties.age
|
||||||
|
rm -f "$HOME/.gradle/gradle.properties"
|
||||||
|
"$SECRETS_BIN" pull dualwrite >/dev/null 2>&1
|
||||||
|
run grep -q 'beaconClerkPkTest=pk_test_new' "$HOME/.gradle/gradle.properties"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue