feat(TLS): add tls features to vault and trust hosts store on containers

This commit is contained in:
Bertrand Lanson 2024-04-06 23:47:35 +02:00
parent 812a2bb04a
commit 1a83036506
16 changed files with 255 additions and 162 deletions

View File

@ -1,125 +0,0 @@
---
##########################
# General options ########
##########################
# enable_vault: "yes"
# enable_consul: "yes"
# enable_nomad: "yes"
# deployment_method: "docker"
# api_interface: "eth0"
# api_interface_address: "{{ ansible_facts[api_interface]['ipv4']['address'] }}"
# configuration_directory: "{{ lookup('env', 'PWD') }}/etc/hashistack"
##########################
# Support options ########
##########################
# hashistack_supported_distributions:
# - ubuntu
# - debian
# hashistack_supported_distribution_versions:
# debian:
# - "11"
# - "12"
# ubuntu:
# - "20.04"
# - "22.04"
# preflight_enable_host_ntp_checks: true
# vault_required_ports: [8200,8201]
# consul_required_ports: [8300,8301,8302,8500,8501,8502,8503,8600]
# nomad_required_ports: []
##########################
# Nomad options ##########
##########################
# hashi_nomad_cni_plugins_install: true
# hashi_nomad_start_service: true
# hashi_nomad_cni_plugins_version: latest
# hashi_nomad_cni_plugins_install_path: /opt/cni/bin
# hashi_nomad_version: latest
# hashi_nomad_deploy_method: host # deployment method, either host or docker
# hashi_nomad_env_variables: {}
# hashi_nomad_data_dir: /opt/nomad
# hashi_nomad_extra_files: false
# hashi_nomad_extra_files_src: /tmp/extra_files
# hashi_nomad_extra_files_dst: /etc/nomad.d/extra_files
# #! nomad configuration
# hashi_nomad_configuration: {}
##########################
# Consul options #########
##########################
# hashi_consul_start_service: true
# hashi_consul_version: latest
# hashi_consul_deploy_method: host # deployment method, either host or docker.
# hashi_consul_env_variables: {}
# hashi_consul_data_dir: "/opt/consul"
# hashi_consul_extra_files: false
# hashi_consul_extra_files_src: /tmp/extra_files
# hashi_consul_extra_files_dst: /etc/consul.d/extra_files
# hashi_consul_envoy_install: false
# hashi_consul_envoy_version: latest
# #! consul configuration
# hashi_consul_configuration: {}
##########################
# Vault options ##########
##########################
# vault_cluster_name: vault
# vault_storage_configuration:
# raft:
# path: "{{ hashi_vault_data_dir }}/data"
# node_id: "{{ ansible_hostname }}"
# retry_join: |
# [
# {% for host in groups['vault_servers'] %}
# {
# 'leader_api_addr': 'http://{{ hostvars[host].api_interface_address }}:8200'
# }{% if not loop.last %},{% endif %}
# {% endfor %}
# ]
# extra_vault_container_volumes: []
# default_container_extra_volumes:
# - "/etc/timezone:/etc/timezone"
# - "/etc/localtime:/etc/localtime"
# hashi_vault_start_service: true
hashi_vault_version: "latest"
# hashi_vault_deploy_method: "{{ deployment_method }}" # deployment method, either host or docker
# hashi_vault_env_variables: {}
# hashi_vault_data_dir: "/opt/vault"
# hashi_vault_extra_files: false
# hashi_vault_extra_files_src: /tmp/extra_files
# hashi_vault_extra_files_dst: /etc/vault.d/extra_files
# hashi_vault_extra_container_volumes: "{{ default_container_extra_volumes | union(extra_vault_container_volumes) | unique }}"
##! vault configuration
# hashi_vault_configuration:
# cluster_name: "{{ vault_cluster_name }}"
# cluster_addr: "http://{{ api_interface_address }}:8201"
# api_addr: "http://{{ api_interface_address }}:8200"
# ui: true
# disable_mlock: false
# disable_cache: false
# listener:
# tcp:
# address: "0.0.0.0:8200"
# tls_disable: true
# #tls_disable_client_certs: true
# #tls_cert_file: "{{ hashi_vault_data_dir }}/tls/cert.pem"
# #tls_key_file: "{{ hashi_vault_data_dir }}/tls/key.pem"
# storage: "{{ vault_storage_configuration }}"
vault_extra_configuration:
user_lockout:
all:
lockout_duration: "10m"
lockout_counter_reset: "10m"

