diff --git a/acceptance/Gemfile b/acceptance/Gemfile index a4fe65e95..29949fee9 100644 --- a/acceptance/Gemfile +++ b/acceptance/Gemfile @@ -1,13 +1,13 @@ source ENV['GEM_SOURCE'] || "https://rubygems.org" -gem "beaker", "~> 1.11.0" +gem "beaker", "~> 1.17" gem 'rake', "~> 10.1.0" group(:test) do gem "rspec", "~> 2.11.0", :require => false gem "mocha", "~> 0.10.5", :require => false end if File.exists? "#{__FILE__}.local" eval(File.read("#{__FILE__}.local"), binding) end diff --git a/acceptance/Rakefile b/acceptance/Rakefile index 45555a21c..b47e51d72 100644 --- a/acceptance/Rakefile +++ b/acceptance/Rakefile @@ -1,343 +1,355 @@ require 'rake/clean' require 'pp' require 'yaml' $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), 'lib')) require 'puppet/acceptance/git_utils' extend Puppet::Acceptance::GitUtils ONE_DAY_IN_SECS = 24 * 60 * 60 REPO_CONFIGS_DIR = "repo-configs" CLEAN.include('*.tar', REPO_CONFIGS_DIR, 'merged_options.rb') module HarnessOptions DEFAULTS = { :type => 'git', :helper => ['lib/helper.rb'], :tests => ['tests'], :log_level => 'debug', :color => false, :root_keys => true, :ssh => { :keys => ["id_rsa-acceptance"], }, :xml => true, :timesync => false, :repo_proxy => true, :add_el_extras => true, :preserve_hosts => 'onfail', - :forge_host => 'forge-aio01-petest.puppetlabs.com' + :forge_host => 'forge-aio01-petest.puppetlabs.com', + :'master-start-curl-retries' => 30, } class Aggregator attr_reader :mode def initialize(mode) @mode = mode end def get_options(file_path) puts file_path if File.exists? file_path options = eval(File.read(file_path), binding) else puts "No options file found at #{File.expand_path(file_path)}" end options || {} end def get_mode_options get_options("./config/#{mode}/options.rb") end def get_local_options get_options("./local_options.rb") end def final_options(intermediary_options = {}) mode_options = get_mode_options local_overrides = get_local_options final_options = DEFAULTS.merge(mode_options) final_options.merge!(intermediary_options) final_options.merge!(local_overrides) return final_options end end def self.options(mode, options) final_options = Aggregator.new(mode).final_options(options) final_options end end def beaker_test(mode = :packages, options = {}) delete_options = options.delete(:__delete_options__) || [] final_options = HarnessOptions.options(mode, options) preserve_config = final_options.delete(:__preserve_config__) if mode == :git # Build up project git urls based on git server and fork env variables or defaults final_options[:install].map! do |install| if md = /^(\w+)#(\w+)$/.match(install) project, project_sha = md.captures "#{build_giturl(project)}##{project_sha}" elsif md = /^(\w+)$/.match(install) project = md[1] "#{build_giturl(project)}##{sha}" end end end delete_options.each do |delete_me| final_options.delete(delete_me) end options_file = 'merged_options.rb' File.open(options_file, 'w') do |merged| merged.puts <<-EOS # Copy this file to local_options.rb and adjust as needed if you wish to run # with some local overrides. EOS merged.puts(final_options.pretty_inspect) end tests = ENV['TESTS'] || ENV['TEST'] tests_opt = "--tests=#{tests}" if tests config_opt = "--hosts=#{config}" if config overriding_options = ENV['OPTIONS'] args = ["--options-file", options_file, config_opt, tests_opt, overriding_options].compact begin sh("beaker", *args) ensure preserve_configuration(final_options, options_file) if preserve_config end end def preserve_configuration(final_options, options_file) if (hosts_file = config || final_options[:hosts_file]) && hosts_file !~ /preserved_config/ cp(hosts_file, "log/latest/config.yml") generate_config_for_latest_hosts end mv(options_file, "log/latest") end def generate_config_for_latest_hosts preserved_config_hash = { 'HOSTS' => {} } puts "\nPreserving configuration so that any preserved nodes can be tested again locally..." config_hash = YAML.load_file('log/latest/config.yml') if !config_hash || !config_hash.include?('HOSTS') puts "Warning: No HOSTS configuration found in log/latest/config.yml" return else nodes = config_hash['HOSTS'].map do |node_label,hash| { :node_label => node_label, :roles => hash['roles'], :platform => hash['platform'] } end pre_suite_log = File.read('log/latest/pre_suite-run.log') nodes.each do |node_info| host_regex = /^([\w.]+) \(#{node_info[:node_label]}\)/ if matched = host_regex.match(pre_suite_log) hostname = matched[1] fqdn = "#{hostname}.delivery.puppetlabs.net" elsif /^#{node_info[:node_label]} /.match(pre_suite_log) fqdn = "#{node_info[:node_label]}" puts "* Couldn't find any log lines for #{host_regex}, assuming #{fqdn} is the fqdn" end if fqdn preserved_config_hash['HOSTS'][fqdn] = { 'roles' => node_info[:roles], 'platform' => node_info[:platform], } else puts "* Couldn't match #{node_info[:node_label]} in pre_suite-run.log" end end pp preserved_config_hash File.open('log/latest/preserved_config.yaml', 'w') do |config_file| YAML.dump(preserved_config_hash, config_file) end end rescue Errno::ENOENT => e puts "Warning: Couldn't generate preserved_config.yaml #{e}" end def list_preserved_configurations(secs_ago = ONE_DAY_IN_SECS) preserved = {} Dir.glob('log/*_*').each do |dir| preserved_config_path = "#{dir}/preserved_config.yaml" yesterday = Time.now - secs_ago.to_i if preserved_config = File.exists?(preserved_config_path) directory = File.new(dir) if directory.ctime > yesterday hosts = [] preserved_config = YAML.load_file(preserved_config_path).to_hash preserved_config['HOSTS'].each do |hostname,values| hosts << "#{hostname}: #{values['platform']}, #{values['roles']}" end preserved[hosts] = directory.to_path end end end preserved.map { |k,v| [v,k] }.sort { |a,b| a[0] <=> b[0] }.reverse end def list_preserved_hosts(secs_ago = ONE_DAY_IN_SECS) hosts = Set.new Dir.glob('log/**/pre*suite*run.log').each do |log| yesterday = Time.now - secs_ago.to_i File.open(log, 'r') do |file| if file.ctime > yesterday file.each_line do |line| matchdata = /^(\w+) \(.*?\) \d\d:\d\d:\d\d\$/.match(line.encode!('UTF-8', 'UTF-8', :invalid => :replace)) hosts.add(matchdata[1]) if matchdata end end end end hosts end def release_hosts(hosts = nil, secs_ago = ONE_DAY_IN_SECS) secs_ago ||= ONE_DAY_IN_SECS hosts ||= list_preserved_hosts(secs_ago) require 'beaker' vcloud_pooled = Beaker::VcloudPooled.new(hosts.map { |h| { 'vmhostname' => h } }, :logger => Beaker::Logger.new, :dot_fog => "#{ENV['HOME']}/.fog", 'pooling_api' => 'http://vcloud.delivery.puppetlabs.net' , 'datastore' => 'not-used', 'resourcepool' => 'not-used', 'folder' => 'not-used') vcloud_pooled.cleanup end def print_preserved(preserved) preserved.each_with_index do |entry,i| puts "##{i}: #{entry[0]}" entry[1].each { |h| puts " #{h}" } end end def beaker_run_type type = ENV['TYPE'] || :packages type = type.to_sym end def sha ENV['SHA'] end def config ENV['CONFIG'] end namespace :ci do task :check_env do raise(USAGE) unless sha end namespace :test do USAGE = <<-EOS Requires commit SHA to be put under test as environment variable: SHA=''. Also must set CONFIG=config/nodes/foo.yaml or include it in an options.rb for Beaker. You may set TESTS=path/to/test,and/more/tests. You may set additional Beaker OPTIONS='--more --options' If testing from git checkouts, you may optionally set the github fork to checkout from using PUPPET_FORK='some-other-puppet-fork' (you may change the HIERA_FORK and FACTER_FORK as well if you wish). You may also optionally set the git server to checkout repos from using GIT_SERVER='some.git.mirror'. Or you may set PUPPET_GIT_SERVER='my.host.with.git.daemon', specifically, if you have set up a `git daemon` to pull local commits from. (You will need to allow the git daemon to serve the repo (see `git help daemon` and the docs/acceptance_tests.md for more details)). If there is a Beaker options hash in a ./local_options.rb, it will be included. Commandline options set through the above environment variables will override settings in this file. EOS desc <<-EOS Run the acceptance tests through Beaker and install packages on the configuration targets. #{USAGE} EOS task :packages => 'ci:check_env' do beaker_test end desc <<-EOS Run the acceptance tests through Beaker and install from git on the configuration targets. #{USAGE} EOS task :git => 'ci:check_env' do beaker_test(:git) end end desc "Capture the master and agent hostname from the latest log and construct a preserved_config.yaml for re-running against preserved hosts without provisioning." task :extract_preserved_config do generate_config_for_latest_hosts end desc <<-EOS Run an acceptance test for a given node configuration and preserve the hosts. Defaults to a packages run, but you can set it to 'git' with TYPE='git'. #{USAGE} EOS task :test_and_preserve_hosts => 'ci:check_env' do beaker_test(beaker_run_type, :preserve_hosts => 'always', :__preserve_config__ => true) end desc "List acceptance runs from the past day which had hosts preserved." task :list_preserved do preserved = list_preserved_configurations print_preserved(preserved) end desc <<-EOS Shutdown and destroy any hosts that we have preserved for testing. These should be reaped daily by scripts, but this will free up resources immediately. Specify a list of comma separated HOST_NAMES if you have a set of dynamic vcloud host names you want to purge outside of what can be grepped from the logs. You can go back through the last SECS_AGO logs. Default is one day ago in secs. EOS task :release_hosts do host_names = ENV['HOST_NAMES'].split(',') if ENV['HOST_NAMES'] secs_ago = ENV['SECS_AGO'] release_hosts(host_names, secs_ago) end task :destroy_preserved_hosts => 'ci:release_hosts' do puts "Note: we are now releasing hosts back to the vcloud pooling api rather than destroying them directly. The rake task for this is ci:release_hosts" end desc <<-EOS Rerun an acceptance test using the last captured preserved_config.yaml to skip provisioning. Or specify a CONFIG_NUMBER from `rake ci:list_preserved`. Defaults to a packages run, but you can set it to 'git' with TYPE='git'. EOS task :test_against_preserved_hosts do config_number = (ENV['CONFIG_NUMBER'] || 0).to_i preserved = list_preserved_configurations print_preserved(preserved) config_path = preserved[config_number][0] + puts "Using ##{config_number}: #{config_path}" - beaker_test(beaker_run_type, + + options = { :hosts_file => "#{config_path}/preserved_config.yaml", :no_provision => true, :preserve_hosts => 'always', - :__delete_options__ => [:pre_suite] - ) + } + run_type = beaker_run_type + if run_type == :packages + options.merge!(:pre_suite => [ + 'setup/packages/pre-suite/015_PackageHostsPresets.rb', + 'setup/packages/pre-suite/045_EnsureMasterStartedOnPassenger.rb', + ]) + else + options.merge!(:__delete_options__ => [:pre_suite]) + end + beaker_test(beaker_run_type, options) end end task :default do sh('rake -T') end task :spec do sh('rspec lib') end diff --git a/acceptance/config/packages/options.rb b/acceptance/config/packages/options.rb index 4adaab5de..343cbb48a 100644 --- a/acceptance/config/packages/options.rb +++ b/acceptance/config/packages/options.rb @@ -1,8 +1,11 @@ { + :type => 'foss-packages', :pre_suite => [ 'setup/packages/pre-suite/010_Install.rb', + 'setup/packages/pre-suite/015_PackageHostsPresets.rb', 'setup/common/pre-suite/025_StopFirewall.rb', 'setup/common/pre-suite/040_ValidateSignCert.rb', + 'setup/packages/pre-suite/045_EnsureMasterStartedOnPassenger.rb', 'setup/common/pre-suite/100_SetParser.rb', ], } diff --git a/acceptance/lib/puppet/acceptance/common_utils.rb b/acceptance/lib/puppet/acceptance/common_utils.rb index 5213595ed..c9aeb1d15 100644 --- a/acceptance/lib/puppet/acceptance/common_utils.rb +++ b/acceptance/lib/puppet/acceptance/common_utils.rb @@ -1,107 +1,125 @@ module Puppet module Acceptance module CronUtils def clean(agent, o={}) o = {:user => 'tstuser'}.merge(o) run_cron_on(agent, :remove, o[:user]) apply_manifest_on(agent, %[user { '%s': ensure => absent, managehome => false }] % o[:user]) end def setup(agent, o={}) o = {:user => 'tstuser'}.merge(o) apply_manifest_on(agent, %[user { '%s': ensure => present, managehome => false }] % o[:user]) apply_manifest_on(agent, %[case $operatingsystem { centos, redhat: {$cron = 'cronie'} solaris: { $cron = 'core-os' } default: {$cron ='cron'} } package {'cron': name=> $cron, ensure=>present, }]) end end module CAUtils def initialize_ssl hostname = on(master, 'facter hostname').stdout.strip fqdn = on(master, 'facter fqdn').stdout.strip + if master.use_service_scripts? + step "Ensure puppet is stopped" + # Passenger, in particular, must be shutdown for the cert setup steps to work, + # but any running puppet master will interfere with webrick starting up and + # potentially ignore the puppet.conf changes. + on(master, puppet('resource', 'service', master['puppetservice'], "ensure=stopped")) + end + step "Clear SSL on all hosts" hosts.each do |host| ssldir = on(host, puppet('agent --configprint ssldir')).stdout.chomp on(host, "rm -rf '#{ssldir}'") end step "Master: Start Puppet Master" do - with_puppet_running_on(master, :main => { :dns_alt_names => "puppet,#{hostname},#{fqdn}", :verbose => true, :daemonize => true }) do + master_opts = { + :main => { + :dns_alt_names => "puppet,#{hostname},#{fqdn}", + }, + :__service_args__ => { + # apache2 service scripts can't restart if we've removed the ssl dir + :bypass_service_script => true, + }, + } + with_puppet_running_on(master, master_opts) do hosts.each do |host| next if host['roles'].include? 'master' step "Agents: Run agent --test first time to gen CSR" on host, puppet("agent --test --server #{master}"), :acceptable_exit_codes => [1] end # Sign all waiting certs step "Master: sign all certs" on master, puppet("cert --sign --all"), :acceptable_exit_codes => [0,24] step "Agents: Run agent --test second time to obtain signed cert" on agents, puppet("agent --test --server #{master}"), :acceptable_exit_codes => [0,2] end end end def clean_cert(host, cn, check = true) on(host, puppet('cert', 'clean', cn), :acceptable_exit_codes => check ? [0] : [0, 24]) if check assert_match(/remov.*Certificate.*#{cn}/i, stdout, "Should see a log message that certificate request was removed.") on(host, puppet('cert', 'list', '--all')) assert_no_match(/#{cn}/, stdout, "Should not see certificate in list anymore.") end end def clear_agent_ssl return if master.is_pe? step "All: Clear agent only ssl settings (do not clear master)" hosts.each do |host| next if host == master ssldir = on(host, puppet('agent --configprint ssldir')).stdout.chomp on( host, host_command("rm -rf '#{ssldir}'") ) end end def reset_agent_ssl(resign = true) return if master.is_pe? clear_agent_ssl hostname = master.execute('facter hostname') fqdn = master.execute('facter fqdn') step "Clear old agent certificates from master" do agents.each do |agent| + next if agent == master && agent.is_using_passenger? agent_cn = on(agent, puppet('agent --configprint certname')).stdout.chomp clean_cert(master, agent_cn, false) if agent_cn end end if resign step "Master: Ensure the master is listening and autosigning" with_puppet_running_on(master, :master => { :dns_alt_names => "puppet,#{hostname},#{fqdn}", :autosign => true, } ) do agents.each do |agent| - + next if agent == master && agent.is_using_passenger? step "Agents: Run agent --test once to obtain auto-signed cert" do on agent, puppet('agent', "--test --server #{master}"), :acceptable_exit_codes => [0,2] end end end end end end end end diff --git a/acceptance/setup/packages/pre-suite/010_Install.rb b/acceptance/setup/packages/pre-suite/010_Install.rb index dfdfc1f37..4397211fe 100644 --- a/acceptance/setup/packages/pre-suite/010_Install.rb +++ b/acceptance/setup/packages/pre-suite/010_Install.rb @@ -1,52 +1,49 @@ require 'puppet/acceptance/install_utils' extend Puppet::Acceptance::InstallUtils test_name "Install Packages" step "Install repositories on target machines..." do sha = ENV['SHA'] repo_configs_dir = 'repo-configs' hosts.each do |host| install_repos_on(host, sha, repo_configs_dir) end end MASTER_PACKAGES = { :redhat => [ 'puppet-server', ], :debian => [ - 'puppetmaster', + 'puppetmaster-passenger', ], # :solaris => [ # 'puppet-server', # ], # :windows => [ # 'puppet-server', # ], } AGENT_PACKAGES = { :redhat => [ 'puppet', ], :debian => [ 'puppet', ], # :solaris => [ # 'puppet', # ], # :windows => [ # 'puppet', # ], } install_packages_on(master, MASTER_PACKAGES) -if master['platform'] =~ /debian|ubuntu/ - on(master, '/etc/init.d/puppetmaster stop') -end install_packages_on(agents, AGENT_PACKAGES) diff --git a/acceptance/setup/packages/pre-suite/015_PackageHostsPresets.rb b/acceptance/setup/packages/pre-suite/015_PackageHostsPresets.rb new file mode 100644 index 000000000..1a1101aa5 --- /dev/null +++ b/acceptance/setup/packages/pre-suite/015_PackageHostsPresets.rb @@ -0,0 +1,5 @@ +if master['platform'] =~ /debian|ubuntu/ + master.uses_passenger! +elsif master['platform'] =~ /redhat|el|centos|scientific/ + master['use-service'] = true +end diff --git a/acceptance/setup/packages/pre-suite/045_EnsureMasterStartedOnPassenger.rb b/acceptance/setup/packages/pre-suite/045_EnsureMasterStartedOnPassenger.rb new file mode 100644 index 000000000..20f4fdfb5 --- /dev/null +++ b/acceptance/setup/packages/pre-suite/045_EnsureMasterStartedOnPassenger.rb @@ -0,0 +1,3 @@ +if master.graceful_restarts? + on(master, puppet('resource', 'service', master['puppetservice'], "ensure=running")) +end diff --git a/acceptance/tests/config/puppet_manages_own_configuration_in_robust_manner.rb b/acceptance/tests/config/puppet_manages_own_configuration_in_robust_manner.rb index 8b3a91537..ea4694ad3 100644 --- a/acceptance/tests/config/puppet_manages_own_configuration_in_robust_manner.rb +++ b/acceptance/tests/config/puppet_manages_own_configuration_in_robust_manner.rb @@ -1,71 +1,80 @@ # User story: # A new user has installed puppet either from source or from a gem, which does # not put the "puppet" user or group on the system. They run the puppet master, # which fails because of the missing user and then correct their actions. They # expect that after correcting their actions, puppet will work correctly. test_name "Puppet manages its own configuration in a robust manner" # when owner/group works on windows for settings, this confine should be removed. confine :except, :platform => 'windows' # when managhome roundtrips for solaris, this confine should be removed confine :except, :platform => 'solaris' # pe setup includes ownership of external directories such as the passenger # document root, which puppet itself knows nothing about confine :except, :type => 'pe' +# same issue for a foss passenger run +if master.is_using_passenger? + skip_test 'Cannot test with passenger.' +end + +if master.use_service_scripts? + # Beaker defaults to leaving puppet running when using service scripts, + # Need to shut it down so we can modify user/group and test startup failure + on(master, puppet('resource', 'service', master['puppetservice'], 'ensure=stopped')) +end step "Clear out yaml directory because of a bug in the indirector/yaml. (See #21145)" on master, 'rm -rf $(puppet master --configprint yamldir)' original_state = {} step "Record original state of system users" do hosts.each do |host| original_state[host] = {} original_state[host][:user] = user = host.execute('puppet config print user') original_state[host][:group] = group = host.execute('puppet config print group') original_state[host][:ug_resources] = on(host, puppet('resource', 'user', user)).stdout original_state[host][:ug_resources] += on(host, puppet('resource', 'group', group)).stdout original_state[host][:ug_resources] += "Group['#{group}'] -> User['#{user}']\n" end end teardown do # And cleaning up yaml dir again here because we are changing service # user and group ids back to the original uid and gid on master, 'rm -rf $(puppet master --configprint yamldir)' hosts.each do |host| apply_manifest_on(host, <<-ORIG) #{original_state[host][:ug_resources]} ORIG end with_puppet_running_on(master, {}) do agents.each do |agent| on agent, puppet('agent', '-t', '--server', master) end end end step "Remove system users" do hosts.each do |host| on host, puppet('resource', 'user', original_state[host][:user], 'ensure=absent') on host, puppet('resource', 'group', original_state[host][:group], 'ensure=absent') end end step "Ensure master fails to start when missing system user" do on master, puppet('master'), :acceptable_exit_codes => [74] do assert_match(/could not change to group "#{original_state[master][:group]}"/, result.output) assert_match(/Could not change to user #{original_state[master][:user]}/, result.output) end end step "Ensure master starts when making users after having previously failed startup" do with_puppet_running_on(master, - :__commandline_args__ => '--debug --trace', :master => { :mkusers => true }) do agents.each do |agent| on agent, puppet('agent', '-t', '--server', master) end end end diff --git a/acceptance/tests/environment/cmdline_overrides_environment.rb b/acceptance/tests/environment/cmdline_overrides_environment.rb index 09319f749..274ccd908 100644 --- a/acceptance/tests/environment/cmdline_overrides_environment.rb +++ b/acceptance/tests/environment/cmdline_overrides_environment.rb @@ -1,293 +1,320 @@ test_name "Commandline modulepath and manifest settings override environment" testdir = master.tmpdir('cmdline_and_environment') environmentpath = "#{testdir}/environments" modulepath = "#{testdir}/modules" manifests = "#{testdir}/manifests" sitepp = "#{manifests}/site.pp" other_manifestdir = "#{testdir}/other_manifests" other_sitepp = "#{other_manifestdir}/site.pp" other_modulepath = "#{testdir}/some_other_modulepath" cmdline_manifest = "#{testdir}/cmdline.pp" step "Prepare manifests and modules" apply_manifest_on(master, <<-MANIFEST, :catch_failures => true) File { ensure => directory, owner => #{master['user']}, group => #{master['group']}, mode => 0750, } ############################################## # A production directory environment file { "#{testdir}":; "#{environmentpath}":; "#{environmentpath}/production":; "#{environmentpath}/production/manifests":; "#{environmentpath}/production/modules":; "#{environmentpath}/production/modules/amod":; "#{environmentpath}/production/modules/amod/manifests":; } file { "#{environmentpath}/production/modules/amod/manifests/init.pp": ensure => file, mode => 0640, content => 'class amod { notify { "amod from production environment": } }' } file { "#{environmentpath}/production/manifests/production.pp": ensure => file, mode => 0640, content => ' notify { "in production.pp": } include amod ' } ############################################################## # To be set as default manifests and modulepath in puppet.conf file { "#{modulepath}":; "#{modulepath}/amod/":; "#{modulepath}/amod/manifests":; } file { "#{modulepath}/amod/manifests/init.pp": ensure => file, mode => 0640, content => 'class amod { notify { "amod from modulepath": } }' } file { "#{manifests}": } file { "#{sitepp}": ensure => file, mode => 0640, content => ' notify { "in site.pp": } include amod ' } file { "#{other_manifestdir}": } file { "#{other_sitepp}": ensure => file, mode => 0640, content => ' notify { "in other manifestdir site.pp": } include amod ' } ################################ # To be specified on commandline file { "#{other_modulepath}":; "#{other_modulepath}/amod/":; "#{other_modulepath}/amod/manifests":; } file { "#{other_modulepath}/amod/manifests/init.pp": ensure => file, mode => 0640, content => 'class amod { notify { "amod from commandline modulepath": } }' } file { "#{cmdline_manifest}": ensure => file, mode => 0640, content => ' notify { "in cmdline.pp": } include amod ' } MANIFEST +def shutdown_puppet_if_running_as_a_service + if master.use_service_scripts? + # Beaker defaults to leaving puppet running when using service scripts, + # Need to shut it down so we can start up with commandline options + on(master, puppet('resource', 'service', master['puppetservice'], 'ensure=stopped')) + end +end + +teardown do + if master.use_service_scripts? + # Beaker defaults to leaving puppet running when using service scripts, + on(master, puppet('resource', 'service', master['puppetservice'], 'ensure=running')) + end +end + # Note: this is the semantics seen with legacy environments if commandline # manifest/modulepath are set. step "CASE 1: puppet master with --manifest and --modulepath overrides set production directory environment" do - if master.is_pe? + if master.is_using_passenger? step "Skipping for Passenger (PE) setup; since the equivalent of a commandline override would be adding the setting to config.ru, which seems like a very odd thing to do." else + + shutdown_puppet_if_running_as_a_service + master_opts = { 'master' => { 'environmentpath' => environmentpath, 'manifest' => sitepp, 'modulepath' => modulepath, - } + }, + :__service_args__ => { + :bypass_service_script => true, + }, } master_opts_with_cmdline = master_opts.merge(:__commandline_args__ => "--manifest=#{cmdline_manifest} --modulepath=#{other_modulepath}") with_puppet_running_on master, master_opts_with_cmdline, testdir do agents.each do |agent| on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2] ) do assert_match(/in cmdline\.pp/, stdout) assert_match(/amod from commandline modulepath/, stdout) assert_no_match(/production/, stdout) end step "CASE 1a: even if environment is specified" on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2]) do assert_match(/in cmdline\.pp/, stdout) assert_match(/amod from commandline modulepath/, stdout) assert_no_match(/production/, stdout) end end end step "CASE 2: or if you set --manifestdir" do master_opts_with_cmdline = master_opts.merge(:__commandline_args__ => "--manifestdir=#{other_manifestdir} --modulepath=#{other_modulepath}") step "CASE 2: it is ignored if manifest is set in puppet.conf to something not using $manifestdir" with_puppet_running_on master, master_opts_with_cmdline, testdir do agents.each do |agent| on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2]) do assert_match(/in production\.pp/, stdout) assert_match(/amod from commandline modulepath/, stdout) end end end step "CASE 2a: but does pull in the default manifest via manifestdir if manifest is not set" master_opts_with_cmdline = master_opts.merge(:__commandline_args__ => "--manifestdir=#{other_manifestdir} --modulepath=#{other_modulepath}") master_opts_with_cmdline['master'].delete('manifest') with_puppet_running_on master, master_opts_with_cmdline, testdir do agents.each do |agent| on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2]) do assert_match(/in other manifestdir site\.pp/, stdout) assert_match(/amod from commandline modulepath/, stdout) assert_no_match(/production/, stdout) end end end end end end step "CASE 3: puppet master with manifest and modulepath set in puppet.conf is overriden by an existing and set production directory environment" do master_opts = { 'master' => { 'environmentpath' => environmentpath, 'manifest' => sitepp, 'modulepath' => modulepath, } } if master.is_pe? master_opts['master']['basemodulepath'] = master['sitemoduledir'] end with_puppet_running_on master, master_opts, testdir do agents.each do |agent| step "CASE 3: this case is unfortunate, but will be irrelevant when we remove legacyenv in 4.0" on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2] ) do assert_match(/in production\.pp/, stdout) assert_match(/amod from production environment/, stdout) end step "CASE 3a: if environment is specified" on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2]) do assert_match(/in production\.pp/, stdout) assert_match(/amod from production environment/, stdout) end end end end step "CASE 4: puppet master with default manifest, modulepath, environment, environmentpath and an existing '#{environmentpath}/production' directory environment that has not been set" do - if master.is_pe? + if master.is_using_passenger? step "Skipping for PE because PE requires most of the existing puppet.conf and /etc/puppetlabs/puppet configuration, and we cannot simply point to a new conf directory." else + + shutdown_puppet_if_running_as_a_service + ssldir = on(master, puppet("master --configprint ssldir")).stdout.chomp master_opts = { + :__service_args__ => { + :bypass_service_script => true, + }, :__commandline_args__ => "--confdir=#{testdir} --ssldir=#{ssldir}" } with_puppet_running_on master, master_opts, testdir do agents.each do |agent| step "CASE 4: #{environmentpath}/production directory environment does not take precedence because default environmentpath is ''" on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2] ) do assert_match(/in site\.pp/, stdout) assert_match(/amod from modulepath/, stdout) end on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2]) do assert_match(/in site\.pp/, stdout) assert_match(/amod from modulepath/, stdout) end end end end end step "CASE 5: puppet master with explicit dynamic environment settings and empty environmentpath" do step "CASE 5: Prepare an additional modulepath module" apply_manifest_on(master, <<-MANIFEST, :catch_failures => true) File { ensure => directory, owner => #{master['user']}, group => #{master['group']}, mode => 0750, } # A second module in another modules dir file { "#{other_modulepath}":; "#{other_modulepath}/bmod/":; "#{other_modulepath}/bmod/manifests":; } file { "#{other_modulepath}/bmod/manifests/init.pp": ensure => file, mode => 0640, content => 'class bmod { notify { "bmod from other modulepath": } }' } file { "#{environmentpath}/production/manifests/production.pp": ensure => file, mode => 0640, content => ' notify { "in production.pp": } include amod include bmod ' } MANIFEST master_opts = { 'master' => { 'manifest' => "#{environmentpath}/$environment/manifests", 'modulepath' => "#{environmentpath}/$environment/modules:#{other_modulepath}", } } if master.is_pe? master_opts['master']['modulepath'] << ":#{master['sitemoduledir']}" end with_puppet_running_on master, master_opts, testdir do agents.each do |agent| step "CASE 5: pulls in the production environment based on $environment default" on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2] ) do assert_match(/in production\.pp/, stdout) assert_match(/amod from production environment/, stdout) step "CASE 5: and sees modules located in later elements of the modulepath (which would not be seen by a directory env (PUP-2158)" assert_match(/bmod from other modulepath/, stdout) end step "CASE 5a: pulls in the production environment when explicitly set" on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2] ) do assert_match(/in production\.pp/, stdout) assert_match(/amod from production environment/, stdout) step "CASE 5a: and sees modules located in later elements of the modulepath (which would not be seen by a directory env (PUP-2158)" assert_match(/bmod from other modulepath/, stdout) end end end end diff --git a/acceptance/tests/external_ca_support/apache_external_root_ca.rb b/acceptance/tests/external_ca_support/apache_external_root_ca.rb index 4019b56d5..029479aba 100644 --- a/acceptance/tests/external_ca_support/apache_external_root_ca.rb +++ b/acceptance/tests/external_ca_support/apache_external_root_ca.rb @@ -1,190 +1,201 @@ begin require 'puppet_x/acceptance/external_cert_fixtures' rescue LoadError $LOAD_PATH.unshift(File.expand_path('../../../lib', __FILE__)) require 'puppet_x/acceptance/external_cert_fixtures' end # This test only runs on EL-6 master roles. confine :to, :platform => 'el-6' confine :except, :type => 'pe' +if master.use_service_scripts? + # Beaker defaults to leaving puppet running when using service scripts, + # Need to shut it down so we can start up our apache instance + on(master, puppet('resource', 'service', master['puppetservice'], 'ensure=stopped')) + + teardown do + # And ensure that it is up again after everything is done + on(master, puppet('resource', 'service', master['puppetservice'], 'ensure=running')) + end +end + # Verify that a trivial manifest can be run to completion. # Supported Setup: Single, Root CA # - Agent and Master SSL cert issued by the Root CA # - Revocation disabled on the agent `certificate_revocation = false` # - CA disabled on the master `ca = false` # # SUPPORT NOTES # # * If the x509 alt names extension is used when issuing SSL server certificates # for the Puppet master, then the client SSL certificate issued by an external # CA must posses the DNS common name in the alternate name field. This is # due to a bug in Ruby. If the CN is not duplicated in the Alt Names, then # the following error will appear on the agent with MRI 1.8.7: # # Warning: Server hostname 'master1.example.org' did not match server # certificate; expected one of master1.example.org, DNS:puppet, # DNS:master-ca.example.org # # See: https://bugs.ruby-lang.org/issues/6493 test_name "Puppet agent works with Apache, both configured with externally issued certificates from independent intermediate CA's" step "Copy certificates and configuration files to the master..." fixture_dir = File.expand_path('../fixtures', __FILE__) testdir = master.tmpdir('apache_external_root_ca') fixtures = PuppetX::Acceptance::ExternalCertFixtures.new(fixture_dir, testdir) # We need this variable in scope. disable_and_reenable_selinux = nil # Register our cleanup steps early in a teardown so that they will happen even # if execution aborts part way. teardown do step "Cleanup Apache (httpd) and /etc/hosts" # Restore /etc/hosts on master, "cp -p '#{testdir}/hosts' /etc/hosts" # stop the service before moving files around on master, "/etc/init.d/httpd stop" on master, "mv --force /etc/httpd/conf/httpd.conf{,.external_ca_test}" on master, "mv --force /etc/httpd/conf/httpd.conf{.orig,}" if disable_and_reenable_selinux step "Restore the original state of SELinux" on master, "setenforce 1" end end # Read all of the CA certificates. # Copy all of the x.509 fixture data over to the master. create_remote_file master, "#{testdir}/ca_root.crt", fixtures.root_ca_cert create_remote_file master, "#{testdir}/ca_agent.crt", fixtures.agent_ca_cert create_remote_file master, "#{testdir}/ca_master.crt", fixtures.master_ca_cert create_remote_file master, "#{testdir}/ca_master.crl", fixtures.master_ca_crl create_remote_file master, "#{testdir}/ca_master_bundle.crt", "#{fixtures.master_ca_cert}\n#{fixtures.root_ca_cert}\n" create_remote_file master, "#{testdir}/ca_agent_bundle.crt", "#{fixtures.agent_ca_cert}\n#{fixtures.root_ca_cert}\n" create_remote_file master, "#{testdir}/agent.crt", fixtures.agent_cert create_remote_file master, "#{testdir}/agent.key", fixtures.agent_key create_remote_file master, "#{testdir}/agent_email.crt", fixtures.agent_email_cert create_remote_file master, "#{testdir}/agent_email.key", fixtures.agent_email_key create_remote_file master, "#{testdir}/master.crt", fixtures.master_cert create_remote_file master, "#{testdir}/master.key", fixtures.master_key create_remote_file master, "#{testdir}/master_rogue.crt", fixtures.master_cert_rogue create_remote_file master, "#{testdir}/master_rogue.key", fixtures.master_key_rogue ## # Now create the master and agent puppet.conf # # We need to create the public directory for Passenger and the modules # directory to avoid `Error: Could not evaluate: Could not retrieve information # from environment production source(s) puppet://master1.example.org/plugins` on master, "mkdir -p #{testdir}/etc/{master/{public,modules/empty/lib},agent}" # Backup /etc/hosts on master, "cp -p /etc/hosts '#{testdir}/hosts'" # Make master1.example.org resolve if it doesn't already. on master, "grep -q -x '#{fixtures.host_entry}' /etc/hosts || echo '#{fixtures.host_entry}' >> /etc/hosts" create_remote_file master, "#{testdir}/etc/agent/puppet.conf", fixtures.agent_conf create_remote_file master, "#{testdir}/etc/agent/puppet.conf.crl", fixtures.agent_conf_crl create_remote_file master, "#{testdir}/etc/agent/puppet.conf.email", fixtures.agent_conf_email create_remote_file master, "#{testdir}/etc/master/puppet.conf", fixtures.master_conf # auth.conf to allow *.example.com access to the rest API create_remote_file master, "#{testdir}/etc/master/auth.conf", fixtures.auth_conf create_remote_file master, "#{testdir}/etc/master/config.ru", fixtures.config_ru step "Set filesystem permissions and ownership for the master" # These permissions are required for Passenger to start Puppet as puppet on master, "chown -R puppet:puppet #{testdir}/etc/master" # These permissions are just for testing, end users should protect their # private keys. on master, "chmod -R a+rX #{testdir}" agent_cmd_prefix = "--confdir #{testdir}/etc/agent --vardir #{testdir}/etc/agent/var" step "Configure EPEL" epel_release_path = "http://mirror.us.leaseweb.net/epel/6/i386/epel-release-6-8.noarch.rpm" on master, "rpm -q epel-release || (yum -y install #{epel_release_path} && yum -y upgrade epel-release)" step "Configure Apache and Passenger" packages = [ 'httpd', 'mod_ssl', 'mod_passenger', 'rubygem-passenger', 'policycoreutils-python' ] packages.each do |pkg| on master, "rpm -q #{pkg} || (yum -y install #{pkg})" end create_remote_file master, "#{testdir}/etc/httpd.conf", fixtures.httpd_conf on master, 'test -f /etc/httpd/conf/httpd.conf.orig || cp -p /etc/httpd/conf/httpd.conf{,.orig}' on master, "cat #{testdir}/etc/httpd.conf > /etc/httpd/conf/httpd.conf" step "Make SELinux and Apache play nicely together..." on master, "sestatus" do if stdout.match(/Current mode:.*enforcing/) disable_and_reenable_selinux = true else disable_and_reenable_selinux = false end end if disable_and_reenable_selinux on master, "setenforce 0" end step "Start the Apache httpd service..." on master, 'service httpd restart' # Move the agent SSL cert and key into place. # The filename must match the configured certname, otherwise Puppet will try # and generate a new certificate and key step "Configure the agent with the externally issued certificates" on master, "mkdir -p #{testdir}/etc/agent/ssl/{public_keys,certs,certificate_requests,private_keys,private}" create_remote_file master, "#{testdir}/etc/agent/ssl/certs/#{fixtures.agent_name}.pem", fixtures.agent_cert create_remote_file master, "#{testdir}/etc/agent/ssl/private_keys/#{fixtures.agent_name}.pem", fixtures.agent_key # Now, try and run the agent on the master against itself. step "Successfully run the puppet agent on the master" on master, puppet_agent("#{agent_cmd_prefix} --test"), :acceptable_exit_codes => (0..255) do assert_no_match /Creating a new SSL key/, stdout assert_no_match /\Wfailed\W/i, stderr assert_no_match /\Wfailed\W/i, stdout assert_no_match /\Werror\W/i, stderr assert_no_match /\Werror\W/i, stdout # Assert the exit code so we get a "Failed test" instead of an "Errored test" assert exit_code == 0 end step "Agent refuses to connect to a rogue master" on master, puppet_agent("#{agent_cmd_prefix} --ssl_client_ca_auth=#{testdir}/ca_master.crt --masterport=8141 --test"), :acceptable_exit_codes => (0..255) do assert_no_match /Creating a new SSL key/, stdout assert_match /certificate verify failed/i, stderr assert_match /The server presented a SSL certificate chain which does not include a CA listed in the ssl_client_ca_auth file/i, stderr assert exit_code == 1 end step "Master accepts client cert with email address in subject" on master, "cp #{testdir}/etc/agent/puppet.conf{,.no_email}" on master, "cp #{testdir}/etc/agent/puppet.conf{.email,}" on master, puppet_agent("#{agent_cmd_prefix} --test"), :acceptable_exit_codes => (0..255) do assert_no_match /\Wfailed\W/i, stdout assert_no_match /\Wfailed\W/i, stderr assert_no_match /\Werror\W/i, stdout assert_no_match /\Werror\W/i, stderr # Assert the exit code so we get a "Failed test" instead of an "Errored test" assert exit_code == 0 end step "Agent refuses to connect to revoked master" on master, "cp #{testdir}/etc/agent/puppet.conf{,.no_crl}" on master, "cp #{testdir}/etc/agent/puppet.conf{.crl,}" revoke_opts = "--hostcrl #{testdir}/ca_master.crl" on master, puppet_agent("#{agent_cmd_prefix} #{revoke_opts} --test"), :acceptable_exit_codes => (0..255) do assert_match /certificate revoked.*?example.org/, stderr assert exit_code == 1 end step "Finished testing External Certificates" diff --git a/acceptance/tests/helpful_error_message_when_hostname_not_match_server_certificate.rb b/acceptance/tests/helpful_error_message_when_hostname_not_match_server_certificate.rb index 7d18cc298..11394598f 100644 --- a/acceptance/tests/helpful_error_message_when_hostname_not_match_server_certificate.rb +++ b/acceptance/tests/helpful_error_message_when_hostname_not_match_server_certificate.rb @@ -1,17 +1,17 @@ test_name "generate a helpful error message when hostname doesn't match server certificate" -skip_test( 'Changing certnames of the master will break PE' )if master.is_pe? +skip_test( 'Changing certnames of the master will break PE/Passenger installations' ) if master.is_using_passenger? # Start the master with a certname not matching its hostname master_opts = { 'master' => { 'certname' => 'foobar_not_my_hostname', 'dns_alt_names' => 'one_cert,two_cert,red_cert,blue_cert' } } with_puppet_running_on master, master_opts do run_agent_on(agents, "--test --server #{master}", :acceptable_exit_codes => (1..255)) do msg = "Server hostname '#{master}' did not match server certificate; expected one of foobar_not_my_hostname, DNS:blue_cert, DNS:foobar_not_my_hostname, DNS:one_cert, DNS:red_cert, DNS:two_cert" assert_match(msg, stderr) end end diff --git a/acceptance/tests/reports/submission.rb b/acceptance/tests/reports/submission.rb index 9b96080df..96e7afafe 100644 --- a/acceptance/tests/reports/submission.rb +++ b/acceptance/tests/reports/submission.rb @@ -1,54 +1,55 @@ test_name "Report submission" if master.is_pe? require "time" def query_last_report_time_on(agent) time_query_script = <<-EOS require "net/http" require "json" puppetdb_url = URI("http://localhost:8080/v3/reports") puppetdb_url.query = URI.escape(%Q{query=["=","certname","#{agent}"]}) result = Net::HTTP.get(puppetdb_url) json = JSON.load(result) puts json.first["receive-time"] EOS puppetdb = hosts.detect { |h| h['roles'].include?('database') } on(puppetdb, "#{master[:puppetbindir]}/ruby -e '#{time_query_script}'").output.chomp end last_times = {} agents.each do |agent| last_times[agent] = query_last_report_time_on(agent) end with_puppet_running_on(master, {}) do agents.each do |agent| on(agent, puppet('agent', "-t --server #{master}")) current_time = Time.parse(query_last_report_time_on(agent)) last_time = Time.parse(last_times[agent]) assert(current_time > last_time, "Most recent report time #{current_time} is not newer than last report time #{last_time}") end end else testdir = master.tmpdir('report_submission') + on(master, "chown puppet:puppet #{testdir}") teardown do on master, "rm -rf #{testdir}" end with_puppet_running_on(master, :main => { :reportdir => testdir, :reports => 'store' }) do agents.each do |agent| on(agent, puppet('agent', "-t --server #{master}")) on master, "grep -q #{agent} #{testdir}/*/*" end end end diff --git a/acceptance/tests/resource/file/source_attribute.rb b/acceptance/tests/resource/file/source_attribute.rb index b89c1e74a..8f249bfa3 100644 --- a/acceptance/tests/resource/file/source_attribute.rb +++ b/acceptance/tests/resource/file/source_attribute.rb @@ -1,91 +1,98 @@ test_name "The source attribute" step "when using a puppet:/// URI with a master/agent setup" testdir = master.tmpdir('file_source_attr') source_path = "#{testdir}/modules/source_test_module/files/source_file" on master, "mkdir -p #{File.dirname(source_path)}" create_remote_file master, source_path, < [0,1]) + end +end + mod_manifest = "#{testdir}/modules/source_test_module/manifests/init.pp" on master, "mkdir -p #{File.dirname(mod_manifest)}" create_remote_file master, mod_manifest, < '#{target_file_on_windows}', default => '#{target_file_on_nix}' } file { $target_file: source => 'puppet:///modules/source_test_module/source_file', ensure => present } } EOF manifest = "#{testdir}/site.pp" create_remote_file master, manifest, < { 'manifest' => manifest, 'node_terminus' => 'plain', 'modulepath' => "#{testdir}/modules" } } with_puppet_running_on master, master_opts, testdir do agents.each do |agent| on(agent, puppet('agent', "--test --server #{master}"), :acceptable_exit_codes => [2]) do file_to_check = agent['platform'] =~ /windows/ ? target_file_on_windows : target_file_on_nix on agent, "cat #{file_to_check}" do assert_match(/the content is present/, stdout, "Result file not created") end end end end # TODO: Add tests for puppet:// URIs with multi-master/agent setups. # step "when using a puppet://$server/ URI with a master/agent setup" agents.each do |agent| step "Setup testing local file sources" a_testdir = agent.tmpdir('local_source_file_test') source = "#{a_testdir}/source_mod/files/source" target = "#{a_testdir}/target" on agent, "mkdir -p #{File.dirname(source)}" create_remote_file agent, source, 'Yay, this is the local file.' step "Using a local file path" apply_manifest_on agent, "file { '#{target}': source => '#{source}', ensure => present }" on agent, "cat #{target}" do assert_match(/Yay, this is the local file./, stdout, "FIRST: File contents not matched on #{agent}") end step "Using a puppet:/// URI with puppet apply" on agent, "rm -rf #{target}" manifest = %{"file { '#{target}': source => 'puppet:///modules/source_mod/source', ensure => 'present' }"} on agent, puppet( %{apply --modulepath=#{a_testdir} -e #{manifest}}) on agent, "cat #{target}" do assert_match(/Yay, this is the local file./, stdout, "FIRST: File contents not matched on #{agent}") end end diff --git a/acceptance/tests/security/cve-2013-3567_yaml_deserialization_again.rb b/acceptance/tests/security/cve-2013-3567_yaml_deserialization_again.rb index bb216b93b..0526bd150 100644 --- a/acceptance/tests/security/cve-2013-3567_yaml_deserialization_again.rb +++ b/acceptance/tests/security/cve-2013-3567_yaml_deserialization_again.rb @@ -1,31 +1,40 @@ test_name "CVE-2013-3567 Arbitrary YAML Deserialization" reportdir = master.tmpdir('yaml_deserialization') dangerous_yaml = "--- !ruby/object:Puppet::Transaction::Report { metrics: { resources: !ruby/object:ERB { src: 'exit 0' } }, logs: [], resource_statuses: [], host: '$(puppet master --configprint certname)' }" submit_bad_yaml = [ "curl -k -X PUT", "--cacert $(puppet master --configprint cacert)", "--cert $(puppet master --configprint hostcert)", "--key $(puppet master --configprint hostprivkey)", "-H 'Content-Type: text/yaml'", "-d \"#{dangerous_yaml}\"", "\"https://#{master}:8140/production/report/$(puppet master --configprint certname)\"" ].join(' ') master_opts = { 'master' => { 'reportdir' => reportdir, 'reports' => 'store', } } +# In PE, the master is running as non-root. We need to set the +# reportdir permissions correctly for it. +on master, "chmod 750 #{reportdir}" +if options.is_pe? + on master, "chown pe-puppet:pe-puppet #{reportdir}" +elsif master.is_using_passenger? + on master, "chown puppet:puppet #{reportdir}" +end + with_puppet_running_on(master, master_opts) do on master, submit_bad_yaml on master, "cat #{reportdir}/$(puppet master --configprint certname)/*" do assert_no_match(/ERB/, stdout, "Improperly propagated ERB object from input into puppet code") end end on master, "rm -rf #{reportdir}" diff --git a/acceptance/tests/ssl/autosign_command.rb b/acceptance/tests/ssl/autosign_command.rb index 54c6a969e..b161c1be5 100644 --- a/acceptance/tests/ssl/autosign_command.rb +++ b/acceptance/tests/ssl/autosign_command.rb @@ -1,129 +1,128 @@ require 'puppet/acceptance/common_utils' extend Puppet::Acceptance::CAUtils test_name "autosign command and csr attributes behavior (#7243,#7244)" do def assert_key_generated(name) assert_match(/Creating a new SSL key for #{name}/, stdout, "Expected agent to create a new SSL key for autosigning") end testdirs = {} step "generate tmp dirs on all hosts" do hosts.each { |host| testdirs[host] = host.tmpdir('autosign_command') } end teardown do step "Remove autosign configuration" testdirs.each do |host,testdir| on(host, host_command("rm -rf '#{testdir}'") ) end reset_agent_ssl end hostname = master.execute('facter hostname') fqdn = master.execute('facter fqdn') reset_agent_ssl(false) step "Step 1: ensure autosign command can approve CSRs" do master_opts = { 'master' => { 'autosign' => '/bin/true', 'dns_alt_names' => "puppet,#{hostname},#{fqdn}", } } with_puppet_running_on(master, master_opts) do agents.each do |agent| next if agent == master on(agent, puppet("agent --test --server #{master} --waitforcert 0 --certname #{agent}-autosign")) assert_key_generated(agent) assert_match(/Caching certificate for #{agent}/, stdout, "Expected certificate to be autosigned") end end end reset_agent_ssl(false) step "Step 2: ensure autosign command can reject CSRs" do master_opts = { 'master' => { 'autosign' => '/bin/false', 'dns_alt_names' => "puppet,#{hostname},#{fqdn}", } } with_puppet_running_on(master, master_opts) do agents.each do |agent| next if agent == master on(agent, puppet("agent --test --server #{master} --waitforcert 0 --certname #{agent}-reject"), :acceptable_exit_codes => [1]) assert_key_generated(agent) assert_match(/no certificate found/, stdout, "Expected certificate to not be autosigned") end end end autosign_inspect_csr_path = "#{testdirs[master]}/autosign_inspect_csr.rb" step "Step 3: setup an autosign command that inspects CSR attributes" do autosign_inspect_csr = <<-END #!/usr/bin/env ruby require 'openssl' def unwrap_attr(attr) set = attr.value str = set.value.first str.value end csr_text = STDIN.read csr = OpenSSL::X509::Request.new(csr_text) passphrase = csr.attributes.find { |a| a.oid == '1.3.6.1.4.1.34380.2.1' } # And here we jump hoops to unwrap ASN1's Attr Set Str if unwrap_attr(passphrase) == 'my passphrase' exit 0 end exit 1 END create_remote_file(master, autosign_inspect_csr_path, autosign_inspect_csr) on master, "chmod 777 #{testdirs[master]}" on master, "chmod 777 #{autosign_inspect_csr_path}" end agent_csr_attributes = {} step "Step 4: create attributes for inclusion on csr on agents" do csr_attributes = <<-END custom_attributes: 1.3.6.1.4.1.34380.2.0: hostname.domain.com 1.3.6.1.4.1.34380.2.1: my passphrase 1.3.6.1.4.1.34380.2.2: # system IPs in hex - 0xC0A80001 # 192.168.0.1 - 0xC0A80101 # 192.168.1.1 END agents.each do |agent| agent_csr_attributes[agent] = "#{testdirs[agent]}/csr_attributes.yaml" create_remote_file(agent, agent_csr_attributes[agent], csr_attributes) end end reset_agent_ssl(false) step "Step 5: successfully obtain a cert" do master_opts = { 'master' => { 'autosign' => autosign_inspect_csr_path, 'dns_alt_names' => "puppet,#{hostname},#{fqdn}", }, - :__commandline_args__ => '--debug --trace', } with_puppet_running_on(master, master_opts) do agents.each do |agent| next if agent == master step "attempting to obtain cert for #{agent}" on(agent, puppet("agent --test --server #{master} --waitforcert 0 --csr_attributes '#{agent_csr_attributes[agent]}' --certname #{agent}-attrs"), :acceptable_exit_codes => [0]) assert_key_generated(agent) end end end end