diff --git a/roles/vault/defaults/main.yml b/roles/vault/defaults/main.yml new file mode 100644 index 0000000..c0614c4 --- /dev/null +++ b/roles/vault/defaults/main.yml @@ -0,0 +1,124 @@ +--- + +# Version of Vault to install +vault_version: 1.11.2 +# URL of the archive +vault_archive_url: https://releases.hashicorp.com/vault/{{ vault_version }}/vault_{{ vault_version }}_linux_amd64.zip +# Expected sha256 of the archive +vault_archive_sha256: ace4138269cb7214c73529f984b793d66074e3a3ad373eaa77bc9b39490d9ef9 + +# Root dir where Nomad will be installed +vault_root_dir: /opt/vault + +# user under which vault will run. +vault_user: vault + +# Setting vault_letsencrypt_cert will automate cert configuration +# using Let's Encrypt. The server need to have the letsencrypt role assigned +# Note that you probably want to use dns-01 challenges in this case so you won't have to +# expose your vault server on the public internet +# vault_letsencrypt_cert: "{{ inventory_hostname }}" + +# Ports used by vault, and the IP/CIDR for which the port will be opened on the local firewall +vault_base_services: + api: + port: 8200 + src_ip: [] + cluster: + port: 8201 + src_ip: [] # You should set this to the IP / CIDR of your other servers + +# Exemple +# vault_extra_services: +# cluster: +# src_ip: +# - 10.127.0.10 +# - 10.145.99.60 +vault_extra_services: {} +vault_services: "{{ vault_base_services | combine(vault_extra_services, recursive=True) }}" + +# Configuration of the service (which will be converted to JSON) +# The configuration is splited in a base conf, an extra conf, and a host conf so you can override part of the config easily +vault_base_conf: + # Name of the Vault cluster + cluster_name: Vault Cluster + + # Log settings + log_level: INFO + log_format: standard + + # Plugin settings + plugin_directory: "{{ vault_root_dir }}/plugins" + # This means vault will expect plugins to be owned by root + plugin_file_uid: 0 + + # Is the UI enabled ? + ui: True + + # TCP listeners + listeners: + # Address/port on which vault will bind for API requests + - address: 0.0.0.0:{{ vault_services.api.port }} + # Address/port on which vault will bind for inter-node communications + cluster_address: 0.0.0.0:{{ vault_services.cluster.port }} + + # Path of the certificate and key to use. The default is to use a self-signed certificate which will be generated + # by ansible. Do not modify these paths when using Let's Encrypt cert, as they will be placed here + # Only change if you want to manually control the certificate to use + tls_cert_file: "{{ vault_root_dir }}/tls/vault.crt" + tls_key_file: "{{ vault_root_dir }}/tls/vault.key" + + # List of IP address for which the X-Forwarded-For header will be trusted. List here your reverse proxy IP/CIDR + x_forwarded_for_authorized_addrs: [] + # If x_forwarded_for_authorized_addrs is set and a request does not have X-Forwarded-For address, should it be rejected + # Default is False which means you can reach vault both directly or through your reverse proxy + x_forwarded_for_reject_not_present: False + + # URL of the API to advertise + api_addr: https://{{ inventory_hostname }}:{{ vault_services.api.port }} + # URL of the inter-node communication endpoint to advertise + cluster_addr: https://{{ inventory_hostname }}:{{ vault_services.cluster.port }} + + # When using integrated raft storage, mlock should be disabled + disable_mlock: True + + storage: + # Integrated raf storage + raft: + path: "{{ vault_root_dir }}/data" + node_id: "{{ inventory_hostname }}" + performance_multiplier: 1 + # retry_join: + # - leader_api_addr: https://vault-1.example.org:8200 + # leader_ca_cert: /opt/vault/tls/ca-vault-1.crt + # - https://vault-2.example.org:8200 + # - https://vault-3.example.org:8200 + retry_join: [] + + # Service registration on consul + #service_registration: + # address: http://localhost:8500 + # service: vault + # token: XXXXX + # service_tags: + # - "traefik.enable=true" + # - "traefik.http.routers.http.entrypoints=https" + # - "traefik.http.routers.http.rule=Host(`vault.example.org`)" + # tls_ca_file: /opt/vault/tls/consul_ca.crt + # tls_cert_file: /opt/vault/tls/consul_cert.crt + # tls_key_file: /opt/vault/tls/consul_key.crt + +# You can add additional paramters in vault_extra_conf (or vault_host_conf) +# they will be merged into the vault_base_conf before rendering +# Example +# vault_extra_conf: +# cluster_name: Vault Production +# storage: +# raft: +# retry_join: +# leader_api_addr: https://vault1.example.org:8201 +vault_extra_conf: {} +vault_host_conf: {} +# Merge all the conf +vault_conf: "{{ vault_base_conf | combine(vault_extra_conf, recursive=True) | combine(vault_host_conf, recursive=True) }}" + diff --git a/roles/vault/handlers/main.yml b/roles/vault/handlers/main.yml new file mode 100644 index 0000000..0b9a86f --- /dev/null +++ b/roles/vault/handlers/main.yml @@ -0,0 +1,8 @@ +--- + +- name: restart vault + service: name=vault state=restarted + when: vault_service_started is not defined or not vault_service_started.changed + +- name: reload vault + service: name=vault state=reloaded diff --git a/roles/vault/meta/main.yml b/roles/vault/meta/main.yml new file mode 100644 index 0000000..dc58dfa --- /dev/null +++ b/roles/vault/meta/main.yml @@ -0,0 +1,4 @@ +--- + +dependencies: + - role: mkdir diff --git a/roles/vault/tasks/archive_post.yml b/roles/vault/tasks/archive_post.yml new file mode 100644 index 0000000..75dcade --- /dev/null +++ b/roles/vault/tasks/archive_post.yml @@ -0,0 +1,15 @@ +--- + +- name: Compress previous version + command: tar cf {{ vault_root_dir }}/archives/{{ vault_current_version }}.tar.zst --use-compress-program=zstd ./ + args: + chdir: "{{ vault_root_dir }}/archives/{{ vault_current_version }}" + warn: False + environment: + ZSTD_CLEVEL: 10 + tags: vault + +- name: Remove archive dir + file: path={{ vault_root_dir }}/archives/{{ vault_current_version }} state=absent + tags: vault + diff --git a/roles/vault/tasks/archive_pre.yml b/roles/vault/tasks/archive_pre.yml new file mode 100644 index 0000000..236f6e5 --- /dev/null +++ b/roles/vault/tasks/archive_pre.yml @@ -0,0 +1,10 @@ +--- + +- name: Create the archive dir + file: path={{ vault_root_dir }}/archives/{{ vault_current_version }} state=directory + tags: vault + +- name: Backup previous version + copy: src={{ vault_root_dir }}/bin/vault dest={{ vault_root_dir }}/archives/{{ vault_current_version }}/ remote_src=True + tags: vault + diff --git a/roles/vault/tasks/cleanup.yml b/roles/vault/tasks/cleanup.yml new file mode 100644 index 0000000..fea4ff1 --- /dev/null +++ b/roles/vault/tasks/cleanup.yml @@ -0,0 +1,8 @@ +--- + +- name: Remove tmp and obsolete files + file: path={{ item }} state=absent + loop: + - "{{ vault_root_dir }}/tmp/vault_{{ vault_version }}_linux_amd64.zip" + - "{{ vault_root_dir }}/tmp/vault" + tags: vault diff --git a/roles/vault/tasks/conf.yml b/roles/vault/tasks/conf.yml new file mode 100644 index 0000000..e0a6b84 --- /dev/null +++ b/roles/vault/tasks/conf.yml @@ -0,0 +1,20 @@ +--- + +- name: Generate self-signed certificate + import_tasks: ../includes/create_selfsigned_cert.yml + vars: + cert_path: "{{ vault_root_dir }}/tls/vault.crt" + cert_key_path: "{{ vault_root_dir }}/tls/vault.key" + cert_key_group: "{{ vault_user }}" + cert_key_mode: 640 + tags: vault + +- name: Deploy vault configuration + template: + src: vault.hcl.j2 + dest: "{{ vault_root_dir }}/etc/vault.hcl" + owner: "{{ vault_user }}" + group: "{{ vault_user }}" + mode: 0400 + notify: restart vault + tags: vault diff --git a/roles/vault/tasks/directories.yml b/roles/vault/tasks/directories.yml new file mode 100644 index 0000000..7bc2963 --- /dev/null +++ b/roles/vault/tasks/directories.yml @@ -0,0 +1,42 @@ +--- + +- name: Create needed directories + file: path={{ vault_root_dir }}/{{ item.dir }} state=directory owner={{ item.owner | default(omit) }} group={{ item.group | default(omit) }} mode={{ item.mode | default(omit) }} recurse={{ item.recurse | default(omit) }} + loop: + - dir: / + owner: root + group: root + mode: 755 + - dir: archives + owner: root + group: root + mode: 700 + - dir: backup + owner: root + group: root + mode: 700 + - dir: meta + owner: root + group: root + mode: 700 + - dir: bin + - dir: plugins + - dir: tmp + owner: "{{ vault_user }}" + group: "{{ vault_user }}" + mode: u=rwX,g=-,o=- + recurse: True + - dir: data + owner: "{{ vault_user }}" + group: "{{ vault_user }}" + mode: u=rwX,g=-,o=- + recurse: True + - dir: etc + owner: "{{ vault_user }}" + group: "{{ vault_user }}" + mode: 700 + - dir: tls + owner: root + group: "{{ vault_user }}" + mode: 750 + tags: vault diff --git a/roles/vault/tasks/facts.yml b/roles/vault/tasks/facts.yml new file mode 100644 index 0000000..e72cabd --- /dev/null +++ b/roles/vault/tasks/facts.yml @@ -0,0 +1,12 @@ +--- + +- name: Detect installed version + block: + - import_tasks: ../includes/webapps_set_install_mode.yml + vars: + - root_dir: "{{ vault_root_dir }}" + - version: "{{ vault_version }}" + - set_fact: vault_install_mode={{ install_mode | default('none') }} + - set_fact: vault_current_version={{ current_version | default('') }} + tags: vault + diff --git a/roles/vault/tasks/install.yml b/roles/vault/tasks/install.yml new file mode 100644 index 0000000..be53277 --- /dev/null +++ b/roles/vault/tasks/install.yml @@ -0,0 +1,67 @@ +--- + +- name: Install needed tools + package: + name: + - tar + - zstd + - unzip + tags: vault + +- when: vault_install_mode != 'none' + block: + - name: Download vault + get_url: + url: "{{ vault_archive_url }}" + dest: "{{ vault_root_dir }}/tmp" + checksum: sha256:{{ vault_archive_sha256 }} + + - name: Extract the archive + unarchive: + src: "{{ vault_root_dir }}/tmp/vault_{{ vault_version }}_linux_amd64.zip" + dest: "{{ vault_root_dir }}/tmp" + remote_src: True + + - name: Install vault binary + copy: + src: "{{ vault_root_dir }}/tmp/vault" + dest: "{{ vault_root_dir }}/bin/vault" + remote_src: True + mode: 755 + + - name: Link in /usr/local/bin + file: src={{ vault_root_dir }}/bin/vault dest=/usr/local/bin/vault state=link force=True + + tags: vault + +- name: Install bash completion support + copy: + content: | + complete -C {{ vault_root_dir }}/bin/vault vault + dest: /etc/bash_completion.d/vault + mode: 755 + tags: vault + +- name: Deploy systemd service unit + template: src=vault.service.j2 dest=/etc/systemd/system/vault.service + register: vault_unit + notify: restart vault + tags: vault + +- name: Reload systemd + systemd: daemon_reload=True + when: vault_unit.changed + tags: vault + +- name: Install dehydrated hook + template: src=dehydrated_hook.j2 dest=/etc/dehydrated/hooks_deploy_cert.d/vault mode=755 + tags: vault + +- name: Install profile script + copy: + content: | + #!/bin/sh + export VAULT_ADDR={{ vault_conf.api_addr }} + dest: /etc/profile.d/vault.sh + mode: 0755 + tags: vault diff --git a/roles/vault/tasks/iptables.yml b/roles/vault/tasks/iptables.yml new file mode 100644 index 0000000..e7235f7 --- /dev/null +++ b/roles/vault/tasks/iptables.yml @@ -0,0 +1,10 @@ +--- + +- name: Handle vault ports in the firewall + iptables_raw: + name: vault_port_{{ item }} + state: "{{ (vault_services[item].src_ip | length > 0) | ternary('present', 'absent') }}" + rules: | + -A INPUT -m state --state NEW -p tcp --dport {{ vault_services[item].port }} -j ACCEPT + loop: "{{ vault_services.keys() | list }}" + tags: firewall,vault diff --git a/roles/vault/tasks/main.yml b/roles/vault/tasks/main.yml new file mode 100644 index 0000000..ed739f9 --- /dev/null +++ b/roles/vault/tasks/main.yml @@ -0,0 +1,38 @@ +--- + +- include_tasks: user.yml + tags: always + +- include_tasks: directories.yml + tags: always + +- include_tasks: facts.yml + tags: always + +- include_tasks: archive_pre.yml + when: vault_install_mode | default('none') == 'upgrade' + tags: always + +- include_tasks: install.yml + tags: always + +- include_tasks: conf.yml + tags: always + +- include_tasks: iptables.yml + when: iptables_manage | default(True) + tags: always + +- include_tasks: services.yml + tags: always + +- include_tasks: write_version.yml + tags: always + +- include_tasks: archive_post.yml + when: vault_install_mode | default('none') == 'upgrade' + tags: always + +- include_tasks: cleanup.yml + tags: always + diff --git a/roles/vault/tasks/services.yml b/roles/vault/tasks/services.yml new file mode 100644 index 0000000..ece06af --- /dev/null +++ b/roles/vault/tasks/services.yml @@ -0,0 +1,6 @@ +--- + +- name: Start and enable vault service + service: name=vault state=started enabled=True + register: vault_service_started + tags: vault diff --git a/roles/vault/tasks/user.yml b/roles/vault/tasks/user.yml new file mode 100644 index 0000000..44856e7 --- /dev/null +++ b/roles/vault/tasks/user.yml @@ -0,0 +1,9 @@ +--- + +- name: Create vault user + user: + name: "{{ vault_user }}" + home: "{{ vault_root_dir }}" + system: True + shell: /sbin/nologin + tags: vault diff --git a/roles/vault/tasks/write_version.yml b/roles/vault/tasks/write_version.yml new file mode 100644 index 0000000..8ca57ec --- /dev/null +++ b/roles/vault/tasks/write_version.yml @@ -0,0 +1,5 @@ +--- + +- name: Write installed version + copy: content={{ vault_version }} dest={{ vault_root_dir }}/meta/ansible_version + tags: vault diff --git a/roles/vault/templates/dehydrated_hook.j2 b/roles/vault/templates/dehydrated_hook.j2 new file mode 100644 index 0000000..6a360a2 --- /dev/null +++ b/roles/vault/templates/dehydrated_hook.j2 @@ -0,0 +1,22 @@ +#!/bin/sh + +set -eo pipefail + +{% if vault_letsencrypt_cert is defined %} + +if [ $1 == "{{ pg_letsencrypt_cert }}" ]; then + cp /var/lib/dehydrated/certificates/certs/{{ vault_letsencrypt_cert }}/fullchain.pem {{ vault_root_dir }}/tls/vault.crt + cp /var/lib/dehydrated/certificates/certs/{{ vault_letsencrypt_cert }}/privkey.pem {{ vault_root_dir }}/tls/vault.key + chown root:vault {{ vault_root_dir }}/tls/vault.key + chown root:root {{ vault_root_dir }}/tls/vault.crt + chmod 640 {{ vault_root_dir }}/tls/vault.key + chmod 644 {{ vault_root_dir }}/tls/vault.crt + systemctl reload vault +fi + +{% else %} + +# No Let's Encrypt cert configured, nothing to do +exit 0 + +{% endif %} diff --git a/roles/vault/templates/vault.hcl.j2 b/roles/vault/templates/vault.hcl.j2 new file mode 100644 index 0000000..82903cb --- /dev/null +++ b/roles/vault/templates/vault.hcl.j2 @@ -0,0 +1,57 @@ +cluster_name = "{{ vault_conf.cluster_name }}" + +log_level = "{{ vault_conf.log_level }}" +log_format = "{{ vault_conf.log_format }}" + +plugin_directory = "{{ vault_conf.plugin_directory }}" +plugin_file_uid = {{ vault_conf.plugin_file_uid }} + +disable_mlock = {{ vault_conf.disable_mlock | ternary('true', 'false') }} + +{% for listener in vault_conf.listeners %} +listener "tcp" { + address = "{{ listener.address }}" + cluster_address = "{{ listener.cluster_address }}" + tls_cert_file = "{{ listener.tls_cert_file }}" + tls_key_file = "{{ listener.tls_key_file }}" +{% if listener.x_forwarded_for_authorized_addrs | length > 0 %} + x_forwarded_for_authorized_addrs = "{{ listener.x_forwarded_for_authorized_addrs | join(',') }}" + x_forwarded_for_reject_not_present = {{ listener.x_forwarded_for_reject_not_present | ternary('true', 'false') }} +{% endif %} +} +{% endfor %} + +api_addr = "{{ vault_conf.api_addr }}" +cluster_addr = "{{ vault_conf.cluster_addr }}" + +storage "raft" { + path = "{{ vault_conf.storage.raft.path }}" + node_id = "{{ vault_conf.storage.raft.node_id }}" + performance_multiplier = {{ vault_conf.storage.raft.performance_multiplier }} +{% if vault_conf.storage.raft.retry_join | length > 0 %} +{% for server in vault_conf.storage.raft.retry_join %} + retry_join { +{% for key in server.keys() | list %} + {{ key }} = "{{ server[key] }}" +{% endfor %} + } +{% endfor %} +{% endif %} +} + +{% if vault_conf.service_registration is defined %} +service_registration "consul" { +{% for key in ['address', 'service', 'token', 'tls_ca_file', 'tls_cert_file', 'tls_key_file'] %} +{% if vault_conf.service_registration[key] is defined %} + {{ key }} = "{{ vault_conf.service_registration[key] }}" +{% endif %} +{% endfor %} +{% if vault_conf.service_registration.service_tags is defined %} + service_tags = [ +{% for tag in vault_conf.service_registration.service_tags %} + "{{ tag }}", +{% endfor %} + ] +{% endif %} +} +{% endif %} diff --git a/roles/vault/templates/vault.service.j2 b/roles/vault/templates/vault.service.j2 new file mode 100644 index 0000000..4666f15 --- /dev/null +++ b/roles/vault/templates/vault.service.j2 @@ -0,0 +1,34 @@ +[Unit] +Description="HashiCorp Vault - A tool for managing secrets" +Documentation=https://www.vaultproject.io/docs/ +Requires=network-online.target +After=network-online.target +ConditionFileNotEmpty={{ vault_root_dir }}/etc/vault.hcl +StartLimitIntervalSec=60 +StartLimitBurst=3 + +[Service] +Type=notify +User={{ vault_user }} +Group={{ vault_user }} +ProtectSystem=full +ProtectHome=read-only +PrivateTmp=yes +PrivateDevices=yes +SecureBits=keep-caps +AmbientCapabilities=CAP_IPC_LOCK CAP_NET_BIND_SERVICE +CapabilityBoundingSet=CAP_SYSLOG CAP_IPC_LOCK CAP_NET_BIND_SERVICE +NoNewPrivileges=yes +ExecStart={{ vault_root_dir }}/bin/vault server -config={{ vault_root_dir }}/etc/ +ExecReload=/bin/kill --signal HUP $MAINPID +KillMode=process +KillSignal=SIGINT +Restart=on-failure +RestartSec=5 +TimeoutStopSec=30 +LimitNOFILE=65536 +LimitMEMLOCK=infinity + +[Install] +WantedBy=multi-user.target +