View File

@ -9,6 +9,12 @@
- name: "Import variables"
ansible.builtin.import_tasks:
file: tasks/load_vars.yml
tags:
- always
# - haproxy
# - consul
# - vault
# - nomad
- name: "Deploy Consul Control Plane"
ansible.builtin.import_tasks:

View File

@ -71,15 +71,15 @@
- internal
delegate_to: localhost
vars:
hashistack_ca_key_path: "{{ sub_configuration_directories['certificates'] }}/internal/ca.key"
hashistack_ca_cert_path: "{{ sub_configuration_directories['certificates'] }}/internal/ca.pem"
hashistack_ca_key_path: "{{ sub_configuration_directories['certificates'] }}/ca/ca.key"
hashistack_ca_cert_path: "{{ sub_configuration_directories['certificates'] }}/ca/ca.crt"
block:
- name: "Create internal CA" # noqa: run-once[task]
run_once: true
block:
- name: "Create temporary cert directory in {{ sub_configuration_directories['certificates'] }}" # noqa: run-once[task]
ansible.builtin.file:
path: "{{ sub_configuration_directories['certificates'] }}/internal"
path: "{{ sub_configuration_directories['certificates'] }}/ca"
state: directory
owner: "{{ lookup('env', 'USER') }}"
group: "{{ lookup('env', 'USER') }}"
@ -147,6 +147,11 @@
- "DNS:localhost"
- "IP:{{ api_interface_address }}"
- "IP:127.0.0.1"
key_usage_critical: true
key_usage:
- Digital Signature
- Key Encipherment
- Key Agreement
extended_key_usage:
- TLS Web Server Authentication
- TLS Web Client Authentication
@ -166,10 +171,10 @@
- name: "Concatenate CA and Child certificates"
block:
- name: "Read content of ca.pem"
- name: "Read content of ca.crt"
ansible.builtin.slurp:
src: "{{ hashistack_ca_cert_path }}"
register: ca_pem_content
register: ca_crt_content
- name: "Read content of cert.pem"
ansible.builtin.slurp:
@ -179,10 +184,92 @@
- name: "Concatenate certificates"
ansible.builtin.copy:
content: |
{{ cert_pem_content['content'] | b64decode }}{{ ca_pem_content['content'] | b64decode }}
{{ cert_pem_content['content'] | b64decode }}{{ ca_crt_content['content'] | b64decode }}
dest: "{{ vault_certificate_path }}"
owner: "{{ lookup('env', 'USER') }}"
group: "{{ lookup('env', 'USER') }}"
mode: "0644"
- fail:
- name: "Create Consul certificates"
when:
- "'consul_servers' in group_names"
vars:
consul_private_key_path: "{{ sub_configuration_directories['certificates'] }}/consul/{{ inventory_hostname }}/key.pem"
consul_certificate_path: "{{ sub_configuration_directories['certificates'] }}/consul/{{ inventory_hostname }}/cert.pem"
block:
- name: "Create temporary cert directory in {{ sub_configuration_directories['certificates'] }}" # noqa: run-once[task]
ansible.builtin.file:
path: "{{ sub_configuration_directories['certificates'] }}/consul/{{ inventory_hostname }}"
state: directory
owner: "{{ lookup('env', 'USER') }}"
group: "{{ lookup('env', 'USER') }}"
mode: "0755"
- name: "Create Consul certificate keys"
community.crypto.openssl_privatekey:
path: "{{ consul_private_key_path }}"
owner: "{{ lookup('env', 'USER') }}"
group: "{{ lookup('env', 'USER') }}"
- name: "Create CSRs for Consul servers"
vars:
consul_csr_sans: >-
{%- set sans_list = [
'DNS:' + inventory_hostname,
'DNS:consul.service.consul',
'DNS:localhost',
'IP:' + api_interface_address,
'IP:127.0.0.1'
] -%}
{%- if hashi_consul_configuration.server -%}
{%- set _ = sans_list.append('DNS:server.' ~ hashi_consul_configuration.datacenter ~ '.' ~ hashi_consul_configuration.domain) -%}
{%- endif -%}
{{ sans_list }}
community.crypto.openssl_csr_pipe:
privatekey_path: "{{ consul_private_key_path }}"
common_name: "{{ inventory_hostname }}"
subject_alt_name: "{{ consul_csr_sans }}"
key_usage_critical: true
key_usage:
- Digital Signature
- Key Encipherment
- Key Agreement
extended_key_usage:
- TLS Web Server Authentication
- TLS Web Client Authentication
organization_name: EDNZ Cloud
use_common_name_for_san: false
register: consul_csr
- name: "Sign certificates with internal CA"
community.crypto.x509_certificate:
path: "{{ consul_certificate_path }}"
csr_content: "{{ consul_csr.csr }}"
provider: ownca
ownca_path: "{{ hashistack_ca_cert_path }}"
ownca_privatekey_path: "{{ hashistack_ca_key_path }}"
ownca_not_after: "+365d"
ownca_not_before: "-1d"
- name: "Concatenate CA and Child certificates"
block:
- name: "Read content of ca.crt"
ansible.builtin.slurp:
src: "{{ hashistack_ca_cert_path }}"
register: ca_crt_content
- name: "Read content of cert.pem"
ansible.builtin.slurp:
src: "{{ consul_certificate_path }}"
register: cert_pem_content
- name: "Concatenate certificates"
ansible.builtin.copy:
content: |
{{ cert_pem_content['content'] | b64decode }}{{ ca_crt_content['content'] | b64decode }}
dest: "{{ consul_certificate_path }}"
owner: "{{ lookup('env', 'USER') }}"
group: "{{ lookup('env', 'USER') }}"
mode: "0644"
# - fail:

