diff --git a/.drone.yml b/.drone.yml index 24e911fe..9a49e596 100644 --- a/.drone.yml +++ b/.drone.yml @@ -28,3 +28,42 @@ steps: - "git remote set-url origin git@github.com:ViktorBarzin/infra.git" - "git commit -m 'Drone CI deploy commit' || echo 'No changes'" - "GIT_SSH_COMMAND='ssh -i ./secrets/deploy_key -o IdentitiesOnly=yes' git push origin master" + +--- +kind: pipeline +type: kubernetes +name: renew-tls-certificate +trigger: + event: + - cron + cron: + - nightly + +steps: + - name: Prepare terraform files + image: alpine + commands: + - "apk update && apk add jq curl git git-crypt" + - | + curl -k https://kubernetes:6443/api/v1/namespaces/drone/configmaps/git-crypt-key -H "Authorization:Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" | jq -r .data.key | base64 -d > /tmp/key + - "git-crypt unlock /tmp/key" + - name: Run renew script + image: alpine + environment: + TF_VAR_prod: "true" + commands: + - "apk update && apk add git certbot expect curl gzip" + # Install terraform cli + - "curl https://releases.hashicorp.com/terraform/0.14.6/terraform_0.14.6_linux_amd64.zip | gzip -d > /usr/local/bin/terraform && chmod 775 /usr/local/bin/terraform" + - "terraform init" + - "./modules/kubernetes/setup_tls_secret/renew.sh" + - name: Commit updated certificates + image: alpine + commands: + - "apk update && apk add openssh-client git git-crypt" + - "mkdir ~/.ssh && ssh-keyscan -H github.com >> ~/.ssh/known_hosts" + - 'chmod 400 secrets/deploy_key' + - "git add ." + - "git remote set-url origin git@github.com:ViktorBarzin/infra.git" + - "git commit -m 'Drone CI Update TLS Certificates Commit' || echo 'No changes'" + - "GIT_SSH_COMMAND='ssh -i ./secrets/deploy_key -o IdentitiesOnly=yes' git push origin master" diff --git a/modules/kubernetes/bind/extra/viktorbarzin.me b/modules/kubernetes/bind/extra/viktorbarzin.me new file mode 100644 index 00000000..2acd3e4f --- /dev/null +++ b/modules/kubernetes/bind/extra/viktorbarzin.me @@ -0,0 +1,5 @@ +; additional bind records added via terraform automation +; entries are usually programmatically added to this file + +_acme-challenge IN TXT "6TiErEakwCp5Ryy3ICW8XVm_uGCUU9UhNZv4XFFnOkI" + diff --git a/modules/kubernetes/bind/main.tf b/modules/kubernetes/bind/main.tf index c8b75338..c11a7934 100644 --- a/modules/kubernetes/bind/main.tf +++ b/modules/kubernetes/bind/main.tf @@ -16,7 +16,7 @@ resource "kubernetes_config_map" "bind_configmap" { data = { "db.viktorbarzin.lan" = var.db_viktorbarzin_lan - "db.viktorbarzin.me" = var.db_viktorbarzin_me + "db.viktorbarzin.me" = format("%s%s", var.db_viktorbarzin_me, file("${path.module}/extra/viktorbarzin.me")) "named.conf" = var.named_conf "named.conf.local" = var.named_conf_local "named.conf.options" = var.named_conf_options diff --git a/modules/kubernetes/setup_tls_secret/main.tf b/modules/kubernetes/setup_tls_secret/main.tf index 3ad16d9e..a5d11383 100644 --- a/modules/kubernetes/setup_tls_secret/main.tf +++ b/modules/kubernetes/setup_tls_secret/main.tf @@ -1,7 +1,11 @@ -variable namespace {} -variable tls_secret_name {} -variable tls_crt {} -variable tls_key {} +variable "namespace" {} +variable "tls_secret_name" {} +variable "tls_crt" { + default = "" +} +variable "tls_key" { + default = "" +} resource "kubernetes_secret" "tls_secret" { metadata { @@ -9,8 +13,9 @@ resource "kubernetes_secret" "tls_secret" { namespace = var.namespace } data = { - "tls.crt" = var.tls_crt - "tls.key" = var.tls_key + # Cannot set default function in variable so use default behaviour here + "tls.crt" = var.tls_crt == "" ? file("${path.root}/secrets/fullchain.pem") : var.tls_crt + "tls.key" = var.tls_key == "" ? file("${path.root}/secrets/privkey.pem") : var.tls_key } type = "kubernetes.io/tls" } diff --git a/modules/kubernetes/setup_tls_secret/renew.sh b/modules/kubernetes/setup_tls_secret/renew.sh new file mode 100755 index 00000000..4ff4ae3e --- /dev/null +++ b/modules/kubernetes/setup_tls_secret/renew.sh @@ -0,0 +1,79 @@ +#!/usr/bin/expect -f + +set timeout -1 +set le_dir "/tmp/le/" +set config_dir "$le_dir/out/config" +set pwd [pwd] + +spawn certbot certonly --manual --preferred-challenge=dns --email me@viktorbarzin.me --server https://acme-v02.api.letsencrypt.org/directory --agree-tos --manual-public-ip-logging-ok -d *.viktorbarzin.me -d viktorbarzin.me --config-dir $config_dir --work-dir $le_dir/workdir --logs-dir $le_dir/logsdir --no-eff-email + +set prompt "$" +set dns_file "$pwd/modules/kubernetes/bind/extra/viktorbarzin.me" +expect -re "Before continuing, verify" { + set challenge [ exec sh -c "echo '$expect_out(buffer)' | tail -n 3 | head -n 1" ] + set dns_record "_acme-challenge IN TXT \"$challenge\"" + puts $dns_record + puts $dns_file + + # Check if dns record is not already present + try { + set results [exec grep -q $dns_record $dns_file] + set status 0 + } trap CHILDSTATUS {results options} { + set status [lindex [dict get $options -errorcode] 2] + } + if {$status != 0} { + exec echo $dns_record | tee -a $dns_file + puts "Teed into file" + } else { + puts "DNS record '$dns_record' already in file" + } +} + +send -- "\r" +# Do the same for the 2nd dns record +expect -re "Before continuing, verify" { + set challenge [ exec sh -c "echo '$expect_out(buffer)' | tail -n 3 | head -n 1" ] + set dns_record "_acme-challenge IN TXT \"$challenge\"" + puts $dns_record + puts $dns_file + + # Check if dns record is not already present + try { + set results [exec grep -q $dns_record $dns_file] + set status 0 + } trap CHILDSTATUS {results options} { + set status [lindex [dict get $options -errorcode] 2] + } + if {$status != 0} { + exec echo $dns_record | tee -a $dns_file + puts "Teed into file" + } else { + puts "DNS record '$dns_record' already in file" + } +} + +# Force deployment recreation +exec terraform taint module.kubernetes_cluster.module.bind.module.bind-public-deployment.kubernetes_deployment.bind +# Apply changes to configmap and redeploy +exec >@stdout 2>@stderr terraform apply -auto-approve -target=module.kubernetes_cluster.module.bind + +# Wait for deployment update +# TODO: better to use k8s api. What we want is `kubectl rollout status deployment -l app=bind-public` as a curl +# exec bash -c 'while [[ $(kubectl get pods -l app=bind-public -o \'jsonpath={..status.conditions[\?(\@.type=="Ready")].status}\') != "True" ]]; do echo "waiting pod..." && sleep 1; done' +exec >@stdout echo 'Waiting for redeployment of bind...' +exec sleep 10 + +send -- "\r" + +# Clean up +exec sed -i "s/$dns_record//g" "$dns_file" + +# Success +expect ".*Congratulations!" + +# Copy cert and key to secrets dir +exec cp --remove-destination $config_dir/live/viktorbarzin.me/fullchain.pem ./secrets +exec cp --remove-destination $config_dir/live/viktorbarzin.me/privkey.pem ./secrets + +puts "Done renewing cert. Output certificates stored in ./secrets\n" diff --git a/secrets/fullchain.pem b/secrets/fullchain.pem new file mode 100644 index 00000000..60d91fdb Binary files /dev/null and b/secrets/fullchain.pem differ diff --git a/secrets/privkey.pem b/secrets/privkey.pem new file mode 100644 index 00000000..af948f75 Binary files /dev/null and b/secrets/privkey.pem differ diff --git a/terraform.tfstate b/terraform.tfstate index 1bed4093..636149fa 100644 Binary files a/terraform.tfstate and b/terraform.tfstate differ diff --git a/terraform.tfvars b/terraform.tfvars index d48292de..0c9416e2 100644 Binary files a/terraform.tfvars and b/terraform.tfvars differ