Add puppetlabs/certregen module
[mirror/dsa-puppet.git] / 3rdparty / modules / certregen / lib / puppet_x / certregen / ca.rb
diff --git a/3rdparty/modules/certregen/lib/puppet_x/certregen/ca.rb b/3rdparty/modules/certregen/lib/puppet_x/certregen/ca.rb
new file mode 100644 (file)
index 0000000..c9e7457
--- /dev/null
@@ -0,0 +1,137 @@
+require 'securerandom'
+require 'shellwords'
+
+require 'puppet'
+require 'puppet/util/execution'
+require 'puppet/util/package'
+
+require 'puppet/feature/chloride'
+
+module PuppetX
+  module Certregen
+    module CA
+      module_function
+
+      def setup
+        Puppet::SSL::Host.ca_location = :only
+        Puppet.settings.preferred_run_mode = "master"
+
+        if !Puppet::SSL::CertificateAuthority.ca?
+          raise "Unable to set up CA: this node is not a CA server."
+        end
+
+        if Puppet::SSL::Certificate.indirection.find('ca').nil?
+          raise "Unable to set up CA: the CA certificate is not present."
+        end
+
+        Puppet::SSL::CertificateAuthority.instance
+      end
+
+      def backup
+        cacert_backup_path = File.join(Puppet[:cadir], "ca_crt.#{Time.now.to_i}.pem")
+        Puppet.notice("Backing up current CA certificate to #{cacert_backup_path}")
+        FileUtils.cp(Puppet[:cacert], cacert_backup_path)
+      end
+
+      # Generate an updated CA certificate with the same subject as the existing CA certificate
+      # and synchronize the new CA certificate with the local CA certificate.
+      def regenerate(ca, cert = Puppet::SSL::Certificate.indirection.find("ca"))
+        Puppet[:ca_name] = cert.content.subject.to_a[0][1]
+
+        request = Puppet::SSL::CertificateRequest.new(Puppet::SSL::Host::CA_NAME)
+        request.generate(ca.host.key)
+        PuppetX::Certregen::CA.sign(ca, Puppet::SSL::CA_NAME,
+                                    {allow_dns_alt_names: false, self_signing_csr: request})
+        FileUtils.cp(Puppet[:cacert], Puppet[:localcacert])
+      end
+
+      # Copy the current CA certificate and CRL to the given host.
+      #
+      # @note Only Linux systems are supported and requires that the localcacert/hostcrl setting on the
+      #   given host is the default path.
+      #
+      # @param [String] hostname The host to copy the CA cert to
+      # @param [Hash] config the Chloride host config
+      # @return [void]
+      def distribute(hostname, config)
+        host = Chloride::Host.new(hostname, config)
+        host.ssh_connect
+
+        Puppet.debug("SSH status for #{hostname}: #{host.ssh_status}")
+
+        log_events = lambda do |event|
+          event.data[:messages].each do |data|
+            Puppet.info "[#{data.severity}:#{data.hostname}]: #{data.message.inspect}"
+          end
+        end
+
+        distribute_cacert(host, log_events)
+        distribute_crl(host, log_events)
+      end
+
+      def distribute_cacert(host, blk)
+        src = Puppet[:cacert]
+        dst ='/etc/puppetlabs/puppet/ssl/certs/ca.pem' # @todo: query node for localcacert
+        distribute_file(host, src, dst, blk)
+      end
+
+      def distribute_crl(host, blk)
+        src = Puppet[:cacrl]
+        dst ='/etc/puppetlabs/puppet/ssl/crl.pem' # @todo: query node for hostcrl
+        distribute_file(host, src, dst, blk)
+      end
+
+      def distribute_file(host, src, dst, blk)
+        tmp = "#{File.basename(src)}.tmp.#{SecureRandom.uuid}"
+
+        copy_action = Chloride::Action::FileCopy.new(to_host: host, from: src, to: tmp)
+        copy_action.go(&blk)
+        if copy_action.success?
+          Puppet.info "Copied #{src} to #{host.hostname}:#{tmp}"
+        else
+          raise "Failed to copy #{src} to #{host.hostname}:#{tmp}: #{copy_action.status}"
+        end
+
+        move_action = Chloride::Action::Execute.new(host: host, cmd: "cp #{tmp} #{dst}", sudo: true)
+        move_action.go(&blk)
+
+        if move_action.success?
+          Puppet.info "Updated #{host.hostname}:#{dst}"
+        else
+          raise "Failed to copy #{tmp} to #{host.hostname}:#{dst}"
+        end
+
+      end
+
+
+      # Enumerate Puppet nodes without relying on PuppetDB
+      #
+      # If the Puppet CA certificate has expired we cannot rely on PuppetDB working
+      # or being able to connect to Postgres via the network. In order to access
+      # this information while the CA is in a degraded state we perform the query
+      # directly via a local psql call.
+      def certnames
+        psql = '/opt/puppetlabs/server/bin/psql -d pe-puppetdb --pset format=unaligned --pset t=on -c %s'
+        query = 'SELECT certname FROM certnames WHERE deactivated IS NULL AND expired IS NULL;'
+        cmd = psql % Shellwords.escape(query)
+        Puppet::Util::Execution.execute(cmd,
+                                        uid: 'pe-postgres',
+                                        gid: 'pe-postgres').split("\n")
+
+      end
+
+      # Abstract API changes for CA cert signing
+      #
+      # @param ca [Puppet::SSL::CertificateAuthority]
+      # @param hostname [String]
+      # @param options [Hash<Symbol, Object>]
+      def sign(ca, hostname, options)
+        if Puppet::Util::Package.versioncmp(Puppet::PUPPETVERSION, "4.6.0") != -1
+          ca.sign(hostname, options)
+        else
+          ca.sign(hostname, options[:allow_dns_alt_names], options[:self_signing_csr])
+        end
+      end
+    end
+  end
+end