From b891d7767933887372c5f09cdbb792b089cd6965 Mon Sep 17 00:00:00 2001 From: georgejdli Date: Thu, 6 Sep 2018 10:14:18 -0500 Subject: [PATCH] add option to secure helm tiller with tls --- roles/kubernetes-apps/helm/defaults/main.yml | 18 +++ .../helm/files/helm-make-ssl.sh | 76 +++++++++++++ .../helm/tasks/gen_helm_tiller_certs.yml | 107 ++++++++++++++++++ roles/kubernetes-apps/helm/tasks/main.yml | 11 ++ 4 files changed, 212 insertions(+) create mode 100644 roles/kubernetes-apps/helm/files/helm-make-ssl.sh create mode 100644 roles/kubernetes-apps/helm/tasks/gen_helm_tiller_certs.yml diff --git a/roles/kubernetes-apps/helm/defaults/main.yml b/roles/kubernetes-apps/helm/defaults/main.yml index 15e26596a..5c6396b57 100644 --- a/roles/kubernetes-apps/helm/defaults/main.yml +++ b/roles/kubernetes-apps/helm/defaults/main.yml @@ -10,6 +10,24 @@ helm_deployment_type: host # Do not download the local repository cache on helm init helm_skip_refresh: false +# Secure Tiller installation with TLS +tiller_enable_tls: false +helm_config_dir: "{{ kube_config_dir }}/helm" +helm_script_dir: "{{ bin_dir }}/helm-scripts" + +# Store tiller release information as Secret instead of a ConfigMap +tiller_secure_release_info: false + +# Where private root key will be secured for TLS +helm_tiller_cert_dir: "{{ helm_config_dir }}/ssl" +tiller_tls_cert: "{{ helm_tiller_cert_dir }}/tiller.pem" +tiller_tls_key: "{{ helm_tiller_cert_dir }}/tiller-key.pem" +tiller_tls_ca_cert: "{{ helm_tiller_cert_dir }}/ca.pem" + +# Permission owner and group for helm client cert. Will be dependent on the helm_home_dir +helm_cert_group: root +helm_cert_owner: root + # Set URL for stable repository # helm_stable_repo_url: "https://kubernetes-charts.storage.googleapis.com" diff --git a/roles/kubernetes-apps/helm/files/helm-make-ssl.sh b/roles/kubernetes-apps/helm/files/helm-make-ssl.sh new file mode 100644 index 000000000..c4c9b060a --- /dev/null +++ b/roles/kubernetes-apps/helm/files/helm-make-ssl.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +usage() +{ + cat << EOF +Create self signed certificates + +Usage : $(basename $0) -f [-d ] + -h | --help : Show this message + -e | --helm-home : Helm home directory + -d | --ssldir : Directory where the certificates will be installed +EOF +} + +# Options parsing +while (($#)); do + case "$1" in + -h | --help) usage; exit 0;; + -e | --helm-home) HELM_HOME="${2}"; shift 2;; + -d | --ssldir) SSLDIR="${2}"; shift 2;; + *) + usage + echo "ERROR : Unknown option" + exit 3 + ;; + esac +done + +if [ -z ${SSLDIR} ]; then + SSLDIR="/etc/kubernetes/helm/ssl" +fi + +tmpdir=$(mktemp -d /tmp/helm_cacert.XXXXXX) +trap 'rm -rf "${tmpdir}"' EXIT +cd "${tmpdir}" + +mkdir -p "${SSLDIR}" + +# Root CA +if [ -e "$SSLDIR/ca-key.pem" ]; then + # Reuse existing CA + cp $SSLDIR/{ca.pem,ca-key.pem} . +else + openssl genrsa -out ca-key.pem 4096 > /dev/null 2>&1 + openssl req -x509 -new -nodes -key ca-key.pem -days 36500 -out ca.pem -subj "/CN=tiller-ca" > /dev/null 2>&1 +fi + +gen_key_and_cert() { + local name=$1 + local subject=$2 + openssl genrsa -out ${name}-key.pem 4096 > /dev/null 2>&1 + openssl req -new -key ${name}-key.pem -sha256 -out ${name}.csr -subj "${subject}" > /dev/null 2>&1 + openssl x509 -req -in ${name}.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out ${name}.pem -days 36500 > /dev/null 2>&1 +} + +#Generate cert and key for Tiller if they don't exist +if ! [ -e "$SSLDIR/tiller.pem" ]; then + gen_key_and_cert "tiller" "/CN=tiller-server" +fi + +#Generate cert and key for Helm client if they dont exist +if ! [ -e "$SSLDIR/helm.pem" ]; then + gen_key_and_cert "helm" "/CN=helm-client" +fi + +# Secure certs to first master +mv *.pem ${SSLDIR}/ + +# Install Helm client certs to first master +# Copy using Helm default names for convenience +cp ${SSLDIR}/ca.pem ${HELM_HOME}/ca.pem +cp ${SSLDIR}/helm.pem ${HELM_HOME}/cert.pem +cp ${SSLDIR}/helm-key.pem ${HELM_HOME}/key.pem diff --git a/roles/kubernetes-apps/helm/tasks/gen_helm_tiller_certs.yml b/roles/kubernetes-apps/helm/tasks/gen_helm_tiller_certs.yml new file mode 100644 index 000000000..86a0c7451 --- /dev/null +++ b/roles/kubernetes-apps/helm/tasks/gen_helm_tiller_certs.yml @@ -0,0 +1,107 @@ +--- +- name: "Gen_helm_tiller_certs | Create helm config directory (on {{groups['kube-master'][0]}})" + run_once: yes + delegate_to: "{{groups['kube-master'][0]}}" + file: + path: "{{ helm_config_dir }}" + state: directory + owner: kube + +- name: "Gen_helm_tiller_certs | Create helm script directory (on {{groups['kube-master'][0]}})" + run_once: yes + delegate_to: "{{groups['kube-master'][0]}}" + file: + path: "{{ helm_script_dir }}" + state: directory + owner: kube + +- name: Gen_helm_tiller_certs | Copy certs generation script + run_once: yes + delegate_to: "{{groups['kube-master'][0]}}" + copy: + src: "helm-make-ssl.sh" + dest: "{{ helm_script_dir }}/helm-make-ssl.sh" + mode: 0700 + +- name: "Check_helm_certs | check if helm client certs have already been generated on first master (on {{groups['kube-master'][0]}})" + find: + paths: "{{ helm_home_dir }}" + patterns: "*.pem" + get_checksum: true + delegate_to: "{{groups['kube-master'][0]}}" + register: helmcert_master + run_once: true + +- name: Gen_helm_tiller_certs | run cert generation script + run_once: yes + delegate_to: "{{groups['kube-master'][0]}}" + command: "{{ helm_script_dir }}/helm-make-ssl.sh -e {{ helm_home_dir }} -d {{ helm_tiller_cert_dir }}" + +- set_fact: + helm_client_certs: ['ca.pem', 'cert.pem', 'key.pem'] + +- name: "Check_helm_client_certs | check if a cert already exists on master node" + find: + paths: "{{ helm_home_dir }}" + patterns: "*.pem" + get_checksum: true + register: helmcert_node + when: inventory_hostname != groups['kube-master'][0] + +- name: "Check_helm_client_certs | Set 'sync_helm_certs' to true on masters" + set_fact: + sync_helm_certs: true + when: inventory_hostname != groups['kube-master'][0] and + (not item in helmcert_node.files | map(attribute='path') | map("basename") | list or + helmcert_node.files | selectattr("path", "equalto", "{{ helm_home_dir }}/{{ item }}") | map(attribute="checksum")|first|default('') != helmcert_master.files | selectattr("path", "equalto", "{{ helm_home_dir }}/{{ item }}") | map(attribute="checksum")|first|default('')) + with_items: + - "{{ helm_client_certs }}" + +- name: Gen_helm_tiller_certs | Gather helm client certs + shell: "tar cfz - -C {{ helm_home_dir }} -T /dev/stdin <<< {{ helm_client_certs|join(' ') }} | base64 --wrap=0" + args: + executable: /bin/bash + no_log: true + register: helm_client_cert_data + check_mode: no + delegate_to: "{{groups['kube-master'][0]}}" + when: sync_helm_certs|default(false) and inventory_hostname != groups['kube-master'][0] + +- name: Gen_helm_tiller_certs | Use tempfile for unpacking certs on masters + tempfile: + state: file + path: /tmp + prefix: helmcertsXXXXX + suffix: tar.gz + register: helm_cert_tempfile + when: sync_helm_certs|default(false) and inventory_hostname != groups['kube-master'][0] + +- name: Gen_helm_tiller_certs | Write helm client certs to tempfile + copy: + content: "{{helm_client_cert_data.stdout}}" + dest: "{{helm_cert_tempfile.path}}" + owner: root + mode: "0600" + when: sync_helm_certs|default(false) and inventory_hostname != groups['kube-master'][0] + +- name: Gen_helm_tiller_certs | Unpack helm certs on masters + shell: "base64 -d < {{ helm_cert_tempfile.path }} | tar xz -C {{ helm_home_dir }}" + no_log: true + changed_when: false + check_mode: no + when: sync_helm_certs|default(false) and inventory_hostname != groups['kube-master'][0] + +- name: Gen_helm_tiller_certs | Cleanup tempfile on masters + file: + path: "{{helm_cert_tempfile.path}}" + state: absent + when: sync_helm_certs|default(false) and inventory_hostname != groups['kube-master'][0] + +- name: Gen_certs | check certificate permissions + file: + path: "{{ helm_home_dir }}" + group: "{{ helm_cert_group }}" + state: directory + owner: "{{ helm_cert_owner }}" + mode: "u=rwX,g-rwx,o-rwx" + recurse: yes diff --git a/roles/kubernetes-apps/helm/tasks/main.yml b/roles/kubernetes-apps/helm/tasks/main.yml index 158eff5ca..bae058f26 100644 --- a/roles/kubernetes-apps/helm/tasks/main.yml +++ b/roles/kubernetes-apps/helm/tasks/main.yml @@ -27,6 +27,11 @@ with_items: "{{ manifests.results }}" when: dns_mode != 'none' and inventory_hostname == groups['kube-master'][0] +# Generate necessary certs for securing Helm and Tiller connection with TLS +- name: Helm | Set up TLS + include_tasks: "gen_helm_tiller_certs.yml" + when: tiller_enable_tls + - name: Helm | Install/upgrade helm command: > {{ bin_dir }}/helm init --upgrade --tiller-image={{ tiller_image_repo }}:{{ tiller_image_tag }} --tiller-namespace={{ tiller_namespace }} @@ -36,8 +41,11 @@ {% if tiller_node_selectors is defined %} --node-selectors {{ tiller_node_selectors }}{% endif %} {% if tiller_override is defined %} --override {{ tiller_override }}{% endif %} {% if tiller_max_history is defined %} --history-max={{ tiller_max_history }}{% endif %} + {% if tiller_enable_tls %} --tiller-tls --tiller-tls-verify --tiller-tls-cert={{ tiller_tls_cert }} --tiller-tls-key={{ tiller_tls_key }} --tls-ca-cert={{ tiller_tls_ca_cert }} {% endif %} + {% if tiller_secure_release_info %} --override 'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}' {% endif %} register: install_helm changed_when: false + environment: "{{proxy_env}}" # FIXME: https://github.com/helm/helm/issues/4063 - name: Helm | Force apply tiller overrides if necessary @@ -49,9 +57,12 @@ {% if tiller_node_selectors is defined %} --node-selectors {{ tiller_node_selectors }}{% endif %} {% if tiller_override is defined %} --override {{ tiller_override }}{% endif %} {% if tiller_max_history is defined %} --history-max={{ tiller_max_history }}{% endif %} + {% if tiller_enable_tls %} --tiller-tls --tiller-tls-verify --tiller-tls-cert={{ tiller_tls_cert }} --tiller-tls-key={{ tiller_tls_key }} --tls-ca-cert={{ tiller_tls_ca_cert }} {% endif %} + {% if tiller_secure_release_info %} --override 'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}' {% endif %} | kubectl apply -f - changed_when: false when: tiller_override is defined + environment: "{{proxy_env}}" - name: Helm | Set up bash completion shell: "umask 022 && {{ bin_dir }}/helm completion bash >/etc/bash_completion.d/helm.sh"