Initial commit: VaultMesh Skills collection
Collection of operational skills for VaultMesh infrastructure including: - backup-sovereign: Backup and recovery operations - btc-anchor: Bitcoin anchoring - cloudflare-tunnel-manager: Cloudflare tunnel management - container-registry: Container registry operations - disaster-recovery: Disaster recovery procedures - dns-sovereign: DNS management - eth-anchor: Ethereum anchoring - gitea-bootstrap: Gitea setup and configuration - hetzner-bootstrap: Hetzner server provisioning - merkle-forest: Merkle tree operations - node-hardening: Node security hardening - operator-bootstrap: Operator initialization - proof-verifier: Cryptographic proof verification - rfc3161-anchor: RFC3161 timestamping - secrets-vault: Secrets management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
17
secrets-vault/scripts/00_preflight.sh
Normal file
17
secrets-vault/scripts/00_preflight.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
main() {
|
||||
need age
|
||||
need sops
|
||||
need mkdir
|
||||
need chmod
|
||||
need stat
|
||||
need grep
|
||||
need sed
|
||||
need date
|
||||
log_info "Preflight OK."
|
||||
}
|
||||
main "$@"
|
||||
26
secrets-vault/scripts/10_plan.sh
Normal file
26
secrets-vault/scripts/10_plan.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
: "${VAULT_ROOT:=~/infrastructure/vault}"
|
||||
: "${AGE_KEY_DIR:=~/.config/sops/age}"
|
||||
: "${AGE_KEYS_FILE:=~/.config/sops/age/keys.txt}"
|
||||
|
||||
main() {
|
||||
vr="$(expand_path "$VAULT_ROOT")"
|
||||
kd="$(expand_path "$AGE_KEY_DIR")"
|
||||
kf="$(expand_path "$AGE_KEYS_FILE")"
|
||||
|
||||
echo "[PLAN] $(date -Iseconds) secrets-vault (age+sops)"
|
||||
echo "[PLAN] VAULT_ROOT: $vr"
|
||||
echo "[PLAN] AGE_KEY_DIR: $kd"
|
||||
echo "[PLAN] AGE_KEYS_FILE: $kf"
|
||||
echo
|
||||
echo "[PLAN] Will ensure age identity exists (keys.txt)."
|
||||
echo "[PLAN] Will write vault/.sops.yaml and encrypted templates under vault/secrets/."
|
||||
echo "[PLAN] Will NOT print secret plaintext."
|
||||
echo
|
||||
echo "[PLAN] Next: export DRY_RUN=0 && ./scripts/11_apply.sh"
|
||||
}
|
||||
main "$@"
|
||||
130
secrets-vault/scripts/11_apply.sh
Normal file
130
secrets-vault/scripts/11_apply.sh
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
: "${VAULT_ROOT:=~/infrastructure/vault}"
|
||||
: "${AGE_KEY_DIR:=~/.config/sops/age}"
|
||||
: "${AGE_KEYS_FILE:=~/.config/sops/age/keys.txt}"
|
||||
: "${RECIPIENTS_FILE:=$SKILL_ROOT/outputs/recipients.txt}"
|
||||
: "${BACKUP_DIR:=$SKILL_ROOT/outputs/backups}"
|
||||
|
||||
backup_file() {
|
||||
local f="$1"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
if [[ -f "$f" ]]; then
|
||||
ts="$(date -Iseconds | tr ':' '-')"
|
||||
cp -p "$f" "$BACKUP_DIR/$(basename "$f").${ts}.bak"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
confirm_gate
|
||||
|
||||
mkdir -p "$SKILL_ROOT/outputs" "$BACKUP_DIR"
|
||||
vr="$(expand_path "$VAULT_ROOT")"
|
||||
kd="$(expand_path "$AGE_KEY_DIR")"
|
||||
kf="$(expand_path "$AGE_KEYS_FILE")"
|
||||
|
||||
mkdir -p "$vr/secrets"
|
||||
mkdir -p "$kd"
|
||||
|
||||
# 1) ensure age identity exists
|
||||
if [[ ! -f "$kf" ]]; then
|
||||
log_info "Generating age identity: $kf"
|
||||
age-keygen -o "$kf" >/dev/null
|
||||
chmod 600 "$kf"
|
||||
else
|
||||
log_info "Using existing age identity: $kf"
|
||||
chmod 600 "$kf" || true
|
||||
fi
|
||||
|
||||
# extract recipient (public key)
|
||||
recipient="$(grep -E '^# public key: ' "$kf" | head -n1 | sed 's/^# public key: //')"
|
||||
[[ -n "$recipient" ]] || die "Could not parse public key from $kf"
|
||||
|
||||
echo "$recipient" > "$RECIPIENTS_FILE"
|
||||
|
||||
# 2) write .sops.yaml policy
|
||||
policy="$vr/.sops.yaml"
|
||||
backup_file "$policy"
|
||||
cat > "$policy" <<EOF
|
||||
creation_rules:
|
||||
- path_regex: secrets/.*\\.enc\\.(yaml|yml|json|env)$
|
||||
encrypted_regex: '^(data|stringData|secrets|values)$'
|
||||
age: ["$recipient"]
|
||||
EOF
|
||||
|
||||
# 3) scaffold encrypted templates
|
||||
# plaintext templates are generated into a temp file then immediately encrypted and removed.
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmpdir"' EXIT
|
||||
|
||||
# cloudflare
|
||||
cat > "$tmpdir/cloudflare.yaml" <<EOF
|
||||
data:
|
||||
account_id: "REPLACE_ME"
|
||||
tunnel_token: "REPLACE_ME"
|
||||
api_token: "REPLACE_ME"
|
||||
EOF
|
||||
sops --encrypt --age "$recipient" "$tmpdir/cloudflare.yaml" > "$vr/secrets/cloudflare.enc.yaml"
|
||||
|
||||
# gitea
|
||||
cat > "$tmpdir/gitea.yaml" <<EOF
|
||||
data:
|
||||
admin_username: "REPLACE_ME"
|
||||
admin_password: "REPLACE_ME"
|
||||
admin_email: "REPLACE_ME"
|
||||
secret_key: "REPLACE_ME"
|
||||
EOF
|
||||
sops --encrypt --age "$recipient" "$tmpdir/gitea.yaml" > "$vr/secrets/gitea.enc.yaml"
|
||||
|
||||
# registry
|
||||
cat > "$tmpdir/registry.yaml" <<EOF
|
||||
data:
|
||||
htpasswd: "REPLACE_ME"
|
||||
tls_cert_pem: "REPLACE_ME"
|
||||
tls_key_pem: "REPLACE_ME"
|
||||
EOF
|
||||
sops --encrypt --age "$recipient" "$tmpdir/registry.yaml" > "$vr/secrets/registry.enc.yaml"
|
||||
|
||||
# k8s
|
||||
cat > "$tmpdir/k8s.yaml" <<EOF
|
||||
data:
|
||||
kubeconfig: "REPLACE_ME"
|
||||
cluster_token: "REPLACE_ME"
|
||||
EOF
|
||||
sops --encrypt --age "$recipient" "$tmpdir/k8s.yaml" > "$vr/secrets/k8s.enc.yaml"
|
||||
|
||||
# 4) vault README
|
||||
readme="$vr/README.md"
|
||||
backup_file "$readme"
|
||||
cat > "$readme" <<EOF
|
||||
# Vault (age + sops)
|
||||
|
||||
This folder stores **encrypted secrets** for GitOps.
|
||||
|
||||
## Edit a secret
|
||||
\`\`\`bash
|
||||
sops secrets/cloudflare.enc.yaml
|
||||
\`\`\`
|
||||
|
||||
## Decrypt to stdout (careful)
|
||||
\`\`\`bash
|
||||
sops -d secrets/cloudflare.enc.yaml
|
||||
\`\`\`
|
||||
|
||||
## Policy
|
||||
See \`.sops.yaml\`. Only files matching \`secrets/*.enc.(yaml|json|env)\` are covered.
|
||||
|
||||
## Key location
|
||||
- age identity: \`$kf\` (0600)
|
||||
- public recipient: stored in \`outputs/recipients.txt\` by the skill
|
||||
EOF
|
||||
|
||||
echo "$vr" > "$SKILL_ROOT/outputs/vault_root.txt"
|
||||
log_info "Vault forged at: $vr"
|
||||
log_info "Recipient: $recipient"
|
||||
}
|
||||
main "$@"
|
||||
55
secrets-vault/scripts/90_verify.sh
Normal file
55
secrets-vault/scripts/90_verify.sh
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
: "${VAULT_ROOT:=~/infrastructure/vault}"
|
||||
: "${AGE_KEYS_FILE:=~/.config/sops/age/keys.txt}"
|
||||
: "${RECIPIENTS_FILE:=$SKILL_ROOT/outputs/recipients.txt}"
|
||||
|
||||
main() {
|
||||
vr="$(expand_path "$VAULT_ROOT")"
|
||||
kf="$(expand_path "$AGE_KEYS_FILE")"
|
||||
|
||||
status="$SKILL_ROOT/outputs/status_matrix.json"
|
||||
ok_keys=false; ok_perm=false; ok_policy=false; ok_cipher=false
|
||||
|
||||
[[ -f "$kf" ]] && ok_keys=true
|
||||
if [[ -f "$kf" ]]; then
|
||||
perm="$(stat -c '%a' "$kf" 2>/dev/null || echo "")"
|
||||
[[ "$perm" == "600" ]] && ok_perm=true
|
||||
fi
|
||||
|
||||
[[ -f "$vr/.sops.yaml" ]] && ok_policy=true
|
||||
if [[ -f "$vr/secrets/cloudflare.enc.yaml" && -f "$vr/secrets/gitea.enc.yaml" && -f "$vr/secrets/registry.enc.yaml" && -f "$vr/secrets/k8s.enc.yaml" ]]; then
|
||||
ok_cipher=true
|
||||
fi
|
||||
|
||||
blockers="[]"
|
||||
if [[ "$ok_keys" != "true" ]]; then blockers='["missing_age_identity"]'
|
||||
elif [[ "$ok_policy" != "true" ]]; then blockers='["missing_sops_policy"]'
|
||||
elif [[ "$ok_cipher" != "true" ]]; then blockers='["missing_encrypted_templates"]'
|
||||
fi
|
||||
|
||||
cat > "$status" <<EOF
|
||||
{
|
||||
"skill":"secrets-vault",
|
||||
"timestamp":"$(date -Iseconds)",
|
||||
"vault_root":"$(json_escape "$vr")",
|
||||
"checks":[
|
||||
{"name":"age_identity_present", "ok": $ok_keys, "path":"$(json_escape "$kf")"},
|
||||
{"name":"age_identity_perm_600", "ok": $ok_perm},
|
||||
{"name":"sops_policy_present", "ok": $ok_policy, "path":"$(json_escape "$vr/.sops.yaml")"},
|
||||
{"name":"encrypted_templates_present", "ok": $ok_cipher}
|
||||
],
|
||||
"blockers": $blockers,
|
||||
"warnings": [],
|
||||
"next_steps": ["commit vault ciphertext to git", "cloudflare-tunnel-manager", "container-registry"]
|
||||
}
|
||||
EOF
|
||||
|
||||
log_info "Wrote $status"
|
||||
cat "$status"
|
||||
}
|
||||
main "$@"
|
||||
38
secrets-vault/scripts/99_report.sh
Normal file
38
secrets-vault/scripts/99_report.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
main() {
|
||||
status="$SKILL_ROOT/outputs/status_matrix.json"
|
||||
report="$SKILL_ROOT/outputs/audit_report.md"
|
||||
vault_root="$(cat "$SKILL_ROOT/outputs/vault_root.txt" 2>/dev/null || echo "")"
|
||||
|
||||
cat > "$report" <<EOF
|
||||
# Secrets Vault Audit Report (age + sops)
|
||||
|
||||
**Generated:** $(date -Iseconds)
|
||||
**Vault Root:** \`$vault_root\`
|
||||
**Skill Version:** 1.0.0
|
||||
|
||||
## Status Matrix
|
||||
|
||||
$(if [[ -f "$status" ]]; then echo '```json'; cat "$status"; echo '```'; else echo "_Missing status_matrix.json_"; fi)
|
||||
|
||||
## Operating Rules
|
||||
|
||||
- Commit **only ciphertext** (\`*.enc.*\`) to git.
|
||||
- Never store plaintext secrets in the repo.
|
||||
- Keep \`~/.config/sops/age/keys.txt\` at **0600**.
|
||||
- Keep an offline recovery copy of the age identity.
|
||||
|
||||
## EU Compliance
|
||||
|
||||
EU (Ireland - Dublin), Irish jurisdiction. Secrets remain local-first; git stores ciphertext only.
|
||||
EOF
|
||||
|
||||
log_info "Wrote $report"
|
||||
cat "$report"
|
||||
}
|
||||
main "$@"
|
||||
32
secrets-vault/scripts/_common.sh
Normal file
32
secrets-vault/scripts/_common.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
log_info(){ echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn(){ echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error(){ echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
die(){ log_error "$*"; exit 1; }
|
||||
need(){ command -v "$1" >/dev/null 2>&1 || die "Missing required tool: $1"; }
|
||||
|
||||
expand_path() {
|
||||
local p="$1"
|
||||
# shellcheck disable=SC2086
|
||||
eval echo "$p"
|
||||
}
|
||||
|
||||
confirm_gate() {
|
||||
: "${DRY_RUN:=1}"
|
||||
: "${REQUIRE_CONFIRM:=1}"
|
||||
: "${CONFIRM_PHRASE:=I UNDERSTAND THIS WILL CREATE A SECRETS VAULT}"
|
||||
[[ "$DRY_RUN" == "0" ]] || die "DRY_RUN=$DRY_RUN (set DRY_RUN=0)."
|
||||
if [[ "$REQUIRE_CONFIRM" == "1" ]]; then
|
||||
echo "Type to confirm:"
|
||||
echo " $CONFIRM_PHRASE"
|
||||
read -r input
|
||||
[[ "$input" == "$CONFIRM_PHRASE" ]] || die "Confirmation phrase mismatch."
|
||||
fi
|
||||
}
|
||||
|
||||
json_escape() {
|
||||
local s="$1"
|
||||
s="${s//\\/\\\\}"; s="${s//\"/\\\"}"; s="${s//$'\n'/\\n}"
|
||||
printf "%s" "$s"
|
||||
}
|
||||
22
secrets-vault/scripts/rollback/undo_last_changes.sh
Normal file
22
secrets-vault/scripts/rollback/undo_last_changes.sh
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
source "$SKILL_ROOT/scripts/_common.sh"
|
||||
|
||||
: "${BACKUP_DIR:=$SKILL_ROOT/outputs/backups}"
|
||||
|
||||
main() {
|
||||
confirm_gate
|
||||
if [[ ! -d "$BACKUP_DIR" ]]; then
|
||||
log_warn "No backups found; nothing to restore."
|
||||
exit 0
|
||||
fi
|
||||
log_warn "Backups exist in: $BACKUP_DIR"
|
||||
log_warn "This rollback restores only files we backed up (.sops.yaml, README.md)."
|
||||
log_warn "Encrypted templates are safe to keep; remove manually if desired."
|
||||
echo "Listing backups:"
|
||||
ls -1 "$BACKUP_DIR" || true
|
||||
log_info "Rollback is conservative by design. Restore manually by copying desired *.bak to target."
|
||||
}
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user