View File

@ -3,7 +3,7 @@
# helper options #
##################
# manage_pip_packages_allow_break_system_packages: true
manage_pip_packages_allow_break_system_packages: true
vault_versions:
host: "{{ vault_version if vault_version != 'latest' else vault_version + '*' }}"
@ -26,9 +26,13 @@ sub_configuration_directories:
configuration_global_vars_file: "globals.yml"
hashistack_remote_config_dir: "/etc/hashistack"
hashistack_remote_data_dir: "/opt/hashistack"
default_container_extra_volumes:
- "/etc/timezone:/etc/timezone"
- "/etc/localtime:/etc/localtime"
- "/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro"
###################
# support options #

View File

@ -51,7 +51,7 @@ hashi_consul_start_service: true
hashi_consul_version: "{{ consul_versions[deployment_method] }}"
hashi_consul_deploy_method: "{{ deployment_method }}"
hashi_consul_env_variables: {}
hashi_cosul_config_dir: "/etc/consul.d"
hashi_consul_config_dir: "/etc/consul.d"
hashi_consul_data_dir: "/opt/consul"
hashi_consul_extra_files: false
hashi_consul_extra_files_src: "{{ sub_configuration_directories.consul_servers }}/config"
@ -83,7 +83,7 @@ hashi_consul_configuration:
dns_config: "{{ consul_dns_configuration }}"
ports:
dns: 8600
http: 8500
http: "{{ ('8500'|int) if not }}"
https: -1
grpc: 8502
grpc_tls: 8503

View File

@ -11,7 +11,7 @@ enable_nomad: "no"
haproxy_version: "2.8"
nomad_version: "latest"
consul_version: "latest"
vault_version: "latest"
vault_version: "1.15.6"
deployment_method: "docker"
@ -96,6 +96,21 @@ consul_ui_configuration:
consul_mesh_configuration:
enabled: true
############################
# consul tls configuration #
############################
consul_enable_tls: false
consul_tls_configuration:
default:
ca_file: "/etc/ssl/certs/ca-certificates.crt"
cert_file: "{{ hashi_consul_config_dir }}/tls/cert.pem"
key_file: "{{ hashi_consul_config_dir }}/tls/key.pem"
verify_incoming: false
verify_outgoing: true
internal_rpc:
verify_server_hostname: true
#######################
# extra configuration #
#######################
@ -126,7 +141,7 @@ vault_storage_configuration:
[
{% for host in groups['vault_servers'] %}
{
'leader_api_addr': 'http://{{ hostvars[host].api_interface_address }}:8200'
'leader_api_addr': '{{ "https" if vault_enable_tls else "http"}}://{{ hostvars[host].api_interface_address }}:8200'
}{% if not loop.last %},{% endif %}
{% endfor %}
]
@ -135,7 +150,8 @@ vault_storage_configuration:
# vault listener #
##################
vault_enable_tls: false
vault_enable_tls: true
vault_tls_verify: false
vault_listener_configuration:
tcp:
address: "0.0.0.0:8200"
@ -144,8 +160,9 @@ vault_listener_configuration:
vault_tls_listener_configuration:
tcp:
tls_disable: false
tls_cert_file: "{{ hashi_vault_extra_files_dst }}/tls/cert.pem"
tls_key_file: "{{ hashi_vault_extra_files_dst }}/tls/key.pem"
tls_cert_file: "{{ vault_certificates_directory }}/cert.pem"
tls_key_file: "{{ vault_certificates_directory }}/key.pem"
tls_disable_client_certs: true
vault_extra_listener_configuration: {}
@ -164,7 +181,7 @@ vault_service_registration_configuration:
# vault plugins #
#################
vault_enable_plugins: true
vault_enable_plugins: false
###########
# logging #

View File

@ -27,7 +27,7 @@ vault_external_backend_options:
vault_external_backend_servers: |
[
{% for host in groups['vault_servers'] %}
'server vault-{{ hostvars[host].api_interface_address }} {{ hostvars[host].api_interface_address }}:8200 check inter 5s'{% if not loop.last %},{% endif %}
'server vault-{{ hostvars[host].api_interface_address }} {{ hostvars[host].api_interface_address }}:8200 check {{ 'ssl verify none ' if vault_enable_tls }}inter 5s'{% if not loop.last %},{% endif %}
{% endfor %}
]
@ -38,7 +38,7 @@ vault_external_backend_servers: |
vault_certificates_directory: "{{ hashi_vault_config_dir }}/tls"
vault_certificates_extra_files_dir:
- src: "{{ sub_configuration_directories['certificates'] }}/vault/{{ inventory_hostname }}"
dest: "{{ hashi_vault_config_dir }}/tls/cert"
dest: "{{ hashi_vault_config_dir }}/tls"
#################
# vault plugins #
@ -71,8 +71,8 @@ hashi_vault_extra_files_dst: "{{ hashi_vault_config_dir }}/config"
hashi_vault_extra_container_volumes: "{{ default_container_extra_volumes | union(extra_vault_container_volumes) | unique }}"
hashi_vault_configuration:
cluster_name: "{{ vault_cluster_name }}"
cluster_addr: "http://{{ api_interface_address }}:8201"
api_addr: "http://{{ api_interface_address }}:8200"
cluster_addr: "{{ 'https' if vault_enable_tls else 'http'}}://{{ api_interface_address }}:8201"
api_addr: "{{ 'https' if vault_enable_tls else 'http'}}://{{ api_interface_address }}:8200"
ui: "{{ vault_enable_ui }}"
disable_mlock: false
disable_cache: false

View File

@ -112,6 +112,7 @@
- name: "Checking host OS distribution"
#TODO: This needs to work with debian and ubuntu, major version works for debian but not ubuntu, simple version works the other way around...
#? seems to work
ansible.builtin.assert:
that:
- "(ansible_facts.distribution | lower) in hashistack_supported_distributions"

View File

@ -6,7 +6,7 @@
ansible.builtin.set_fact:
hashi_consul_configuration: "{{
hashi_consul_configuration |
combine(_config_to_merge|from_yaml)
combine(_config_to_merge|from_yaml, recursive=true)
}}"
when:
- hashi_consul_configuration_string is defined
@ -18,10 +18,21 @@
ansible.builtin.set_fact:
hashi_consul_configuration: "{{
hashi_consul_configuration |
combine(_config_to_merge)
combine(_config_to_merge, recursive=true)
}}"
when: consul_address_configuration is defined
- name: "Consul | Merge TLS configuration"
vars:
_config_to_merge:
tls: "{{ consul_tls_configuration }}"
ansible.builtin.set_fact:
hashi_consul_configuration: "{{
hashi_consul_configuration |
combine(_config_to_merge, recursive=true)
}}"
when: consul_enable_tls
- name: "Consul | Merge token configuration"
delegate_to: localhost
block:
@ -61,6 +72,6 @@
ansible.builtin.set_fact:
hashi_consul_configuration: "{{
hashi_consul_configuration |
combine(_config_to_merge)
combine(_config_to_merge, recursive=true)
}}"
when: consul_extra_configuration is defined

