#!/usr/bin/env nu # Info: Prepare for etcd installation # Author: JesusPerezLorenzo # Release: 1.0.2 # Date: 26-02-2024 use lib_provisioning/cmd/env.nu * use lib_provisioning/cmd/lib.nu * use lib_provisioning/utils/ui.nu * use lib_provisioning/utils/files.nu find_file use lib_provisioning/sops * def get_domain_name [ defs: record source: string ] { match $source { "$defaults" => $defs.server.main_domain, _ => $source } } def openssl_ecc_cert [ defs: record src: string run_root: string cluster_name: string hostname: string signature: string long_sign: int ] { let etcd_cn = ( $defs.taskserv.cn | default "") let ca_signature = ($defs.taskserv.ca_sign | default "") let ssl_curve = ($defs.taskserv.ssl_curve | default "") let sign_sha = ($defs.taskserv.sign_sha | default "") let sign_cipher = ($defs.taskserv.cipher | default "") let sign_days = ($defs.taskserv.sign_days | default "") let on_error = { |msg: string| print $"🛑 (_ansi red)Error(_ansi reset) (_ansi yellow)ECC(_ansi reset): ($msg)" rm -f ($src | path join "pass") } ^openssl ecparam -genkey -name $ssl_curve -out ($src | path join $"($cluster_name).key") | ignore let res = (^openssl req -new $"-SHA($sign_sha)" -key ($src | path join $"($cluster_name).key") -nodes -out ($src | path join $"($cluster_name).csr") -subj $"/CN=($etcd_cn)" -config ($src | path join "openssl.conf") -extensions ssl_peer | complete ) if $res.exit_code != 0 { do $on_error $"openssl csr error ($res.stdout)" exit 1 } let res = (^openssl x509 -req $"-SHA($sign_sha)" -in ($src | path join $"($cluster_name).csr") -CA ($src | path join "ca.crt") -CAkey ($src | path join "ca.key") -CAcreateserial -out ($src | path join $"($cluster_name).crt") -days $sign_days -extensions ssl_peer -extfile ($src | path join "openssl.conf") | complete ) if $res.exit_code != 0 { do $on_error $"openssl x509 req error ($res.exit_code)($res.stdout)" exit 1 } ^openssl ecparam -genkey -name $ssl_curve -out ($src | path join $"($hostname).key") | ignore let res = (^openssl req -noenc -new $"-SHA($sign_sha)" -key ($src | path join $"($hostname).key") -nodes -out ($src | path join $"($hostname).csr") -subj $"/CN=($etcd_cn)" -config ($src | path join "openssl.conf") -extensions ssl_peer | complete ) if res.exit_code != 0 and not ($src | path join $"($hostname).csr" | path exists) { do $on_error $"🛑 openssl req csr error ($res.exit_code) ($res.stdout)" exit 1 } let res = (^openssl x509 -req -noenc $"-SHA($sign_sha)" -in ($src | path join $"($hostname).csr") -CA ($src | path join "ca.crt") -CAkey ($src | path join "ca.key") -CAcreateserial -out ($src | path join $"($hostname).crt") -days $sign_days -extensions ssl_peer -extfile ($src | path join "openssl.conf") | complete ) if res.exit_code != 0 and not ($src | path join $"($hostname).crt" | path exists) { do $on_error $"🛑 openssl x509 req error ($res.stdout)" exit 1 } } def openssl_rsa_cert [ defs: record src: string run_root: string cluster_name: string hostname: string signature: string long_sign: int ] { let etcd_cn = ( $defs.taskserv.cn | default "") let sign_cipher = ($defs.taskserv.cipher | default "") let sign_days = ($defs.taskserv.sign_days | default "") let on_error = { |msg: string| print $"🛑 (_ansi red)Error(_ansi reset) (_ansi yellow)RSA(_ansi reset): ($msg)" rm -f ($src | path join "pass") } if not ($src | path join "pass" | path exists) { $defs.taskserv.sign_pass | save -f ($src | path join "pass") } ^openssl genrsa -passout $"file:($src | path join "pass")" $sign_cipher -out ($src | path join $"($cluster_name)_p.key") $long_sign ^openssl rsa -in "$src/$cluster_name"_p.key -out ($src | path join $"($cluster_name).key") if not ($src | path join "openssl.conf" | path exists) { do $on_error $"openssl.con not found in ($src |path join "openssl.conf")" exit 1 } let res = (^openssl req -newkey rsa:($long_sign) -passout $"file:($src | path join "pass")" -key ($src | path join $"($cluster_name).key") -out ($src | path join $"($cluster_name).csr") -subj $"/CN=($etcd_cn)" -config ($src | path join "openssl.conf") -extensions ssl_client | complete) if $res.exit_code != 0 { do $on_error $"openssl req error ($res.exit_code) ($res.stdout)" exit 1 } print $"openssl gemrsa error ($res.exit_code) ($res.stdout)" (^openssl x509 -req -in ($src | path join $"($cluster_name).csr") -CA ($src | path join "ca.crt") -CAkey ($src | path join "ca.key") -out ($src | path join $"($cluster_name).crt") -days $sign_days -extensions ssl_client -extfile ($src | path join "openssl.conf") ) let res = (^openssl genrsa -passout $"file:($src | path join "pass")" $sign_cipher -out ($src | path join $"($hostname)_p.key") $long_sign | complete) if $res.exit_code != 0 { do $on_error $"openssl genrsa error ($res.exit_code) ($res.stdout)" exit 1 } ^openssl rsa -in ($src | path join $"($hostname)_p.key") -out ($src | path join $"($hostname).key") if not ($src | path join "openssl.conf" | path exists) { print $"openssl.con not found in ($src | path join "openssl.conf") " rm -f ($src | path join "pass") exit 1 } let res = (^openssl req -newkey rsa:$long_sign -passout $"file:($src | path join "pass")" -key ($src | path join $"($hostname).key") -out ($src | path join $"($hostname).csr") -subj $"/CN=($etcd_cn)" -config ($src | path join "openssl.conf") -extensions ssl_peer | complete) if $res.exit_code == 0 { do $on_error $"openssl req key error ($res.exit_code) ($res.stdout)" exit 1 } let res = (^openssl x509 -req -in ($src | path join $"($hostname).csr") -CA ($src | path join "ca.crt") -CAkey ($src | path join "ca.key") -out ($src | path join $"($hostname).crt") -days $sign_days -extensions ssl_peer -extfile ($src | path join "openssl.conf") | complete) if $res.exit_code != 0 { do $on_error $"openssl x509 req cst error ($res.exit_code) ($res.stdout)" exit 1 } rm -f ($src | path join "pass") } def openssl_mode [ defs: record src: string run_root: string cluster_name: string hostname: string signature: string long_sign: int ] { let etcd_cn = ( $defs.taskserv.cn | default "") let ca_signature = ($defs.taskserv.ca_sign | default "") let ssl_curve = ($defs.taskserv.ssl_curve | default "") let sign_sha = ($defs.taskserv.sign_sha | default "") let sign_cipher = ($defs.taskserv.cipher | default "") let sign_days = ($defs.taskserv.sign_days | default "") let ca_sign_days = ($defs.taskserv.ca_sign_days | default "") mut openssl = (^bash -c "type -P openssl") if $openssl == "" { ^sudo apt install openssl -y $openssl = (^bash -c "type -P openssl") } if openssl == "" { print $"openssl not installed " ; exit 1 } if not ($src | path join "openssl.conf" | path exists) and ($run_root | path join "openssl.conf.tpl" | path exists) { cp ($run_root | path join "openssl.conf.tpl") ($src | path join "openssl.conf") if ($src | path join "openssl_conf_alt_names" | path exists ) { open ($src | path join "openssl_conf_alt_names") -r | save -a ($src | path join "openssl.conf") } } print $"CA signature: ($ca_signature)" if not ($src | path join "ca.key" | path exists) { sops_cmd "decrypt" ($src | path join "ca.key") ($src | path join "ca.key") --error_exit #sudo mv "$src/ca.key.$$" "$src/ca.key" } if $ca_signature == "ECC" { if not ($src | path join "ca.key" | path exists) and not ($src| path join "ca.crt" | path exists) { ^openssl ecparam -genkey -name $ssl_curve -out ($src | path join "ca.key") let res = (^openssl req -x509 -extensions v3_ca -config ($src | path join "openssl.conf") -new $"-SHA($sign_sha)" -nodes -key ($src | path join "ca.key") -days $ca_sign_days -out ($src | path join "ca.crt") -subj $"/CN=($etcd_cn)" | complete ) if $res.exit_code != 0 { print $"🛑 openssl key ($ca_signature) error ($res.stdout)" exit 1 } } } else if not ($src | path join "ca.key" | path exists) and not ($src |path join "ca.crt" | path exists) { $defs.taskserv.sign_pass | save -f ($src | path join "pass") ^openssl genrsa -passout $"file:($src | path join "pass")" $sign_cipher -out ($src | path join "ca_p.key") $long_sign ^openssl rsa -in ($src |path join "ca_p.key") -out ($src | path join "ca.key") let res = (^openssl req -x509 -extensions v3_ca -config ($src | path join "openssl.conf") -newkey rsa:($long_sign) -nodes -key ($src | path join "ca.key") -days $sign_days -out ($src | path join "ca.crt") -subj $"CN=($etcd_cn)" | complete ) if $res.exit_code != 0 { print $"🛑 openssl ca ($ca_signature) error ($res.stdout)" exit 1 } } print $"Certs signature: ($signature)" if not ($src | path join $"($cluster_name).crt" | path exists) or not ($src | path join $"($cluster_name).key" | path exists) { match $signature { "ECC" => { (openssl_ecc_cert $defs $src $run_root $cluster_name $hostname $signature $long_sign) }, _ => { (openssl_rsa_cert $defs $src $run_root $cluster_name $hostname $signature $long_sign) }, } } copy_certs $defs $src $run_root $cluster_name $signature } def cfssl_mode [ defs: record src: string run_root: string cluster_name: string hostname: string signature: string long_sign: int ] { let domain_name = (get_domain_name $defs ($defs.taskserv.domain_name | default "")) let source_name = $"($cluster_name | default "").($domain_name)" let ORG = $env.PWD let etcd_c = ($defs.taskserv.c | default "") mut CFSSL = (^bash -c "type -P cfssl") if "$CFSSL" == "" { let cfssl_install_bin = ($env.PROVISIONING | path join "core"| path join "bin" | path join "cfssl-install.sh") if ($cfssl_install_bin | path exists) { ^$cfssl_install_bin } $CFSSL = (^bash -c "type -P cfssl") } if "$CFSSL" == "" { print $"cfssl not installed " ; exit 1 } let CFSSLJSON = (^bash -c "type -P cfssljson") let csr_json_file = ($src | path join "csr.json") if not ($csr_json_file) { "{" | tee { save -f $csr_json_file } | ignore $"\"hosts\": [" | tee { save -a $csr_json_file } | ignore for server in $defs.defs.servers { let ip = ($server.network_private_ip | default "") if $ip == "" { continue } $"\"($server.hostname)\",\"($server.hostname).($domain_name)\",\"($ip)\"," | tee { save -a $csr_json_file } | ignore } if $source_name != "" and $source_name != $"($cluster_name).($domain_name)" { print $"\"($source_name)\","| tee { save -a ($src | path join "csr.json") } | ignore } $"\"${domain_name}\", \"$cluster_name\"],\"key\": {" | tee { save -a $csr_json_file } | ignore if $signature == "ECC" { $"\"algo\": \"ecdsa\",\"size\": ($long_sign) " | tee { save -a $csr_json_file } | ignore } else { $"\"algo\": \"rsa\",\"size\": ($long_sign) " | tee { save -a $csr_json_file } | ignore } $"}, \"names\": [{ \"C\":\"($etcd_c)\", \"CN\": \"($domain_name)\" }]" | tee { save -a $csr_json_file } | ignore $"}" | tee { save -a $csr_json_file } | ignore #sudo echo '{"CN":"CA","key":{"algo":"rsa","size":2048}}' | cfssl gencert -initca - | cfssljson -bare ca - #$sudo echo '{"signing":{"default":{"expiry":"43800h","usages":["signing","key encipherment","server auth","client auth"]}}}' \&ca-config.json } if not ( $"($cluster_name).key" | path exists) { cd $src if ((^($CFSSL) genkey -initca csr.json | ^($CFSSLJSON) -bare ca) | complete).exit_code == 0 { if ((^($CFSSL) gencert -ca ca.pem -ca-key ca-key.pem csr.json | ^($CFSSLJSON) -bare $cluster_name) | complete).exit_code == 0 { mv ca.pem ca.crt sudo mv ca-key.pem ca.key mv $"($cluster_name).pem" $"($cluster_name).crt" sudo mv $"($cluster_name)-key.pem" $"($cluster_name).key" for server in $defs.defs.servers { cp $"($cluster_name).crt" $"($server.hostname).crt" sudo cp $"($cluster_name).key" $"($server.hostname).key" } cd $ORG copy_certs $defs $src $run_root $cluster_name $signature } } cd $ORG } else { copy_certs $defs $src $run_root $cluster_name $signature } } export def make_certs [ defs: record src: string run_root: string cluster_name: string signature: string ssl_mode: string settings_root: string long_sign: int ] { if $signature == "" { print $"No signatures found" ; return 1 } if not ($src | path exists) { print $"Directory ($src) not found" ; return 1 } let hostname = ($defs.server.hostname | default "") if $hostname == "" { print $"hostname not found in ($env.PROVISIONING_VARS)" ; exit 1 } let servers_list = ($defs.defs.servers | select "hostname" | flatten | get -i "hostname") match $ssl_mode { "open" | "openssl" => { openssl_mode $defs $src $run_root $cluster_name $hostname $signature $long_sign }, "cf" | "cfssl" => { cfssl_mode $defs $src $run_root $cluster_name $hostname $signature $long_sign }, } } export def etcd_conf [ defs: record src: string run_root: string cluster_name: string signature: string ssl_mode: string ] { if not ($src | path exists) { mkdir $src } let domain_name = (get_domain_name $defs ($defs.taskserv.domain_name | default "")) let etcd_cn = ( $defs.taskserv.cn | default "") let source_name = $"($cluster_name | default "").($domain_name)" if $domain_name == "" or $domain_name == "" { print $"No names \( cluster_name and domain \) are defined" ; return 1 } if $env.PROVISIONING_DEBUG { print $"nodeport: ($defs.taskserv.peer_port) \nprotocol: ($defs.taskserv.etcd_protocol) \n" } let conf_alt_names_path = ($src | path join "openssl_conf_alt_names") let setup_tpl_path = ($src | path join "setup.tpl") mut n = 0 match $ssl_mode { "open"| "openssl" => { rm -f $conf_alt_names_path $setup_tpl_path if $defs.taskserv.use_localhost { if $env.PROVISIONING_DEBUG { print $"localhost: 127.0.0.1" } match $ssl_mode { "open"| "openssl" => { $n += 1 $"DNS.$n = localhost" | tee { save -a $conf_alt_names_path } | ignore $"IP.$n = 127.0.0.1" | tee { save -a $conf_alt_names_path } | ignore } } } $n += 1 $"DNS.($n) = ($cluster_name)" | tee { save -a $conf_alt_names_path } | ignore $n += 1 $"DNS.($n) = ($etcd_cn)" | tee { save -a $conf_alt_names_path } | ignore } } mut cluster_list = "" for server in $defs.defs.servers { let ip = ($server.network_private_ip | default "") if $ip == "" { continue } if $env.PROVISIONING_DEBUG { print $"($server.hostname): ($ip)" } if $cluster_list != "" { $cluster_list += "," } $cluster_list += $"($server.hostname)=($defs.taskserv.etcd_protocol)://($ip):($defs.taskserv.peer_port)" $n += 1 match $ssl_mode { "open"| "openssl" => { $"export Node($n)_IP=($ip)" | tee { save -a $setup_tpl_path } | ignore $"DNS.($n) = ($server.hostname)" | tee { save -a $conf_alt_names_path } | ignore $"IP.($n) = ($ip)" | tee { save -a $conf_alt_names_path } | ignore $n += 1 $"DNS.($n) = ($server.hostname).($domain_name)" | tee { save -a $conf_alt_names_path } | ignore } } } match $ssl_mode { "open"| "openssl" => { if $source_name != "" and $source_name != $"($cluster_name).($domain_name)" { $n += 1 print $"DNS.($n) = ($source_name)" | tee { save -a $conf_alt_names_path } | ignore } } } if $env.PROVISIONING_DEBUG { print $"\ncluster_list: ($cluster_list)" } return 0 } export def copy_certs [ defs: record src: string run_root: string cluster_name: string signature: string ] { print $"Copy certs to ($run_root) ..." let hostname = $defs.server.hostname if $hostname == "" { print $"hostname not found for ($env.PROVISIONING_VARS)" ; exit 1 } if (glob ($src | path join "*.csr") | length) > 0 { rm -f ...(glob ($src | path join "*.csr")) } if not ($run_root | path join "certs" | path exists) { mkdir ($run_root | path join "certs") } for name in [ ca $hostname $cluster_name] { if not ($src | path join $"($name).key" | path exists) { continue } if (sops_cmd "is_sops" ($src | path join $"($name).key")) { let content = (sops_cmd "decrypt" ($src | path join $"($name).key") --error_exit) if $content != "" { $content | save -f ($run_root | path join "certs" | path join $"($name).key") } } else { cp ($src | path join $"($name).key") ($run_root | path join "certs" | path join $"($name).key" ) sops_cmd "encrypt" ($src | path join $"($name).key") --error_exit | save -f ($src | path join $"($name).key") } chmod 400 ($src | path join $"($name).key") ($run_root | path join "certs" | path join $"($name).key") if ($src | path join $"($name).crt" | path exists) { cp ($src | path join $"($name).crt") ($run_root | path join "certs") } } if ($src | path join $"($cluster_name).crt" | path exists) { #if not ($run_root | path join "certs" | path join $"($cluster_name).crt" | path exists) { # cp ($src | path join $"($cluster_name).crt") ($run_root | path join "certs") #} if not ($run_root | path join "certs" | path join $"($hostname).crt" | path exists) { cp ($src | path join $"($cluster_name).crt") ($run_root | path join "certs" | path join $"($hostname).crt") } if not ($run_root | path join "certs" | path join $"($hostname).key" | path exists) { cp ($run_root | path join "certs" | path join $"($cluster_name).key") ($run_root | path join "certs" | path join $"($hostname).key") } print $"Certificate for ($hostname) signed ($signature) in ($src) copy to deployment" } if (glob ($run_root | path join "openssl.*") | length) > 0 { rm -r ...(glob ($run_root | path join "openssl.*")) } } def main [] { print $"(_ansi green_bold)ETCD(_ansi reset) with ($env.PROVISIONING_VARS?) " let run_root = $env.PROVISIONING_WK_ENV_PATH let defs = load_defs let src = ($env.PROVISIONING_SETTINGS_SRC_PATH | path join "resources" | path join $defs.taskserv.prov_path) if not ($env.PROVISIONING_SETTINGS_SRC_PATH | path join "resources" | path exists) { ^mkdir -p ($env.PROVISIONING_SETTINGS_SRC_PATH | path join "resources") } let provision_path = ($defs.taskserv.prov_path | default "" | str replace "~" $env.HOME) if $provision_path == "" { print $"🛑 prov_path not found taskserv definition" exit 1 } let cluster_name = $defs.taskserv.cluster_name | default "" if $cluster_name == "" { print $"🛑 cluster_name not foundi taskserv definition" exit 1 } let domain_name = (get_domain_name $defs ($defs.taskserv.domain_name | default "")) if $domain_name == "" { print $"🛑 domain_name nor found in settings" exit 1 } let source_name = $"($cluster_name | default "").($domain_name)" let settings_root = ($env.PROVISIONING_SETTINGS_SRC_PATH | default "" ) let signature = ($defs.taskserv.ssl_sign | default "") let ssl_mode = ($defs.taskserv.ssl_mode | default "") let long_sign = ($defs.taskserv.long_sign | default 0) if ($env.PROVISIONING_SETTINGS_SRC_PATH | path join $provision_path | path join $"($cluster_name).crt" | path exists) { copy_certs $defs $src $run_root $cluster_name $signature } else { if not ($env.PROVISIONING_SETTINGS_SRC_PATH | path join $provision_path | path exists) { ^mkdir -p ($env.PROVISIONING_SETTINGS_SRC_PATH | path join $provision_path) } etcd_conf $defs $src $run_root $cluster_name $signature $ssl_mode make_certs $defs $src $run_root $cluster_name $signature $ssl_mode $settings_root $long_sign } }