From bf0af1cd3d1c7648b4b731b9dc46aa4f053641fd Mon Sep 17 00:00:00 2001 From: mkrasilnikov Date: Fri, 1 Sep 2017 22:51:37 +0300 Subject: [PATCH] Vault role updates: * using separated vault roles for generate certs with different `O` (Organization) subject field; * configure vault roles for issuing certificates with different `CN` (Common name) subject field; * set `CN` and `O` to `kubernetes` and `etcd` certificates; * vault/defaults vars definition was simplified; * vault dirs variables defined in kubernetes-defaults foles for using shared tasks in etcd and kubernetes/secrets roles; * upgrade vault to 0.8.1; * generate random vault user password for each role by default; * fix `serial` file name for vault certs; * move vault auth request to issue_cert tasks; * enable `RBAC` in vault CI; --- .gitlab-ci.yml | 1 + roles/etcd/tasks/gen_certs_vault.yml | 41 +--- .../secrets/tasks/gen_certs_vault.yml | 57 ++---- .../secrets/tasks/sync_kube_master_certs.yml | 6 +- roles/kubespray-defaults/defaults/main.yaml | 7 + roles/vault/defaults/main.yml | 187 ++++++++++++------ .../tasks/bootstrap/create_etcd_role.yml | 17 -- roles/vault/tasks/bootstrap/create_mounts.yml | 12 ++ roles/vault/tasks/bootstrap/create_roles.yml | 10 + .../vault/tasks/bootstrap/gen_vault_certs.yml | 20 +- roles/vault/tasks/bootstrap/main.yml | 80 +++----- roles/vault/tasks/cluster/create_mounts.yml | 13 ++ roles/vault/tasks/cluster/create_roles.yml | 14 +- roles/vault/tasks/cluster/main.yml | 44 ++--- roles/vault/tasks/shared/create_role.yml | 5 +- roles/vault/tasks/shared/gen_ca.yml | 4 +- roles/vault/tasks/shared/gen_userpass.yml | 1 - roles/vault/tasks/shared/issue_cert.yml | 45 ++++- 18 files changed, 283 insertions(+), 281 deletions(-) delete mode 100644 roles/vault/tasks/bootstrap/create_etcd_role.yml create mode 100644 roles/vault/tasks/bootstrap/create_mounts.yml create mode 100644 roles/vault/tasks/bootstrap/create_roles.yml create mode 100644 roles/vault/tasks/cluster/create_mounts.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 864cadde5..465b7ac57 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -366,6 +366,7 @@ before_script: .ubuntu_vault_sep_variables: &ubuntu_vault_sep_variables # stage: deploy-gce-part1 + AUTHORIZATION_MODES: "{ 'authorization_modes': [ 'RBAC' ] }" KUBE_NETWORK_PLUGIN: canal CERT_MGMT: vault CLOUD_IMAGE: ubuntu-1604-xenial diff --git a/roles/etcd/tasks/gen_certs_vault.yml b/roles/etcd/tasks/gen_certs_vault.yml index 4f27eff86..0048a7003 100644 --- a/roles/etcd/tasks/gen_certs_vault.yml +++ b/roles/etcd/tasks/gen_certs_vault.yml @@ -7,51 +7,14 @@ when: inventory_hostname in etcd_node_cert_hosts tags: etcd-secrets -- name: gen_certs_vault | Read in the local credentials - command: cat /etc/vault/roles/etcd/userpass - register: etcd_vault_creds_cat - delegate_to: "{{ groups['vault'][0] }}" - -- name: gen_certs_vault | Set facts for read Vault Creds - set_fact: - etcd_vault_creds: "{{ etcd_vault_creds_cat.stdout|from_json }}" - delegate_to: "{{ groups['vault'][0] }}" - -- name: gen_certs_vault | Log into Vault and obtain an token - uri: - url: "{{ hostvars[groups.vault|first]['vault_leader_url'] }}/v1/auth/userpass/login/{{ etcd_vault_creds.username }}" - headers: - Accept: application/json - Content-Type: application/json - method: POST - body_format: json - body: - password: "{{ etcd_vault_creds.password }}" - register: etcd_vault_login_result - delegate_to: "{{ groups['vault'][0] }}" - -- name: gen_certs_vault | Set fact for vault_client_token - set_fact: - vault_client_token: "{{ etcd_vault_login_result.get('json', {}).get('auth', {}).get('client_token') }}" - run_once: true - -- name: gen_certs_vault | Set fact for Vault API token - set_fact: - etcd_vault_headers: - Accept: application/json - Content-Type: application/json - X-Vault-Token: "{{ vault_client_token }}" - run_once: true - when: vault_client_token != "" - # Issue master certs to Etcd nodes - include: ../../vault/tasks/shared/issue_cert.yml vars: + issue_cert_common_name: "etcd:master:{{ item.rsplit('/', 1)[1].rsplit('.', 1)[0] }}" issue_cert_alt_names: "{{ groups.etcd + ['localhost'] }}" issue_cert_copy_ca: "{{ item == etcd_master_certs_needed|first }}" issue_cert_file_group: "{{ etcd_cert_group }}" issue_cert_file_owner: kube - issue_cert_headers: "{{ etcd_vault_headers }}" issue_cert_hosts: "{{ groups.etcd }}" issue_cert_ip_sans: >- [ @@ -74,11 +37,11 @@ # Issue node certs to everyone else - include: ../../vault/tasks/shared/issue_cert.yml vars: + issue_cert_common_name: "etcd:node:{{ item.rsplit('/', 1)[1].rsplit('.', 1)[0] }}" issue_cert_alt_names: "{{ etcd_node_cert_hosts }}" issue_cert_copy_ca: "{{ item == etcd_node_certs_needed|first }}" issue_cert_file_group: "{{ etcd_cert_group }}" issue_cert_file_owner: kube - issue_cert_headers: "{{ etcd_vault_headers }}" issue_cert_hosts: "{{ etcd_node_cert_hosts }}" issue_cert_ip_sans: >- [ diff --git a/roles/kubernetes/secrets/tasks/gen_certs_vault.yml b/roles/kubernetes/secrets/tasks/gen_certs_vault.yml index 31abdbf5b..82535fd20 100644 --- a/roles/kubernetes/secrets/tasks/gen_certs_vault.yml +++ b/roles/kubernetes/secrets/tasks/gen_certs_vault.yml @@ -1,56 +1,23 @@ --- - include: sync_kube_master_certs.yml when: inventory_hostname in groups['kube-master'] - tags: k8s-secrets - include: sync_kube_node_certs.yml when: inventory_hostname in groups['k8s-cluster'] - tags: k8s-secrets -- name: gen_certs_vault | Read in the local credentials - command: cat /etc/vault/roles/kube/userpass - register: kube_vault_creds_cat - delegate_to: "{{ groups['k8s-cluster'][0] }}" - -- name: gen_certs_vault | Set facts for read Vault Creds - set_fact: - kube_vault_creds: "{{ kube_vault_creds_cat.stdout|from_json }}" - delegate_to: "{{ groups['k8s-cluster'][0] }}" - -- name: gen_certs_vault | Log into Vault and obtain an token - uri: - url: "{{ hostvars[groups['vault'][0]]['vault_leader_url'] }}/v1/auth/userpass/login/{{ kube_vault_creds.username }}" - headers: - Accept: application/json - Content-Type: application/json - method: POST - body_format: json - body: - password: "{{ kube_vault_creds.password }}" - register: kube_vault_login_result - delegate_to: "{{ groups['k8s-cluster'][0] }}" - -- name: gen_certs_vault | Set fact for Vault API token - set_fact: - kube_vault_headers: - Accept: application/json - Content-Type: application/json - X-Vault-Token: "{{ kube_vault_login_result.get('json',{}).get('auth', {}).get('client_token') }}" - run_once: true - -# Issue certs to kube-master nodes +# Issue admin certs to kube-master hosts - include: ../../../vault/tasks/shared/issue_cert.yml vars: - issue_cert_copy_ca: "{{ item == kube_master_certs_needed|first }}" + issue_cert_common_name: "admin:{{ item.rsplit('/', 1)[1].rsplit('.', 1)[0] }}" + issue_cert_copy_ca: "{{ item == kube_admin_certs_needed|first }}" issue_cert_file_group: "{{ kube_cert_group }}" issue_cert_file_owner: kube - issue_cert_headers: "{{ kube_vault_headers }}" issue_cert_hosts: "{{ groups['kube-master'] }}" issue_cert_path: "{{ item }}" - issue_cert_role: kube + issue_cert_role: kube-master issue_cert_url: "{{ hostvars[groups.vault|first]['vault_leader_url'] }}" issue_cert_mount_path: "{{ kube_vault_mount_path }}" - with_items: "{{ kube_master_certs_needed|d([]) }}" + with_items: "{{ kube_admin_certs_needed|d([]) }}" when: inventory_hostname in groups['kube-master'] - name: gen_certs_vault | Set fact about certificate alt names @@ -69,12 +36,13 @@ when: loadbalancer_apiserver is defined and apiserver_loadbalancer_domain_name is defined run_once: true +# Issue master components certs to kube-master hosts - include: ../../../vault/tasks/shared/issue_cert.yml vars: + issue_cert_common_name: "kubernetes" issue_cert_alt_names: "{{ kube_cert_alt_names }}" issue_cert_file_group: "{{ kube_cert_group }}" issue_cert_file_owner: kube - issue_cert_headers: "{{ kube_vault_headers }}" issue_cert_hosts: "{{ groups['kube-master'] }}" issue_cert_ip_sans: >- [ @@ -87,7 +55,7 @@ "127.0.0.1","::1","{{ kube_apiserver_ip }}" ] issue_cert_path: "{{ item }}" - issue_cert_role: kube + issue_cert_role: kube-master issue_cert_url: "{{ hostvars[groups.vault|first]['vault_leader_url'] }}" issue_cert_mount_path: "{{ kube_vault_mount_path }}" with_items: "{{ kube_master_components_certs_needed|d([]) }}" @@ -97,27 +65,28 @@ # Issue node certs to k8s-cluster nodes - include: ../../../vault/tasks/shared/issue_cert.yml vars: + issue_cert_common_name: "system:node:{{ item.rsplit('/', 1)[1].rsplit('.', 1)[0] }}" issue_cert_copy_ca: "{{ item == kube_node_certs_needed|first }}" issue_cert_file_group: "{{ kube_cert_group }}" issue_cert_file_owner: kube - issue_cert_headers: "{{ kube_vault_headers }}" issue_cert_hosts: "{{ groups['k8s-cluster'] }}" issue_cert_path: "{{ item }}" - issue_cert_role: kube + issue_cert_role: kube-node issue_cert_url: "{{ hostvars[groups.vault|first]['vault_leader_url'] }}" issue_cert_mount_path: "{{ kube_vault_mount_path }}" with_items: "{{ kube_node_certs_needed|d([]) }}" when: inventory_hostname in groups['k8s-cluster'] +# Issue proxy certs to k8s-cluster nodes - include: ../../../vault/tasks/shared/issue_cert.yml vars: + issue_cert_common_name: "system:kube-proxy:{{ item.rsplit('/', 1)[1].rsplit('.', 1)[0] }}" issue_cert_copy_ca: "{{ item == kube_proxy_certs_needed|first }}" issue_cert_file_group: "{{ kube_cert_group }}" issue_cert_file_owner: kube - issue_cert_headers: "{{ kube_vault_headers }}" issue_cert_hosts: "{{ groups['k8s-cluster'] }}" issue_cert_path: "{{ item }}" - issue_cert_role: kube + issue_cert_role: kube-proxy issue_cert_url: "{{ hostvars[groups.vault|first]['vault_leader_url'] }}" issue_cert_mount_path: "{{ kube_vault_mount_path }}" with_items: "{{ kube_proxy_certs_needed|d([]) }}" diff --git a/roles/kubernetes/secrets/tasks/sync_kube_master_certs.yml b/roles/kubernetes/secrets/tasks/sync_kube_master_certs.yml index 6fa861a36..277038612 100644 --- a/roles/kubernetes/secrets/tasks/sync_kube_master_certs.yml +++ b/roles/kubernetes/secrets/tasks/sync_kube_master_certs.yml @@ -2,7 +2,7 @@ - name: sync_kube_master_certs | Create list of needed kube admin certs set_fact: - kube_master_cert_list: "{{ kube_master_cert_list|d([]) + ['admin-' + item + '.pem'] }}" + kube_admin_cert_list: "{{ kube_admin_cert_list|d([]) + ['admin-' + item + '.pem'] }}" with_items: "{{ groups['kube-master'] }}" - include: ../../../vault/tasks/shared/sync_file.yml @@ -13,11 +13,11 @@ sync_file_hosts: "{{ groups['kube-master'] }}" sync_file_is_cert: true sync_file_owner: kube - with_items: "{{ kube_master_cert_list|d([]) }}" + with_items: "{{ kube_admin_cert_list|d([]) }}" - name: sync_kube_master_certs | Set facts for kube admin sync_file results set_fact: - kube_master_certs_needed: "{{ kube_master_certs_needed|default([]) + [item.path] }}" + kube_admin_certs_needed: "{{ kube_admin_certs_needed|default([]) + [item.path] }}" with_items: "{{ sync_file_results|d([]) }}" when: item.no_srcs|bool diff --git a/roles/kubespray-defaults/defaults/main.yaml b/roles/kubespray-defaults/defaults/main.yaml index 5405e2577..c86f322fc 100644 --- a/roles/kubespray-defaults/defaults/main.yaml +++ b/roles/kubespray-defaults/defaults/main.yaml @@ -135,3 +135,10 @@ rbac_enabled: "{{ 'RBAC' in authorization_modes }}" ## List of key=value pairs that describe feature gates for ## the k8s cluster. kube_feature_gates: [] + +# Vault data dirs. +vault_base_dir: /etc/vault +vault_cert_dir: "{{ vault_base_dir }}/ssl" +vault_config_dir: "{{ vault_base_dir }}/config" +vault_roles_dir: "{{ vault_base_dir }}/roles" +vault_secrets_dir: "{{ vault_base_dir }}/secrets" diff --git a/roles/vault/defaults/main.yml b/roles/vault/defaults/main.yml index eb2ffd122..d4e2ed66c 100644 --- a/roles/vault/defaults/main.yml +++ b/roles/vault/defaults/main.yml @@ -1,4 +1,6 @@ --- +vault_bootstrap: false +vault_deployment_type: docker vault_adduser_vars: comment: "Hashicorp Vault User" @@ -6,41 +8,18 @@ vault_adduser_vars: name: vault shell: /sbin/nologin system: yes + +# This variables redefined in kubespray-defaults for using shared tasks +# in etcd and kubernetes/secrets roles vault_base_dir: /etc/vault -# https://releases.hashicorp.com/vault/0.6.4/vault_0.6.4_SHA256SUMS -vault_version: 0.6.4 -vault_binary_checksum: 04d87dd553aed59f3fe316222217a8d8777f40115a115dac4d88fac1611c51a6 -vault_bootstrap: false -vault_ca_options: - common_name: vault - format: pem - ttl: 87600h vault_cert_dir: "{{ vault_base_dir }}/ssl" -vault_client_headers: - Accept: "application/json" - Content-Type: "application/json" -vault_config: - backend: - etcd: - address: "{{ vault_etcd_url }}" - ha_enabled: "true" - redirect_addr: "https://{{ ansible_default_ipv4.address }}:{{ vault_port }}" - tls_ca_file: "{{ vault_etcd_cert_dir }}/ca.pem" - cluster_name: "kubernetes-vault" - default_lease_ttl: "{{ vault_default_lease_ttl }}" - listener: - tcp: - address: "0.0.0.0:{{ vault_port }}" - tls_cert_file: "{{ vault_cert_dir }}/api.pem" - tls_key_file: "{{ vault_cert_dir }}/api-key.pem" - max_lease_ttl: "{{ vault_max_lease_ttl }}" vault_config_dir: "{{ vault_base_dir }}/config" -vault_container_name: kube-hashicorp-vault -# This variable is meant to match the GID of vault inside Hashicorp's official Vault Container -vault_default_lease_ttl: 720h -vault_default_role_permissions: - allow_any_name: true -vault_deployment_type: docker +vault_roles_dir: "{{ vault_base_dir }}/roles" +vault_secrets_dir: "{{ vault_base_dir }}/secrets" +vault_log_dir: "/var/log/vault" + +vault_version: 0.8.1 +vault_binary_checksum: 3c4d70ba71619a43229e65c67830e30e050eab7a81ac6b28325ff707e5914188 vault_download_url: "https://releases.hashicorp.com/vault/{{ vault_version }}/vault_{{ vault_version }}_linux_amd64.zip" vault_download_vars: container: "{{ vault_deployment_type != 'host' }}" @@ -55,17 +34,19 @@ vault_download_vars: unarchive: true url: "{{ vault_download_url }}" version: "{{ vault_version }}" -vault_etcd_url: "https://{{ hostvars[groups.etcd[0]]['ip']|d(hostvars[groups.etcd[0]]['ansible_default_ipv4']['address']) }}:2379" + +vault_container_name: kube-hashicorp-vault +vault_temp_container_name: vault-temp vault_image_repo: "vault" vault_image_tag: "{{ vault_version }}" -vault_log_dir: "/var/log/vault" -vault_max_lease_ttl: 87600h -vault_needs_gen: false + +vault_address: 0.0.0.0 vault_port: 8200 -vault_roles_dir: "{{ vault_base_dir }}/roles" -vault_secret_shares: 1 -vault_secret_threshold: 1 -vault_secrets_dir: "{{ vault_base_dir }}/secrets" +vault_etcd_url: "https://{{ hostvars[groups.etcd[0]]['ip']|d(hostvars[groups.etcd[0]]['ansible_default_ipv4']['address']) }}:2379" + +vault_default_lease_ttl: 720h +vault_max_lease_ttl: 87600h + vault_temp_config: backend: file: @@ -73,29 +54,109 @@ vault_temp_config: default_lease_ttl: "{{ vault_default_lease_ttl }}" listener: tcp: - address: "0.0.0.0:{{ vault_port }}" + address: "{{ vault_address }}:{{ vault_port }}" tls_disable: "true" max_lease_ttl: "{{ vault_max_lease_ttl }}" -vault_temp_container_name: vault-temp -# etcd pki mount options + +vault_config: + backend: + etcd: + address: "{{ vault_etcd_url }}" + ha_enabled: "true" + redirect_addr: "https://{{ ansible_default_ipv4.address }}:{{ vault_port }}" + tls_ca_file: "{{ vault_etcd_cert_dir }}/ca.pem" + cluster_name: "kubernetes-vault" + default_lease_ttl: "{{ vault_default_lease_ttl }}" + max_lease_ttl: "{{ vault_max_lease_ttl }}" + listener: + tcp: + address: "{{ vault_address }}:{{ vault_port }}" + tls_cert_file: "{{ vault_cert_dir }}/api.pem" + tls_key_file: "{{ vault_cert_dir }}/api-key.pem" + +vault_secret_shares: 1 +vault_secret_threshold: 1 + +vault_ca_options: + vault: + common_name: vault + format: pem + ttl: "{{ vault_max_lease_ttl }}" + exclude_cn_from_sans: true + etcd: + common_name: etcd + format: pem + ttl: "{{ vault_max_lease_ttl }}" + exclude_cn_from_sans: true + kube: + common_name: kube + format: pem + ttl: "{{ vault_max_lease_ttl }}" + exclude_cn_from_sans: true + +vault_client_headers: + Accept: "application/json" + Content-Type: "application/json" + vault_etcd_cert_dir: /etc/ssl/etcd/ssl -vault_etcd_mount_path: etcd -vault_etcd_default_lease_ttl: 720h -vault_etcd_max_lease_ttl: 87600h -vault_etcd_role: - name: etcd - group: etcd - policy_rules: default - role_options: default - mount_path: "{{ vault_etcd_mount_path }}" -# kubernetes pki mount options -vault_kube_cert_dir: "{{ kube_cert_dir }}" -vault_kube_mount_path: kube -vault_kube_default_lease_ttl: 720h -vault_kube_max_lease_ttl: 87600h -vault_kube_role: - name: kube - group: k8s-cluster - policy_rules: default - role_options: default - mount_path: "{{ vault_kube_mount_path }}" +vault_kube_cert_dir: /etc/kubernetes/ssl + +vault_pki_mounts: + vault: + name: vault + default_lease_ttl: "{{ vault_default_lease_ttl }}" + max_lease_ttl: "{{ vault_max_lease_ttl }}" + description: "Vault Root CA" + cert_dir: "{{ vault_cert_dir }}" + roles: + - name: vault + group: vault + password: "{{ lookup('pipe','date +%Y%m%d%H%M%S' + cluster_name + 'vault') | to_uuid }}" + policy_rules: default + role_options: default + etcd: + name: etcd + default_lease_ttl: "{{ vault_default_lease_ttl }}" + max_lease_ttl: "{{ vault_max_lease_ttl }}" + description: "Etcd Root CA" + cert_dir: "{{ vault_etcd_cert_dir }}" + roles: + - name: etcd + group: etcd + password: "{{ lookup('pipe','date +%Y%m%d%H%M%S' + cluster_name + 'etcd') | to_uuid }}" + policy_rules: default + role_options: + allow_any_name: true + enforce_hostnames: false + organization: "kube:etcd" + kube: + name: kube + default_lease_ttl: "{{ vault_default_lease_ttl }}" + max_lease_ttl: "{{ vault_max_lease_ttl }}" + description: "Kubernetes Root CA" + cert_dir: "{{ vault_kube_cert_dir }}" + roles: + - name: kube-master + group: kube-master + password: "{{ lookup('pipe','date +%Y%m%d%H%M%S' + cluster_name + 'kube-master') | to_uuid }}" + policy_rules: default + role_options: + allow_any_name: true + enforce_hostnames: false + organization: "system:masters" + - name: kube-node + group: k8s-cluster + password: "{{ lookup('pipe','date +%Y%m%d%H%M%S' + cluster_name + 'kube-node') | to_uuid }}" + policy_rules: default + role_options: + allow_any_name: true + enforce_hostnames: false + organization: "system:nodes" + - name: kube-proxy + group: k8s-cluster + password: "{{ lookup('pipe', 'date +%Y%m%d%H%M%S' + cluster_name + 'kube-proxy') | to_uuid }}" + policy_rules: default + role_options: + allow_any_name: true + enforce_hostnames: false + organization: "system:node-proxier" diff --git a/roles/vault/tasks/bootstrap/create_etcd_role.yml b/roles/vault/tasks/bootstrap/create_etcd_role.yml deleted file mode 100644 index 74cd5fc2f..000000000 --- a/roles/vault/tasks/bootstrap/create_etcd_role.yml +++ /dev/null @@ -1,17 +0,0 @@ ---- -- include: ../shared/auth_backend.yml - vars: - auth_backend_description: A Username/Password Auth Backend primarily used for services needing to issue certificates - auth_backend_path: userpass - auth_backend_type: userpass - delegate_to: "{{ groups.vault|first }}" - run_once: true - -- include: ../shared/create_role.yml - vars: - create_role_name: "{{ vault_etcd_role.name }}" - create_role_group: "{{ vault_etcd_role.group }}" - create_role_policy_rules: "{{ vault_etcd_role.policy_rules }}" - create_role_options: "{{ vault_etcd_role.role_options }}" - create_role_mount_path: "{{ vault_etcd_role.mount_path }}" - when: inventory_hostname in groups.etcd diff --git a/roles/vault/tasks/bootstrap/create_mounts.yml b/roles/vault/tasks/bootstrap/create_mounts.yml new file mode 100644 index 000000000..0010c35c5 --- /dev/null +++ b/roles/vault/tasks/bootstrap/create_mounts.yml @@ -0,0 +1,12 @@ +--- +- include: ../shared/create_mount.yml + vars: + create_mount_path: "{{ item.name }}" + create_mount_default_lease_ttl: "{{ item.default_lease_ttl }}" + create_mount_max_lease_ttl: "{{ item.max_lease_ttl }}" + create_mount_description: "{{ item.description }}" + create_mount_cert_dir: "{{ item.cert_dir }}" + create_mount_config_ca_needed: "{{ item.config_ca }}" + with_items: + - "{{ vault_pki_mounts.vault|combine({'config_ca': not vault_ca_cert_needed}) }}" + - "{{ vault_pki_mounts.etcd|combine({'config_ca': not vault_etcd_ca_cert_needed}) }}" diff --git a/roles/vault/tasks/bootstrap/create_roles.yml b/roles/vault/tasks/bootstrap/create_roles.yml new file mode 100644 index 000000000..11411d236 --- /dev/null +++ b/roles/vault/tasks/bootstrap/create_roles.yml @@ -0,0 +1,10 @@ +--- +- include: ../shared/create_role.yml + vars: + create_role_name: "{{ item.name }}" + create_role_group: "{{ item.group }}" + create_role_policy_rules: "{{ item.policy_rules }}" + create_role_password: "{{ item.password }}" + create_role_options: "{{ item.role_options }}" + create_role_mount_path: "{{ mount.name }}" + with_items: "{{ mount.roles }}" diff --git a/roles/vault/tasks/bootstrap/gen_vault_certs.yml b/roles/vault/tasks/bootstrap/gen_vault_certs.yml index 651c2ac49..ce4538571 100644 --- a/roles/vault/tasks/bootstrap/gen_vault_certs.yml +++ b/roles/vault/tasks/bootstrap/gen_vault_certs.yml @@ -1,29 +1,21 @@ --- - -- name: boostrap/gen_vault_certs | Add the vault role - uri: - url: "{{ vault_leader_url }}/v1/{{ vault_ca_options.common_name }}/roles/vault" - headers: "{{ vault_headers }}" - method: POST - body_format: json - body: "{{ vault_default_role_permissions }}" - status_code: 204 - when: inventory_hostname == groups.vault|first and vault_api_cert_needed - - include: ../shared/issue_cert.yml vars: + issue_cert_common_name: "{{ vault_pki_mounts.vault.roles[0].name }}" issue_cert_alt_names: "{{ groups.vault + ['localhost'] }}" issue_cert_hosts: "{{ groups.vault }}" issue_cert_ip_sans: >- [ {%- for host in groups.vault -%} "{{ hostvars[host]['ansible_default_ipv4']['address'] }}", + {%- if hostvars[host]['ip'] is defined -%} + "{{ hostvars[host]['ip'] }}", + {%- endif -%} {%- endfor -%} "127.0.0.1","::1" ] - issue_cert_mount_path: "{{ vault_ca_options.common_name }}" + issue_cert_mount_path: "{{ vault_pki_mounts.vault.name }}" issue_cert_path: "{{ vault_cert_dir }}/api.pem" - issue_cert_headers: "{{ hostvars[groups.vault|first]['vault_headers'] }}" - issue_cert_role: vault + issue_cert_role: "{{ vault_pki_mounts.vault.roles[0].name }}" issue_cert_url: "{{ vault_leader_url }}" when: vault_api_cert_needed diff --git a/roles/vault/tasks/bootstrap/main.yml b/roles/vault/tasks/bootstrap/main.yml index 768d9e03b..88e5c2050 100644 --- a/roles/vault/tasks/bootstrap/main.yml +++ b/roles/vault/tasks/bootstrap/main.yml @@ -1,5 +1,4 @@ --- - - include: ../shared/check_vault.yml when: inventory_hostname in groups.vault @@ -9,72 +8,57 @@ - include: ../shared/find_leader.yml when: inventory_hostname in groups.vault and vault_cluster_is_initialized|d() -## Sync Certs - - include: sync_vault_certs.yml when: inventory_hostname in groups.vault - include: sync_etcd_certs.yml when: inventory_hostname in groups.etcd -## Generate Certs - -# Start a temporary instance of Vault - include: start_vault_temp.yml - when: >- - inventory_hostname == groups.vault|first and - not vault_cluster_is_initialized + when: inventory_hostname == groups.vault|first and not vault_cluster_is_initialized -# Set vault_leader_url for all nodes based on above -- name: vault | bootstrap +- name: vault | Set fact about vault leader url set_fact: vault_leader_url: "{{ hostvars[groups.vault|first]['vault_leader_url'] }}" when: not vault_cluster_is_initialized -# Ensure vault PKI mounts exists -- include: ../shared/create_mount.yml - vars: - create_mount_path: "{{ vault_ca_options.common_name }}" - create_mount_default_lease_ttl: "{{ vault_default_lease_ttl }}" - create_mount_max_lease_ttl: "{{ vault_max_lease_ttl }}" - create_mount_description: "Vault Root CA" - create_mount_cert_dir: "{{ vault_cert_dir }}" - create_mount_config_ca_needed: "{{ not vault_ca_cert_needed }}" +- include: create_mounts.yml when: inventory_hostname == groups.vault|first -# Generate root CA certs for Vault if none exist +- include: ../shared/auth_backend.yml + vars: + auth_backend_description: A Username/Password Auth Backend primarily used for services needing to issue certificates + auth_backend_path: userpass + auth_backend_type: userpass + when: inventory_hostname == groups.vault|first + +- include: create_roles.yml + with_items: + - "{{ vault_pki_mounts.vault }}" + - "{{ vault_pki_mounts.etcd }}" + loop_control: + loop_var: mount + - include: ../shared/gen_ca.yml vars: - gen_ca_cert_dir: "{{ vault_cert_dir }}" - gen_ca_mount_path: "{{ vault_ca_options.common_name }}" + gen_ca_cert_dir: "{{ vault_pki_mounts.vault.cert_dir }}" + gen_ca_mount_path: "{{ vault_pki_mounts.vault.name }}" + gen_ca_vault_headers: "{{ vault_headers }}" + gen_ca_vault_options: "{{ vault_ca_options.vault }}" when: >- - inventory_hostname in groups.vault and - not vault_cluster_is_initialized and - vault_ca_cert_needed + inventory_hostname in groups.vault + and not vault_cluster_is_initialized + and vault_ca_cert_needed + +- include: ../shared/gen_ca.yml + vars: + gen_ca_cert_dir: "{{ vault_pki_mounts.etcd.cert_dir }}" + gen_ca_mount_path: "{{ vault_pki_mounts.etcd.name }}" + gen_ca_vault_headers: "{{ vault_headers }}" + gen_ca_vault_options: "{{ vault_ca_options.etcd }}" + when: inventory_hostname in groups.etcd and vault_etcd_ca_cert_needed -# Generate Vault API certs - include: gen_vault_certs.yml when: inventory_hostname in groups.vault and vault_api_cert_needed -# Ensure etcd PKI mounts exists -- include: ../shared/create_mount.yml - vars: - create_mount_path: "{{ vault_etcd_mount_path }}" - create_mount_default_lease_ttl: "{{ vault_etcd_default_lease_ttl }}" - create_mount_max_lease_ttl: "{{ vault_etcd_max_lease_ttl }}" - create_mount_description: "Etcd Root CA" - create_mount_cert_dir: "{{ vault_etcd_cert_dir }}" - create_mount_config_ca_needed: "{{ not vault_etcd_ca_cert_needed }}" - when: inventory_hostname == groups.vault|first - -# Generate root CA certs for etcd if none exist -- include: ../shared/gen_ca.yml - vars: - gen_ca_cert_dir: "{{ vault_etcd_cert_dir }}" - gen_ca_mount_path: "{{ vault_etcd_mount_path }}" - when: inventory_hostname in groups.etcd and vault_etcd_ca_cert_needed - -- include: create_etcd_role.yml - -# Update all host's CA bundle, etcd CA will be added in etcd role - include: ca_trust.yml diff --git a/roles/vault/tasks/cluster/create_mounts.yml b/roles/vault/tasks/cluster/create_mounts.yml new file mode 100644 index 000000000..b1be8c9fe --- /dev/null +++ b/roles/vault/tasks/cluster/create_mounts.yml @@ -0,0 +1,13 @@ +--- +- include: ../shared/create_mount.yml + vars: + create_mount_path: "{{ item.name }}" + create_mount_default_lease_ttl: "{{ item.default_lease_ttl }}" + create_mount_max_lease_ttl: "{{ item.max_lease_ttl }}" + create_mount_description: "{{ item.description }}" + create_mount_cert_dir: "{{ item.cert_dir }}" + create_mount_config_ca_needed: "{{ item.name != vault_pki_mounts.kube.name }}" + with_items: + - "{{ vault_pki_mounts.vault }}" + - "{{ vault_pki_mounts.etcd }}" + - "{{ vault_pki_mounts.kube }}" diff --git a/roles/vault/tasks/cluster/create_roles.yml b/roles/vault/tasks/cluster/create_roles.yml index 54aae815d..9314bfa84 100644 --- a/roles/vault/tasks/cluster/create_roles.yml +++ b/roles/vault/tasks/cluster/create_roles.yml @@ -1,18 +1,10 @@ --- -- include: ../shared/auth_backend.yml - vars: - auth_backend_description: A Username/Password Auth Backend primarily used for services needing to issue certificates - auth_backend_path: userpass - auth_backend_type: userpass - when: inventory_hostname == groups.vault|first - - include: ../shared/create_role.yml vars: create_role_name: "{{ item.name }}" create_role_group: "{{ item.group }}" + create_role_password: "{{ item.password }}" create_role_policy_rules: "{{ item.policy_rules }}" create_role_options: "{{ item.role_options }}" - create_role_mount_path: "{{ item.mount_path }}" - with_items: - - "{{ vault_etcd_role }}" - - "{{ vault_kube_role }}" + create_role_mount_path: "{{ vault_pki_mounts.kube.name }}" + with_items: "{{ vault_pki_mounts.kube.roles }}" diff --git a/roles/vault/tasks/cluster/main.yml b/roles/vault/tasks/cluster/main.yml index 7a5acba0a..bca6da5be 100644 --- a/roles/vault/tasks/cluster/main.yml +++ b/roles/vault/tasks/cluster/main.yml @@ -5,8 +5,6 @@ - include: ../shared/check_etcd.yml when: inventory_hostname in groups.vault -## Vault Cluster Setup - - include: configure.yml when: inventory_hostname in groups.vault @@ -25,42 +23,22 @@ - include: ../shared/find_leader.yml when: inventory_hostname in groups.vault -- include: ../shared/create_mount.yml - vars: - create_mount_path: "{{ vault_ca_options.common_name }}" - create_mount_default_lease_ttl: "{{ vault_default_lease_ttl }}" - create_mount_max_lease_ttl: "{{ vault_max_lease_ttl }}" - create_mount_description: "Vault Root CA" - create_mount_cert_dir: "{{ vault_cert_dir }}" - create_mount_config_ca_needed: true - when: inventory_hostname == groups.vault|first - -- include: ../shared/create_mount.yml - vars: - create_mount_path: "{{ vault_etcd_mount_path }}" - create_mount_default_lease_ttl: "{{ vault_etcd_default_lease_ttl }}" - create_mount_max_lease_ttl: "{{ vault_etcd_max_lease_ttl }}" - create_mount_description: "Etcd Root CA" - create_mount_cert_dir: "{{ vault_etcd_cert_dir }}" - create_mount_config_ca_needed: true - when: inventory_hostname == groups.vault|first - -- include: ../shared/create_mount.yml - vars: - create_mount_path: "{{ vault_kube_mount_path }}" - create_mount_default_lease_ttl: "{{ vault_kube_default_lease_ttl }}" - create_mount_max_lease_ttl: "{{ vault_kube_max_lease_ttl }}" - create_mount_description: "Kubernetes Root CA" - create_mount_cert_dir: "{{ vault_kube_cert_dir }}" - create_mount_config_ca_needed: false +- include: create_mounts.yml when: inventory_hostname == groups.vault|first - include: ../shared/gen_ca.yml vars: - gen_ca_cert_dir: "{{ vault_kube_cert_dir }}" - gen_ca_mount_path: "{{ vault_kube_mount_path }}" + gen_ca_cert_dir: "{{ vault_pki_mounts.kube.cert_dir }}" + gen_ca_mount_path: "{{ vault_pki_mounts.kube.name }}" + gen_ca_vault_headers: "{{ vault_headers }}" + gen_ca_vault_options: "{{ vault_ca_options.kube }}" when: inventory_hostname in groups.vault -## Vault Policies, Roles, and Auth Backends +- include: ../shared/auth_backend.yml + vars: + auth_backend_description: A Username/Password Auth Backend primarily used for services needing to issue certificates + auth_backend_path: userpass + auth_backend_type: userpass + when: inventory_hostname == groups.vault|first - include: create_roles.yml diff --git a/roles/vault/tasks/shared/create_role.yml b/roles/vault/tasks/shared/create_role.yml index fae45207a..dc9b5e1c6 100644 --- a/roles/vault/tasks/shared/create_role.yml +++ b/roles/vault/tasks/shared/create_role.yml @@ -1,5 +1,4 @@ --- - # The JSON inside JSON here is intentional (Vault API wants it) - name: create_role | Create a policy for the new role allowing issuing uri: @@ -22,7 +21,7 @@ status_code: 204 when: inventory_hostname == groups[create_role_group]|first -- name: create_role | Create the new role in the {{ create_role_mount_path }} pki mount +- name: create_role | Create {{ create_role_name }} role in the {{ create_role_mount_path }} pki mount uri: url: "{{ hostvars[groups.vault|first]['vault_leader_url'] }}/v1/{{ create_role_mount_path }}/roles/{{ create_role_name }}" headers: "{{ hostvars[groups.vault|first]['vault_headers'] }}" @@ -42,7 +41,7 @@ - include: gen_userpass.yml vars: gen_userpass_group: "{{ create_role_group }}" - gen_userpass_password: "{{ create_role_password|d(''|to_uuid) }}" + gen_userpass_password: "{{ create_role_password }}" gen_userpass_policies: "{{ create_role_name }}" gen_userpass_role: "{{ create_role_name }}" gen_userpass_username: "{{ create_role_name }}" diff --git a/roles/vault/tasks/shared/gen_ca.yml b/roles/vault/tasks/shared/gen_ca.yml index b80ebeb6b..291f42734 100644 --- a/roles/vault/tasks/shared/gen_ca.yml +++ b/roles/vault/tasks/shared/gen_ca.yml @@ -8,10 +8,10 @@ - name: "bootstrap/gen_ca | Generate {{ gen_ca_mount_path }} root CA" uri: url: "{{ vault_leader_url }}/v1/{{ gen_ca_mount_path }}/root/generate/exported" - headers: "{{ vault_headers }}" + headers: "{{ gen_ca_vault_headers }}" method: POST body_format: json - body: "{{ vault_ca_options }}" + body: "{{ gen_ca_vault_options }}" register: vault_ca_gen delegate_to: "{{ groups.vault|first }}" run_once: true diff --git a/roles/vault/tasks/shared/gen_userpass.yml b/roles/vault/tasks/shared/gen_userpass.yml index 4ef301171..2bc0c98f1 100644 --- a/roles/vault/tasks/shared/gen_userpass.yml +++ b/roles/vault/tasks/shared/gen_userpass.yml @@ -1,5 +1,4 @@ --- - - name: shared/gen_userpass | Create the Username/Password combo for the role uri: url: "{{ hostvars[groups.vault|first]['vault_leader_url'] }}/v1/auth/userpass/users/{{ gen_userpass_username }}" diff --git a/roles/vault/tasks/shared/issue_cert.yml b/roles/vault/tasks/shared/issue_cert.yml index fa09bfd2b..0b14d77c5 100644 --- a/roles/vault/tasks/shared/issue_cert.yml +++ b/roles/vault/tasks/shared/issue_cert.yml @@ -11,7 +11,6 @@ # issue_cert_file_mode: Mode of the placed cert file # issue_cert_file_owner: Owner of the placed cert file and directory # issue_cert_format: Format for returned data. Can be pem, der, or pem_bundle -# issue_cert_headers: Headers passed into the issue request # issue_cert_hosts: List of hosts to distribute the cert to # issue_cert_ip_sans: Requested IP Subject Alternative Names, in a list # issue_cert_mount_path: Mount point in Vault to make the request to @@ -27,7 +26,47 @@ mode: "{{ issue_cert_dir_mode | d('0755') }}" owner: "{{ issue_cert_file_owner | d('root') }}" -- name: "issue_cert | Generate the cert for {{ issue_cert_role }}" +- name: "issue_cert | Read in the local credentials" + command: cat {{ vault_roles_dir }}/{{ issue_cert_role }}/userpass + register: vault_creds_cat + delegate_to: "{{ issue_cert_hosts|first }}" + run_once: true + +- name: gen_certs_vault | Set facts for read Vault Creds + set_fact: + user_vault_creds: "{{ vault_creds_cat.stdout|from_json }}" + delegate_to: "{{ issue_cert_hosts|first }}" + run_once: true + +- name: gen_certs_vault | Log into Vault and obtain an token + uri: + url: "{{ hostvars[groups.vault|first]['vault_leader_url'] }}/v1/auth/userpass/login/{{ user_vault_creds.username }}" + headers: + Accept: application/json + Content-Type: application/json + method: POST + body_format: json + body: + password: "{{ user_vault_creds.password }}" + register: vault_login_result + delegate_to: "{{ issue_cert_hosts|first }}" + run_once: true + +- name: gen_certs_vault | Set fact for vault_client_token + set_fact: + vault_client_token: "{{ vault_login_result.get('json', {}).get('auth', {}).get('client_token') }}" + run_once: true + +- name: gen_certs_vault | Set fact for Vault API token + set_fact: + issue_cert_headers: + Accept: application/json + Content-Type: application/json + X-Vault-Token: "{{ vault_client_token }}" + run_once: true + when: vault_client_token != "" + +- name: "issue_cert | Generate {{ issue_cert_path }} for {{ issue_cert_role }} role" uri: url: "{{ issue_cert_url }}/v1/{{ issue_cert_mount_path|d('pki') }}/issue/{{ issue_cert_role }}" headers: "{{ issue_cert_headers }}" @@ -70,7 +109,7 @@ - name: issue_cert | Copy certificate serial to all hosts copy: content: "{{ hostvars[issue_cert_hosts|first]['issue_cert_result']['json']['data']['serial_number'] }}" - dest: "{{ issue_cert_path.rsplit('.', 1)|first }}.serial }}" + dest: "{{ issue_cert_path.rsplit('.', 1)|first }}.serial" group: "{{ issue_cert_file_group | d('root' )}}" mode: "{{ issue_cert_file_mode | d('0640') }}" owner: "{{ issue_cert_file_owner | d('root') }}"