fix: [nomad-step-2] S2-fix-C — make tools/vault-import.sh --sops optional (spec regression) (#921) #924
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"
|
||||
}
|
||||
|
||||
@test "fails with missing --sops argument" {
|
||||
@test "succeeds with --env only (no --sops required)" {
|
||||
# Issue #921: --sops is now optional
|
||||
run "$IMPORT_SCRIPT" \
|
||||
--env "$FIXTURES_DIR/dot-env-complete" \
|
||||
--age-key "$FIXTURES_DIR/age-keys.txt"
|
||||
[ "$status" -ne 0 ]
|
||||
echo "$output" | grep -q "Missing required argument"
|
||||
--env "$FIXTURES_DIR/dot-env-for-env-only"
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | grep -q "Starting Vault import"
|
||||
}
|
||||
|
||||
@test "fails with missing --age-key argument" {
|
||||
|
|
@ -351,3 +351,68 @@ setup() {
|
|||
[ "$status" -ne 0 ]
|
||||
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 \
|
||||
[--dry-run]
|
||||
|
||||
Note: --env and --sops are optional but at least one must be provided.
|
||||
--age-key is only required when using --sops.
|
||||
|
||||
Options:
|
||||
--env Path to .env file (required)
|
||||
--sops Path to sops-encrypted .env.vault.enc file (required)
|
||||
--age-key Path to age keys file (required)
|
||||
--env Path to .env file (optional, use with --sops)
|
||||
--sops Path to sops-encrypted .env.vault.enc file (optional, use with --env)
|
||||
--age-key Path to age keys file (required when using --sops)
|
||||
--dry-run Print import plan without writing to Vault (optional)
|
||||
--help Show this help message
|
||||
|
||||
|
|
@ -272,30 +275,36 @@ EOF
|
|||
esac
|
||||
done
|
||||
|
||||
# Validate required arguments
|
||||
if [ -z "$env_file" ]; then
|
||||
_die "Missing required argument: --env"
|
||||
# --import-sops requires --age-key (can't decrypt without key)
|
||||
if [ -n "${sops_file:-}" ] && [ -z "${age_key_file:-}" ]; then
|
||||
_die "ERROR: --import-sops requires --age-key"
|
||||
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
|
||||
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
|
||||
|
||||
# Validate files exist
|
||||
if [ ! -f "$env_file" ]; then
|
||||
if [ -n "$env_file" ] && [ ! -f "$env_file" ]; then
|
||||
_die "Environment file not found: $env_file"
|
||||
fi
|
||||
if [ ! -f "$sops_file" ]; then
|
||||
if [ -n "$sops_file" ] && [ ! -f "$sops_file" ]; then
|
||||
_die "Sops file not found: $sops_file"
|
||||
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"
|
||||
fi
|
||||
|
||||
# Security check: age key permissions
|
||||
_validate_age_key_perms "$age_key_file"
|
||||
# Security check: age key permissions (only if sops is being used)
|
||||
if [ -n "$age_key_file" ]; then
|
||||
_validate_age_key_perms "$age_key_file"
|
||||
fi
|
||||
|
||||
# Security check: VAULT_ADDR must be localhost
|
||||
_check_vault_addr
|
||||
|
|
@ -303,16 +312,20 @@ EOF
|
|||
# Source the Vault helpers
|
||||
source "$(dirname "$0")/../lib/hvault.sh"
|
||||
|
||||
# Load .env file
|
||||
_log "Loading environment from: $env_file"
|
||||
_load_env_file "$env_file"
|
||||
# Load .env file (if provided)
|
||||
if [ -n "$env_file" ]; then
|
||||
_log "Loading environment from: $env_file"
|
||||
_load_env_file "$env_file"
|
||||
fi
|
||||
|
||||
# Decrypt sops file
|
||||
_log "Decrypting sops file: $sops_file"
|
||||
local sops_env
|
||||
sops_env="$(_decrypt_sops "$sops_file" "$age_key_file")"
|
||||
# shellcheck disable=SC2086
|
||||
eval "$sops_env"
|
||||
# Decrypt sops file (if provided)
|
||||
if [ -n "$sops_file" ]; then
|
||||
_log "Decrypting sops file: $sops_file"
|
||||
local sops_env
|
||||
sops_env="$(_decrypt_sops "$sops_file" "$age_key_file")"
|
||||
# shellcheck disable=SC2086
|
||||
eval "$sops_env"
|
||||
fi
|
||||
|
||||
# Collect all import operations
|
||||
declare -a operations=()
|
||||
|
|
@ -383,15 +396,17 @@ EOF
|
|||
fi
|
||||
done
|
||||
|
||||
# --- From sops-decrypted .env.vault.enc ---
|
||||
# --- From sops-decrypted .env.vault.enc (if provided) ---
|
||||
|
||||
# Runner tokens
|
||||
for token_name in "${RUNNER_TOKENS[@]}"; do
|
||||
local token_val="${!token_name:-}"
|
||||
if [ -n "$token_val" ]; then
|
||||
operations+=("runner|$token_name|$sops_file|$token_name")
|
||||
fi
|
||||
done
|
||||
if [ -n "$sops_file" ]; then
|
||||
# Runner tokens
|
||||
for token_name in "${RUNNER_TOKENS[@]}"; do
|
||||
local token_val="${!token_name:-}"
|
||||
if [ -n "$token_val" ]; then
|
||||
operations+=("runner|$token_name|$sops_file|$token_name")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# If dry-run, just print the plan
|
||||
if $dry_run; then
|
||||
|
|
@ -454,12 +469,17 @@ EOF
|
|||
local vault_key="$subkey"
|
||||
local source_value=""
|
||||
|
||||
if [ "$file" = "$env_file" ]; then
|
||||
# Source from environment file (envvar contains the variable name)
|
||||
source_value="${!envvar:-}"
|
||||
if [ -n "$sops_file" ]; then
|
||||
if [ "$file" = "$env_file" ]; then
|
||||
# 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
|
||||
# Source from sops-decrypted env (envvar contains the variable name)
|
||||
source_value="$(printf '%s' "$sops_env" | grep "^${envvar}=" | sed "s/^${envvar}=//" || true)"
|
||||
# No sops file - source from environment file only
|
||||
source_value="${!envvar:-}"
|
||||
fi
|
||||
|
||||
case "$category" in
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue