- Add test-key (age1wtzdf8...) for shared test environment - Enable mac_only_encrypted: true in .sops.yaml (SOPS >= 3.9.0) Allows adding new YAML fields without decryption key - Re-encrypt all 10 files with mac_only_encrypted metadata - Strict isolation: dev-key ↔ *.dev.enc.yaml, prod-key ↔ *.prod.enc.yaml - test-key can only decrypt *.test.enc.yaml (not dev/prod) - Add dev/verify-sops-isolation.sh — 33-point verification script - Keep dev/prod files with admin+dev / admin+prod only (no test-key) Verified: 33/33 isolation checks passed Co-authored-by: XoR <xor@benadis.ru>
129 lines
4.6 KiB
Bash
Executable File
129 lines
4.6 KiB
Bash
Executable File
#!/bin/bash
|
|
# verify-sops-isolation.sh — Verify SOPS zero trust key isolation
|
|
#
|
|
# Usage: bash dev/verify-sops-isolation.sh [--keys-dir PATH]
|
|
#
|
|
# Verifies that:
|
|
# 1. dev-key can ONLY decrypt *.dev.enc.yaml
|
|
# 2. prod-key can ONLY decrypt *.prod.enc.yaml
|
|
# 3. test-key can decrypt *.test.enc.yaml (if any)
|
|
# 4. test-key CANNOT decrypt dev or prod files
|
|
# 5. mac_only_encrypted is set in all files
|
|
# 6. All files decrypt successfully with appropriate keys
|
|
#
|
|
# Requires: sops, age keys in SOPS_AGE_KEY_DEV/PROD/TEST env vars
|
|
# or provide --keys-dir with separate key files
|
|
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
WARN=0
|
|
|
|
check() {
|
|
local desc=$1 expected=$2 actual=$3
|
|
if [ "$expected" = "$actual" ]; then
|
|
echo -e " ${GREEN}✓${NC} $desc"
|
|
PASS=$((PASS+1))
|
|
else
|
|
echo -e " ${RED}✗${NC} $desc (expected=$expected, got=$actual)"
|
|
FAIL=$((FAIL+1))
|
|
fi
|
|
}
|
|
|
|
ORIG_KEYS=""
|
|
if [ -f ~/.config/sops/age/keys.txt ]; then
|
|
ORIG_KEYS=$(cat ~/.config/sops/age/keys.txt)
|
|
fi
|
|
|
|
restore_keys() {
|
|
if [ -n "$ORIG_KEYS" ]; then
|
|
echo "$ORIG_KEYS" > ~/.config/sops/age/keys.txt
|
|
fi
|
|
}
|
|
trap restore_keys EXIT
|
|
|
|
# --- Check .sops.yaml ---
|
|
echo -e "\n${YELLOW}[1] Checking .sops.yaml configuration${NC}"
|
|
|
|
if [ -f .sops.yaml ]; then
|
|
check ".sops.yaml exists" "yes" "yes"
|
|
else
|
|
check ".sops.yaml exists" "yes" "no"
|
|
exit 1
|
|
fi
|
|
|
|
MAC_RULES=$(grep -c '^\s*mac_only_encrypted: true' .sops.yaml || echo 0)
|
|
check "mac_only_encrypted rules in .sops.yaml (>=4)" "yes" "$([ "$MAC_RULES" -ge 4 ] && echo yes || echo no)"
|
|
|
|
# --- Check all encrypted files ---
|
|
echo -e "\n${YELLOW}[2] Checking mac_only_encrypted in encrypted files${NC}"
|
|
TOTAL_ENC=$(find . -name '*.enc.yaml' -not -path './.git/*' | wc -l)
|
|
MAC_ENC=$(grep -rl 'mac_only_encrypted: true' $(find . -name '*.enc.yaml' -not -path './.git/*' 2>/dev/null) 2>/dev/null | wc -l)
|
|
check "mac_only_encrypted in all encrypted files" "$TOTAL_ENC" "$MAC_ENC"
|
|
|
|
# --- Key isolation tests ---
|
|
echo -e "\n${YELLOW}[3] Key isolation: dev-key${NC}"
|
|
if [ -n "${SOPS_AGE_KEY_DEV:-}" ]; then
|
|
echo "$SOPS_AGE_KEY_DEV" > ~/.config/sops/age/keys.txt
|
|
for f in $(find . -name '*.dev.enc.yaml' -not -path './.git/*'); do
|
|
result=$(sops decrypt "$f" > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
check "dev-key decrypts $(basename $f)" "yes" "$result"
|
|
done
|
|
for f in $(find . -name '*.prod.enc.yaml' -not -path './.git/*'); do
|
|
result=$(sops decrypt "$f" > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
check "dev-key CANNOT decrypt $(basename $f)" "no" "$result"
|
|
done
|
|
else
|
|
echo -e " ${YELLOW}⚠ SOPS_AGE_KEY_DEV not set, skipping${NC}"
|
|
WARN=$((WARN+1))
|
|
fi
|
|
|
|
echo -e "\n${YELLOW}[4] Key isolation: prod-key${NC}"
|
|
if [ -n "${SOPS_AGE_KEY_PROD:-}" ]; then
|
|
echo "$SOPS_AGE_KEY_PROD" > ~/.config/sops/age/keys.txt
|
|
for f in $(find . -name '*.prod.enc.yaml' -not -path './.git/*'); do
|
|
result=$(sops decrypt "$f" > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
check "prod-key decrypts $(basename $f)" "yes" "$result"
|
|
done
|
|
for f in $(find . -name '*.dev.enc.yaml' -not -path './.git/*'); do
|
|
result=$(sops decrypt "$f" > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
check "prod-key CANNOT decrypt $(basename $f)" "no" "$result"
|
|
done
|
|
else
|
|
echo -e " ${YELLOW}⚠ SOPS_AGE_KEY_PROD not set, skipping${NC}"
|
|
WARN=$((WARN+1))
|
|
fi
|
|
|
|
echo -e "\n${YELLOW}[5] Key isolation: test-key${NC}"
|
|
if [ -n "${SOPS_AGE_KEY_TEST:-}" ]; then
|
|
echo "$SOPS_AGE_KEY_TEST" > ~/.config/sops/age/keys.txt
|
|
for f in $(find . -name '*.dev.enc.yaml' -not -path './.git/*'); do
|
|
result=$(sops decrypt "$f" > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
check "test-key CANNOT decrypt $(basename $f)" "no" "$result"
|
|
done
|
|
for f in $(find . -name '*.prod.enc.yaml' -not -path './.git/*'); do
|
|
result=$(sops decrypt "$f" > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
check "test-key CANNOT decrypt $(basename $f)" "no" "$result"
|
|
done
|
|
for f in $(find . -name '*.test.enc.yaml' -not -path './.git/*'); do
|
|
result=$(sops decrypt "$f" > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
check "test-key decrypts $(basename $f)" "yes" "$result"
|
|
done
|
|
else
|
|
echo -e " ${YELLOW}⚠ SOPS_AGE_KEY_TEST not set, skipping${NC}"
|
|
WARN=$((WARN+1))
|
|
fi
|
|
|
|
# --- Summary ---
|
|
echo -e "\n=== Summary ==="
|
|
echo -e "${GREEN}Passed: $PASS${NC} ${RED}Failed: $FAIL${NC} ${YELLOW}Warnings: $WARN${NC}"
|
|
[ $FAIL -eq 0 ] && echo -e "${GREEN}All checks passed!${NC}" || echo -e "${RED}Some checks failed!${NC}"
|
|
exit $FAIL
|