Add puppetlabs/certregen module
[mirror/dsa-puppet.git] / 3rdparty / modules / certregen / lib / puppet_x / certregen / ca.rb
1 require 'securerandom'
2 require 'shellwords'
3
4 require 'puppet'
5 require 'puppet/util/execution'
6 require 'puppet/util/package'
7
8 require 'puppet/feature/chloride'
9
10 module PuppetX
11   module Certregen
12     module CA
13       module_function
14
15       def setup
16         Puppet::SSL::Host.ca_location = :only
17         Puppet.settings.preferred_run_mode = "master"
18
19         if !Puppet::SSL::CertificateAuthority.ca?
20           raise "Unable to set up CA: this node is not a CA server."
21         end
22
23         if Puppet::SSL::Certificate.indirection.find('ca').nil?
24           raise "Unable to set up CA: the CA certificate is not present."
25         end
26
27         Puppet::SSL::CertificateAuthority.instance
28       end
29
30       def backup
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)
34       end
35
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]
40
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])
46       end
47
48       # Copy the current CA certificate and CRL to the given host.
49       #
50       # @note Only Linux systems are supported and requires that the localcacert/hostcrl setting on the
51       #   given host is the default path.
52       #
53       # @param [String] hostname The host to copy the CA cert to
54       # @param [Hash] config the Chloride host config
55       # @return [void]
56       def distribute(hostname, config)
57         host = Chloride::Host.new(hostname, config)
58         host.ssh_connect
59
60         Puppet.debug("SSH status for #{hostname}: #{host.ssh_status}")
61
62         log_events = lambda do |event|
63           event.data[:messages].each do |data|
64             Puppet.info "[#{data.severity}:#{data.hostname}]: #{data.message.inspect}"
65           end
66         end
67
68         distribute_cacert(host, log_events)
69         distribute_crl(host, log_events)
70       end
71
72       def distribute_cacert(host, blk)
73         src = Puppet[:cacert]
74         dst ='/etc/puppetlabs/puppet/ssl/certs/ca.pem' # @todo: query node for localcacert
75         distribute_file(host, src, dst, blk)
76       end
77
78       def distribute_crl(host, blk)
79         src = Puppet[:cacrl]
80         dst ='/etc/puppetlabs/puppet/ssl/crl.pem' # @todo: query node for hostcrl
81         distribute_file(host, src, dst, blk)
82       end
83
84       def distribute_file(host, src, dst, blk)
85         tmp = "#{File.basename(src)}.tmp.#{SecureRandom.uuid}"
86
87         copy_action = Chloride::Action::FileCopy.new(to_host: host, from: src, to: tmp)
88         copy_action.go(&blk)
89         if copy_action.success?
90           Puppet.info "Copied #{src} to #{host.hostname}:#{tmp}"
91         else
92           raise "Failed to copy #{src} to #{host.hostname}:#{tmp}: #{copy_action.status}"
93         end
94
95         move_action = Chloride::Action::Execute.new(host: host, cmd: "cp #{tmp} #{dst}", sudo: true)
96         move_action.go(&blk)
97
98         if move_action.success?
99           Puppet.info "Updated #{host.hostname}:#{dst}"
100         else
101           raise "Failed to copy #{tmp} to #{host.hostname}:#{dst}"
102         end
103
104       end
105
106
107       # Enumerate Puppet nodes without relying on PuppetDB
108       #
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.
113       def certnames
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,
118                                         uid: 'pe-postgres',
119                                         gid: 'pe-postgres').split("\n")
120
121       end
122
123       # Abstract API changes for CA cert signing
124       #
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)
131         else
132           ca.sign(hostname, options[:allow_dns_alt_names], options[:self_signing_csr])
133         end
134       end
135     end
136   end
137 end