diff --git a/hieradata/role/puppetdb.yaml b/hieradata/role/puppetdb.yaml index 88b2f4ab34..8ff7af5fd7 100644 --- a/hieradata/role/puppetdb.yaml +++ b/hieradata/role/puppetdb.yaml @@ -3,15 +3,46 @@ classes: - "profile::core::common" - "profile::core::docker" - "profile::core::docker::prune" + - "profile::core::firewall" - "profile::core::puppetboard" - - "puppetdb" + - "profile::core::puppetdb" +apache::default_vhost: false +# apache::log_level: "debug" # for debugging ldap binding +firewall::ensure: "running" postgresql::globals::manage_dnf_module: true # use appstream packages +profile::core::puppetdb::ldap_servers: + - "ipa1.ls.lsst.org" + - "ipa2.ls.lsst.org" + - "ipa3.ls.lsst.org" puppetdb::database_listen_address: "localhost" puppetdb::globals::version: "7.14.0" +puppetdb::listen_address: "localhost" # http only puppetdb::manage_package_repo: false puppetdb::postgres_version: "15" +puppetdb::ssl_listen_address: "0.0.0.0" puppetdb::java_args: "-Xmx": "1g" "-Xms": "512m" -puppetdb::listen_address: "0.0.0.0" + +profile::core::firewall::firewall: + "250 accept http - redirect to 443": + proto: "tcp" + state: "NEW" + dport: "80" + action: "accept" + "251 accept https - puppetboard ldap": + proto: "tcp" + state: "NEW" + dport: "443" + action: "accept" + "252 accept https - puppetdb x509": + proto: "tcp" + state: "NEW" + dport: "8081" + action: "accept" + "253 accept https - puppetdb ldap": + proto: "tcp" + state: "NEW" + dport: "8443" + action: "accept" diff --git a/hieradata/site/cp/role/puppetdb.yaml b/hieradata/site/cp/role/puppetdb.yaml new file mode 100644 index 0000000000..1e67c57e85 --- /dev/null +++ b/hieradata/site/cp/role/puppetdb.yaml @@ -0,0 +1,5 @@ +--- +profile::core::puppetdb::ldap_servers: + - "ipa1.cp.lsst.org" + - "ipa2.cp.lsst.org" + - "ipa3.cp.lsst.org" diff --git a/hieradata/site/tu/role/puppetdb.yaml b/hieradata/site/tu/role/puppetdb.yaml new file mode 100644 index 0000000000..185be6fff5 --- /dev/null +++ b/hieradata/site/tu/role/puppetdb.yaml @@ -0,0 +1,5 @@ +--- +profile::core::puppetdb::ldap_servers: + - "ipa1.tu.lsst.org" + - "ipa2.tu.lsst.org" + - "ipa3.tu.lsst.org" diff --git a/site/profile/manifests/core/puppetboard.pp b/site/profile/manifests/core/puppetboard.pp index 8c02907894..42e73fc343 100644 --- a/site/profile/manifests/core/puppetboard.pp +++ b/site/profile/manifests/core/puppetboard.pp @@ -10,13 +10,19 @@ docker::image { 'ghcr.io/voxpupuli/puppetboard': } docker::run { 'puppetboard': - image => 'ghcr.io/voxpupuli/puppetboard', - env => [ + image => 'ghcr.io/voxpupuli/puppetboard', + volumes => ['/etc/puppetlabs/puppet/ssl:/etc/puppetlabs/puppet/ssl:ro'], + net => 'host', + env => [ 'PUPPETDB_HOST=127.0.0.1', - 'PUPPETDB_PORT=8080', + 'PUPPETDB_PORT=8081', 'PUPPETBOARD_PORT=8088', + 'ENABLE_CATALOG=true', + 'PUPPETDB_SSL_VERIFY=false', + "PUPPETDB_KEY=/etc/puppetlabs/puppet/ssl/private_keys/${fact('networking.fqdn')}.pem", + "PUPPETDB_CERT=/etc/puppetlabs/puppet/ssl/certs/${fact('networking.fqdn')}.pem", "SECRET_KEY=${secret_key.unwrap}", + 'DEFAULT_ENVIRONMENT=*', ], - net => 'host', } } diff --git a/site/profile/manifests/core/puppetdb.pp b/site/profile/manifests/core/puppetdb.pp new file mode 100644 index 0000000000..ced8a43ad9 --- /dev/null +++ b/site/profile/manifests/core/puppetdb.pp @@ -0,0 +1,105 @@ +# @summary +# Install puppetdb +# +# @param ldap_servers +# An array of LDAP servers to use for authentication +# +# @param ldap_bind_user +# The user to use for binding to the LDAP server(s) +# +# @param ldap_bind_pass +# The password to use for binding to the LDAP server(s) +# +class profile::core::puppetdb ( + Array[String[1]] $ldap_servers, + Sensitive[String[1]] $ldap_bind_user, + Sensitive[String[1]] $ldap_bind_pass, +) { + include apache + include apache::mod::authnz_ldap + include apache::mod::ldap + include profile::core::letsencrypt + include puppetdb + + $fqdn = fact('networking.fqdn') + $le_root = "/etc/letsencrypt/live/${fqdn}" + # apache wants a space separated list of ldap servers as part of the ldap + # url... + $try_ldap_servers = join($ldap_servers, ' ') + + letsencrypt::certonly { $fqdn: + plugin => 'dns-route53', + manage_cron => true, + } + + # A cron job is needed to restart apache if the letsencrypt cert is renewed. + # We are being lazy and just restart apache every day at noon. + cron::job { 'restart-apache-on-letsencrypt-renewal': + minute => '0', + hour => '15', + date => '*', + month => '*', + weekday => '*', + command => '/bin/systemctl restart httpd', + description => 'Restart apache incase the letsencrypt cert is renewed', + } + + apache::vhost { 'redirect-https': + servername => $fqdn, + port => 80, + docroot => '/var/www/html', + redirect_dest => "https://${fqdn}", + } + + apache::vhost { + default: + servername => $fqdn, + docroot => '/var/www/html', + ssl => true, + ssl_cert => "${le_root}/fullchain.pem", + ssl_key => "${le_root}/privkey.pem", + rewrites => [ + { + comment => 'Eliminate Trace and Track', + rewrite_cond => ['%{REQUEST_METHOD} ^(TRACE|TRACK)'], + rewrite_rule => [' .* - [F]'], + }, + ], + proxy_preserve_host => true, + # XXX show_diff isn't part of a forge release yet + # https://github.com/puppetlabs/puppetlabs-apache/pull/2536 + # show_diff => false, # don't show ldap bind pass in diff + directories => [ + { + path => '/', + provider => 'location', + auth_name => 'IPA Authentication', + auth_type => 'Basic', + auth_basic_provider => 'ldap', + # quotes are required around the ldap url because it contains a space + auth_ldap_url => "\"ldaps://${try_ldap_servers}/cn=users,cn=accounts,dc=lsst,dc=cloud?uid?sub?(objectClass=posixAccount)\"", + auth_ldap_bind_dn => "uid=${ldap_bind_user.unwrap},cn=users,cn=accounts,dc=lsst,dc=cloud", + auth_ldap_bind_password => $ldap_bind_pass.unwrap, + require => [ + 'ldap-group cn=admins,cn=groups,cn=accounts,dc=lsst,dc=cloud', + 'ldap-group cn=puppetdb,cn=groups,cn=accounts,dc=lsst,dc=cloud', + ], + }, + ], + ; + 'puppetboard-proxy': + port => 443, + proxy_pass => { + path => '/', + url => 'http://127.0.0.1:8088/', + }, + ; + 'puppetdb-proxy': + port => 8443, + proxy_pass => { + path => '/', + url => 'http://127.0.0.1:8080/', + }, + ; + } +} diff --git a/spec/fixtures/hieradata/common.yaml b/spec/fixtures/hieradata/common.yaml index 32ea6dd0ed..cb126f6f5a 100644 --- a/spec/fixtures/hieradata/common.yaml +++ b/spec/fixtures/hieradata/common.yaml @@ -24,6 +24,8 @@ lookup_options: convert_to: "Sensitive" '^profile::ccs::postfix::auth$': convert_to: "Sensitive" + '^profile::core::puppetdb::ldap_bind_(user|pass)$': + convert_to: "Sensitive" ccs_database::database: "comcamdbprod" ccs_database::password: "foo" foreman_proxy::plugin::dns::route53::aws_access_key: "foo" @@ -43,6 +45,8 @@ profile::core::monitoring::password: "foo" profile::core::monitoring::url: "foo" profile::core::monitoring::username: "foo" profile::core::puppetboard::secret_key: "foo" +profile::core::puppetdb::ldap_bind_pass: "foo" +profile::core::puppetdb::ldap_bind_user: "foo" restic::id: "foo" restic::key: "foo" restic::password: "foo" diff --git a/spec/hosts/roles/puppetdb_spec.rb b/spec/hosts/roles/puppetdb_spec.rb index 6363dabebd..2df8efb20d 100644 --- a/spec/hosts/roles/puppetdb_spec.rb +++ b/spec/hosts/roles/puppetdb_spec.rb @@ -5,12 +5,33 @@ PUPPETDB_VERSION = '7.14.0' shared_examples 'puppetdb' do + %w[ + apache + apache::mod::authnz_ldap + apache::mod::ldap + puppetdb + ].each do |c| + it { is_expected.to contain_class(c) } + end + + it do + is_expected.to contain_letsencrypt__certonly(facts[:networking]['fqdn']).with( + plugin: 'dns-route53', + manage_cron: true, + ) + end + + it { is_expected.to contain_cron__job('restart-apache-on-letsencrypt-renewal') } + it { is_expected.to contain_apache__vhost('redirect-https').with_port(80) } + it { is_expected.to contain_apache__vhost('puppetboard-proxy').with_port(443) } + it { is_expected.to contain_apache__vhost('puppetdb-proxy').with_port(8443) } + it { is_expected.to contain_class('puppetdb::globals').with_version(PUPPETDB_VERSION) } it { is_expected.to contain_yum__versionlock('puppetdb').with_version(PUPPETDB_VERSION) } it do is_expected.to contain_class('puppetdb').with( - listen_address: '0.0.0.0', + listen_address: 'localhost', java_args: { '-Xmx' => '1g', '-Xms' => '512m', @@ -25,23 +46,65 @@ version: '15', ) end + + it do + is_expected.to contain_firewall('250 accept http - redirect to 443').with( + proto: 'tcp', + state: 'NEW', + dport: '80', + action: 'accept', + ) + end + + it do + is_expected.to contain_firewall('251 accept https - puppetboard ldap').with( + proto: 'tcp', + state: 'NEW', + dport: '443', + action: 'accept', + ) + end + + it do + is_expected.to contain_firewall('252 accept https - puppetdb x509').with( + proto: 'tcp', + state: 'NEW', + dport: '8081', + action: 'accept', + ) + end + + it do + is_expected.to contain_firewall('253 accept https - puppetdb ldap').with( + proto: 'tcp', + state: 'NEW', + dport: '8443', + action: 'accept', + ) + end end shared_examples 'puppetboard' do - it { is_expected.to contain_class('docker') } - it { is_expected.to contain_cron__job('docker_prune') } it { is_expected.to contain_docker__image('ghcr.io/voxpupuli/puppetboard') } it do is_expected.to contain_docker__run('puppetboard').with( image: 'ghcr.io/voxpupuli/puppetboard', + volumes: [ + '/etc/puppetlabs/puppet/ssl:/etc/puppetlabs/puppet/ssl:ro', + ], + net: 'host', env: [ 'PUPPETDB_HOST=127.0.0.1', - 'PUPPETDB_PORT=8080', + 'PUPPETDB_PORT=8081', 'PUPPETBOARD_PORT=8088', + 'ENABLE_CATALOG=true', + 'PUPPETDB_SSL_VERIFY=false', + "PUPPETDB_KEY=/etc/puppetlabs/puppet/ssl/private_keys/#{facts[:networking]['fqdn']}.pem", + "PUPPETDB_CERT=/etc/puppetlabs/puppet/ssl/certs/#{facts[:networking]['fqdn']}.pem", 'SECRET_KEY=foo', + 'DEFAULT_ENVIRONMENT=*', ], - net: 'host', ) end end @@ -71,8 +134,35 @@ it { is_expected.to compile.with_all_deps } include_examples 'common', os_facts: os_facts, site: site + include_examples 'docker' + it { is_expected.to contain_cron__job('docker_prune') } + + include_examples 'ipset' + include_examples 'firewall default', os_facts: os_facts + include_examples 'firewall node_exporter scraping', site: site include_examples 'puppetdb' include_examples 'puppetboard' + + case site + when 'dev', 'ls' + it do + expect(catalogue.resource('apache::vhost', 'puppetboard-proxy')[:directories].first).to include( + 'auth_ldap_url' => '"ldaps://ipa1.ls.lsst.org ipa2.ls.lsst.org ipa3.ls.lsst.org/cn=users,cn=accounts,dc=lsst,dc=cloud?uid?sub?(objectClass=posixAccount)"', + ) + end + when 'tu' + it do + expect(catalogue.resource('apache::vhost', 'puppetboard-proxy')[:directories].first).to include( + 'auth_ldap_url' => '"ldaps://ipa1.tu.lsst.org ipa2.tu.lsst.org ipa3.tu.lsst.org/cn=users,cn=accounts,dc=lsst,dc=cloud?uid?sub?(objectClass=posixAccount)"', + ) + end + when 'cp' + it do + expect(catalogue.resource('apache::vhost', 'puppetboard-proxy')[:directories].first).to include( + 'auth_ldap_url' => '"ldaps://ipa1.cp.lsst.org ipa2.cp.lsst.org ipa3.cp.lsst.org/cn=users,cn=accounts,dc=lsst,dc=cloud?uid?sub?(objectClass=posixAccount)"', + ) + end + end end # host end # lsst_sites end