summaryrefslogtreecommitdiff
path: root/code/environments/production/modules/certregen/spec
diff options
context:
space:
mode:
Diffstat (limited to 'code/environments/production/modules/certregen/spec')
-rw-r--r--code/environments/production/modules/certregen/spec/acceptance/ca_spec.rb60
-rw-r--r--code/environments/production/modules/certregen/spec/acceptance/healthcheck_spec.rb135
-rw-r--r--code/environments/production/modules/certregen/spec/acceptance/help_spec.rb39
-rw-r--r--code/environments/production/modules/certregen/spec/acceptance/helpers.rb83
-rw-r--r--code/environments/production/modules/certregen/spec/acceptance/nodesets/centos-7-x64.yml10
-rw-r--r--code/environments/production/modules/certregen/spec/acceptance/nodesets/debian-8-x64.yml10
-rw-r--r--code/environments/production/modules/certregen/spec/acceptance/nodesets/default.yml10
-rw-r--r--code/environments/production/modules/certregen/spec/acceptance/nodesets/docker/centos-7.yml12
-rw-r--r--code/environments/production/modules/certregen/spec/acceptance/nodesets/docker/debian-8.yml11
-rw-r--r--code/environments/production/modules/certregen/spec/acceptance/nodesets/docker/ubuntu-14.04.yml12
-rw-r--r--code/environments/production/modules/certregen/spec/acceptance/workflow_regen_after_expire_spec.rb105
-rw-r--r--code/environments/production/modules/certregen/spec/acceptance/workflow_regen_before_expire_spec.rb77
-rw-r--r--code/environments/production/modules/certregen/spec/classes/client_spec.rb81
-rw-r--r--code/environments/production/modules/certregen/spec/integration/puppet/face/certregen_spec.rb77
-rw-r--r--code/environments/production/modules/certregen/spec/integration/puppet_x/certregen/ca_spec.rb88
-rw-r--r--code/environments/production/modules/certregen/spec/integration/puppet_x/certregen/certificate_spec.rb92
-rw-r--r--code/environments/production/modules/certregen/spec/integration/puppet_x/crl_spec.rb54
-rw-r--r--code/environments/production/modules/certregen/spec/spec_helper.rb16
-rw-r--r--code/environments/production/modules/certregen/spec/spec_helper_acceptance.rb17
-rw-r--r--code/environments/production/modules/certregen/spec/spec_helper_local.rb52
20 files changed, 1041 insertions, 0 deletions
diff --git a/code/environments/production/modules/certregen/spec/acceptance/ca_spec.rb b/code/environments/production/modules/certregen/spec/acceptance/ca_spec.rb
new file mode 100644
index 0000000..c9df863
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/acceptance/ca_spec.rb
@@ -0,0 +1,60 @@
+require 'spec_helper_acceptance'
+
+describe "puppet certregen ca" do
+ if hosts_with_role(hosts, 'master').length>0 then
+ context 'regen ca on master' do
+
+ context 'C99811 - without --ca_serial' do
+ it 'should provide ca serial id via stderr' do
+ on(master, puppet("certregen ca"), :acceptable_exit_codes => 1) do |result|
+ expect(result.stderr).to match(/rerun this command with --ca_serial ([0-9a-fA-F]+)/)
+ end
+ end
+ end
+
+ context "C99815 - 'puppet certregen ca --ca_serial'" do
+ before(:all) do
+ serial = get_ca_serial_id_on(master)
+ today = get_time_on(master)
+ @future = today + 5*YEAR
+ @regen_result = on(master, "puppet certregen ca --ca_serial #{serial}")
+ end
+ it 'should output the updated CA expiration date' do
+ expect(@regen_result.stdout).to match( /CA expiration is now #{@future.utc.strftime('%Y-%m-%d')}/ )
+ end
+ it 'should update CA cert enddate' do
+ enddate = get_ca_enddate_time_on(master)
+ expect(enddate - @future).to be < 10.0
+ end
+ end
+
+ context 'C99816 - invalid ca_serial id' do
+ it 'should yield an error' do
+ on(master, puppet("certregen ca --ca_serial FD"), :acceptable_exit_codes => 1) do |result|
+ expect(result.stderr).to match(/The serial number of the current CA certificate .* does not match the serial number given on the command line \(FD\)/)
+ expect(result.stderr).to match(/rerun this command with --ca_serial ([0-9a-fA-F]+)/)
+ end
+ end
+ end
+
+ context "C99817 - 'puppet certregen ca --ca_serial --ca_ttl 1d'" do
+ before(:all) do
+ today = get_time_on(master)
+ @tomorrow = today + 1*DAY
+
+ serial = get_ca_serial_id_on(master)
+ @regen_result = on(master, "puppet certregen ca --ca_serial #{serial} --ca_ttl 1d")
+ end
+
+ it 'should output the updated CA expiration date' do
+ expect(@regen_result.stdout).to match( /CA expiration is now #{@tomorrow.utc.strftime('%Y-%m-%d')}/ )
+ end
+ it 'should update CA cert enddate' do
+ enddate = get_ca_enddate_time_on(master)
+ expect(enddate - @tomorrow).to be < 10.0
+ end
+ end
+
+ end
+ end
+end
diff --git a/code/environments/production/modules/certregen/spec/acceptance/healthcheck_spec.rb b/code/environments/production/modules/certregen/spec/acceptance/healthcheck_spec.rb
new file mode 100644
index 0000000..387810d
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/acceptance/healthcheck_spec.rb
@@ -0,0 +1,135 @@
+require 'spec_helper_acceptance'
+require 'yaml'
+require 'json'
+
+describe "puppet certregen healthcheck" do
+ if hosts_with_role(hosts, 'master').length>0 then
+
+ context 'C99803 - cert with more than 10 percent of life' do
+ before(:all) do
+ serial = get_ca_serial_id_on(master)
+ on(master, "puppet certregen ca --ca_serial #{serial}")
+ end
+ it 'should not produce a health warning' do
+ on(master, "puppet certregen healthcheck") do |result|
+ expect(result.stderr).to be_empty
+ expect(result.stdout).to match(/No certificates are approaching expiration/)
+ end
+ end
+ end
+
+ context 'C99804 - cert with less than 10 percent of life' do
+ before(:all) do
+ serial = get_ca_serial_id_on(master)
+ # patch puppet to defeat copywrite date check when generating historical CA
+ patch_puppet_date_check_on(master)
+ @today = get_time_on(master)
+ # set back the clock in order to create a CA that will be approaching its EOL
+ past = @today - (5*YEAR - 20*DAY)
+ on(master, "date #{past.strftime('%m%d%H%M%Y')}")
+ # create old CA
+ on(master, "puppet certregen ca --ca_serial #{serial}")
+ # update to current time
+ on(master, "date #{@today.strftime('%m%d%H%M%Y')}")
+ # revert patch to defeat copywrite date check
+ patch_puppet_date_check_on(master, 'reverse')
+ end
+
+ it 'system should have current date' do
+ today = get_time_on(master)
+ expect(today.utc.strftime('%Y-%m-%d')).to eq @today.utc.strftime('%Y-%m-%d')
+ end
+
+ it 'should warn about pending expiration' do
+ enddate = get_ca_enddate_time_on(master)
+ on(master, "puppet certregen healthcheck") do |result|
+ expect(result.stdout).to match(/Status:\s+expiring/)
+ expect(result.stdout).to match(/Expiration date:\s+#{enddate.utc.strftime('%Y-%m-%d')}/)
+ end
+ end
+
+ end
+
+ context 'C99805 - expired cert' do
+ before(:all) do
+ serial = get_ca_serial_id_on(master)
+ on(master, "puppet certregen ca --ca_serial #{serial} --ca_ttl 1s")
+ sleep 2
+ end
+ it 'should produce a health warning' do
+ on(master, "puppet certregen healthcheck") do |result|
+ expect(result.stdout.gsub("\n", " ")).to match(/ca.*Status: expired/)
+ end
+ end
+ end
+
+ context '--all flag' do
+
+ context 'C99806 --all' do
+ before(:all) do
+ on(master, puppet("cert list --all")) do |result|
+ @certs = result.stdout.scan(/\) ([A-F0-9:]+) /)
+ end
+ @result = on(master, "puppet certregen healthcheck --all")
+ end
+ it 'should contain expiration data for ca cert' do
+ expect(@result.stdout).to match(/"ca".*\n\s*Status:\s*[Ee]xpir/)
+ end
+ it 'should contain expiration data for all node certs' do
+ @certs.each do |cert|
+ expect(@result.stdout).to include cert[0]
+ end
+ end
+ end
+
+ context '--render-as flag' do
+
+ context 'C99808 - --render-as yaml' do
+ before(:all) do
+ on(master, puppet("cert list --all")) do |result|
+ @certs = result.stdout.scan(/\) ([A-F0-9:]+) /)
+ end
+ @result = on(master, "puppet certregen healthcheck --all --render-as yaml")
+ @yaml = YAML.load(@result.stdout)
+ end
+ it 'should return valid yaml' do
+ expect(YAML.parse(@result.stdout)).to be_instance_of(Psych::Nodes::Document)
+ end
+ it 'should contain expiration data for ca cert' do
+ ca = @yaml.find { |record| record[:name] == 'ca' }
+ expect(ca).not_to be nil
+ expect(ca[:expiry][:status]).to eq(:expired)
+ end
+ it 'should contain expiration data for all node certs' do
+ @certs.each do |cert|
+ expect(@yaml.find { |record| record[:digest] =~ /#{cert[0]}/ }).not_to be nil
+ end
+ end
+ end
+
+ context 'C99809 - --render-as json prints valid json containing expiration data' do
+ before(:all) do
+ on(master, puppet("cert list --all")) do |result|
+ @certs = result.stdout.scan(/\) ([A-F0-9:]+) /)
+ end
+ @json = JSON.parse(on(master, "puppet certregen healthcheck --all --render-as json").stdout)
+ end
+ it 'should return valid json' do
+ expect(@json).not_to be nil
+ end
+ it 'should contain expiration data for ca cert' do
+ ca = @json.find { |record| record['name'] == 'ca' }
+ expect(ca).not_to be nil
+ end
+ it 'should contain expiration data for all node certs' do
+ @certs.each do |cert|
+ expect(@json.find { |record| record['digest'] =~ /#{cert[0]}/ }).not_to be nil
+ end
+ end
+ end
+
+ end
+ end
+
+ end
+end
diff --git a/code/environments/production/modules/certregen/spec/acceptance/help_spec.rb b/code/environments/production/modules/certregen/spec/acceptance/help_spec.rb
new file mode 100644
index 0000000..7d1e83d
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/acceptance/help_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper_acceptance'
+
+describe "pupper help certregen" do
+ # NOTE: MODULES-4733 certregen is not currently compatible with ruby < 1.9
+ ruby_ver = 0
+ on(default, 'ruby --version') do |result|
+ m = /\d+\.\d+\.\d+/.match(result.stdout)
+ ruby_ver = m[0] if m
+ end
+ unless version_is_less(ruby_ver, '1.9') then
+ describe "C98923 - Verify that 'puppet certregen --help' prints help text" do
+ # NOTE: `--help` only works on puppet version 4+
+ if version_is_less( '3.9.9', on(default, puppet('--version')).stdout)
+ describe command("puppet certregen --help") do
+ its(:stdout) { should match( /.*USAGE: puppet certregen <action>.*/ ) }
+ its(:stdout) { should match( /.*See 'puppet man certregen' or 'man puppet-certregen' for full help.*/ ) }
+ end
+ end
+ end
+ describe "C99812 - Verify that 'puppet help certregen' prints help text" do
+ describe command("puppet help certregen") do
+ its(:stdout) { should match( /.*USAGE: puppet certregen <action>.*/ ) }
+ its(:stdout) { should match( /.*See 'puppet man certregen' or 'man puppet-certregen' for full help.*/ ) }
+ end
+ end
+ describe "C99813 - Verify that 'puppet help certregen healthcheck' prints help text for healthcheck subcommand" do
+ describe command("puppet help certregen healthcheck") do
+ its(:stdout) { should match( /.*USAGE: puppet certregen healthcheck .*/ ) }
+ its(:stdout) { should match( /.*See 'puppet man certregen' or 'man puppet-certregen' for full help.*/ ) }
+ end
+ end
+ describe "C99814 - Verify that 'puppet help certregen ca' prints help text for ca subcommand" do
+ describe command("puppet help certregen ca") do
+ its(:stdout) { should match( /.*USAGE: puppet certregen ca .*/ ) }
+ its(:stdout) { should match( /.*See 'puppet man certregen' or 'man puppet-certregen' for full help.*/ ) }
+ end
+ end
+ end
+end
diff --git a/code/environments/production/modules/certregen/spec/acceptance/helpers.rb b/code/environments/production/modules/certregen/spec/acceptance/helpers.rb
new file mode 100644
index 0000000..dba7d81
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/acceptance/helpers.rb
@@ -0,0 +1,83 @@
+require 'openssl'
+
+# Time constants in seconds
+HOUR = 60 * 60
+DAY = 24 * HOUR
+YEAR = 365 * DAY
+
+# Retrieve CA Certificate from the given host
+#
+# @param [Host] host single Beaker::Host
+#
+# @return [OpenSSL::X509::Certificate] Certificate object
+def get_ca_cert_on(host)
+ if host[:roles].include? 'master' then
+ dir = on(host, puppet('config', 'print', 'cadir')).stdout.chomp
+ ca_path = "#{dir}/ca_crt.pem"
+ else
+ dir = on(host, puppet('config', 'print', 'certdir')).stdout.chomp
+ ca_path = "#{dir}/ca.pem"
+ end
+ on(host, "cat #{ca_path}") do |result|
+ cert = OpenSSL::X509::Certificate.new(result.stdout)
+ return cert
+ end
+end
+
+# Execute `date` command on host with optional arguments
+# and get back a Ruby Time object
+#
+# @param [Host] host single Beaker::Host to run the command on
+# @param [Array<String>] args Array of arguments to be appended to the
+# `date` command
+# @return [Time] Ruby Time object
+def get_time_on(host, args = [])
+ arg_string = args.join(' ')
+ date = on(host, "date #{arg_string}").stdout.chomp
+ return Time.parse(date)
+end
+
+# Retrieve the CA enddate on a given host as a Ruby time object
+#
+# @param [Host] host single Beaker::Host to get CA enddate from
+#
+# @return [Time] Ruby Time object, or nil if error
+def get_ca_enddate_time_on(host)
+ cert = get_ca_cert_on(host)
+ return cert.not_after if cert
+ return nil
+end
+
+# Retrieve the current ca_serial value for `puppet certgen ca` on a given host
+#
+# @param [Host] host single Beaker::Host to get ca_serial from
+#
+# @return [String] ca_serial in hexadecimal, or nil if error
+def get_ca_serial_id_on(host)
+ cert = get_ca_cert_on(host)
+ return cert.serial.to_s(16) if cert
+ return nil
+end
+
+# Patch puppet to get around the date check validation.
+#
+# This method is used to patch puppet in order to prevent it from failing to
+# create a CA if the system clock is turned back in time by years. The same
+# method is used to reverse the patch with the `reverse` parameter.
+#
+# @param [Host] host single Beaker::Host to run the command on
+# @param [String] reverse causes the patch to be reversed
+def patch_puppet_date_check_on(host, reverse=nil)
+ reverse = '--reverse' if reverse
+ apply_manifest_on(host, 'package { "patch": ensure => present}')
+ interface_documentation_file = "/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/interface/documentation.rb"
+ patch =<<EOF
+305c305
+< raise ArgumentError, "copyright with a year \#{fault} is very strange; did you accidentally add or subtract two years?"
+---
+> #raise ArgumentError, "copyright with a year \#{fault} is very strange; did you accidentally add or subtract two years?"
+EOF
+ patch_file = host.tmpfile('iface_doc_patch')
+ create_remote_file(host, patch_file, patch)
+ on(host, "patch #{reverse} #{interface_documentation_file} < #{patch_file}", :acceptable_exit_codes => [0,1])
+end
diff --git a/code/environments/production/modules/certregen/spec/acceptance/nodesets/centos-7-x64.yml b/code/environments/production/modules/certregen/spec/acceptance/nodesets/centos-7-x64.yml
new file mode 100644
index 0000000..5eebdef
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/acceptance/nodesets/centos-7-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ centos-7-x64:
+ roles:
+ - agent
+ - default
+ platform: el-7-x86_64
+ hypervisor: vagrant
+ box: puppetlabs/centos-7.2-64-nocm
+CONFIG:
+ type: foss
diff --git a/code/environments/production/modules/certregen/spec/acceptance/nodesets/debian-8-x64.yml b/code/environments/production/modules/certregen/spec/acceptance/nodesets/debian-8-x64.yml
new file mode 100644
index 0000000..fef6e63
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/acceptance/nodesets/debian-8-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ debian-8-x64:
+ roles:
+ - agent
+ - default
+ platform: debian-8-amd64
+ hypervisor: vagrant
+ box: puppetlabs/debian-8.2-64-nocm
+CONFIG:
+ type: foss
diff --git a/code/environments/production/modules/certregen/spec/acceptance/nodesets/default.yml b/code/environments/production/modules/certregen/spec/acceptance/nodesets/default.yml
new file mode 100644
index 0000000..dba339c
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/acceptance/nodesets/default.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ ubuntu-1404-x64:
+ roles:
+ - agent
+ - default
+ platform: ubuntu-14.04-amd64
+ hypervisor: vagrant
+ box: puppetlabs/ubuntu-14.04-64-nocm
+CONFIG:
+ type: foss
diff --git a/code/environments/production/modules/certregen/spec/acceptance/nodesets/docker/centos-7.yml b/code/environments/production/modules/certregen/spec/acceptance/nodesets/docker/centos-7.yml
new file mode 100644
index 0000000..a3333aa
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/acceptance/nodesets/docker/centos-7.yml
@@ -0,0 +1,12 @@
+HOSTS:
+ centos-7-x64:
+ platform: el-7-x86_64
+ hypervisor: docker
+ image: centos:7
+ docker_preserve_image: true
+ docker_cmd: '["/usr/sbin/init"]'
+ # install various tools required to get the image up to usable levels
+ docker_image_commands:
+ - 'yum install -y crontabs tar wget openssl sysvinit-tools iproute which initscripts'
+CONFIG:
+ trace_limit: 200
diff --git a/code/environments/production/modules/certregen/spec/acceptance/nodesets/docker/debian-8.yml b/code/environments/production/modules/certregen/spec/acceptance/nodesets/docker/debian-8.yml
new file mode 100644
index 0000000..df5c319
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/acceptance/nodesets/docker/debian-8.yml
@@ -0,0 +1,11 @@
+HOSTS:
+ debian-8-x64:
+ platform: debian-8-amd64
+ hypervisor: docker
+ image: debian:8
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ - 'apt-get update && apt-get install -y net-tools wget locales strace lsof && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen'
+CONFIG:
+ trace_limit: 200
diff --git a/code/environments/production/modules/certregen/spec/acceptance/nodesets/docker/ubuntu-14.04.yml b/code/environments/production/modules/certregen/spec/acceptance/nodesets/docker/ubuntu-14.04.yml
new file mode 100644
index 0000000..b1efa58
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/acceptance/nodesets/docker/ubuntu-14.04.yml
@@ -0,0 +1,12 @@
+HOSTS:
+ ubuntu-1404-x64:
+ platform: ubuntu-14.04-amd64
+ hypervisor: docker
+ image: ubuntu:14.04
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ # ensure that upstart is booting correctly in the container
+ - 'rm /usr/sbin/policy-rc.d && rm /sbin/initctl && dpkg-divert --rename --remove /sbin/initctl && apt-get update && apt-get install -y net-tools wget && locale-gen en_US.UTF-8'
+CONFIG:
+ trace_limit: 200
diff --git a/code/environments/production/modules/certregen/spec/acceptance/workflow_regen_after_expire_spec.rb b/code/environments/production/modules/certregen/spec/acceptance/workflow_regen_after_expire_spec.rb
new file mode 100644
index 0000000..3ae0a9e
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/acceptance/workflow_regen_after_expire_spec.rb
@@ -0,0 +1,105 @@
+require 'spec_helper_acceptance'
+require 'json'
+
+# https://forge.puppet.com/puppetlabs/certregen#revive-a-ca-thats-already-expired
+describe "C99821 - workflow - regen CA after it expires" do
+ if find_install_type == 'pe' then
+ # This workflow only works with a master to manage the CA
+ # This workflow only works with a puppetdb instance to query hostnames from
+ context 'create CA to be expired and update agents' do
+ before(:all) do
+ ttl = 60
+ serial = get_ca_serial_id_on(master)
+ on(master, puppet("certregen ca --ca_serial #{serial} --ca_ttl #{ttl}s"))
+ start = Time.now
+ agents.each do |agent|
+ on(agent, puppet('agent -t'), :acceptable_exit_codes => [0,2])
+ end
+ finish = Time.now
+ elapsed_time = (finish - start).to_i
+ sleep (ttl - elapsed_time) if elapsed_time < ttl
+ sleep 1
+ end
+
+ it 'should warn that ca is expired' do
+ on(master, puppet("certregen healthcheck")) do |result|
+ expect(result.stdout).to match(/Status:\s+expired/)
+ end
+ end
+
+ context 'regenerate CA' do
+ before(:all) do
+ serial = get_ca_serial_id_on(master)
+ on(master, puppet("certregen ca --ca_serial #{serial}"))
+ end
+
+ it 'should update CA cert enddate' do
+ enddate = get_ca_enddate_time_on(master)
+ future = get_time_on(master, ['-d', "'5 years'"])
+ expect(future - enddate).to be <= (48*HOUR)
+ end
+
+ context 'automatically distribute new ca to linux hosts' do
+ before(:all) do
+ # distribute ssh key for root to agents
+ on(master, "ssh-keygen -t rsa -f $HOME/.ssh/id_rsa -P ''")
+ on(master, "cat $HOME/.ssh/id_rsa.pub") do |result|
+ key_array = result.stdout.split(' ')
+ fail_test('could not get ssh key from master') unless key_array.size > 1
+ @public_key = key_array[1]
+ end
+ agents.each do |agent|
+ unless agent['platform'] =~ /windows/
+ args = ['ensure=present',
+ "user='root'",
+ "type='rsa'",
+ "key='#{@public_key}'",
+ ]
+ on(agent, puppet_resource('ssh_authorized_key', master.hostname, args))
+ on(master, "ssh -o StrictHostKeyChecking=no #{agent.hostname} ls")
+ end
+ end
+ on(master, "/opt/puppetlabs/puppet/bin/gem install chloride")
+ result = on(master, puppet("certregen redistribute"))
+ @report = JSON.parse(result.stdout)
+ end
+
+ after(:all) do
+ on(master, "rm -f $HOME/.ssh/id_rsa $HOME/.ssh/id_rsa.pub", :acceptable_exit_codes => [0,1])
+ agents.each do |agent|
+ on(agent, puppet_resource('ssh_authorized_key', master.hostname, ['ensure=absent', "user='root'"]), :acceptable_exit_codes => [0,1])
+ end
+ end
+
+ it 'should emit a report in valid json' do
+ expect(@report).not_to be nil
+ end
+ it 'should emit a report with a succeeded key' do
+ expect(@report['succeeded']).not_to be nil
+ end
+ it 'should emit a report with a failed key' do
+ expect(@report['failed']).not_to be nil
+ end
+ it 'should report success on all linux agents' do
+ agents.each do |agent|
+ if agent['platform'] =~ /debian|ubuntu|cumulus|huaweios|el-|centos|fedora|redhat|oracle|scientific|eos|archlinux|sles/
+ expect(@report['succeeded']).to include agent.hostname
+ end
+ end
+ end
+ it 'should update CA cert on all linux agents' do
+ master_enddate = get_ca_enddate_time_on(master)
+ agents.each do |agent|
+ if agent['platform'] =~ /debian|ubuntu|cumulus|huaweios|el-|centos|fedora|redhat|oracle|scientific|eos|archlinux|sles/
+ on(agent, puppet('agent -t'), :acceptable_exit_codes => [0,2])
+ enddate = get_ca_enddate_time_on(agent)
+ expect(enddate).to eq master_enddate
+ end
+ end
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/code/environments/production/modules/certregen/spec/acceptance/workflow_regen_before_expire_spec.rb b/code/environments/production/modules/certregen/spec/acceptance/workflow_regen_before_expire_spec.rb
new file mode 100644
index 0000000..bad7a84
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/acceptance/workflow_regen_before_expire_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper_acceptance'
+
+# https://forge.puppet.com/puppetlabs/certregen#refresh-a-ca-thats-expiring-soon
+describe "C99818 - workflow - regen CA before it expires" do
+ if hosts_with_role(hosts, 'master').length>0 then
+ # This workflow only works with a master to manage the CA
+ context 'setting CA to expire soon' do
+ before(:all) do
+ serial = get_ca_serial_id_on(master)
+
+ # patch puppet to defeat copywrite date check when generating historical CA
+ patch_puppet_date_check_on(master)
+
+ # determine current time on master
+ @today = get_time_on(master)
+
+ # set back the clock in order to create a CA that will be approaching its EOL
+ past = @today - (5*YEAR - 20*DAY)
+ on(master, "date #{past.strftime('%m%d%H%M%Y')}")
+ # create old CA
+ on(master, puppet(" certregen ca --ca_serial #{serial}"))
+ # update to current time
+ on(master, "date #{@today.strftime('%m%d%H%M%Y')}")
+ end
+
+ it 'should have current date' do
+ today = get_time_on(master)
+ expect(today.utc.strftime('%Y-%m-%d')).to eq @today.utc.strftime('%Y-%m-%d')
+ end
+
+ it 'should warn about pending expiration' do
+ enddate = get_ca_enddate_time_on(master)
+ on(master, puppet("certregen healthcheck")) do |result|
+ expect(result.stdout).to match(/Status:\s+expiring/)
+ expect(result.stdout).to match(/Expiration date:\s+#{enddate.utc.strftime('%Y-%m-%d')}/)
+ end
+ end
+
+ context 'restoring previously patched puppet' do
+ before(:all) do
+ # revert patch to defeat copywrite date check
+ patch_puppet_date_check_on(master, 'reverse')
+ end
+
+ context 'regenerating CA prior to expiration' do
+ before(:all) do
+ serial = get_ca_serial_id_on(master)
+ on(master, puppet("certregen ca --ca_serial #{serial}"))
+ end
+ # validate time stamp
+ it 'should update CA cert enddate' do
+ enddate = get_ca_enddate_time_on(master)
+ future = get_time_on(master, ['-d', "'5 years'"])
+ expect(future - enddate).to be <= (48*HOUR)
+ end
+
+ context 'distribute new ca to linux hosts that have been classified with `certregen::client`' do
+ before(:all) do
+ create_remote_file(master, '/etc/puppetlabs/code/environments/production/manifests/ca.pp', 'include certregen::client')
+ on(master, 'chmod 755 /etc/puppetlabs/code/environments/production/manifests/ca.pp')
+ on(master, puppet('agent -t'), :acceptable_exit_codes => [0,2])
+ end
+ it 'should update CA cert on all linux agents' do
+ master_enddate = get_ca_enddate_time_on(master)
+ agents.each do |agent|
+ on(agent, puppet('agent -t'), :acceptable_exit_codes => [0,2])
+ enddate = get_ca_enddate_time_on(agent)
+ expect(enddate).to eq master_enddate
+ end
+ end
+ end
+
+ end
+ end
+ end
+ end
+end
diff --git a/code/environments/production/modules/certregen/spec/classes/client_spec.rb b/code/environments/production/modules/certregen/spec/classes/client_spec.rb
new file mode 100644
index 0000000..843c3b1
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/classes/client_spec.rb
@@ -0,0 +1,81 @@
+require 'spec_helper'
+
+RSpec.shared_examples "managing the CRL on the client" do |setting|
+ describe "when manage_crl is false" do
+ let(:params) {{'manage_crl' => false}}
+
+ it "doesn't manage the hostcrl on the client" do
+ should_not contain_file(client_hostcrl)
+ end
+ end
+
+ describe "when manage_crl is true" do
+ let(:params) {{'manage_crl' => true}}
+
+ it "manages the hostcrl on the client from the server '#{setting}' setting" do
+ should contain_file(client_hostcrl).with(
+ 'ensure' => 'present',
+ 'content' => Puppet.settings.setting(setting).open(&:read),
+ 'mode' => '0644',
+ )
+ end
+ end
+end
+
+RSpec.describe 'certregen::client' do
+ include_context "Initialize CA"
+
+ let(:client_localcacert) { tmpfilename('ca.pem') }
+ let(:client_hostcrl) { tmpfilename('crl.pem') }
+
+ let(:facts) do
+ {
+ 'localcacert' => client_localcacert,
+ 'hostcrl' => client_hostcrl,
+ 'pe_build' => '2016.4.0',
+ }
+ end
+
+ before do
+ Puppet.settings.setting(:localcacert).open('w') { |f| f.write("local CA cert") }
+ Puppet.settings.setting(:hostcrl).open('w') { |f| f.write("local CRL") }
+ end
+
+ describe 'when the compile master has CA ssl files' do
+ before do
+ Puppet.settings.setting(:cacert).open('w') { |f| f.write("CA cert") }
+ Puppet.settings.setting(:cacrl).open('w') { |f| f.write("CA CRL") }
+ end
+
+ describe "managing the localcacert on the client" do
+ it do
+ should contain_file(client_localcacert).with(
+ 'ensure' => 'present',
+ 'content' => Puppet.settings.setting(:cacert).open(&:read),
+ 'mode' => '0644',
+ )
+ end
+ end
+
+ it_behaves_like "managing the CRL on the client", :cacrl
+ end
+
+ describe "when the compile master only has agent SSL files" do
+ before do
+ FileUtils.rm(Puppet[:cacert])
+ FileUtils.rm(Puppet[:cacrl])
+ end
+
+ describe "managing the localcacert on the client" do
+ it 'manages the client CA cert from the `localcacert` setting' do
+ should contain_file(client_localcacert).with(
+ 'ensure' => 'present',
+ 'content' => Puppet.settings.setting(:localcacert).open(&:read),
+ 'mode' => '0644',
+ )
+ end
+ end
+
+ it_behaves_like "managing the CRL on the client", :hostcrl
+ end
+end
diff --git a/code/environments/production/modules/certregen/spec/integration/puppet/face/certregen_spec.rb b/code/environments/production/modules/certregen/spec/integration/puppet/face/certregen_spec.rb
new file mode 100644
index 0000000..342aa5a
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/integration/puppet/face/certregen_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+require 'puppet/face/certregen'
+
+describe Puppet::Face[:certregen, :current] do
+ before(:each) do
+ allow(Puppet::SSL::CertificateAuthority).to receive(:instance) { Puppet::SSL::CertificateAuthority.new }
+ end
+
+ include_context "Initialize CA"
+
+ describe "ca action" do
+ it "invokes the cacert and crl actions" do
+ expect(described_class).to receive(:cacert).with(ca_serial: "01")
+ expect(described_class).to receive(:crl)
+ described_class.ca(ca_serial: "01")
+ end
+ end
+
+ describe "cacert action" do
+ it "raises an error when the ca_serial option is not provided" do
+ expect {
+ described_class.ca
+ }.to raise_error(RuntimeError, /The serial number of the CA certificate to rotate must be provided/)
+ end
+
+ it "raises an error when the ca_serial option is not provided" do
+ expect {
+ described_class.ca(ca_serial: "02")
+ }.to raise_error(RuntimeError, /The serial number of the current CA certificate \(01\) does not match the serial number/)
+ end
+
+ it "backs up the old CA cert and regenerates a new CA cert" do
+ old_cacert_serial = Puppet::SSL::CertificateAuthority.new.host.certificate.content.serial
+ described_class.ca(ca_serial: "01")
+ new_cacert_serial = Puppet::SSL::CertificateAuthority.new.host.certificate.content.serial
+ expect(old_cacert_serial).to_not eq(new_cacert_serial)
+ end
+
+ it "returns the new CA certificate" do
+ returned_cacert = described_class.ca(ca_serial: "01").first
+ new_cacert = Puppet::SSL::CertificateAuthority.new.host.certificate.content
+ expect(returned_cacert.content.serial).to eq new_cacert.serial
+ expect(returned_cacert.content.not_after).to eq new_cacert.not_after
+ end
+ end
+
+ describe 'healthcheck action' do
+ let(:not_before) { Time.now - (60 * 60 * 24 * 365 * 4) }
+ let(:not_after) { Time.now + (60 * 60 * 24 * 30) }
+ it 'warns about expiring CA certificates' do
+ ca = Puppet::SSL::CertificateAuthority.new
+ cert = backdate_certificate(ca, ca.host.certificate, not_before, not_after)
+ Puppet::SSL::Certificate.indirection.save(cert)
+
+ allow(PuppetX::Certregen::CA).to receive(:setup).and_return Puppet::SSL::CertificateAuthority.new
+ healthchecked = described_class.healthcheck
+ expect(healthchecked.size).to eq(1)
+ expect(healthchecked.first.digest.to_s).to eq(cert.digest.to_s)
+ end
+
+ it 'warns about expiring client certificates' do
+ cert = make_certificate("expiring", not_before, not_after)
+ Puppet::SSL::Certificate.indirection.save(cert)
+
+ healthchecked = described_class.healthcheck
+ expect(healthchecked.size).to eq(1)
+ expect(healthchecked.first.digest.to_s).to eq(cert.digest.to_s)
+ end
+
+ it 'orders certificates from shortest expiry to longest expiry' do
+ Puppet::SSL::Certificate.indirection.save(make_certificate("first", not_before, not_after))
+ Puppet::SSL::Certificate.indirection.save(make_certificate("last", not_before + 1, not_after + 1))
+
+ expect(described_class.healthcheck.map(&:name)).to eq %w[first last]
+ end
+ end
+end
diff --git a/code/environments/production/modules/certregen/spec/integration/puppet_x/certregen/ca_spec.rb b/code/environments/production/modules/certregen/spec/integration/puppet_x/certregen/ca_spec.rb
new file mode 100644
index 0000000..bb77a7d
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/integration/puppet_x/certregen/ca_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+require 'puppet_x/certregen/ca'
+
+RSpec.describe PuppetX::Certregen::CA do
+
+ include_context "Initialize CA"
+
+ describe "#setup" do
+ it "errors out when the node is not a CA" do
+ Puppet[:ca] = false
+ expect {
+ described_class.setup
+ }.to raise_error(RuntimeError, "Unable to set up CA: this node is not a CA server.")
+ end
+
+ it "errors out when the node does not have a signed CA certificate" do
+ FileUtils.rm(Puppet[:cacert])
+ expect {
+ described_class.setup
+ }.to raise_error(RuntimeError, "Unable to set up CA: the CA certificate is not present.")
+ end
+ end
+
+ describe '#sign' do
+ let(:ca) { double('ca') }
+
+ it 'uses the positional argument form when the Puppet version predates 4.6.0' do
+ stub_const('Puppet::PUPPETVERSION', '4.5.0')
+ expect(ca).to receive(:sign).with('hello', false, true)
+ described_class.sign(ca, 'hello', allow_dns_alt_names: false, self_signing_csr: true)
+ end
+
+ it 'uses the hash argument form when the Puppet version is 4.6.0 or greater' do
+ stub_const('Puppet::PUPPETVERSION', '4.8.0')
+ expect(ca).to receive(:sign).with('hello', allow_dns_alt_names: false, self_signing_csr: false)
+ described_class.sign(ca, 'hello', allow_dns_alt_names: false, self_signing_csr: false)
+ end
+ end
+
+ describe '#backup_cacert' do
+ it 'backs up the CA cert based on the current timestamp' do
+ now = Time.now
+ expect(Time).to receive(:now).at_least(:once).and_return now
+ described_class.backup
+ backup = File.join(Puppet[:cadir], "ca_crt.#{Time.now.to_i}.pem")
+ expect(File.read(backup)).to eq(File.read(Puppet[:cacert]))
+ end
+ end
+
+ describe '#regenerate_cacert' do
+ it 'generates a certificate with a different serial number' do
+ old_serial = Puppet::SSL::CertificateAuthority.new.host.certificate.content.serial
+ described_class.regenerate(Puppet::SSL::CertificateAuthority.new)
+ new_serial = Puppet::SSL::Certificate.indirection.find("ca").content.serial
+ expect(old_serial).to_not eq new_serial
+ end
+
+ before do
+ Puppet[:ca_name] = 'bar'
+ described_class.regenerate(Puppet::SSL::CertificateAuthority.new)
+ end
+
+ it 'copies the old subject CN to the new certificate' do
+ new_cacert = Puppet::SSL::Certificate.indirection.find("ca")
+ expect(new_cacert.content.subject.to_a[0][1]).to eq 'Puppet CA: foo'
+ end
+
+ it "matches the issuer field with the old CA and new CA" do
+ new_cacert = Puppet::SSL::Certificate.indirection.find("ca")
+ expect(new_cacert.content.issuer.to_a[0][1]).to eq 'Puppet CA: foo'
+ end
+
+ it "matches the Authority Key Identifier field with the old CA and new CA" do
+ new_cacert = Puppet::SSL::Certificate.indirection.find("ca")
+ aki = new_cacert.content.extensions.find { |ext| ext.oid == 'authorityKeyIdentifier' }
+ expect(aki.value).to match(/Puppet CA: foo/)
+ end
+
+ it 'copies the cacert to the localcacert' do
+ described_class.regenerate(Puppet::SSL::CertificateAuthority.new)
+ cacert = Puppet::SSL::Certificate.from_instance(
+ OpenSSL::X509::Certificate.new(File.read(Puppet[:cacert])))
+ localcacert = Puppet::SSL::Certificate.from_instance(
+ OpenSSL::X509::Certificate.new(File.read(Puppet[:localcacert])))
+ expect(cacert.content.serial).to eq localcacert.content.serial
+ end
+ end
+end
diff --git a/code/environments/production/modules/certregen/spec/integration/puppet_x/certregen/certificate_spec.rb b/code/environments/production/modules/certregen/spec/integration/puppet_x/certregen/certificate_spec.rb
new file mode 100644
index 0000000..e60a11b
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/integration/puppet_x/certregen/certificate_spec.rb
@@ -0,0 +1,92 @@
+require 'spec_helper'
+require 'puppet_x/certregen/certificate'
+
+RSpec.describe PuppetX::Certregen::Certificate do
+ include_context "Initialize CA"
+
+ let(:ok_certificate) do
+ Puppet::SSL::CertificateAuthority.new.generate("ok")
+ end
+
+ let(:expired_certificate) do
+ one_year = 60 * 60 * 24 * 365
+ not_before = Time.now - one_year * 6
+ not_after = Time.now - one_year
+ make_certificate("expired", not_before, not_after)
+ end
+
+ let(:expiring_certificate) do
+ not_before = Time.now - (60 * 60 * 24 * 365 * 4)
+ not_after = Time.now + (60 * 60 * 24 * 30)
+ make_certificate("expiring", not_before, not_after)
+ end
+
+ let(:short_lived_certificate) do
+ not_before = Time.now - 86400
+ not_after = Time.now + (60 * 5)
+ make_certificate("expiring", not_before, not_after)
+ end
+
+ describe "#expiring?" do
+ it "is false for nodes outside of the expiration window" do
+ expect(described_class.expiring?(ok_certificate)).to eq(false)
+ end
+
+ it "is true for newly generated short lived certificates" do
+ expect(described_class.expiring?(short_lived_certificate)).to eq(false)
+ end
+
+ it "is true for expired nodes" do
+ expect(described_class.expiring?(expired_certificate)).to eq(true)
+ end
+
+ it "is true for nodes within the expiration window" do
+ expect(described_class.expiring?(expiring_certificate)).to eq(true)
+ end
+ end
+
+ describe '#expiry' do
+ describe "with an expired cert" do
+ subject { described_class.expiry(expired_certificate) }
+ it "has a status of expired" do
+ expect(subject[:status]).to eq :expired
+ end
+
+ it "includes the not after date" do
+ expect(subject[:expiration_date]).to eq expired_certificate.content.not_after
+ end
+ end
+
+ describe "with an expiring cert" do
+ subject { described_class.expiry(expiring_certificate) }
+
+ it "has a status of expiring" do
+ expect(subject[:status]).to eq :expiring
+ end
+
+ it "includes the not after date" do
+ expect(subject[:expiration_date]).to eq expiring_certificate.content.not_after
+ end
+
+ it "includes the time till expiration" do
+ expect(subject[:expires_in]).to match(/29 days, 23 hours, 59 minutes/)
+ end
+ end
+
+ describe "with an ok cert" do
+ subject { described_class.expiry(ok_certificate) }
+
+ it "has a status of ok" do
+ expect(subject[:status]).to eq :ok
+ end
+
+ it "includes the not after date" do
+ expect(subject[:expiration_date]).to eq ok_certificate.content.not_after
+ end
+
+ it "includes the time till expiration" do
+ expect(subject[:expires_in]).to match(/4 years, 364 days, 23 hours, 59 minutes/)
+ end
+ end
+ end
+end
diff --git a/code/environments/production/modules/certregen/spec/integration/puppet_x/crl_spec.rb b/code/environments/production/modules/certregen/spec/integration/puppet_x/crl_spec.rb
new file mode 100644
index 0000000..3d50cfc
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/integration/puppet_x/crl_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+require 'puppet_x/certregen/crl'
+
+RSpec.describe PuppetX::Certregen::CRL do
+ include_context "Initialize CA"
+
+ describe '.refresh' do
+ def normalize_time(t)
+ t.utc.round
+ end
+
+ let(:stub_time) { normalize_time(Time.now + 60 * 60 * 24 * 365) }
+ let(:oldcrl) { @oldcrl }
+
+ before do
+ @oldcrl = Puppet::SSL::CertificateRevocationList.indirection.find("ca")
+ allow(Time).to receive(:now).and_return stub_time
+ described_class.refresh(Puppet::SSL::CertificateAuthority.new)
+ end
+
+ subject { Puppet::SSL::CertificateRevocationList.indirection.find('ca') }
+
+ it 'updates the lastUpdate field' do
+ last_update = normalize_time(subject.content.last_update.utc)
+ expect(last_update).to eq normalize_time(stub_time - 1)
+ end
+
+ it 'updates the nextUpdate field' do
+ next_update = normalize_time(subject.content.next_update.utc)
+ expect(next_update).to eq normalize_time(stub_time + described_class::FIVE_YEARS)
+ end
+
+ def crl_number(crl)
+ crl.content.extensions.find { |ext| ext.oid == 'crlNumber' }.value
+ end
+
+ it "increments the CRL number" do
+ newcrl = Puppet::SSL::CertificateRevocationList.from_instance(
+ OpenSSL::X509::CRL.new(File.read(Puppet[:cacrl])), 'ca')
+
+ old_crl_number = crl_number(oldcrl).to_i
+ new_crl_number = crl_number(newcrl).to_i
+ expect(new_crl_number).to eq old_crl_number + 1
+ end
+
+ it 'copies the cacrl to the hostcrl' do
+ cacrl = Puppet::SSL::CertificateRevocationList.from_instance(
+ OpenSSL::X509::CRL.new(File.read(Puppet[:cacrl])), 'ca')
+ hostcrl = Puppet::SSL::CertificateRevocationList.from_instance(
+ OpenSSL::X509::CRL.new(File.read(Puppet[:hostcrl])), 'ca')
+ expect(crl_number(cacrl)).to eq crl_number(hostcrl)
+ end
+ end
+end
diff --git a/code/environments/production/modules/certregen/spec/spec_helper.rb b/code/environments/production/modules/certregen/spec/spec_helper.rb
new file mode 100644
index 0000000..9ae37b1
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/spec_helper.rb
@@ -0,0 +1,16 @@
+#This file is generated by ModuleSync, do not edit.
+require 'puppetlabs_spec_helper/module_spec_helper'
+
+if Puppet.version.to_f >= 4.5
+ RSpec.configure do |c|
+ c.before :each do
+ Puppet.settings[:strict] = :error
+ end
+ end
+end
+
+# put local configuration and setup into spec_helper_local
+begin
+ require 'spec_helper_local'
+rescue LoadError
+end
diff --git a/code/environments/production/modules/certregen/spec/spec_helper_acceptance.rb b/code/environments/production/modules/certregen/spec/spec_helper_acceptance.rb
new file mode 100644
index 0000000..18c4fe4
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/spec_helper_acceptance.rb
@@ -0,0 +1,17 @@
+require 'beaker-rspec'
+require 'beaker/puppet_install_helper'
+require 'beaker/module_install_helper'
+require 'acceptance/helpers'
+
+run_puppet_install_helper
+install_ca_certs unless ENV['PUPPET_INSTALL_TYPE'] =~ /pe/i
+hosts.each do |host|
+ install_module_on(host)
+end
+install_module_dependencies_on(hosts)
+
+RSpec.configure do |c|
+ # Readable test descriptions
+ c.formatter = :documentation
+end
+
diff --git a/code/environments/production/modules/certregen/spec/spec_helper_local.rb b/code/environments/production/modules/certregen/spec/spec_helper_local.rb
new file mode 100644
index 0000000..3dfb8aa
--- /dev/null
+++ b/code/environments/production/modules/certregen/spec/spec_helper_local.rb
@@ -0,0 +1,52 @@
+RSpec.configure do |c|
+ c.include PuppetlabsSpec::Files
+ c.mock_with :rspec
+
+ c.before(:each) do
+ # Suppress cert fingerprint logging
+ allow_any_instance_of(Puppet::SSL::CertificateAuthority).to receive(:puts)
+
+ # remove the stub that causes puppet to believe it is
+ # always being run as root.
+ # See https://github.com/puppetlabs/puppetlabs_spec_helper/blob/master/lib/puppetlabs_spec_helper/module_spec_helper.rb#L29
+ Puppet.features.unstub(:root?)
+
+ Puppet[:vardir] = tmpdir('var')
+ Puppet[:confdir] = tmpdir('conf')
+ end
+
+ def backdate_certificate(ca, cert, not_before, not_after)
+ cert.content.not_before = not_before
+ cert.content.not_after = not_after
+ signer = Puppet::SSL::CertificateSigner.new
+ signer.sign(cert.content, ca.host.key.content)
+ cert
+ end
+
+ def make_certificate(name, not_before, not_after)
+ ca = Puppet::SSL::CertificateAuthority.new
+ cert = ca.generate(name)
+ backdate_certificate(ca, cert, not_before, not_after)
+ end
+end
+
+RSpec.shared_context "Initialize CA" do
+ # PKI generation is done by initializing a CertificateAuthority object, which has the effect of
+ # applying the settings catalog, generating a RSA keypair, and generating a CA certificate.
+ # Since we're regenerating the CA state between each test we need to create a new
+ # CertificateAuthority object instead of using CertificateAuthority.instance, since that will
+ # memoize a single instance and will not generate the ca folder structure and PKI files.
+ def generate_pki
+ Puppet::SSL::CertificateAuthority.new
+ end
+
+ before(:each) do
+ Puppet::SSL::Host.ca_location = :only
+ Puppet.settings.preferred_run_mode = "master"
+
+ Puppet[:ca] = true
+ Puppet[:ca_name] = 'Puppet CA: foo'
+
+ generate_pki
+ end
+end