Adds KMS, secrets management, config encryption, and auth plugins to enable zero-trust security architecture across the provisioning platform.
395 lines
13 KiB
Plaintext
395 lines
13 KiB
Plaintext
# Configuration Encryption CLI Commands
|
||
# Provides user-friendly commands for config encryption operations
|
||
|
||
use encryption.nu *
|
||
use accessor.nu *
|
||
|
||
# Encrypt a configuration file
|
||
export def "config encrypt" [
|
||
file: path # Configuration file to encrypt
|
||
--output (-o): path # Output path (default: <file>.enc)
|
||
--kms (-k): string = "age" # KMS backend: age, aws-kms, vault, cosmian
|
||
--in-place (-i) # Encrypt in-place (overwrites original)
|
||
--debug (-d) # Enable debug output
|
||
] {
|
||
if not ($file | path exists) {
|
||
print $"❌ File not found: ($file)"
|
||
return
|
||
}
|
||
|
||
print $"🔒 Encrypting configuration file: ($file)"
|
||
print $" Backend: ($kms)"
|
||
|
||
if $in_place {
|
||
print $" Mode: In-place (will overwrite original)"
|
||
} else if ($output | is-not-empty) {
|
||
print $" Output: ($output)"
|
||
} else {
|
||
print $" Output: ($file).enc"
|
||
}
|
||
|
||
try {
|
||
if $in_place {
|
||
encrypt-config $file --kms=$kms --in-place --debug=$debug
|
||
} else {
|
||
encrypt-config $file $output --kms=$kms --debug=$debug
|
||
}
|
||
} catch { |err|
|
||
print $"❌ Encryption failed: ($err.msg)"
|
||
}
|
||
}
|
||
|
||
# Decrypt a configuration file
|
||
export def "config decrypt" [
|
||
file: path # Encrypted configuration file
|
||
--output (-o): path # Output path (default: removes .enc extension)
|
||
--in-place (-i) # Decrypt in-place (overwrites original)
|
||
--debug (-d) # Enable debug output
|
||
] {
|
||
if not ($file | path exists) {
|
||
print $"❌ File not found: ($file)"
|
||
return
|
||
}
|
||
|
||
print $"🔓 Decrypting configuration file: ($file)"
|
||
|
||
try {
|
||
if $in_place {
|
||
decrypt-config $file --in-place --debug=$debug
|
||
} else {
|
||
decrypt-config $file $output --debug=$debug
|
||
}
|
||
} catch { |err|
|
||
print $"❌ Decryption failed: ($err.msg)"
|
||
}
|
||
}
|
||
|
||
# Edit encrypted configuration file securely
|
||
export def "config edit-secure" [
|
||
file: path # Encrypted configuration file
|
||
--editor (-e): string # Editor to use (default: $EDITOR or vim)
|
||
--debug (-d) # Enable debug output
|
||
] {
|
||
if not ($file | path exists) {
|
||
print $"❌ File not found: ($file)"
|
||
return
|
||
}
|
||
|
||
try {
|
||
if ($editor | is-not-empty) {
|
||
edit-encrypted-config $file --editor=$editor --debug=$debug
|
||
} else {
|
||
edit-encrypted-config $file --debug=$debug
|
||
}
|
||
} catch { |err|
|
||
print $"❌ Edit failed: ($err.msg)"
|
||
}
|
||
}
|
||
|
||
# Rotate encryption keys for a configuration file
|
||
export def "config rotate-keys" [
|
||
file: path # Encrypted configuration file
|
||
new_key: string # New key ID or recipient
|
||
--debug (-d) # Enable debug output
|
||
] {
|
||
if not ($file | path exists) {
|
||
print $"❌ File not found: ($file)"
|
||
return
|
||
}
|
||
|
||
print $"🔄 Rotating encryption keys"
|
||
print $" File: ($file)"
|
||
print $" New key: ($new_key)"
|
||
|
||
try {
|
||
rotate-encryption-keys $file $new_key --debug=$debug
|
||
} catch { |err|
|
||
print $"❌ Key rotation failed: ($err.msg)"
|
||
}
|
||
}
|
||
|
||
# Check if a configuration file is encrypted
|
||
export def "config is-encrypted" [
|
||
file: path # Configuration file to check
|
||
] {
|
||
if not ($file | path exists) {
|
||
print $"❌ File not found: ($file)"
|
||
return
|
||
}
|
||
|
||
let encrypted = (is-encrypted-config $file)
|
||
|
||
if $encrypted {
|
||
print $"🔒 File is encrypted: ($file)"
|
||
} else {
|
||
print $"🔓 File is not encrypted: ($file)"
|
||
}
|
||
|
||
$encrypted
|
||
}
|
||
|
||
# Validate encryption configuration and setup
|
||
export def "config validate-encryption" [] {
|
||
print $"🔍 Validating encryption configuration..."
|
||
print ""
|
||
|
||
let validation = (validate-encryption-config)
|
||
|
||
# Show summary
|
||
print $"📊 Encryption Configuration Summary"
|
||
print $"=================================="
|
||
print $" SOPS installed: ($validation.summary.sops_installed)"
|
||
print $" Age backend: ($validation.summary.age_backend)"
|
||
print $" KMS enabled: ($validation.summary.kms_enabled)"
|
||
print $" Errors: ($validation.summary.total_errors)"
|
||
print $" Warnings: ($validation.summary.total_warnings)"
|
||
print ""
|
||
|
||
# Show errors
|
||
if ($validation.errors | length) > 0 {
|
||
print $"❌ Errors:"
|
||
for error in $validation.errors {
|
||
print $" • ($error.message)"
|
||
}
|
||
print ""
|
||
}
|
||
|
||
# Show warnings
|
||
if ($validation.warnings | length) > 0 {
|
||
print $"⚠️ Warnings:"
|
||
for warning in $validation.warnings {
|
||
print $" • ($warning.message)"
|
||
}
|
||
print ""
|
||
}
|
||
|
||
if $validation.valid {
|
||
print $"✅ Encryption configuration is valid"
|
||
} else {
|
||
print $"❌ Encryption configuration has errors"
|
||
}
|
||
|
||
$validation
|
||
}
|
||
|
||
# Scan directory for unencrypted sensitive configurations
|
||
export def "config scan-sensitive" [
|
||
directory: path = "." # Directory to scan
|
||
--recursive (-r) # Scan recursively
|
||
] {
|
||
if not ($directory | path exists) {
|
||
print $"❌ Directory not found: ($directory)"
|
||
return
|
||
}
|
||
|
||
print $"🔍 Scanning for unencrypted sensitive configs in: ($directory)"
|
||
|
||
let results = (scan-unencrypted-configs $directory --recursive=$recursive)
|
||
|
||
if ($results | is-empty) {
|
||
print $"✅ No unencrypted sensitive configs found"
|
||
} else {
|
||
print $"\n⚠️ Found ($results | length) unencrypted sensitive configs:"
|
||
print ""
|
||
print $results
|
||
print ""
|
||
print $"💡 Run 'config encrypt-all ($directory)' to encrypt them"
|
||
}
|
||
|
||
$results
|
||
}
|
||
|
||
# Encrypt all sensitive configurations in directory
|
||
export def "config encrypt-all" [
|
||
directory: path = "." # Directory to encrypt
|
||
--kms (-k): string = "age" # KMS backend: age, aws-kms, vault, cosmian
|
||
--recursive (-r) # Scan recursively
|
||
--dry-run (-n) # Dry run (no actual encryption)
|
||
] {
|
||
if not ($directory | path exists) {
|
||
print $"❌ Directory not found: ($directory)"
|
||
return
|
||
}
|
||
|
||
try {
|
||
encrypt-sensitive-configs $directory --kms=$kms --recursive=$recursive --dry-run=$dry_run
|
||
} catch { |err|
|
||
print $"❌ Bulk encryption failed: ($err.msg)"
|
||
}
|
||
}
|
||
|
||
# Show encryption information for configuration
|
||
export def "config encryption-info" [
|
||
file: path # Configuration file
|
||
] {
|
||
if not ($file | path exists) {
|
||
print $"❌ File not found: ($file)"
|
||
return
|
||
}
|
||
|
||
print $"📋 Encryption Information"
|
||
print $"========================"
|
||
print $" File: ($file)"
|
||
|
||
let encrypted = (is-encrypted-config $file)
|
||
print $" Encrypted: ($encrypted)"
|
||
|
||
if $encrypted {
|
||
# Try to extract SOPS metadata
|
||
try {
|
||
let content = (open $file --raw)
|
||
if ($content | str contains "sops:") {
|
||
print $" Type: SOPS encrypted"
|
||
|
||
# Extract some metadata (without decrypting)
|
||
if ($content | str contains "age:") {
|
||
print $" Backend: Age"
|
||
}
|
||
if ($content | str contains "kms:") {
|
||
print $" Backend: AWS KMS"
|
||
}
|
||
if ($content | str contains "vault:") {
|
||
print $" Backend: Vault"
|
||
}
|
||
}
|
||
} catch {
|
||
print $" Type: Encrypted (unknown format)"
|
||
}
|
||
} else {
|
||
let sensitive = (contains-sensitive-data $file)
|
||
print $" Contains sensitive data: ($sensitive)"
|
||
|
||
if $sensitive {
|
||
print ""
|
||
print $"⚠️ This file contains sensitive data but is not encrypted!"
|
||
print $"💡 Run 'config encrypt ($file)' to encrypt it"
|
||
}
|
||
}
|
||
}
|
||
|
||
# Initialize encryption setup (generate keys, create SOPS config)
|
||
export def "config init-encryption" [
|
||
--kms (-k): string = "age" # KMS backend to initialize: age, aws-kms, vault
|
||
--force (-f) # Force re-initialization
|
||
] {
|
||
print $"🔧 Initializing encryption setup with ($kms)"
|
||
print ""
|
||
|
||
match $kms {
|
||
"age" => {
|
||
# Check if age is installed
|
||
let age_check = (^which age | complete)
|
||
if $age_check.exit_code != 0 {
|
||
print $"❌ Age is not installed"
|
||
print $"💡 Install with: brew install age"
|
||
return
|
||
}
|
||
|
||
# Generate age key if not exists
|
||
let age_key_file = ($env.HOME | path join ".config" | path join "sops" | path join "age" | path join "keys.txt")
|
||
let age_key_dir = ($age_key_file | path dirname)
|
||
|
||
if ($age_key_file | path exists) and not $force {
|
||
print $"✅ Age key already exists: ($age_key_file)"
|
||
print $" Use --force to regenerate"
|
||
} else {
|
||
# Create directory
|
||
mkdir $age_key_dir
|
||
|
||
# Generate new age key
|
||
print $"🔑 Generating new Age key..."
|
||
let key_output = (^age-keygen -o $age_key_file | complete)
|
||
|
||
if $key_output.exit_code == 0 {
|
||
print $"✅ Age key generated: ($age_key_file)"
|
||
|
||
# Extract recipient
|
||
let key_content = (open $age_key_file --raw)
|
||
let recipient = ($key_content | lines | where ($it | str starts-with "# public key:") | first | split row ": " | get 1)
|
||
|
||
print $" Public key (recipient): ($recipient)"
|
||
print ""
|
||
print $"💡 Set this environment variable:"
|
||
print $" export SOPS_AGE_RECIPIENTS=($recipient)"
|
||
} else {
|
||
print $"❌ Failed to generate Age key"
|
||
return
|
||
}
|
||
}
|
||
|
||
# Create .sops.yaml if not exists
|
||
let sops_config = ($env.PWD | path join ".sops.yaml")
|
||
if not ($sops_config | path exists) or $force {
|
||
print $"📝 Creating SOPS configuration: ($sops_config)"
|
||
|
||
let key_content = (open $age_key_file --raw)
|
||
let recipient = ($key_content | lines | where ($it | str starts-with "# public key:") | first | split row ": " | get 1)
|
||
|
||
let sops_yaml = $"creation_rules:
|
||
- path_regex: .*\\.enc\\.yaml$
|
||
age: ($recipient)
|
||
- path_regex: .*\\.enc\\.yml$
|
||
age: ($recipient)
|
||
- path_regex: .*\\.enc\\.toml$
|
||
age: ($recipient)
|
||
- path_regex: .*\\.enc\\.json$
|
||
age: ($recipient)
|
||
- path_regex: workspace/.*/config/secure\\.yaml$
|
||
age: ($recipient)
|
||
"
|
||
|
||
$sops_yaml | save --force $sops_config
|
||
print $"✅ SOPS configuration created"
|
||
}
|
||
}
|
||
"aws-kms" => {
|
||
print $"⚠️ AWS KMS requires manual configuration"
|
||
print $"💡 Follow these steps:"
|
||
print $" 1. Create KMS key in AWS Console"
|
||
print $" 2. Update .sops.yaml with KMS ARN"
|
||
print $" 3. Configure AWS credentials"
|
||
}
|
||
"vault" | "cosmian" => {
|
||
print $"⚠️ ($kms) KMS requires manual configuration"
|
||
print $"💡 Configure KMS settings in config file"
|
||
}
|
||
_ => {
|
||
print $"❌ Unknown KMS backend: ($kms)"
|
||
print $" Supported: age, aws-kms, vault, cosmian"
|
||
}
|
||
}
|
||
|
||
print ""
|
||
print $"✅ Encryption initialization completed"
|
||
}
|
||
|
||
# Main help command
|
||
export def main [] {
|
||
print "Configuration Encryption Commands"
|
||
print "================================="
|
||
print ""
|
||
print "Encryption/Decryption:"
|
||
print " config encrypt <file> Encrypt configuration file"
|
||
print " config decrypt <file> Decrypt configuration file"
|
||
print " config edit-secure <file> Edit encrypted file securely"
|
||
print " config rotate-keys <file> Rotate encryption keys"
|
||
print ""
|
||
print "Information:"
|
||
print " config is-encrypted <file> Check if file is encrypted"
|
||
print " config encryption-info <file> Show encryption details"
|
||
print " config validate-encryption Validate encryption setup"
|
||
print ""
|
||
print "Bulk Operations:"
|
||
print " config scan-sensitive <dir> Find unencrypted sensitive configs"
|
||
print " config encrypt-all <dir> Encrypt all sensitive configs"
|
||
print ""
|
||
print "Setup:"
|
||
print " config init-encryption Initialize encryption (generate keys)"
|
||
print ""
|
||
print "Examples:"
|
||
print " config encrypt workspace/config/secure.yaml"
|
||
print " config edit-secure workspace/config/secure.enc.yaml"
|
||
print " config scan-sensitive workspace/config --recursive"
|
||
print " config init-encryption --kms age"
|
||
}
|