185 lines
5.2 KiB
Text
185 lines
5.2 KiB
Text
|
|
#!/usr/bin/env nu
|
||
|
|
# Generate .sops.yaml configuration files
|
||
|
|
# Fetches Age public keys from vault-service and creates environment-specific SOPS rules
|
||
|
|
# Usage: sops-init [--environment dev|staging|prod] [--output-dir <path>] [--validate]
|
||
|
|
|
||
|
|
use std log
|
||
|
|
|
||
|
|
def get-vault-url [] {
|
||
|
|
$env.VAULT_SERVICE_URL? // "http://localhost:9094"
|
||
|
|
}
|
||
|
|
|
||
|
|
def get-vault-token [] {
|
||
|
|
$env.VAULT_SERVICE_TOKEN? // ""
|
||
|
|
}
|
||
|
|
|
||
|
|
def fetch-age-public-key [environment: string] {
|
||
|
|
let url = $"(get-vault-url)/api/v1/age/get-public?env=($environment)"
|
||
|
|
let token = (get-vault-token)
|
||
|
|
|
||
|
|
let headers = if ($token | is-empty) {
|
||
|
|
{}
|
||
|
|
} else {
|
||
|
|
{"X-Vault-Token": $token}
|
||
|
|
}
|
||
|
|
|
||
|
|
let response = (http get -H $headers $url | complete)
|
||
|
|
|
||
|
|
if $response.exit_code != 0 {
|
||
|
|
error make {
|
||
|
|
msg: "Cannot connect to vault-service"
|
||
|
|
label: {
|
||
|
|
text: $"Check VAULT_SERVICE_URL (set to: (get-vault-url))"
|
||
|
|
span: (metadata $environment).span
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let json = ($response.stdout | from json)
|
||
|
|
$json.public_key
|
||
|
|
}
|
||
|
|
|
||
|
|
def generate-sops-yaml [environment: string, public_key: string] {
|
||
|
|
let encrypted_regex = match $environment {
|
||
|
|
"dev" => "^(password|token|key|secret|api_key)$",
|
||
|
|
"staging" => "^(password|token|key|secret|api_key|database_url)$",
|
||
|
|
"prod" => "^(password|token|key|secret|api_key|database_url|tls_cert|tls_key)$",
|
||
|
|
_ => "^(password|token|key|secret|api_key)$"
|
||
|
|
}
|
||
|
|
|
||
|
|
let extension = match $environment {
|
||
|
|
"dev" => ".dev.yaml",
|
||
|
|
"staging" => ".staging.yaml",
|
||
|
|
"prod" => ".prod.yaml",
|
||
|
|
_ => ".yaml"
|
||
|
|
}
|
||
|
|
|
||
|
|
let header = "# SOPS creation rules - evaluated sequentially, first match wins"
|
||
|
|
let rule1 = $" - path_regex: \\($extension)$"
|
||
|
|
let rule1_age = $" age: '$public_key'"
|
||
|
|
let rule1_regex = $" encrypted_regex: '$encrypted_regex'"
|
||
|
|
let rule2 = " -"
|
||
|
|
let rule2_age = $" age: '$public_key'"
|
||
|
|
let rule2_regex = $" encrypted_regex: '$encrypted_regex'"
|
||
|
|
|
||
|
|
$"$header
|
||
|
|
creation_rules:
|
||
|
|
$rule1
|
||
|
|
$rule1_age
|
||
|
|
$rule1_regex
|
||
|
|
|
||
|
|
$rule2
|
||
|
|
$rule2_age
|
||
|
|
$rule2_regex
|
||
|
|
"
|
||
|
|
}
|
||
|
|
|
||
|
|
def validate-sops-yaml [yaml_content: string] {
|
||
|
|
let has_creation_rules = ($yaml_content | str contains "creation_rules:")
|
||
|
|
let has_age_key = ($yaml_content | str contains "age:")
|
||
|
|
let has_public_key = ($yaml_content | str contains "age1") # Age keys start with "age1"
|
||
|
|
|
||
|
|
if not $has_creation_rules {
|
||
|
|
error make {
|
||
|
|
msg: "Invalid SOPS YAML: missing creation_rules section"
|
||
|
|
label: { text: "YAML must have creation_rules" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if not $has_age_key {
|
||
|
|
error make {
|
||
|
|
msg: "Invalid SOPS YAML: missing age key configuration"
|
||
|
|
label: { text: "Each rule must have an age key" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if not $has_public_key {
|
||
|
|
error make {
|
||
|
|
msg: "Invalid SOPS YAML: Age public key format invalid"
|
||
|
|
label: { text: "Public key should start with 'age1'" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
true
|
||
|
|
}
|
||
|
|
|
||
|
|
def determine-output-path [environment: string, output_dir: string] {
|
||
|
|
let dir = if ($output_dir | is-empty) {
|
||
|
|
match $environment {
|
||
|
|
"dev" => "config/secrets/dev",
|
||
|
|
"staging" => "config/secrets/staging",
|
||
|
|
"prod" => "config/secrets/prod",
|
||
|
|
_ => "config/secrets"
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
$output_dir
|
||
|
|
}
|
||
|
|
|
||
|
|
$"($dir)/.sops.yaml"
|
||
|
|
}
|
||
|
|
|
||
|
|
def main [
|
||
|
|
--environment: string = "dev"
|
||
|
|
--output-dir: string = ""
|
||
|
|
--validate
|
||
|
|
] {
|
||
|
|
# Validate environment
|
||
|
|
if $environment not-in ["dev", "staging", "prod"] {
|
||
|
|
error make {
|
||
|
|
msg: "Invalid environment"
|
||
|
|
label: {
|
||
|
|
text: "Must be: dev, staging, or prod"
|
||
|
|
span: (metadata $environment).span
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
print $"🔐 Generating .sops.yaml for ($environment) environment..."
|
||
|
|
|
||
|
|
# Fetch Age public key
|
||
|
|
print $"Fetching Age public key from vault-service..."
|
||
|
|
let public_key = (fetch-age-public-key $environment)
|
||
|
|
print $"✓ Age public key retrieved: ($public_key | str substring 0..20)..."
|
||
|
|
|
||
|
|
# Generate SOPS configuration
|
||
|
|
print "Generating SOPS rules..."
|
||
|
|
let sops_yaml = (generate-sops-yaml $environment $public_key)
|
||
|
|
|
||
|
|
# Validate if requested
|
||
|
|
if $validate {
|
||
|
|
print "Validating SOPS YAML configuration..."
|
||
|
|
let valid = (validate-sops-yaml $sops_yaml)
|
||
|
|
if $valid {
|
||
|
|
print "✓ SOPS configuration is valid"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Determine output path
|
||
|
|
let output_path = (determine-output-path $environment $output_dir)
|
||
|
|
let output_dir_path = ($output_path | path dirname)
|
||
|
|
|
||
|
|
# Create directory if needed
|
||
|
|
if not ($output_dir_path | path exists) {
|
||
|
|
print $"Creating directory: ($output_dir_path)"
|
||
|
|
^mkdir -p $output_dir_path
|
||
|
|
}
|
||
|
|
|
||
|
|
# Write .sops.yaml file
|
||
|
|
print $"Writing .sops.yaml to ($output_path)"
|
||
|
|
$sops_yaml | save -f $output_path
|
||
|
|
|
||
|
|
print ""
|
||
|
|
print "✓ SOPS configuration generated successfully"
|
||
|
|
print $"Location: ($output_path)"
|
||
|
|
print $"Environment: ($environment)"
|
||
|
|
print $"Age public key: ($public_key | str substring 0..30)..."
|
||
|
|
|
||
|
|
{
|
||
|
|
success: true
|
||
|
|
environment: $environment
|
||
|
|
output_path: $output_path
|
||
|
|
public_key: ($public_key | str substring 0..30)
|
||
|
|
message: "SOPS .yaml configuration created"
|
||
|
|
}
|
||
|
|
}
|