diff --git a/config/settings.d/puppetca.yml.example b/config/settings.d/puppetca.yml.example index b19ddf56d1..32d68ba721 100644 --- a/config/settings.d/puppetca.yml.example +++ b/config/settings.d/puppetca.yml.example @@ -2,7 +2,9 @@ # Can be true, false, or http/https to enable just one of the protocols :enabled: false +# valid providers: +# - puppetca_hostverify (verify CSRs based on hostnames) +#:use_provider: puppetca_hostverify #:ssldir: /var/lib/puppet/ssl -#:autosignfile: /etc/puppet/autosign.conf #:puppetca_use_sudo: true #:sudo_command: /usr/bin/sudo diff --git a/config/settings.d/puppetca_hostverify.yml.example b/config/settings.d/puppetca_hostverify.yml.example new file mode 100644 index 0000000000..89d317ed80 --- /dev/null +++ b/config/settings.d/puppetca_hostverify.yml.example @@ -0,0 +1,6 @@ +--- +# +# Configuration of the PuppetCA hostverify provider +# + +#:autosignfile: /etc/puppet/autosign.conf diff --git a/lib/smart_proxy_main.rb b/lib/smart_proxy_main.rb index 66846237b8..d8b834b633 100644 --- a/lib/smart_proxy_main.rb +++ b/lib/smart_proxy_main.rb @@ -67,6 +67,7 @@ module Proxy require 'dhcp_native_ms/dhcp_native_ms' require 'dhcp_libvirt/dhcp_libvirt' require 'puppetca/puppetca' + require 'puppetca_hostverify/puppetca_hostverify' require 'puppet_proxy/puppet' require 'puppet_proxy_customrun/puppet_proxy_customrun' require 'puppet_proxy_legacy/puppet_proxy_legacy' diff --git a/modules/puppetca/dependency_injection.rb b/modules/puppetca/dependency_injection.rb new file mode 100644 index 0000000000..442b7c0f1f --- /dev/null +++ b/modules/puppetca/dependency_injection.rb @@ -0,0 +1,8 @@ +module Proxy::PuppetCa + module DependencyInjection + include Proxy::DependencyInjection::Accessors + def container_instance + @container_instance ||= ::Proxy::Plugins.instance.find {|p| p[:name] == :puppetca }[:di_container] + end + end +end diff --git a/modules/puppetca/plugin_configuration.rb b/modules/puppetca/plugin_configuration.rb new file mode 100644 index 0000000000..2e7b7856f1 --- /dev/null +++ b/modules/puppetca/plugin_configuration.rb @@ -0,0 +1,13 @@ +module ::Proxy::PuppetCa + class PluginConfiguration + def load_classes + require 'puppetca/puppetca_certmanager' + require 'puppetca/dependency_injection' + require 'puppetca/puppetca_api' + end + + def load_dependency_injection_wirings(container_instance, settings) + container_instance.dependency :cert_manager, lambda { ::Proxy::PuppetCa::Certmanager.new } + end + end +end diff --git a/modules/puppetca/puppetca.rb b/modules/puppetca/puppetca.rb index ebe5297f64..5c6c305cbc 100644 --- a/modules/puppetca/puppetca.rb +++ b/modules/puppetca/puppetca.rb @@ -1,3 +1,2 @@ +require 'puppetca/plugin_configuration' require 'puppetca/puppetca_plugin' - -module Proxy::PuppetCa; end diff --git a/modules/puppetca/puppetca_api.rb b/modules/puppetca/puppetca_api.rb index 2da9e631a8..a34b573417 100644 --- a/modules/puppetca/puppetca_api.rb +++ b/modules/puppetca/puppetca_api.rb @@ -2,6 +2,10 @@ module Proxy::PuppetCa class Api < ::Sinatra::Base + extend Proxy::PuppetCa::DependencyInjection + inject_attr :cert_manager, :cert_manager + inject_attr :autosigner, :autosigner + helpers ::Proxy::Helpers authorize_with_trusted_hosts authorize_with_ssl_client @@ -9,7 +13,7 @@ class Api < ::Sinatra::Base get "/?" do content_type :json begin - Proxy::PuppetCa.list.to_json + cert_manager.list.to_json rescue => e log_halt 406, "Failed to list certificates: #{e}" end @@ -18,7 +22,7 @@ class Api < ::Sinatra::Base get "/autosign" do content_type :json begin - Proxy::PuppetCa.autosign_list.to_json + autosigner.autosign_list.to_json rescue => e log_halt 406, "Failed to list autosign entries: #{e}" end @@ -28,7 +32,7 @@ class Api < ::Sinatra::Base content_type :json certname = params[:certname] begin - Proxy::PuppetCa.autosign(certname) + autosigner.autosign(certname) rescue => e log_halt 406, "Failed to enable autosign for #{certname}: #{e}" end @@ -38,7 +42,7 @@ class Api < ::Sinatra::Base content_type :json certname = params[:certname] begin - Proxy::PuppetCa.disable(certname) + autosigner.disable(certname) rescue Proxy::PuppetCa::NotPresent => e log_halt 404, e.to_s rescue => e @@ -50,7 +54,7 @@ class Api < ::Sinatra::Base content_type :json certname = params[:certname] begin - Proxy::PuppetCa.sign(certname) + cert_manager.sign(certname) rescue => e log_halt 406, "Failed to sign certificate(s) for #{certname}: #{e}" end @@ -60,7 +64,7 @@ class Api < ::Sinatra::Base begin content_type :json certname = params[:certname] - Proxy::PuppetCa.clean(certname) + cert_manager.clean(certname) rescue Proxy::PuppetCa::NotPresent => e log_halt 404, e.to_s rescue => e diff --git a/modules/puppetca/puppetca_main.rb b/modules/puppetca/puppetca_certmanager.rb similarity index 75% rename from modules/puppetca/puppetca_main.rb rename to modules/puppetca/puppetca_certmanager.rb index 401d454248..5f804a13c0 100644 --- a/modules/puppetca/puppetca_main.rb +++ b/modules/puppetca/puppetca_certmanager.rb @@ -2,12 +2,13 @@ require 'set' module Proxy::PuppetCa - extend ::Proxy::Log - extend ::Proxy::Util class NotPresent < RuntimeError; end - class << self + class Certmanager + include ::Proxy::Log + include ::Proxy::Util + def sign certname puppetca("sign", certname) end @@ -16,53 +17,6 @@ def clean certname puppetca("clean", certname) end - #remove certname from autosign if exists - def disable certname - raise "No such file #{autosign_file}" unless File.exist?(autosign_file) - - found = false - entries = File.readlines(autosign_file).collect do |l| - if l.chomp != certname - l - else - found = true - nil - end - end.uniq.compact - if found - open(autosign_file, File::TRUNC|File::RDWR) do |autosign| - autosign.write entries.join - end - logger.debug "Removed #{certname} from autosign" - else - logger.debug "Attempt to remove nonexistent client autosign for #{certname}" - raise NotPresent, "Attempt to remove nonexistent client autosign for #{certname}" - end - end - - # add certname to puppet autosign file - # parameter is certname to use - def autosign certname - FileUtils.touch(autosign_file) unless File.exist?(autosign_file) - - open(autosign_file, File::RDWR) do |autosign| - # Check that we don't have that host already - found = autosign.readlines.find { |line| line.chomp == certname } - autosign.puts certname unless found - end - logger.debug "Added #{certname} to autosign" - end - - # list of hosts which are now allowed to be installed via autosign - def autosign_list - return [] unless File.exist?(autosign_file) - File.read(autosign_file).split("\n").reject do |v| - v =~ /^\s*#.*|^$/ ## Remove comments and empty lines - end.map do |v| - v.chomp ## Strip trailing spaces - end - end - # list of all certificates and their state/fingerprint def list find_puppetca @@ -129,10 +83,6 @@ def ssldir Proxy::PuppetCa::Plugin.settings.ssldir end - def autosign_file - Proxy::PuppetCa::Plugin.settings.autosignfile - end - # parse the puppetca --list output def certificate str case str diff --git a/modules/puppetca/puppetca_plugin.rb b/modules/puppetca/puppetca_plugin.rb index 331960f303..94787f6506 100644 --- a/modules/puppetca/puppetca_plugin.rb +++ b/modules/puppetca/puppetca_plugin.rb @@ -3,8 +3,13 @@ class Plugin < ::Proxy::Plugin http_rackup_path File.expand_path("http_config.ru", File.expand_path("../", __FILE__)) https_rackup_path File.expand_path("http_config.ru", File.expand_path("../", __FILE__)) - default_settings :ssldir => '/var/lib/puppet/ssl', :autosignfile => '/etc/puppet/autosign.conf' + default_settings :ssldir => '/var/lib/puppet/ssl' + uses_provider + default_settings :use_provider => 'puppetca_hostverify' + + load_classes ::Proxy::PuppetCa::PluginConfiguration + load_dependency_injection_wirings ::Proxy::PuppetCa::PluginConfiguration plugin :puppetca, ::Proxy::VERSION end end diff --git a/modules/puppetca_hostverify/plugin_configuration.rb b/modules/puppetca_hostverify/plugin_configuration.rb new file mode 100644 index 0000000000..d6ba5257f5 --- /dev/null +++ b/modules/puppetca_hostverify/plugin_configuration.rb @@ -0,0 +1,12 @@ +module ::Proxy::PuppetCa::Hostverify + class PluginConfiguration + def load_classes + require 'puppetca_hostverify/puppetca_hostverify_autosigner' + end + + def load_dependency_injection_wirings(container_instance, settings) + container_instance.dependency :autosigner, lambda { ::Proxy::PuppetCa::Hostverify::Autosigner.new } + end + end +end + diff --git a/modules/puppetca_hostverify/puppetca_hostverify.rb b/modules/puppetca_hostverify/puppetca_hostverify.rb new file mode 100644 index 0000000000..b8c9627a91 --- /dev/null +++ b/modules/puppetca_hostverify/puppetca_hostverify.rb @@ -0,0 +1,2 @@ +require 'puppetca_hostverify/plugin_configuration' +require 'puppetca_hostverify/puppetca_hostverify_plugin' diff --git a/modules/puppetca_hostverify/puppetca_hostverify_autosigner.rb b/modules/puppetca_hostverify/puppetca_hostverify_autosigner.rb new file mode 100644 index 0000000000..a96286ed9a --- /dev/null +++ b/modules/puppetca_hostverify/puppetca_hostverify_autosigner.rb @@ -0,0 +1,57 @@ +module ::Proxy::PuppetCa::Hostverify + class Autosigner + include ::Proxy::Log + include ::Proxy::Util + + def autosign_file + Proxy::PuppetCa::Hostverify::Plugin.settings.autosignfile + end + + #remove certname from autosign if exists + def disable certname + raise "No such file #{autosign_file}" unless File.exist?(autosign_file) + + found = false + entries = File.readlines(autosign_file).collect do |l| + if l.chomp != certname + l + else + found = true + nil + end + end.uniq.compact + if found + open(autosign_file, File::TRUNC|File::RDWR) do |autosign| + autosign.write entries.join + end + logger.debug "Removed #{certname} from autosign" + else + logger.debug "Attempt to remove nonexistent client autosign for #{certname}" + raise ::Proxy::PuppetCa::NotPresent, "Attempt to remove nonexistent client autosign for #{certname}" + end + end + + # add certname to puppet autosign file + # parameter is certname to use + def autosign certname + FileUtils.touch(autosign_file) unless File.exist?(autosign_file) + + open(autosign_file, File::RDWR) do |autosign| + # Check that we don't have that host already + found = autosign.readlines.find { |line| line.chomp == certname } + autosign.puts certname unless found + end + logger.debug "Added #{certname} to autosign" + end + + # list of hosts which are now allowed to be installed via autosign + def autosign_list + return [] unless File.exist?(autosign_file) + File.read(autosign_file).split("\n").reject do |v| + v =~ /^\s*#.*|^$/ ## Remove comments and empty lines + end.map do |v| + v.chomp ## Strip trailing spaces + end + end + end +end diff --git a/modules/puppetca_hostverify/puppetca_hostverify_plugin.rb b/modules/puppetca_hostverify/puppetca_hostverify_plugin.rb new file mode 100644 index 0000000000..a7d6064aa1 --- /dev/null +++ b/modules/puppetca_hostverify/puppetca_hostverify_plugin.rb @@ -0,0 +1,11 @@ +module ::Proxy::PuppetCa::Hostverify + class Plugin < ::Proxy::Provider + plugin :puppetca_hostverify, ::Proxy::VERSION + + requires :puppetca, ::Proxy::VERSION + default_settings :autosignfile => '/etc/puppet/autosign.conf' + + load_classes ::Proxy::PuppetCa::Hostverify::PluginConfiguration + load_dependency_injection_wirings ::Proxy::PuppetCa::Hostverify::PluginConfiguration + end +end diff --git a/test/puppetca/puppetca_test.rb b/test/puppetca/puppetca_certmanager_test.rb similarity index 58% rename from test/puppetca/puppetca_test.rb rename to test/puppetca/puppetca_certmanager_test.rb index 393dd5cc37..7d42aa5547 100644 --- a/test/puppetca/puppetca_test.rb +++ b/test/puppetca/puppetca_certmanager_test.rb @@ -3,71 +3,11 @@ require 'fileutils' require 'puppetca/puppetca' -require 'puppetca/puppetca_main' +require 'puppetca/puppetca_certmanager' -class ProxyTest < Test::Unit::TestCase - ## Helper for autosign files. - def create_temp_autosign_file - file = Tempfile.new('autosign_test') - begin - ## Setup - FileUtils.cp './test/fixtures/autosign.conf', file.path - Proxy::PuppetCa.stubs(:autosign_file).returns(file.path) - rescue - file.close - file.unlink - file = nil - end - file - end - - def test_should_list_autosign_entries - Proxy::PuppetCa.stubs(:autosign_file).returns('./test/fixtures/autosign.conf') - assert_equal Proxy::PuppetCa.autosign_list, ['foo.example.com', '*.bar.example.com'] - end - - def test_should_add_autosign_entry - file = create_temp_autosign_file - content = [] - begin - ## Execute - Proxy::PuppetCa.autosign 'foobar.example.com' - ## Read output - content = file.read.split("\n") - ensure - file.close - file.unlink - end - assert_true content.include?('foobar.example.com') - end - - def test_should_not_duplicate_autosign_entry - file = create_temp_autosign_file - begin - before_content = file.read - file.seek(0) - ## Execute - Proxy::PuppetCa.autosign 'foo.example.com' - ## Read output - after_content = file.read - ensure - file.close - file.unlink - end - assert_equal before_content, after_content - end - - def test_should_remove_autosign_entry - file = create_temp_autosign_file - begin - Proxy::PuppetCa.disable 'foo.example.com' - content = file.read - ensure - file.close - file.unlink - end - assert_false content.split("\n").include?('foo.example.com') - assert_true content.end_with?("\n") +class PuppetCaCertmanagerTest < Test::Unit::TestCase + def setup + @cert_manager = Proxy::PuppetCa::Certmanager.new end def test_which_should_return_a_binary_path @@ -76,7 +16,7 @@ def test_which_should_return_a_binary_path FileTest.stubs(:file?).with("#{p}/ls").returns(r) FileTest.stubs(:executable?).with("#{p}/ls").returns(r) end - assert_equal '/bin/ls', Proxy::PuppetCa.which('ls') + assert_equal '/bin/ls', @cert_manager.which('ls') end INVENTORY_CONTENTS =< {:serial => 4, :not_before => "2017-01-11T15:04:35UTC", :not_after => "2022-01-11T15:04:35UTC"}, "active.my.domain" => {:serial => 3, :not_before => "2015-09-02T08:34:59UTC", :not_after => "2020-09-01T08:34:59UTC"}, "second-active.my.domain" => {:serial => 5, :not_before => "2017-01-14T12:01:22UTC", :not_after => "2022-01-14T12:01:22UTC"}}, - ::Proxy::PuppetCa.parse_inventory(INVENTORY_CONTENTS)) + @cert_manager.parse_inventory(INVENTORY_CONTENTS)) end CRL_CONTENTS =<{:serial=>4, :not_before=>"2017-01-11T15:04:35UTC", :not_after=>"2022-01-11T15:04:35UTC", :state=>"revoked"}, "active.my.domain"=>{:serial=>3, :not_before=>"2015-09-02T08:34:59UTC", :not_after=>"2020-09-01T08:34:59UTC"}, "second-active.my.domain" => {:serial => 5, :not_before => "2017-01-14T12:01:22UTC", :not_after => "2022-01-14T12:01:22UTC"}}, - ::Proxy::PuppetCa.compute_ca_inventory(INVENTORY_CONTENTS, CRL_CONTENTS)) + @cert_manager.compute_ca_inventory(INVENTORY_CONTENTS, CRL_CONTENTS)) end def test_should_clean_host #TODO - assert_respond_to Proxy::PuppetCa, :clean - end - - def test_should_disable_host - #TODO - assert_respond_to Proxy::PuppetCa, :disable + assert_respond_to @cert_manager, :clean end def test_should_sign_host #TODO - assert_respond_to Proxy::PuppetCa, :sign + assert_respond_to @cert_manager, :sign end end diff --git a/test/puppetca/puppetca_config_test.rb b/test/puppetca/puppetca_config_test.rb index 2d05f1632f..b4189b0f0c 100644 --- a/test/puppetca/puppetca_config_test.rb +++ b/test/puppetca/puppetca_config_test.rb @@ -1,10 +1,10 @@ require 'test_helper' -require 'puppetca/puppetca_plugin' +require 'puppetca/puppetca' class PuppetCAConfigTest < Test::Unit::TestCase def test_omitted_settings_have_default_values Proxy::PuppetCa::Plugin.load_test_settings({}) assert_equal '/var/lib/puppet/ssl', Proxy::PuppetCa::Plugin.settings.ssldir - assert_equal '/etc/puppet/autosign.conf', Proxy::PuppetCa::Plugin.settings.autosignfile + assert_equal 'puppetca_hostverify', Proxy::PuppetCa::Plugin.settings.use_provider end end diff --git a/test/puppetca_hostverify/puppetca_hostverify_autosigner_test.rb b/test/puppetca_hostverify/puppetca_hostverify_autosigner_test.rb new file mode 100644 index 0000000000..0ac02452a4 --- /dev/null +++ b/test/puppetca_hostverify/puppetca_hostverify_autosigner_test.rb @@ -0,0 +1,68 @@ +require 'test_helper' +require 'tempfile' +require 'fileutils' + +require 'puppetca/puppetca' +require 'puppetca_hostverify/puppetca_hostverify' +require 'puppetca_hostverify/puppetca_hostverify_autosigner' + +class PuppetCaHostverifyAutosignerTest < Test::Unit::TestCase + def setup + @file = Tempfile.new('autosign_test') + begin + ## Setup + FileUtils.cp './test/fixtures/autosign.conf', @file.path + rescue + @file.close + @file.unlink + @file = nil + end + @autosigner = Proxy::PuppetCa::Hostverify::Autosigner.new + @autosigner.stubs(:autosign_file).returns(@file.path) + end + + def test_should_list_autosign_entries + assert_equal @autosigner.autosign_list, ['foo.example.com', '*.bar.example.com'] + end + + def test_should_add_autosign_entry + content = [] + begin + ## Execute + @autosigner.autosign 'foobar.example.com' + ## Read output + content = @file.read.split("\n") + ensure + @file.close + @file.unlink + end + assert_true content.include?('foobar.example.com') + end + + def test_should_not_duplicate_autosign_entry + begin + before_content = @file.read + @file.seek(0) + ## Execute + @autosigner.autosign 'foo.example.com' + ## Read output + after_content = @file.read + ensure + @file.close + @file.unlink + end + assert_equal before_content, after_content + end + + def test_should_remove_autosign_entry + begin + @autosigner.disable 'foo.example.com' + content = @file.read + ensure + @file.close + @file.unlink + end + assert_false content.split("\n").include?('foo.example.com') + assert_true content.end_with?("\n") + end +end diff --git a/test/puppetca_hostverify/puppetca_hostverify_config_test.rb b/test/puppetca_hostverify/puppetca_hostverify_config_test.rb new file mode 100644 index 0000000000..69b223e428 --- /dev/null +++ b/test/puppetca_hostverify/puppetca_hostverify_config_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' +require 'puppetca/puppetca' +require 'puppetca_hostverify/puppetca_hostverify' +require 'puppetca_hostverify/puppetca_hostverify_plugin' + +class PuppetCAHostverifyConfigTest < Test::Unit::TestCase + def test_omitted_settings_have_default_values + Proxy::PuppetCa::Hostverify::Plugin.load_test_settings({}) + assert_equal '/etc/puppet/autosign.conf', Proxy::PuppetCa::Hostverify::Plugin.settings.autosignfile + end +end