5 require 'puppet/util/execution'
6 require 'puppet/util/package'
8 require 'puppet/feature/chloride'
16 Puppet::SSL::Host.ca_location = :only
17 Puppet.settings.preferred_run_mode = "master"
19 if !Puppet::SSL::CertificateAuthority.ca?
20 raise "Unable to set up CA: this node is not a CA server."
23 if Puppet::SSL::Certificate.indirection.find('ca').nil?
24 raise "Unable to set up CA: the CA certificate is not present."
27 Puppet::SSL::CertificateAuthority.instance
31 cacert_backup_path = File.join(Puppet[:cadir], "ca_crt.#{Time.now.to_i}.pem")
32 Puppet.notice("Backing up current CA certificate to #{cacert_backup_path}")
33 FileUtils.cp(Puppet[:cacert], cacert_backup_path)
36 # Generate an updated CA certificate with the same subject as the existing CA certificate
37 # and synchronize the new CA certificate with the local CA certificate.
38 def regenerate(ca, cert = Puppet::SSL::Certificate.indirection.find("ca"))
39 Puppet[:ca_name] = cert.content.subject.to_a[0][1]
41 request = Puppet::SSL::CertificateRequest.new(Puppet::SSL::Host::CA_NAME)
42 request.generate(ca.host.key)
43 PuppetX::Certregen::CA.sign(ca, Puppet::SSL::CA_NAME,
44 {allow_dns_alt_names: false, self_signing_csr: request})
45 FileUtils.cp(Puppet[:cacert], Puppet[:localcacert])
48 # Copy the current CA certificate and CRL to the given host.
50 # @note Only Linux systems are supported and requires that the localcacert/hostcrl setting on the
51 # given host is the default path.
53 # @param [String] hostname The host to copy the CA cert to
54 # @param [Hash] config the Chloride host config
56 def distribute(hostname, config)
57 host = Chloride::Host.new(hostname, config)
60 Puppet.debug("SSH status for #{hostname}: #{host.ssh_status}")
62 log_events = lambda do |event|
63 event.data[:messages].each do |data|
64 Puppet.info "[#{data.severity}:#{data.hostname}]: #{data.message.inspect}"
68 distribute_cacert(host, log_events)
69 distribute_crl(host, log_events)
72 def distribute_cacert(host, blk)
74 dst ='/etc/puppetlabs/puppet/ssl/certs/ca.pem' # @todo: query node for localcacert
75 distribute_file(host, src, dst, blk)
78 def distribute_crl(host, blk)
80 dst ='/etc/puppetlabs/puppet/ssl/crl.pem' # @todo: query node for hostcrl
81 distribute_file(host, src, dst, blk)
84 def distribute_file(host, src, dst, blk)
85 tmp = "#{File.basename(src)}.tmp.#{SecureRandom.uuid}"
87 copy_action = Chloride::Action::FileCopy.new(to_host: host, from: src, to: tmp)
89 if copy_action.success?
90 Puppet.info "Copied #{src} to #{host.hostname}:#{tmp}"
92 raise "Failed to copy #{src} to #{host.hostname}:#{tmp}: #{copy_action.status}"
95 move_action = Chloride::Action::Execute.new(host: host, cmd: "cp #{tmp} #{dst}", sudo: true)
98 if move_action.success?
99 Puppet.info "Updated #{host.hostname}:#{dst}"
101 raise "Failed to copy #{tmp} to #{host.hostname}:#{dst}"
107 # Enumerate Puppet nodes without relying on PuppetDB
109 # If the Puppet CA certificate has expired we cannot rely on PuppetDB working
110 # or being able to connect to Postgres via the network. In order to access
111 # this information while the CA is in a degraded state we perform the query
112 # directly via a local psql call.
114 psql = '/opt/puppetlabs/server/bin/psql -d pe-puppetdb --pset format=unaligned --pset t=on -c %s'
115 query = 'SELECT certname FROM certnames WHERE deactivated IS NULL AND expired IS NULL;'
116 cmd = psql % Shellwords.escape(query)
117 Puppet::Util::Execution.execute(cmd,
119 gid: 'pe-postgres').split("\n")
123 # Abstract API changes for CA cert signing
125 # @param ca [Puppet::SSL::CertificateAuthority]
126 # @param hostname [String]
127 # @param options [Hash<Symbol, Object>]
128 def sign(ca, hostname, options)
129 if Puppet::Util::Package.versioncmp(Puppet::PUPPETVERSION, "4.6.0") != -1
130 ca.sign(hostname, options)
132 ca.sign(hostname, options[:allow_dns_alt_names], options[:self_signing_csr])