View File

@ -64,6 +64,86 @@
loop_var: item
delegate_to: localhost
- name: "Ensure remote directories exists"
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: 0755
recurse: yes
loop:
- "{{ hashistack_remote_config_dir }}"
- "{{ hashistack_remote_data_dir }}"
- name: "Load custom CA certificates"
block:
- name: "Check if CA directory exists"
ansible.builtin.stat:
path: "{{ sub_configuration_directories['certificates'] }}/ca"
register: _hashistack_ca_directory
delegate_to: localhost
- name: "Find custom ca certificates to copy"
ansible.builtin.find:
paths: "{{ sub_configuration_directories['certificates'] }}/ca"
patterns: "*.crt"
register: _hashistack_cacert_files
delegate_to: localhost
when: _hashistack_ca_directory.stat.exists and _hashistack_ca_directory.stat.isdir
- ansible.builtin.debug:
msg: "{{ _hashistack_cacert_files }}"
- name: "Ensure remote ca directory exists"
ansible.builtin.file:
path: "{{ hashistack_remote_config_dir }}/ca"
state: directory
owner: root
group: root
mode: 0755
- name: "Copy custom ca certificates"
ansible.builtin.copy:
src: "{{ item.path }}"
dest: "{{ hashistack_remote_config_dir }}/ca/{{ item.path | basename }}"
owner: root
group: root
mode: 0644
loop: "{{ _hashistack_cacert_files.files }}"
register: _hashistack_copied_ca
- name: "Copy and update trust store"
block:
- name: "Copy ca certificates to /usr/loca/share/ca-certificates"
ansible.builtin.file:
state: link
src: "{{ item.dest }}"
dest: "/usr/local/share/ca-certificates/hashistack-customca-{{ item.dest | basename }}"
owner: root
group: root
loop: "{{ _hashistack_copied_ca.results }}"
register: _hashistack_usr_local_share_ca_certificates
- name: "Update the trust store"
ansible.builtin.command: update-ca-certificates
changed_when: false
when: _hashistack_usr_local_share_ca_certificates.changed
# - name: "Initialize list of CA certificates"
# ansible.builtin.set_fact:
# hashistack_cacert_extra_files: []
# delegate_to: localhost
# - name: "Add custom CA to list of extra certificates"
# ansible.builtin.set_fact:
# hashistack_cacert_extra_files: "{{
# hashistack_cacert_extra_files | default([])
# + [{'src': item.path, 'dest': '/etc/ssl/certs/hashistack-custom-' + item.path | basename}] }}"
# loop: "{{ _hashistack_cacert_files.files }}"
# delegate_to: localhost
# when: _hashistack_cacert_files.matched > 0
- name: "Merge consul configurations"
ansible.builtin.import_tasks:
file: "consul/consul_vars.yml"

View File

@ -8,6 +8,7 @@
- name: "Initialize vault cluster" # noqa: run-once[task]
ednz_cloud.hashistack.vault_init:
api_url: "{{ hashi_vault_configuration['api_addr'] }}"
tls_verify: "{{ vault_tls_verify }}"
key_shares: "{{ vault_seal_configuration['key_shares'] }}"
key_threshold: "{{ vault_seal_configuration['key_threshold'] }}"
run_once: true
@ -36,6 +37,7 @@
- name: "Unseal the bootstrap node" # noqa: run-once[task] no-handler
ednz_cloud.hashistack.vault_unseal:
api_url: "{{ hashi_vault_configuration['api_addr'] }}"
tls_verify: "{{ vault_tls_verify }}"
key_shares: "{{ _vault_cluster_config['keys'] }}"
run_once: true
delegate_to: "{{ groups['vault_servers'] | first }}"
@ -45,6 +47,7 @@
- name: "Unseal all vault nodes"
ednz_cloud.hashistack.vault_unseal:
api_url: "{{ hashi_vault_configuration['api_addr'] }}"
tls_verify: "{{ vault_tls_verify }}"
key_shares: "{{ _vault_cluster_config['keys'] }}"
retries: 5
delay: 5

View File

@ -4,8 +4,8 @@
ansible.builtin.set_fact:
vault_listener_configuration: "{{
vault_listener_configuration |
combine((vault_enable_tls | bool) | ternary(vault_tls_listener_configuration, {})) |
combine(vault_extra_listener_configuration | default({}))
combine((vault_enable_tls | bool) | ternary(vault_tls_listener_configuration, {}), recursive=True) |
combine(vault_extra_listener_configuration | default({}), recursive=True)
}}"
- name: "Vault | Merge service registration configuration"

View File

@ -92,9 +92,9 @@ else:
def initialize_vault(
api_url: str, key_shares: int, key_threshold: int
api_url: str, tls_verify: bool, key_shares: int, key_threshold: int
) -> Tuple[bool, dict]:
client = hvac.Client(url=api_url)
client = hvac.Client(url=api_url, verify=tls_verify)
try:
if not client.sys.is_initialized():
@ -108,6 +108,7 @@ def initialize_vault(
def run_module():
module_args = dict(
api_url=dict(type="str", required=True),
tls_verify=dict(type="bool", required=False, default=True),
key_shares=dict(type="int", required=False, default=5),
key_threshold=dict(type="int", required=False, default=3),
)
@ -123,9 +124,10 @@ def run_module():
try:
vault_init_result, response_data = initialize_vault(
module.params["api_url"],
module.params["key_shares"],
module.params["key_threshold"],
api_url=module.params["api_url"],
tls_verify=module.params["tls_verify"],
key_shares=module.params["key_shares"],
key_threshold=module.params["key_threshold"],
)
result["changed"] = vault_init_result

View File

@ -81,8 +81,8 @@ else:
HAS_HVAC = True
def unseal_vault(api_url: str, key_shares: list) -> Tuple[bool, dict]:
client = hvac.Client(url=api_url)
def unseal_vault(api_url: str, tls_verify: bool, key_shares: list) -> Tuple[bool, dict]:
client = hvac.Client(url=api_url, verify=tls_verify)
try:
if client.sys.is_sealed():
@ -96,13 +96,16 @@ def unseal_vault(api_url: str, key_shares: list) -> Tuple[bool, dict]:
def run_module():
module_args = dict(
api_url=dict(type="str", required=True),
tls_verify=dict(type="bool", required=False, default=True),
key_shares=dict(type="list", required=False, default=[]),
)
result = dict(changed=False, state="")
module = AnsibleModule(argument_spec=module_args, supports_check_mode=False)
client = hvac.Client(url=module.params["api_url"])
client = hvac.Client(
url=module.params["api_url"], verify=module.params["tls_verify"]
)
if not client.sys.is_sealed():
module.exit_json(**result)
@ -113,10 +116,14 @@ def run_module():
msg="Missing required library: hvac", exception=HVAC_IMPORT_ERROR
)
vault_unseal_result, response_data = unseal_vault(
api_url=module.params["api_url"], key_shares=module.params["key_shares"]
api_url=module.params["api_url"],
tls_verify=module.params["tls_verify"],
key_shares=module.params["key_shares"],
)
if hvac.Client(url=module.params["api_url"]).sys.is_sealed():
if hvac.Client(
url=module.params["api_url"], verify=module.params["tls_verify"]
).sys.is_sealed():
module.fail_json(
msg="Vault unsealing failed. The unseal operation worked, but the vault is still sealed, maybe you didn't pass enough keys ?"
)

@ -1 +1 @@
Subproject commit 59868f06aa64f72e1f8547bcd78c48f26ce58b9c
Subproject commit c6fbfe5b78ef8b8884af129ea84afb26da754833

@ -1 +1 @@
Subproject commit 36b74e452acb204bf7d76e8037ffa8449e5508f5
Subproject commit 738c347df8efd4965eda14167171343be13bed75