diff options
Diffstat (limited to 'code/environments/production/modules/certregen/lib')
12 files changed, 0 insertions, 505 deletions
diff --git a/code/environments/production/modules/certregen/lib/facter/has_puppet.rb b/code/environments/production/modules/certregen/lib/facter/has_puppet.rb deleted file mode 100644 index 05f2e80..0000000 --- a/code/environments/production/modules/certregen/lib/facter/has_puppet.rb +++ /dev/null @@ -1,10 +0,0 @@ -Facter.add(:has_puppet) do - setcode do - begin - require 'puppet' - true - rescue LoadError - false - end - end -end diff --git a/code/environments/production/modules/certregen/lib/facter/hostcrl.rb b/code/environments/production/modules/certregen/lib/facter/hostcrl.rb deleted file mode 100644 index 1d69a66..0000000 --- a/code/environments/production/modules/certregen/lib/facter/hostcrl.rb +++ /dev/null @@ -1,4 +0,0 @@ -Facter.add(:hostcrl) do - confine :has_puppet => true - setcode { Puppet[:hostcrl] } -end diff --git a/code/environments/production/modules/certregen/lib/facter/localcacert.rb b/code/environments/production/modules/certregen/lib/facter/localcacert.rb deleted file mode 100644 index 278ca8b..0000000 --- a/code/environments/production/modules/certregen/lib/facter/localcacert.rb +++ /dev/null @@ -1,4 +0,0 @@ -Facter.add(:localcacert) do - confine :has_puppet => true - setcode { Puppet[:localcacert] } -end diff --git a/code/environments/production/modules/certregen/lib/puppet/application/certregen.rb b/code/environments/production/modules/certregen/lib/puppet/application/certregen.rb deleted file mode 100644 index 73d6ca2..0000000 --- a/code/environments/production/modules/certregen/lib/puppet/application/certregen.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'puppet/application/face_base' - -class Puppet::Application::Certregen < Puppet::Application::FaceBase -end diff --git a/code/environments/production/modules/certregen/lib/puppet/face/certregen.rb b/code/environments/production/modules/certregen/lib/puppet/face/certregen.rb deleted file mode 100644 index 24c4b30..0000000 --- a/code/environments/production/modules/certregen/lib/puppet/face/certregen.rb +++ /dev/null @@ -1,205 +0,0 @@ -require 'puppet/face' -require 'puppet_x/certregen/ca' -require 'puppet_x/certregen/certificate' -require 'puppet_x/certregen/crl' -require 'puppet/feature/chloride' - -Puppet::Face.define(:certregen, '0.1.0') do - copyright "Puppet", 2016 - summary "Regenerate the Puppet CA and client certificates" - - description <<-EOT - This subcommand provides tools for monitoring the health of the Puppet CA, regenerating - expiring CA certificates, and remediation for expired CA certificates. - EOT - - action(:ca) do - summary "Refresh the Puppet CA certificate and CRL" - - option('--ca_serial SERIAL') do - summary 'The serial number (in hexadecimal) of the CA to rotate.' - end - - when_invoked do |opts| - cert = Puppet::Face[:certregen, :current].cacert(:ca_serial => opts[:ca_serial]) - crl = Puppet::Face[:certregen, :current].crl() - [cert, crl] - end - - when_rendering :console do |(cert, crl)| - "CA expiration is now #{cert.content.not_after}\n" + \ - "CRL next update is now #{crl.content.next_update}" - end - end - - action(:cacert) do - summary "Regenerate the Puppet CA certificate" - - description <<-EOT - This subcommand generates a new CA certificate that can replace the existing CA certificate. - The new CA certificate uses the same subject as the current CA certificate and reuses the - key pair associated with the current CA certificate, so all certificates signed by the old - CA certificate will remain valid. - EOT - - option('--ca_serial SERIAL') do - summary 'The serial number (in hexadecimal) of the CA to rotate.' - end - - when_invoked do |opts| - ca = PuppetX::Certregen::CA.setup - - current_ca_serial = ca.host.certificate.content.serial.to_s(16) - if opts[:ca_serial].nil? - raise "The serial number of the CA certificate to rotate must be provided. If you " \ - "are sure that you want to rotate the CA certificate, rerun this command with " \ - "--ca_serial #{current_ca_serial}" - elsif opts[:ca_serial] != current_ca_serial - raise "The serial number of the current CA certificate (#{current_ca_serial}) "\ - "does not match the serial number given on the command line (#{opts[:ca_serial]}). "\ - "If you are sure that you want to rotate the CA certificate, rerun this command with "\ - "--ca_serial #{current_ca_serial}" - end - - PuppetX::Certregen::CA.backup - PuppetX::Certregen::CA.regenerate(ca) - Puppet::SSL::Certificate.indirection.find(Puppet::SSL::CA_NAME) - end - - when_rendering(:console) do |cert| - "CA expiration is now #{cert.content.not_after}" - end - end - - action(:crl) do - summary 'Update the lastUpdate and nextUpdate field for the CA CRL' - - when_invoked do |opts| - ca = PuppetX::Certregen::CA.setup - PuppetX::Certregen::CRL.refresh(ca) - end - - when_rendering(:console) do |crl| - "CRL next update is now #{crl.content.next_update}" - end - end - - action(:healthcheck) do - summary "Check for expiring certificates" - - description <<-EOT - This subcommand checks for certificates that are nearing or past expiration. - EOT - - option('--all') do - summary "Report certificate expiry for all nodes, including nodes that aren't near expiration." - end - - when_invoked do |opts| - ca = PuppetX::Certregen::CA.setup - - certs = Puppet::SSL::Certificate.indirection.search('*').select do |cert| - opts[:all] || PuppetX::Certregen::Certificate.expiring?(cert) - end - - cacert = ca.host.certificate - certs << cacert if (opts[:all] || PuppetX::Certregen::Certificate.expiring?(cacert)) - - certs.sort { |a, b| a.content.not_after <=> b.content.not_after } - end - - when_rendering :console do |certs| - if certs.empty? - "No certificates are approaching expiration." - else - certs.map do |cert| - str = "#{cert.name.inspect} #{cert.digest.to_s}\n" - expiry = PuppetX::Certregen::Certificate.expiry(cert) - str << "Status: #{expiry[:status]}\n" - str << "Expiration date: #{expiry[:expiration_date]}\n" - if expiry[:expires_in] - str << "Expires in: #{expiry[:expires_in]}\n" - end - str - end - end - end - - when_rendering :pson do |certs| - certs.map do |cert| - { - :name => cert.name, - :digest => cert.digest.to_s, - :expiry => PuppetX::Certregen::Certificate.expiry(cert) - } - end - end - - when_rendering :yaml do |certs| - certs.map do |cert| - { - :name => cert.name, - :digest => cert.digest.to_s, - :expiry => PuppetX::Certregen::Certificate.expiry(cert) - } - end - end - end - - action(:redistribute) do - summary "Redistribute the regenerated CA certificate and CRL to nodes in PuppetDB" - - description <<-EOT - Redistribute the regenerated CA certificate and CRL to active nodes in PuppetDB. This command is - only necessary if the CA certificate is expired and a new CA certificate needs to be manually - distributed via SSH. - - This subcommand depends on the `chloride` gem, which is not included with this Puppet face. - - Distributing the CA certificate via SSH requires either a private ssh key (given by the - `--ssh_key_file` flag) or entering the password when prompted. If password auth is used, - the `highline` gem should be installed so that the entered password is not echoed to the - terminal. - EOT - - option('--username USER') do - summary "The username to use when logging into the remote machine" - end - - option('--ssh_key_file FILE') do - summary "The SSH key file to use for authentication" - default_to { "~/.ssh/id_rsa" } - end - - when_invoked do |opts| - unless Puppet.features.chloride? - raise "Unable to distribute CA certificate: the chloride gem is not available." - end - - config = {} - - config.merge!(username: opts[:username]) if opts[:username] - config.merge!(ssh_key_file: File.expand_path(opts[:ssh_key_file])) if opts[:ssh_key_file] - - ca = PuppetX::Certregen::CA.setup - cacert = ca.host.certificate - if PuppetX::Certregen::Certificate.expiring?(cacert) - Puppet.err "Refusing to distribute CA certificate: certificate is pending expiration." - exit 1 - end - - rv = {succeeded: [], failed: []} - PuppetX::Certregen::CA.certnames.each do |certname| - begin - PuppetX::Certregen::CA.distribute(certname, config) - rv[:succeeded] << certname - rescue => e - Puppet.log_exception(e) - rv[:failed] << certname - end - end - - rv - end - end -end diff --git a/code/environments/production/modules/certregen/lib/puppet/feature/chloride.rb b/code/environments/production/modules/certregen/lib/puppet/feature/chloride.rb deleted file mode 100644 index ea777cb..0000000 --- a/code/environments/production/modules/certregen/lib/puppet/feature/chloride.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'puppet/util/feature' - -Puppet.features.add(:chloride, libs: 'chloride') diff --git a/code/environments/production/modules/certregen/lib/puppet/functions/is_classified_with.rb b/code/environments/production/modules/certregen/lib/puppet/functions/is_classified_with.rb deleted file mode 100644 index 0f6d54b..0000000 --- a/code/environments/production/modules/certregen/lib/puppet/functions/is_classified_with.rb +++ /dev/null @@ -1,9 +0,0 @@ -Puppet::Functions.create_function(:is_classified_with) do - dispatch :is_classified_with do - param 'String', :str - end - - def is_classified_with(str) - closure_scope.find_global_scope.compiler.node.classes.keys.include?(str.to_s) - end -end diff --git a/code/environments/production/modules/certregen/lib/puppet/parser/functions/is_classified_with.rb b/code/environments/production/modules/certregen/lib/puppet/parser/functions/is_classified_with.rb deleted file mode 100644 index 1e17887..0000000 --- a/code/environments/production/modules/certregen/lib/puppet/parser/functions/is_classified_with.rb +++ /dev/null @@ -1,4 +0,0 @@ -Puppet::Parser::Functions::newfunction(:is_classified_with, :arity => 1, - :type => :rvalue) do |(str)| - compiler.node.classes.keys.include?(str) -end diff --git a/code/environments/production/modules/certregen/lib/puppet_x/certregen/ca.rb b/code/environments/production/modules/certregen/lib/puppet_x/certregen/ca.rb deleted file mode 100644 index c9e7457..0000000 --- a/code/environments/production/modules/certregen/lib/puppet_x/certregen/ca.rb +++ /dev/null @@ -1,137 +0,0 @@ -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 diff --git a/code/environments/production/modules/certregen/lib/puppet_x/certregen/certificate.rb b/code/environments/production/modules/certregen/lib/puppet_x/certregen/certificate.rb deleted file mode 100644 index 56ad970..0000000 --- a/code/environments/production/modules/certregen/lib/puppet_x/certregen/certificate.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'puppet_x/certregen/util' - -module PuppetX - module Certregen - module Certificate - module_function - - # @param cert [Puppet::SSL::Certificate] - # @return [Hash<Symbol, String>] - def expiry(cert) - if cert.content.not_after < Time.now - status = :expired - elsif expiring?(cert) - status = :expiring - else - status = :ok - end - - data = { - :status => status, - :expiration_date => cert.content.not_after - } - - if status != :expired - data[:expires_in] = PuppetX::Certregen::Util.duration(cert.content.not_after - Time.now) - end - - data - end - - # Is this certificate expiring or expired? - # - # @param cert [Puppet::SSL::Certificate] - # @param percent [Integer] - def expiring?(cert, percent = 10) - remaining = cert.content.not_after - Time.now - lifetime = cert.content.not_after - (cert.content.not_before + 86400) - remaining / lifetime < (percent / 100.0) - end - end - end -end diff --git a/code/environments/production/modules/certregen/lib/puppet_x/certregen/crl.rb b/code/environments/production/modules/certregen/lib/puppet_x/certregen/crl.rb deleted file mode 100644 index e82f929..0000000 --- a/code/environments/production/modules/certregen/lib/puppet_x/certregen/crl.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'fileutils' -require 'openssl' - -module PuppetX - module Certregen - # @api private - # @see {Puppet::SSL::CertificateRevocationList} - module CRL - module_function - - FIVE_YEARS = 5 * 365*24*60*60 - - def refresh(ca) - crl = ca.crl - crl_content = crl.content - update_to_next_crl_number(crl_content) - update_valid_time_range_to_start_at(crl_content, Time.now) - sign_with(crl_content, ca.host.key.content) - Puppet::SSL::CertificateRevocationList.indirection.save(crl) - FileUtils.cp(Puppet[:cacrl], Puppet[:hostcrl]) - Puppet::SSL::CertificateRevocationList.indirection.find("ca") - end - - # @api private - def update_valid_time_range_to_start_at(crl_content, time) - # The CRL is not valid if the time of checking == the time of last_update. - # So to have it valid right now we need to say that it was updated one second ago. - crl_content.last_update = time - 1 - crl_content.next_update = time + FIVE_YEARS - end - - # @api private - def update_to_next_crl_number(crl_content) - crl_content.extensions = with_next_crl_number_from(crl_content, crl_content.extensions) - end - - # @api private - def with_next_crl_number_from(crl_content, existing_extensions) - existing_crl_num = existing_extensions.find { |e| e.oid == 'crlNumber' } - new_crl_num = existing_crl_num ? existing_crl_num.value.to_i + 1 : 0 - extensions_without_crl_num = existing_extensions.reject { |e| e.oid == 'crlNumber' } - extensions_without_crl_num + [crl_number_of(new_crl_num)] - end - - # @api private - def crl_number_of(number) - OpenSSL::X509::Extension.new('crlNumber', OpenSSL::ASN1::Integer(number)) - end - - # @api private - def sign_with(crl_content, cakey) - crl_content.sign(cakey, OpenSSL::Digest::SHA1.new) - end - end - end -end diff --git a/code/environments/production/modules/certregen/lib/puppet_x/certregen/util.rb b/code/environments/production/modules/certregen/lib/puppet_x/certregen/util.rb deleted file mode 100644 index e21298a..0000000 --- a/code/environments/production/modules/certregen/lib/puppet_x/certregen/util.rb +++ /dev/null @@ -1,27 +0,0 @@ -module PuppetX - module Certregen - module Util - module_function - - def duration(epoch) - seconds = epoch.to_i - minutes = (epoch / 60).to_i; seconds %= 60 if minutes > 0 - hours = (minutes / 60).to_i; minutes %= 60 if hours > 0 - days = (hours / 24).to_i; hours %= 24 if days > 0 - years = (days / 365).to_i; days %= 365 if years > 0 - - list = [] - list << "#{years} #{pluralize('year', years)}" if years > 0 - list << "#{days} #{pluralize('day', days)}" if days > 0 - list << "#{hours} #{pluralize('hour', hours)}" if hours > 0 - list << "#{minutes} #{pluralize('minute', minutes)}" if minutes > 0 - list << "#{seconds} #{pluralize('second', seconds)}" if seconds > 0 - list.join(", ") - end - - def pluralize(str, count) - count == 1 ? str : str + 's' - end - end - end -end |