fix: [nomad-step-2] S2-fix-C — make tools/vault-import.sh --sops optional (spec regression) (#921)
This commit is contained in:
parent
3e29a9a61d
commit
6971d5e2ff
3 changed files with 144 additions and 42 deletions
17
tests/fixtures/dot-env-for-env-only
vendored
Normal file
17
tests/fixtures/dot-env-for-env-only
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Test fixture .env file for env-only vault-import.sh test
|
||||||
|
# Contains minimal keys for testing env-only import (no sops)
|
||||||
|
|
||||||
|
# Generic forge creds
|
||||||
|
FORGE_TOKEN=generic-forge-token
|
||||||
|
FORGE_PASS=generic-forge-pass
|
||||||
|
FORGE_ADMIN_TOKEN=generic-admin-token
|
||||||
|
|
||||||
|
# Bot tokens (review only for minimal test)
|
||||||
|
FORGE_REVIEW_TOKEN=review-token
|
||||||
|
FORGE_REVIEW_PASS=review-pass
|
||||||
|
|
||||||
|
# Woodpecker secrets
|
||||||
|
WOODPECKER_AGENT_SECRET=wp-agent-secret
|
||||||
|
|
||||||
|
# Chat secrets
|
||||||
|
FORWARD_AUTH_SECRET=forward-auth-secret
|
||||||
|
|
@ -309,12 +309,12 @@ setup() {
|
||||||
echo "$output" | grep -q "Missing required argument"
|
echo "$output" | grep -q "Missing required argument"
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "fails with missing --sops argument" {
|
@test "succeeds with --env only (no --sops required)" {
|
||||||
|
# Issue #921: --sops is now optional
|
||||||
run "$IMPORT_SCRIPT" \
|
run "$IMPORT_SCRIPT" \
|
||||||
--env "$FIXTURES_DIR/dot-env-complete" \
|
--env "$FIXTURES_DIR/dot-env-for-env-only"
|
||||||
--age-key "$FIXTURES_DIR/age-keys.txt"
|
[ "$status" -eq 0 ]
|
||||||
[ "$status" -ne 0 ]
|
echo "$output" | grep -q "Starting Vault import"
|
||||||
echo "$output" | grep -q "Missing required argument"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "fails with missing --age-key argument" {
|
@test "fails with missing --age-key argument" {
|
||||||
|
|
@ -351,3 +351,68 @@ setup() {
|
||||||
[ "$status" -ne 0 ]
|
[ "$status" -ne 0 ]
|
||||||
echo "$output" | grep -q "not found"
|
echo "$output" | grep -q "not found"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- Optional --sops argument tests (issue #921) ─────────────────────────────────
|
||||||
|
|
||||||
|
@test "env-only import succeeds (no --sops)" {
|
||||||
|
run "$IMPORT_SCRIPT" \
|
||||||
|
--env "$FIXTURES_DIR/dot-env-for-env-only"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
echo "$output" | grep -q "Starting Vault import"
|
||||||
|
|
||||||
|
# Verify forge path was written
|
||||||
|
run curl -sf -H "X-Vault-Token: ${VAULT_TOKEN}" \
|
||||||
|
"${VAULT_ADDR}/v1/secret/data/disinto/shared/forge"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
echo "$output" | grep -q "generic-forge-token"
|
||||||
|
echo "$output" | grep -q "generic-admin-token"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "env-only import warns about age-key without sops" {
|
||||||
|
run "$IMPORT_SCRIPT" \
|
||||||
|
--env "$FIXTURES_DIR/dot-env-for-env-only" \
|
||||||
|
--age-key "$FIXTURES_DIR/age-keys.txt"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
echo "$output" | grep -q "WARNING.*--age-key given without --import-sops"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "sops-only import succeeds (no --env)" {
|
||||||
|
run "$IMPORT_SCRIPT" \
|
||||||
|
--sops "$FIXTURES_DIR/.env.vault.enc" \
|
||||||
|
--age-key "$FIXTURES_DIR/age-keys.txt"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
echo "$output" | grep -q "Starting Vault import"
|
||||||
|
|
||||||
|
# Verify runner path was written (from sops)
|
||||||
|
run curl -sf -H "X-Vault-Token: ${VAULT_TOKEN}" \
|
||||||
|
"${VAULT_ADDR}/v1/secret/data/disinto/runner/GITHUB_TOKEN"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
echo "$output" | jq -e '.data.data.value == "github-test-token-abc123"'
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "sops without --age-key errors" {
|
||||||
|
run "$IMPORT_SCRIPT" \
|
||||||
|
--sops "$FIXTURES_DIR/.env.vault.enc"
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
echo "$output" | grep -q "requires --age-key"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "no arguments errors" {
|
||||||
|
run "$IMPORT_SCRIPT"
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
echo "$output" | grep -q "must provide --import-env and/or --import-sops"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "env-only import with dry-run works" {
|
||||||
|
run "$IMPORT_SCRIPT" \
|
||||||
|
--env "$FIXTURES_DIR/dot-env-for-env-only" \
|
||||||
|
--dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
echo "$output" | grep -q "DRY-RUN"
|
||||||
|
echo "$output" | grep -q "Import plan"
|
||||||
|
|
||||||
|
# Verify nothing was written to Vault
|
||||||
|
run curl -sf -H "X-Vault-Token: ${VAULT_TOKEN}" \
|
||||||
|
"${VAULT_ADDR}/v1/secret/data/disinto/shared/forge"
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -240,10 +240,13 @@ Usage:
|
||||||
--age-key /path/to/age/keys.txt \
|
--age-key /path/to/age/keys.txt \
|
||||||
[--dry-run]
|
[--dry-run]
|
||||||
|
|
||||||
|
Note: --env and --sops are optional but at least one must be provided.
|
||||||
|
--age-key is only required when using --sops.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--env Path to .env file (required)
|
--env Path to .env file (optional, use with --sops)
|
||||||
--sops Path to sops-encrypted .env.vault.enc file (required)
|
--sops Path to sops-encrypted .env.vault.enc file (optional, use with --env)
|
||||||
--age-key Path to age keys file (required)
|
--age-key Path to age keys file (required when using --sops)
|
||||||
--dry-run Print import plan without writing to Vault (optional)
|
--dry-run Print import plan without writing to Vault (optional)
|
||||||
--help Show this help message
|
--help Show this help message
|
||||||
|
|
||||||
|
|
@ -272,30 +275,36 @@ EOF
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# Validate required arguments
|
# --import-sops requires --age-key (can't decrypt without key)
|
||||||
if [ -z "$env_file" ]; then
|
if [ -n "${sops_file:-}" ] && [ -z "${age_key_file:-}" ]; then
|
||||||
_die "Missing required argument: --env"
|
_die "ERROR: --import-sops requires --age-key"
|
||||||
fi
|
fi
|
||||||
if [ -z "$sops_file" ]; then
|
|
||||||
_die "Missing required argument: --sops"
|
# --age-key without --import-sops is a no-op — accept it but warn
|
||||||
|
if [ -z "${sops_file:-}" ] && [ -n "${age_key_file:-}" ]; then
|
||||||
|
echo "WARNING: --age-key given without --import-sops; ignoring" >&2
|
||||||
fi
|
fi
|
||||||
if [ -z "$age_key_file" ]; then
|
|
||||||
_die "Missing required argument: --age-key"
|
# At least one of --import-env / --import-sops must be provided
|
||||||
|
if [ -z "${env_file:-}" ] && [ -z "${sops_file:-}" ]; then
|
||||||
|
_die "ERROR: must provide --import-env and/or --import-sops"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate files exist
|
# Validate files exist
|
||||||
if [ ! -f "$env_file" ]; then
|
if [ -n "$env_file" ] && [ ! -f "$env_file" ]; then
|
||||||
_die "Environment file not found: $env_file"
|
_die "Environment file not found: $env_file"
|
||||||
fi
|
fi
|
||||||
if [ ! -f "$sops_file" ]; then
|
if [ -n "$sops_file" ] && [ ! -f "$sops_file" ]; then
|
||||||
_die "Sops file not found: $sops_file"
|
_die "Sops file not found: $sops_file"
|
||||||
fi
|
fi
|
||||||
if [ ! -f "$age_key_file" ]; then
|
if [ -n "$age_key_file" ] && [ ! -f "$age_key_file" ]; then
|
||||||
_die "Age key file not found: $age_key_file"
|
_die "Age key file not found: $age_key_file"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Security check: age key permissions
|
# Security check: age key permissions (only if sops is being used)
|
||||||
_validate_age_key_perms "$age_key_file"
|
if [ -n "$age_key_file" ]; then
|
||||||
|
_validate_age_key_perms "$age_key_file"
|
||||||
|
fi
|
||||||
|
|
||||||
# Security check: VAULT_ADDR must be localhost
|
# Security check: VAULT_ADDR must be localhost
|
||||||
_check_vault_addr
|
_check_vault_addr
|
||||||
|
|
@ -303,16 +312,20 @@ EOF
|
||||||
# Source the Vault helpers
|
# Source the Vault helpers
|
||||||
source "$(dirname "$0")/../lib/hvault.sh"
|
source "$(dirname "$0")/../lib/hvault.sh"
|
||||||
|
|
||||||
# Load .env file
|
# Load .env file (if provided)
|
||||||
_log "Loading environment from: $env_file"
|
if [ -n "$env_file" ]; then
|
||||||
_load_env_file "$env_file"
|
_log "Loading environment from: $env_file"
|
||||||
|
_load_env_file "$env_file"
|
||||||
|
fi
|
||||||
|
|
||||||
# Decrypt sops file
|
# Decrypt sops file (if provided)
|
||||||
_log "Decrypting sops file: $sops_file"
|
if [ -n "$sops_file" ]; then
|
||||||
local sops_env
|
_log "Decrypting sops file: $sops_file"
|
||||||
sops_env="$(_decrypt_sops "$sops_file" "$age_key_file")"
|
local sops_env
|
||||||
# shellcheck disable=SC2086
|
sops_env="$(_decrypt_sops "$sops_file" "$age_key_file")"
|
||||||
eval "$sops_env"
|
# shellcheck disable=SC2086
|
||||||
|
eval "$sops_env"
|
||||||
|
fi
|
||||||
|
|
||||||
# Collect all import operations
|
# Collect all import operations
|
||||||
declare -a operations=()
|
declare -a operations=()
|
||||||
|
|
@ -383,15 +396,17 @@ EOF
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# --- From sops-decrypted .env.vault.enc ---
|
# --- From sops-decrypted .env.vault.enc (if provided) ---
|
||||||
|
|
||||||
# Runner tokens
|
if [ -n "$sops_file" ]; then
|
||||||
for token_name in "${RUNNER_TOKENS[@]}"; do
|
# Runner tokens
|
||||||
local token_val="${!token_name:-}"
|
for token_name in "${RUNNER_TOKENS[@]}"; do
|
||||||
if [ -n "$token_val" ]; then
|
local token_val="${!token_name:-}"
|
||||||
operations+=("runner|$token_name|$sops_file|$token_name")
|
if [ -n "$token_val" ]; then
|
||||||
fi
|
operations+=("runner|$token_name|$sops_file|$token_name")
|
||||||
done
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# If dry-run, just print the plan
|
# If dry-run, just print the plan
|
||||||
if $dry_run; then
|
if $dry_run; then
|
||||||
|
|
@ -454,12 +469,17 @@ EOF
|
||||||
local vault_key="$subkey"
|
local vault_key="$subkey"
|
||||||
local source_value=""
|
local source_value=""
|
||||||
|
|
||||||
if [ "$file" = "$env_file" ]; then
|
if [ -n "$sops_file" ]; then
|
||||||
# Source from environment file (envvar contains the variable name)
|
if [ "$file" = "$env_file" ]; then
|
||||||
source_value="${!envvar:-}"
|
# Source from environment file (envvar contains the variable name)
|
||||||
|
source_value="${!envvar:-}"
|
||||||
|
else
|
||||||
|
# Source from sops-decrypted env (envvar contains the variable name)
|
||||||
|
source_value="$(printf '%s' "$sops_env" | grep "^${envvar}=" | sed "s/^${envvar}=//" || true)"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
# Source from sops-decrypted env (envvar contains the variable name)
|
# No sops file - source from environment file only
|
||||||
source_value="$(printf '%s' "$sops_env" | grep "^${envvar}=" | sed "s/^${envvar}=//" || true)"
|
source_value="${!envvar:-}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "$category" in
|
case "$category" in
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue