diff --git a/Puppetfile b/Puppetfile index 85a9ba9..5701a66 100644 --- a/Puppetfile +++ b/Puppetfile @@ -28,6 +28,7 @@ mod 'puppet-selinux', '4.1.0' mod 'puppet-prometheus', '13.4.0' mod 'puppet-grafana', '13.1.0' mod 'puppet-consul', '8.0.0' +mod 'puppet-vault', '4.1.0' # other mod 'ghoneycutt-puppet', '3.3.0' @@ -36,6 +37,7 @@ mod 'dalen-puppetdbquery', '3.0.1' mod 'markt-galera', '3.1.0' mod 'kogitoapp-minio', '1.1.4' mod 'broadinstitute-certs', '3.0.1' +mod 'stm-file_capability', '6.0.0' mod 'bind', :git => 'https://git.unkin.net/unkinben/puppet-bind.git', diff --git a/doc/vault/setup.md b/doc/vault/setup.md new file mode 100644 index 0000000..1ec2ca2 --- /dev/null +++ b/doc/vault/setup.md @@ -0,0 +1,48 @@ +# root ca + vault secrets enable -path=pki_root pki + + vault write -field=certificate pki_root/root/generate/internal \ + common_name="unkin.net" \ + issuer_name="unkinroot-2024" \ + ttl=87600h > unkinroot_2024_ca.crt + + vault read pki_root/issuer/$(vault list -format=json pki_root/issuers/ | jq -r '.[]') | tail -n 6 + + vault write pki_root/roles/2024-servers allow_any_name=true + + vault write pki_root/config/urls \ + issuing_certificates="$VAULT_ADDR/v1/pki_root/ca" \ + crl_distribution_points="$VAULT_ADDR/v1/pki_root/crl" + +# intermediate + vault secrets enable -path=pki_int pki + vault secrets tune -max-lease-ttl=43800h pki_int + + vault write -format=json pki_int/intermediate/generate/internal \ + common_name="unkin.net Intermediate Authority" \ + issuer_name="unkin-dot-net-intermediate" \ + | jq -r '.data.csr' > pki_intermediate.csr + + vault write -format=json pki_root/root/sign-intermediate \ + issuer_ref="unkinroot-2024" \ + csr=@pki_intermediate.csr \ + format=pem_bundle ttl="43800h" \ + | jq -r '.data.certificate' > intermediate.cert.pem + + vault write pki_int/intermediate/set-signed certificate=@intermediate.cert.pem + +# create role + vault write pki_int/roles/unkin-dot-net \ + issuer_ref="$(vault read -field=default pki_int/config/issuers)" \ + allowed_domains="unkin.net" \ + allow_subdomains=true \ + max_ttl="2160h" + +# test generating a domain cert + vault write pki_int/issue/unkin-dot-net common_name="test.unkin.net" ttl="24h" + vault write pki_int/issue/unkin-dot-net common_name="test.main.unkin.net" ttl="24h" + vault write pki_int/issue/unkin-dot-net common_name="*.test.main.unkin.net" ttl="24h" + + +# remove expired certificates + vault write pki_int/tidy tidy_cert_store=true tidy_revoked_certs=true diff --git a/hieradata/common.yaml b/hieradata/common.yaml index dc4b711..a257843 100644 --- a/hieradata/common.yaml +++ b/hieradata/common.yaml @@ -49,6 +49,7 @@ profiles::packages::base::add: - sysstat - tmux - traceroute + - unzip - vim - vnstat - wget diff --git a/hieradata/roles/infra/storage/vault.eyaml b/hieradata/roles/infra/storage/vault.eyaml new file mode 100644 index 0000000..4e68db5 --- /dev/null +++ b/hieradata/roles/infra/storage/vault.eyaml @@ -0,0 +1,7 @@ +--- +vault::unseal_keys: + - ENC[PKCS7,MIIBmQYJKoZIhvcNAQcDoIIBijCCAYYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEAobuA26wCD25sP6t0w3VgcrjBdpwVsglUz119X/y9o5FHkUibXuHkesNktUKzSMWpVBV+EsoTQ9HFisUOX3ykSIyYOVntm4BTECYBZKAyVXzkolAqX32IWtYqV4H4eKb3/OXw8zayJn9cPWbQkVtNDq3mZUMzgGj46DPFpH8aNQOI/xpovldY+WK1wo6WY7MGSCKw/+0mXK1Qa6wjh2K1X8cCg344ODZQD/h2SCT25qYPFVSBEzFwzQyLWULFOozg5RdE4OXkviqtq2PFtWxCD4S87WiG4elpttJAH0bbKFJs4PMZFgi0HdHqnMHXWiQgB/A9lNRhSPaU16Y2OTJ2HzBcBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCw46SHa6ZZKMhxpG8VG7fJgDDj4cQ2/7//RszV99oDP3OyCHRPt6PylAyWVBCxj54faILV/2OaF3GwW05gWzlmwVI=] + - ENC[PKCS7,MIIBmQYJKoZIhvcNAQcDoIIBijCCAYYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEAKeXpQMz90SUeIojz659+AX9hdTn5EiPOprrdI4EjqPL5BItr2xkrk8XbDnhlgM6PWwbo5jzEKBYGFTRbnHHF6/xdQQoUALMyhD3SSwDZOv8B92zSWkRMUpXlrUFMJeHldDMXS9enpg4Y4jL7i7GWsf7PEuEOTidNid3ZViKT32miSoJvGRENZvGyYA8Rd1Vh+3lIxtJNWqEUiw6yDUUO6H8bRKQ0JVBxW4JQOkLeaKbz+M2WjvE+PDeYykCWaApzfcxE3XPxv7MhBLJskf/37h/pHFGLhAUEcATiXrdUHzDuGnCLHQohW4B6NnKH7GNTby7Nbxzuq5zA3g9yavKj1DBcBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCbXFapwcT7R268E9Zih4i2gDC4cSSmU+33i5j6uKbgydDGqeooT4j2GzWCr0Ya52aVZnoeqYEFdp+dBFcCnvwFTQU=] + - ENC[PKCS7,MIIBmQYJKoZIhvcNAQcDoIIBijCCAYYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEApI/exTnGbzLFuIYAVqLi27O8CxBXDOPDB5K9GVIDIL8pyXoqv8LDO1fkg0TZJREZIHUNBwx4DNg0ruveuEcHXTleLLKGk51Cn89YQB3bvUPJ7BfBq8GRV6TpNNj50jstjGqyesw+q4r9cx8F/l1qxlHBmJPT6h332GXO1Fzmh6wIF+poqD2KfsbppOtk/YGLtdTa87RuNS0eQ9LcayMIqWE2+vrkUlEtjNYgNWHnKFQhlB0kety4IKV6rdZd9thVIbyQctJmoNSf8mB2vLm+ufgQqQHc6RpwwZEkAaX+i6pACN44tgEnFuQ96KMW+GX5LCZ7cAUfrLXDNoGfbve2LDBcBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBC/1fBbDfgMoKR98FOvafR2gDDhENLMdjZWJBNHmMYJj2xYOnfv9tAe7F/JCIPrs2yPMwriyzOsgoAHFGkznKWHyvw=] + - ENC[PKCS7,MIIBmQYJKoZIhvcNAQcDoIIBijCCAYYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEAbx8zkPuZRlyeA/27xJZqXyjNkEA4JjsJQPh7BwuiLAqXUDeLLcUWTPOZ/YcJUS2IxLcYXsyWC/WAuhvKrLPCObHDndyWOq32sjI2ywaehMJc2w2cG0Iq4wdHo9Plfmu8T2jA2Tbe/cSuV84bh+toBTIKgckqulcBcgCMKSb5NUbdb33pB/YGieUMdrMfVyLVQUT88lmIXpKkfPN4z49cGEHXxbI7mgi7iUM0JbDJDddkH3jD1v3AI8Cr+/3y68+KMMxPVn1kwzPmLAjxkIJ/WySf7uEBEPrbshvsqS0D+OLk4ujOoBb3dpk5o07O6Sv4UA3R3Qa3Co63l8hGykjNSzBcBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBA70ywaIoOH465wLatsqHP3gDDB+GjjyySugoeismeS+5WMCuepilQ9mBned/nqw0i+8+WrNhsSTTP23hmpWeYAdug=] + - ENC[PKCS7,MIIBmQYJKoZIhvcNAQcDoIIBijCCAYYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEAoVY4a2yUJnvWJAC1gs0+ZgYcMMI1ZDGniNbIc4h6vb3ic7OPSa+QKZtEyboNSGWyLgLDhO6IPeeW+YgOhwo6tpgltBu34/1czwWiRSd9pZo7kq9J+UnnmvXxlfcD9S/hGAqzv+ouPQjWcpOm9rMYGrq78e3Z/VnscA3LtdtQVQtXLFERCIc3xCDNat47rQnWLvCGsDMSqCbUFOX/xnExmifLnHoRlOrg8K+Iw+oIIbI6LlOiE1lb5b8ml8RqckcKTx3ppRQiVNSCGIrjyVyWLtOU+zDmHFqsSf6JLZ0Twlfboafu2/Iz3NZiikSma564NcexTVkbk5bsZeMWB3+oWDBcBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDoWpCRs2064iTVyEvjpXg9gDB/Y/86kLRp6IqmPjFH71oslQ674PK3SfO1jpJJRyJ/61zrdoef+jaK0eEvvJIW70o=] diff --git a/hieradata/roles/infra/storage/vault.yaml b/hieradata/roles/infra/storage/vault.yaml new file mode 100644 index 0000000..df6387a --- /dev/null +++ b/hieradata/roles/infra/storage/vault.yaml @@ -0,0 +1,8 @@ +--- +profiles::vault::server::members_role: roles::infra::storage::vault +profiles::vault::server::members_lookup: true +profiles::vault::server::data_dir: /data/vault +profiles::vault::server::primary_datacenter: 'au-drw1' +profiles::vault::server::manage_storage_dir: true +profiles::vault::server::tls_disable: false +vault::download_url: http://repos.main.unkin.net/unkin/8/x86_64/os/Archives/vault_1.15.5_linux_amd64.zip diff --git a/site/profiles/manifests/pki/puppetcerts.pp b/site/profiles/manifests/pki/puppetcerts.pp index c3d2920..bf02ecd 100644 --- a/site/profiles/manifests/pki/puppetcerts.pp +++ b/site/profiles/manifests/pki/puppetcerts.pp @@ -24,7 +24,7 @@ class profiles::pki::puppetcerts { ensure => 'file', owner => 'root', group => 'root', - mode => '0600', + mode => '0644', source => "/etc/puppetlabs/puppet/ssl/private_keys/${facts['networking']['fqdn']}.pem", require => File['/etc/pki/tls/puppet'], } diff --git a/site/profiles/manifests/vault/server.pp b/site/profiles/manifests/vault/server.pp new file mode 100644 index 0000000..a0e760a --- /dev/null +++ b/site/profiles/manifests/vault/server.pp @@ -0,0 +1,90 @@ +# profiles::vault::server +class profiles::vault::server ( + Boolean $members_lookup = false, + String $members_role = undef, + Array $vault_servers = [], + Enum[ + 'archive', + 'repo' + ] $install_method = 'archive', + Boolean $tls_disable = false, + Stdlib::Port $client_port = 8200, + Stdlib::Port $cluster_port = 8201, + Boolean $manage_storage_dir = false, + Stdlib::Absolutepath $data_dir = '/opt/vault', + Stdlib::Absolutepath $bin_dir = '/usr/bin', +){ + + # use puppet certs as base + include profiles::pki::puppetcerts + + # set a datacentre/cluster name + $vault_cluster = "${::facts['country']}-${::facts['region']}" + + # if lookup is enabled, find all the hosts in the specified role and create the servers_array + if $members_lookup { + + # check that the role is also set + unless !($members_role == undef) { + fail("members_role must be provided for ${title} when members_lookup is True") + } + + # if it is, find hosts, sort them so they dont cause changes every run + $servers_array = sort(query_nodes("enc_role='${members_role}' and region='${::facts['region']}'", 'networking.fqdn')) + + # else use provided array from params + }else{ + $servers_array = $vault_servers + } + + # set http scheme + $http_scheme = $tls_disable ? { + true => 'http', + false => 'https' + } + + # create vault urls + $server_urls = $servers_array.map |$fqdn| { + { + leader_api_addr => "${http_scheme}://${fqdn}:${client_port}", + leader_client_cert_file => "/etc/pki/tls/puppet/${facts['networking']['fqdn']}.crt", + leader_client_key_file => "/etc/pki/tls/puppet/${facts['networking']['fqdn']}.key", + leader_ca_cert_file => '/etc/pki/tls/puppet/ca.pem', + } + } + + class { 'vault': + install_method => $install_method, + manage_storage_dir => $manage_storage_dir, + enable_ui => true, + storage => { + raft => { + node_id => $::facts['networking']['fqdn'], + path => $data_dir, + retry_join => $server_urls, + } + }, + api_addr => "${http_scheme}://${::facts['networking']['fqdn']}:${client_port}", + extra_config => { + cluster_addr => "${http_scheme}://${::facts['networking']['fqdn']}:${cluster_port}", + }, + listener => [ + { + tcp => { + address => "127.0.0.1:${client_port}", + cluster_address => "127.0.0.1:${cluster_port}", + tls_disable => true, + } + }, + { + tcp => { + address => "${::facts['networking']['ip']}:${client_port}", + cluster_address => "${::facts['networking']['ip']}:${cluster_port}", + tls_disable => $tls_disable, + tls_cert_file => "/etc/pki/tls/puppet/${facts['networking']['fqdn']}.crt", + tls_key_file => "/etc/pki/tls/puppet/${facts['networking']['fqdn']}.key", + } + } + ] + } +} diff --git a/site/profiles/manifests/vault/unseal.pp b/site/profiles/manifests/vault/unseal.pp new file mode 100644 index 0000000..81ccb89 --- /dev/null +++ b/site/profiles/manifests/vault/unseal.pp @@ -0,0 +1,37 @@ +# profiles::vault::unseal +class profiles::vault::unseal ( + Array[String] $unseal_keys = lookup('vault::unseal_keys', Array[String], 'first', []), + Variant[ + Stdlib::HTTPSUrl, + Stdlib::HTTPUrl + ] $vault_address = 'http://127.0.0.1:8200', +){ + + # deploy the unseal keys file + file { '/etc/vault/unseal_keys': + ensure => file, + owner => 'root', + group => 'root', + mode => '0600', + content => Sensitive(template('profiles/vault/unseal_keys.erb')), + require => Class['vault'], + } + + # deploy the unseal script + file { '/usr/local/bin/vault-unseal.sh': + ensure => file, + owner => 'root', + group => 'root', + mode => '0750', + content => template('profiles/vault/vault_unseal.sh.erb'), + } + + # create systemd service unit + systemd::unit_file { 'vault-unseal.service': + content => template('profiles/vault/vault-unseal.service.erb'), + active => true, + enable => true, + require => File['/usr/local/bin/vault-unseal.sh'], + subscribe => Service['vault'], + } +} diff --git a/site/profiles/templates/vault/unseal_keys.erb b/site/profiles/templates/vault/unseal_keys.erb new file mode 100644 index 0000000..0ee4751 --- /dev/null +++ b/site/profiles/templates/vault/unseal_keys.erb @@ -0,0 +1,3 @@ +<% @unseal_keys.each do |key| -%> +<%= key %> +<% end -%> diff --git a/site/profiles/templates/vault/vault-unseal.service.erb b/site/profiles/templates/vault/vault-unseal.service.erb new file mode 100644 index 0000000..83b0e1a --- /dev/null +++ b/site/profiles/templates/vault/vault-unseal.service.erb @@ -0,0 +1,14 @@ +[Unit] +Description=Unseal Vault Service +After=vault.service network.target +Requires=vault.service +PartOf=vault.service + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/vault-unseal.sh +RemainAfterExit=yes +User=root + +[Install] +WantedBy=multi-user.target diff --git a/site/profiles/templates/vault/vault_unseal.sh.erb b/site/profiles/templates/vault/vault_unseal.sh.erb new file mode 100644 index 0000000..5e4d5aa --- /dev/null +++ b/site/profiles/templates/vault/vault_unseal.sh.erb @@ -0,0 +1,23 @@ +#!/bin/bash + +# Script to unseal Vault + +VAULT_ADDR='<%= @vault_address %>' +UNSEAL_KEYS_FILE='/etc/vault/unseal_keys' + +# Check if Vault is sealed +is_sealed=$(curl -s ${VAULT_ADDR}/v1/sys/seal-status | jq -r '.sealed') +if [ "$is_sealed" != "true" ]; then + echo "Vault is already unsealed." + exit 0 +fi + +# Retrieve unseal keys from plaintext file +unseal_keys=$(cat "$UNSEAL_KEYS_FILE") + +# Loop through the unseal keys and use them to unseal Vault +for key in $unseal_keys; do + curl --request PUT --data '{"key": "'$key'"}' $VAULT_ADDR/v1/sys/unseal +done + +echo "Vault has been unsealed." diff --git a/site/roles/manifests/infra/storage/vault.pp b/site/roles/manifests/infra/storage/vault.pp index 9d17004..b6afe40 100644 --- a/site/roles/manifests/infra/storage/vault.pp +++ b/site/roles/manifests/infra/storage/vault.pp @@ -2,4 +2,7 @@ class roles::infra::storage::vault { include profiles::defaults include profiles::base + include profiles::base::datavol + include profiles::vault::server + include profiles::vault::unseal }