Files
deploy-app-kargo-private/dev/verify-sops-isolation.sh
Dear XoR 42cb7ac5bf feat: zero trust SOPS key isolation (deploy-k3s#32)
- 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>
2026-03-12 17:11:29 +03:00

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