From efa180392b8e7cca75cac8e11534b55f99cb9ee4 Mon Sep 17 00:00:00 2001
From: Etienne Champetier <e.champetier@ateme.com>
Date: Mon, 22 Mar 2021 14:22:48 -0400
Subject: [PATCH] Auto renew control plane certificates (#7358)

While at it remove force_certificate_regeneration
This boolean only forced the renewal of the apiserver certs
Either manually use k8s-certs-renew.sh or set auto_renew_certificates

Signed-off-by: Etienne Champetier <e.champetier@ateme.com>
---
 .../group_vars/k8s-cluster/k8s-cluster.yml    |  5 ++--
 .../control-plane/defaults/main/main.yml      |  5 ++--
 .../control-plane/tasks/kubeadm-setup.yml     |  4 ++--
 roles/kubernetes/control-plane/tasks/main.yml | 24 +++++++++++++++++++
 .../templates/k8s-certs-renew.service.j2      |  6 +++++
 .../templates/k8s-certs-renew.sh.j2           | 23 ++++++++++++++++++
 .../templates/k8s-certs-renew.timer.j2        |  9 +++++++
 roles/reset/tasks/main.yml                    |  3 +++
 8 files changed, 73 insertions(+), 6 deletions(-)
 create mode 100644 roles/kubernetes/control-plane/templates/k8s-certs-renew.service.j2
 create mode 100644 roles/kubernetes/control-plane/templates/k8s-certs-renew.sh.j2
 create mode 100644 roles/kubernetes/control-plane/templates/k8s-certs-renew.timer.j2

diff --git a/inventory/sample/group_vars/k8s-cluster/k8s-cluster.yml b/inventory/sample/group_vars/k8s-cluster/k8s-cluster.yml
index c3ec03e7f..fe1c184e6 100644
--- a/inventory/sample/group_vars/k8s-cluster/k8s-cluster.yml
+++ b/inventory/sample/group_vars/k8s-cluster/k8s-cluster.yml
@@ -329,5 +329,6 @@ persistent_volumes_enabled: false
 
 ## Amount of time to retain events. (default 1h0m0s)
 event_ttl_duration: "1h0m0s"
-##  Force regeneration of kubernetes control plane certificates without the need of bumping the cluster version
-force_certificate_regeneration: false
+
+## Automatically renew K8S control plane certificates on first Monday of each month
+auto_renew_certificates: false
diff --git a/roles/kubernetes/control-plane/defaults/main/main.yml b/roles/kubernetes/control-plane/defaults/main/main.yml
index 24c1ddff0..c671326dd 100644
--- a/roles/kubernetes/control-plane/defaults/main/main.yml
+++ b/roles/kubernetes/control-plane/defaults/main/main.yml
@@ -194,5 +194,6 @@ secrets_encryption_query: "resources[*].providers[0].{{kube_encryption_algorithm
 
 ## Amount of time to retain events. (default 1h0m0s)
 event_ttl_duration: "1h0m0s"
-##  Force regeneration of kubernetes control plane certificates without the need of bumping the cluster version
-force_certificate_regeneration: false
+
+## Automatically renew K8S control plane certificates on first Monday of each month
+auto_renew_certificates: false
diff --git a/roles/kubernetes/control-plane/tasks/kubeadm-setup.yml b/roles/kubernetes/control-plane/tasks/kubeadm-setup.yml
index eab2fff1a..5a51a24be 100644
--- a/roles/kubernetes/control-plane/tasks/kubeadm-setup.yml
+++ b/roles/kubernetes/control-plane/tasks/kubeadm-setup.yml
@@ -99,7 +99,7 @@
   when:
     - inventory_hostname == groups['kube-master']|first
     - kubeadm_already_run.stat.exists
-    - apiserver_sans_check.changed or force_certificate_regeneration
+    - apiserver_sans_check.changed
 
 - name: kubeadm | regenerate apiserver cert 2/2
   command: >-
@@ -109,7 +109,7 @@
   when:
     - inventory_hostname == groups['kube-master']|first
     - kubeadm_already_run.stat.exists
-    - apiserver_sans_check.changed or force_certificate_regeneration
+    - apiserver_sans_check.changed
 
 - name: kubeadm | Initialize first master
   command: >-
diff --git a/roles/kubernetes/control-plane/tasks/main.yml b/roles/kubernetes/control-plane/tasks/main.yml
index 8bfc8d75d..6fba951c2 100644
--- a/roles/kubernetes/control-plane/tasks/main.yml
+++ b/roles/kubernetes/control-plane/tasks/main.yml
@@ -66,3 +66,27 @@
 - name: Include kubelet client cert rotation fixes
   include_tasks: kubelet-fix-client-cert-rotation.yml
   when: kubelet_rotate_certificates
+
+- name: Install script to renew K8S control plane certificates
+  template:
+    src: k8s-certs-renew.sh.j2
+    dest: "{{ bin_dir }}/k8s-certs-renew.sh"
+    mode: '755'
+
+- name: Renew K8S control plane certificates monthly 1/2
+  template:
+    src: "{{ item }}.j2"
+    dest: "/etc/systemd/system/{{ item }}"
+  with_items:
+    - k8s-certs-renew.service
+    - k8s-certs-renew.timer
+  register: k8s_certs_units
+  when: auto_renew_certificates
+
+- name: Renew K8S control plane certificates monthly 2/2
+  systemd:
+    name: k8s-certs-renew.timer
+    enabled: yes
+    state: started
+    daemon-reload: "{{ k8s_certs_units is changed }}"
+  when: auto_renew_certificates
diff --git a/roles/kubernetes/control-plane/templates/k8s-certs-renew.service.j2 b/roles/kubernetes/control-plane/templates/k8s-certs-renew.service.j2
new file mode 100644
index 000000000..64610c2bc
--- /dev/null
+++ b/roles/kubernetes/control-plane/templates/k8s-certs-renew.service.j2
@@ -0,0 +1,6 @@
+[Unit]
+Description=Renew K8S control plane certificates
+
+[Service]
+Type=oneshot
+ExecStart={{ bin_dir }}/k8s-certs-renew.sh
diff --git a/roles/kubernetes/control-plane/templates/k8s-certs-renew.sh.j2 b/roles/kubernetes/control-plane/templates/k8s-certs-renew.sh.j2
new file mode 100644
index 000000000..41656e3bb
--- /dev/null
+++ b/roles/kubernetes/control-plane/templates/k8s-certs-renew.sh.j2
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+echo "## Expiration before renewal ##"
+{{ bin_dir }}/kubeadm certs check-expiration
+
+echo "## Renewing certificates managed by kubeadm ##"
+{{ bin_dir }}/kubeadm certs renew all
+
+echo "## Restarting control plane pods managed by kubeadm ##"
+{% if container_manager == "docker" %}
+{{ docker_bin_dir }}/docker ps -af 'name=k8s_POD_(kube-apiserver|kube-controller-manager|kube-scheduler|etcd)-*' -q | /usr/bin/xargs {{ docker_bin_dir }}/docker rm -f"
+{% else %}
+{{ bin_dir }}/crictl pods --namespace kube-system --name 'kube-scheduler-*|kube-controller-manager-*|kube-apiserver-*|etcd-*' -q | /usr/bin/xargs {{ bin_dir }}/crictl rmp -f
+{% endif %}
+
+echo "## Updating /root/.kube/config ##"
+/usr/bin/cp {{ kube_config_dir }}/admin.conf /root/.kube/config
+
+echo "## Waiting for apiserver to be up again ##"
+until printf "" 2>>/dev/null >>/dev/tcp/127.0.0.1/6443; do sleep 1; done
+
+echo "## Expiration after renewal ##"
+{{ bin_dir }}/kubeadm certs check-expiration
diff --git a/roles/kubernetes/control-plane/templates/k8s-certs-renew.timer.j2 b/roles/kubernetes/control-plane/templates/k8s-certs-renew.timer.j2
new file mode 100644
index 000000000..3c5e0c18f
--- /dev/null
+++ b/roles/kubernetes/control-plane/templates/k8s-certs-renew.timer.j2
@@ -0,0 +1,9 @@
+[Unit]
+Description=Timer to renew K8S control plane certificates
+
+[Timer]
+# First Monday of each month
+OnCalendar=Mon *-*-1..7 03:{{ groups['kube-master'].index(inventory_hostname) }}0:00
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/reset/tasks/main.yml b/roles/reset/tasks/main.yml
index 1a78788f7..9a65c1135 100644
--- a/roles/reset/tasks/main.yml
+++ b/roles/reset/tasks/main.yml
@@ -21,6 +21,8 @@
     - containerd.service.d/http-proxy.conf
     - crio.service.d/http-proxy.conf
     - vault.service.d/http-proxy.conf
+    - k8s-certs-renew.service
+    - k8s-certs-renew.timer
   register: services_removed
   tags:
     - services
@@ -292,6 +294,7 @@
     - "{{ bin_dir }}/weave"
     - "{{ bin_dir }}/crictl"
     - "{{ bin_dir }}/netctl"
+    - "{{ bin_dir }}/k8s-certs-renew.sh"
     - /var/lib/cni
     - /etc/openvswitch
     - /run/openvswitch
-- 
GitLab