Revision e3256834
Added by Julian Todt almost 6 years ago
config/settings.d/puppetca.yml.example | ||
---|---|---|
# Can be true, false, or http/https to enable just one of the protocols
|
||
:enabled: false
|
||
|
||
# valid providers:
|
||
# - puppetca_hostname_whitelisting (verify CSRs based on a hostname whitelist)
|
||
#:use_provider: puppetca_hostname_whitelisting
|
||
#:ssldir: /var/lib/puppet/ssl
|
||
#:autosignfile: /etc/puppet/autosign.conf
|
||
#:puppetca_use_sudo: true
|
||
#:sudo_command: /usr/bin/sudo
|
config/settings.d/puppetca_hostname_whitelisting.yml.example | ||
---|---|---|
---
|
||
#
|
||
# Configuration of the PuppetCA hostname_whitelisting provider
|
||
#
|
||
|
||
#:autosignfile: /etc/puppet/autosign.conf
|
extra/migrations/2018062000000_migrate_puppetca_settings.rb | ||
---|---|---|
require 'yaml'
|
||
|
||
class MigratePuppetCaSettings < ::Proxy::Migration
|
||
def migrate
|
||
copy_original_configuration_except(path('settings.d', 'puppetca.yml'),
|
||
path('settings.d', 'puppetca_hostname_whitelisting.yml.example'))
|
||
|
||
module_settings = YAML.load_file(path(src_dir, 'settings.d', 'puppetca.yml'))
|
||
provider_settings = YAML.load_file(path(src_dir, 'settings.d', 'puppetca_hostname_whitelisting.yml'))
|
||
|
||
write_yaml(path(dst_dir, 'settings.d', 'puppetca_hostname_whitelisting.yml'),
|
||
transform_provider_yaml(module_settings, provider_settings))
|
||
write_yaml(path(dst_dir, 'settings.d', 'puppetca.yml'), transform_puppetca_yaml(module_settings))
|
||
end
|
||
|
||
def transform_puppetca_yaml(input)
|
||
input.delete(:autosignfile)
|
||
input[:use_provider] = 'puppetca_hostname_whitelisting'
|
||
input
|
||
end
|
||
|
||
def transform_provider_yaml(module_settings, provider_settings)
|
||
provider_settings = {} unless provider_settings.is_a? Hash
|
||
provider_settings[:autosignfile] = module_settings[:autosignfile]
|
||
provider_settings
|
||
end
|
||
|
||
def write_yaml(filepath, yaml)
|
||
File.open(filepath, 'w') do |f|
|
||
f.write(yaml.to_yaml)
|
||
end
|
||
end
|
||
end
|
lib/smart_proxy_main.rb | ||
---|---|---|
require 'dhcp_native_ms/dhcp_native_ms'
|
||
require 'dhcp_libvirt/dhcp_libvirt'
|
||
require 'puppetca/puppetca'
|
||
require 'puppetca_hostname_whitelisting/puppetca_hostname_whitelisting'
|
||
require 'puppet_proxy/puppet'
|
||
require 'puppet_proxy_customrun/puppet_proxy_customrun'
|
||
require 'puppet_proxy_legacy/puppet_proxy_legacy'
|
modules/puppetca/dependency_injection.rb | ||
---|---|---|
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
|
modules/puppetca/plugin_configuration.rb | ||
---|---|---|
module ::Proxy::PuppetCa
|
||
class PluginConfiguration
|
||
def load_classes
|
||
require 'puppetca/puppetca_puppet_cert'
|
||
require 'puppetca/dependency_injection'
|
||
require 'puppetca/puppetca_api'
|
||
end
|
||
|
||
def load_dependency_injection_wirings(container_instance, settings)
|
||
container_instance.dependency :puppet_cert, lambda { ::Proxy::PuppetCa::PuppetCert.new }
|
||
end
|
||
end
|
||
end
|
modules/puppetca/puppetca.rb | ||
---|---|---|
require 'puppetca/plugin_configuration'
|
||
require 'puppetca/puppetca_plugin'
|
||
|
||
module Proxy::PuppetCa; end
|
modules/puppetca/puppetca_api.rb | ||
---|---|---|
require 'puppetca/puppetca_main'
|
||
|
||
module Proxy::PuppetCa
|
||
class Api < ::Sinatra::Base
|
||
extend Proxy::PuppetCa::DependencyInjection
|
||
inject_attr :puppet_cert, :puppet_cert
|
||
inject_attr :autosigner, :autosigner
|
||
|
||
helpers ::Proxy::Helpers
|
||
authorize_with_trusted_hosts
|
||
authorize_with_ssl_client
|
||
... | ... | |
get "/?" do
|
||
content_type :json
|
||
begin
|
||
Proxy::PuppetCa.list.to_json
|
||
puppet_cert.list.to_json
|
||
rescue => e
|
||
log_halt 406, "Failed to list certificates: #{e}"
|
||
end
|
||
... | ... | |
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
|
||
... | ... | |
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
|
||
... | ... | |
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
|
||
... | ... | |
content_type :json
|
||
certname = params[:certname]
|
||
begin
|
||
Proxy::PuppetCa.sign(certname)
|
||
puppet_cert.sign(certname)
|
||
rescue => e
|
||
log_halt 406, "Failed to sign certificate(s) for #{certname}: #{e}"
|
||
end
|
||
... | ... | |
begin
|
||
content_type :json
|
||
certname = params[:certname]
|
||
Proxy::PuppetCa.clean(certname)
|
||
puppet_cert.clean(certname)
|
||
rescue Proxy::PuppetCa::NotPresent => e
|
||
log_halt 404, e.to_s
|
||
rescue => e
|
modules/puppetca/puppetca_main.rb | ||
---|---|---|
require 'openssl'
|
||
require 'set'
|
||
|
||
module Proxy::PuppetCa
|
||
extend ::Proxy::Log
|
||
extend ::Proxy::Util
|
||
|
||
class NotPresent < RuntimeError; end
|
||
|
||
class << self
|
||
def sign certname
|
||
puppetca("sign", certname)
|
||
end
|
||
|
||
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
|
||
command = "#{@sudo} #{@puppetca} --list --all"
|
||
logger.debug "Executing #{command}"
|
||
response = `#{command}`
|
||
unless $? == 0
|
||
logger.warn "Failed to run puppetca: #{response}"
|
||
raise "Execution of puppetca failed, check log files"
|
||
end
|
||
|
||
hash = {}
|
||
response.split("\n").each do |line|
|
||
hash.merge! certificate(line) rescue logger.warn("Failed to parse line: #{line}")
|
||
end
|
||
# merge all data into one
|
||
# note that this ignores certificates which were revoked multiple times, displaying only the last
|
||
# revocation state
|
||
# additionally, we don't merge revocation info if the host has a pending certificate request
|
||
hash.merge(ca_inventory) {|key, h1, h2| h1[:state] == "pending" ? h1 : h1.merge(h2)}
|
||
end
|
||
|
||
def pending
|
||
all.delete_if {|k,v| v[:state] != "pending"}
|
||
end
|
||
|
||
# helper to find puppetca and sudo binaries
|
||
# checks if our CA really exists
|
||
def find_puppetca
|
||
ssl_dir = Pathname.new ssldir
|
||
unless (ssl_dir + "ca").directory?
|
||
logger.warn "PuppetCA: SSL/CA unavailable on this machine: ssldir not found at #{ssl_dir}"
|
||
raise "SSL/CA unavailable on this machine"
|
||
end
|
||
|
||
default_path = ["/opt/puppet/bin", "/opt/puppet/sbin", "/opt/puppetlabs/bin"]
|
||
@puppetca = which("puppetca", default_path) || which("puppet", default_path)
|
||
|
||
unless File.exist?(@puppetca.to_s)
|
||
logger.warn "unable to find puppetca binary"
|
||
raise "unable to find puppetca"
|
||
end
|
||
# Append cert to the puppet command if we are not using the old puppetca command
|
||
logger.debug "Found puppetca at #{@puppetca}"
|
||
@puppetca << " cert" unless @puppetca.include?("puppetca")
|
||
|
||
# Tell puppetca to use the ssl dir that Foreman has been told to use
|
||
@puppetca << " --ssldir #{ssl_dir}"
|
||
|
||
if to_bool(::Proxy::PuppetCa::Plugin.settings.puppetca_use_sudo, true)
|
||
@sudo = ::Proxy::PuppetCa::Plugin.settings.sudo_command || which("sudo")
|
||
unless File.exist?(@sudo)
|
||
logger.warn "unable to find sudo binary"
|
||
raise "Unable to find sudo"
|
||
end
|
||
logger.debug "Found sudo at #{@sudo}"
|
||
@sudo = "#{@sudo} -S"
|
||
else
|
||
@sudo = ""
|
||
end
|
||
end
|
||
|
||
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
|
||
when /(\+|\-)\s+["]{0,1}(.*\w)["]{0,1}\s+\((\S+)\)/
|
||
state = $1 == "-" ? "revoked" : "valid"
|
||
return { $2.strip => { :state => state, :fingerprint => $3 } }
|
||
when /\s*["]{0,1}(.*\w)["]{0,1}\s+\((\S+)\)/
|
||
return { $1.strip => { :state => "pending", :fingerprint => $2 } }
|
||
else
|
||
return {}
|
||
end
|
||
end
|
||
|
||
def ca_inventory
|
||
inventory = Pathname.new(ssldir).join("ca","inventory.txt")
|
||
raise "Unable to find CA inventory file at #{inventory}" unless File.exist?(inventory)
|
||
crl_path = Pathname.new(ssldir).join("ca","ca_crl.pem")
|
||
raise "Unable to find CRL" unless File.exist?(crl_path)
|
||
compute_ca_inventory(File.read(inventory), File.read(crl_path))
|
||
end
|
||
|
||
def compute_ca_inventory(inventory_contents, crl_cert_contents)
|
||
inventory = parse_inventory(inventory_contents)
|
||
crl = revoked_serials(crl_cert_contents)
|
||
inventory.each do |_, values|
|
||
values[:state] = "revoked" if crl.include?(values[:serial])
|
||
end
|
||
inventory
|
||
end
|
||
|
||
def parse_inventory(inventory_contents)
|
||
to_return = {}
|
||
inventory_contents.each_line do |cert|
|
||
# 0x005a 2011-04-16T07:12:46GMT 2016-04-14T07:12:46GMT /CN=uuid
|
||
# 0x005c 2017-01-07T11:23:20GMT 2022-01-17T11:23:20GMT /CN=name.mcollective/OU=mcollective
|
||
if cert =~ /(0(x|X)(\d|[a-f]|[A-F])+)\s+(\d+\S+)\s+(\d+\S+)\s+\/CN=([^\s\/]+)/
|
||
to_return[$6] = {:serial => $1.to_i(16), :not_before => $4, :not_after => $5}
|
||
end
|
||
end
|
||
to_return
|
||
end
|
||
|
||
def revoked_serials(crl_cert_contents)
|
||
Set.new(OpenSSL::X509::CRL.new(crl_cert_contents).revoked.collect {|r| r.serial.to_i})
|
||
end
|
||
|
||
def puppetca mode, certname
|
||
raise "Invalid mode #{mode}" unless mode =~ /^(clean|sign)$/
|
||
find_puppetca
|
||
certname.downcase!
|
||
command = "#{@sudo} #{@puppetca} --#{mode} #{certname}"
|
||
logger.debug "Executing #{command}"
|
||
response = `#{command} 2>&1`
|
||
if $?.success?
|
||
logger.debug "#{mode}ed puppet certificate for #{certname}"
|
||
elsif response =~ /Could not find client certificate/ || $?.exitstatus == 24
|
||
logger.debug "Attempt to remove nonexistent client certificate for #{certname}"
|
||
raise NotPresent, "Attempt to remove nonexistent client certificate for #{certname}"
|
||
else
|
||
logger.warn "Failed to run puppetca: #{response}"
|
||
raise "Execution of puppetca failed, check log files"
|
||
end
|
||
$?.success?
|
||
end
|
||
end
|
||
end
|
modules/puppetca/puppetca_plugin.rb | ||
---|---|---|
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_hostname_whitelisting'
|
||
|
||
load_classes ::Proxy::PuppetCa::PluginConfiguration
|
||
load_dependency_injection_wirings ::Proxy::PuppetCa::PluginConfiguration
|
||
plugin :puppetca, ::Proxy::VERSION
|
||
end
|
||
end
|
modules/puppetca/puppetca_puppet_cert.rb | ||
---|---|---|
require 'openssl'
|
||
require 'set'
|
||
|
||
module Proxy::PuppetCa
|
||
|
||
class NotPresent < RuntimeError; end
|
||
|
||
class PuppetCert
|
||
include ::Proxy::Log
|
||
include ::Proxy::Util
|
||
|
||
def sign certname
|
||
puppetca("sign", certname)
|
||
end
|
||
|
||
def clean certname
|
||
puppetca("clean", certname)
|
||
end
|
||
|
||
# list of all certificates and their state/fingerprint
|
||
def list
|
||
find_puppetca
|
||
command = "#{@sudo} #{@puppetca} --list --all"
|
||
logger.debug "Executing #{command}"
|
||
response = `#{command}`
|
||
unless $? == 0
|
||
logger.warn "Failed to run puppetca: #{response}"
|
||
raise "Execution of puppetca failed, check log files"
|
||
end
|
||
|
||
hash = {}
|
||
response.split("\n").each do |line|
|
||
hash.merge! certificate(line) rescue logger.warn("Failed to parse line: #{line}")
|
||
end
|
||
# merge all data into one
|
||
# note that this ignores certificates which were revoked multiple times, displaying only the last
|
||
# revocation state
|
||
# additionally, we don't merge revocation info if the host has a pending certificate request
|
||
hash.merge(ca_inventory) {|key, h1, h2| h1[:state] == "pending" ? h1 : h1.merge(h2)}
|
||
end
|
||
|
||
def pending
|
||
all.delete_if {|k,v| v[:state] != "pending"}
|
||
end
|
||
|
||
# helper to find puppetca and sudo binaries
|
||
# checks if our CA really exists
|
||
def find_puppetca
|
||
ssl_dir = Pathname.new ssldir
|
||
unless (ssl_dir + "ca").directory?
|
||
logger.warn "PuppetCA: SSL/CA unavailable on this machine: ssldir not found at #{ssl_dir}"
|
||
raise "SSL/CA unavailable on this machine"
|
||
end
|
||
|
||
default_path = ["/opt/puppet/bin", "/opt/puppet/sbin", "/opt/puppetlabs/bin"]
|
||
@puppetca = which("puppetca", default_path) || which("puppet", default_path)
|
||
|
||
unless File.exist?(@puppetca.to_s)
|
||
logger.warn "unable to find puppetca binary"
|
||
raise "unable to find puppetca"
|
||
end
|
||
# Append cert to the puppet command if we are not using the old puppetca command
|
||
logger.debug "Found puppetca at #{@puppetca}"
|
||
@puppetca << " cert" unless @puppetca.include?("puppetca")
|
||
|
||
# Tell puppetca to use the ssl dir that Foreman has been told to use
|
||
@puppetca << " --ssldir #{ssl_dir}"
|
||
|
||
if to_bool(::Proxy::PuppetCa::Plugin.settings.puppetca_use_sudo, true)
|
||
@sudo = ::Proxy::PuppetCa::Plugin.settings.sudo_command || which("sudo")
|
||
unless File.exist?(@sudo)
|
||
logger.warn "unable to find sudo binary"
|
||
raise "Unable to find sudo"
|
||
end
|
||
logger.debug "Found sudo at #{@sudo}"
|
||
@sudo = "#{@sudo} -S"
|
||
else
|
||
@sudo = ""
|
||
end
|
||
end
|
||
|
||
def ssldir
|
||
Proxy::PuppetCa::Plugin.settings.ssldir
|
||
end
|
||
|
||
# parse the puppetca --list output
|
||
def certificate str
|
||
case str
|
||
when /(\+|\-)\s+["]{0,1}(.*\w)["]{0,1}\s+\((\S+)\)/
|
||
state = $1 == "-" ? "revoked" : "valid"
|
||
return { $2.strip => { :state => state, :fingerprint => $3 } }
|
||
when /\s*["]{0,1}(.*\w)["]{0,1}\s+\((\S+)\)/
|
||
return { $1.strip => { :state => "pending", :fingerprint => $2 } }
|
||
else
|
||
return {}
|
||
end
|
||
end
|
||
|
||
def ca_inventory
|
||
inventory = Pathname.new(ssldir).join("ca","inventory.txt")
|
||
raise "Unable to find CA inventory file at #{inventory}" unless File.exist?(inventory)
|
||
crl_path = Pathname.new(ssldir).join("ca","ca_crl.pem")
|
||
raise "Unable to find CRL" unless File.exist?(crl_path)
|
||
compute_ca_inventory(File.read(inventory), File.read(crl_path))
|
||
end
|
||
|
||
def compute_ca_inventory(inventory_contents, crl_cert_contents)
|
||
inventory = parse_inventory(inventory_contents)
|
||
crl = revoked_serials(crl_cert_contents)
|
||
inventory.each do |_, values|
|
||
values[:state] = "revoked" if crl.include?(values[:serial])
|
||
end
|
||
inventory
|
||
end
|
||
|
||
def parse_inventory(inventory_contents)
|
||
to_return = {}
|
||
inventory_contents.each_line do |cert|
|
||
# 0x005a 2011-04-16T07:12:46GMT 2016-04-14T07:12:46GMT /CN=uuid
|
||
# 0x005c 2017-01-07T11:23:20GMT 2022-01-17T11:23:20GMT /CN=name.mcollective/OU=mcollective
|
||
if cert =~ /(0(x|X)(\d|[a-f]|[A-F])+)\s+(\d+\S+)\s+(\d+\S+)\s+\/CN=([^\s\/]+)/
|
||
to_return[$6] = {:serial => $1.to_i(16), :not_before => $4, :not_after => $5}
|
||
end
|
||
end
|
||
to_return
|
||
end
|
||
|
||
def revoked_serials(crl_cert_contents)
|
||
Set.new(OpenSSL::X509::CRL.new(crl_cert_contents).revoked.collect {|r| r.serial.to_i})
|
||
end
|
||
|
||
def puppetca mode, certname
|
||
raise "Invalid mode #{mode}" unless mode =~ /^(clean|sign)$/
|
||
find_puppetca
|
||
certname.downcase!
|
||
command = "#{@sudo} #{@puppetca} --#{mode} #{certname}"
|
||
logger.debug "Executing #{command}"
|
||
response = `#{command} 2>&1`
|
||
if $?.success?
|
||
logger.debug "#{mode}ed puppet certificate for #{certname}"
|
||
elsif response =~ /Could not find client certificate/ || $?.exitstatus == 24
|
||
logger.debug "Attempt to remove nonexistent client certificate for #{certname}"
|
||
raise NotPresent, "Attempt to remove nonexistent client certificate for #{certname}"
|
||
else
|
||
logger.warn "Failed to run puppetca: #{response}"
|
||
raise "Execution of puppetca failed, check log files"
|
||
end
|
||
$?.success?
|
||
end
|
||
end
|
||
end
|
modules/puppetca_hostname_whitelisting/plugin_configuration.rb | ||
---|---|---|
module ::Proxy::PuppetCa::HostnameWhitelisting
|
||
class PluginConfiguration
|
||
def load_classes
|
||
require 'puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_autosigner'
|
||
end
|
||
|
||
def load_dependency_injection_wirings(container_instance, settings)
|
||
container_instance.dependency :autosigner, lambda { ::Proxy::PuppetCa::HostnameWhitelisting::Autosigner.new }
|
||
end
|
||
end
|
||
end
|
||
|
modules/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting.rb | ||
---|---|---|
require 'puppetca_hostname_whitelisting/plugin_configuration'
|
||
require 'puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_plugin'
|
modules/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_autosigner.rb | ||
---|---|---|
module ::Proxy::PuppetCa::HostnameWhitelisting
|
||
class Autosigner
|
||
include ::Proxy::Log
|
||
include ::Proxy::Util
|
||
|
||
def autosign_file
|
||
Proxy::PuppetCa::HostnameWhitelisting::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
|
modules/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_plugin.rb | ||
---|---|---|
module ::Proxy::PuppetCa::HostnameWhitelisting
|
||
class Plugin < ::Proxy::Provider
|
||
plugin :puppetca_hostname_whitelisting, ::Proxy::VERSION
|
||
|
||
requires :puppetca, ::Proxy::VERSION
|
||
default_settings :autosignfile => '/etc/puppet/autosign.conf'
|
||
|
||
load_classes ::Proxy::PuppetCa::HostnameWhitelisting::PluginConfiguration
|
||
load_dependency_injection_wirings ::Proxy::PuppetCa::HostnameWhitelisting::PluginConfiguration
|
||
end
|
||
end
|
test/puppetca/puppetca_config_test.rb | ||
---|---|---|
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_hostname_whitelisting', Proxy::PuppetCa::Plugin.settings.use_provider
|
||
end
|
||
end
|
test/puppetca/puppetca_puppet_cert_test.rb | ||
---|---|---|
require 'test_helper'
|
||
require 'tempfile'
|
||
require 'fileutils'
|
||
|
||
require 'puppetca/puppetca'
|
||
require 'puppetca/puppetca_puppet_cert'
|
||
|
||
class PuppetCaCertmanagerTest < Test::Unit::TestCase
|
||
def setup
|
||
@puppet_cert = Proxy::PuppetCa::PuppetCert.new
|
||
end
|
||
|
||
def test_which_should_return_a_binary_path
|
||
ENV.stubs(:[]).with('PATH').returns(['/foo', '/bin', '/usr/bin'].join(File::PATH_SEPARATOR))
|
||
{ '/foo' => false, '/bin' => true, '/usr/bin' => false, '/usr/sbin' => false, '/usr/local/bin' => false, '/usr/local/sbin' => false }.each do |p,r|
|
||
FileTest.stubs(:file?).with("#{p}/ls").returns(r)
|
||
FileTest.stubs(:executable?).with("#{p}/ls").returns(r)
|
||
end
|
||
assert_equal '/bin/ls', @puppet_cert.which('ls')
|
||
end
|
||
|
||
INVENTORY_CONTENTS =<<EOF
|
||
0x0002 2015-09-01T15:15:57UTC 2020-08-31T15:15:57UTC /CN=revoked.my.domain
|
||
0x0003 2015-09-02T08:34:59UTC 2020-09-01T08:34:59UTC /CN=active.my.domain
|
||
0x0004 2017-01-11T15:04:35UTC 2022-01-11T15:04:35UTC /CN=revoked.my.domain
|
||
0x0005 2017-01-14T12:01:22UTC 2022-01-14T12:01:22UTC /CN=second-active.my.domain/OU=mydepartment
|
||
EOF
|
||
def test_parse_inventory
|
||
assert_equal({"revoked.my.domain" => {: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"}},
|
||
@puppet_cert.parse_inventory(INVENTORY_CONTENTS))
|
||
end
|
||
|
||
CRL_CONTENTS =<<EOF
|
||
-----BEGIN X509 CRL-----
|
||
MIIC9DCB3QIBATANBgkqhkiG9w0BAQUFADA0MTIwMAYDVQQDDClQdXBwZXQgQ0E6
|
||
IGx1Y2lkLW5vbnNlbnNlLmFwcGxpZWRsb2dpYy5jYRcNMTcwMTEyMTUzNjM1WhcN
|
||
MjIwMTExMTUzNjM2WjBEMCACAQIXDTE3MDExMjEzMDEwOVowDDAKBgNVHRUEAwoB
|
||
ATAgAgEEFw0xNzAxMTIxNTM2MzZaMAwwCgYDVR0VBAMKAQGgLzAtMB8GA1UdIwQY
|
||
MBaAFPXwC6fTTZGAEGWebeMJobxzTq0IMAoGA1UdFAQDAgECMA0GCSqGSIb3DQEB
|
||
BQUAA4ICAQBevzkpnkJOelipZsd8GbV5r7b/5Mc/X9fIoNb7wfDGzRWMNDvp/pqd
|
||
3TeXvHKsgFqjgchQlI+dd+K1eouJm3pYSsT5MYVrJYUJ6kzPgC89tgtEDApnYOjx
|
||
rZIWyF6PeWjL8E7ZKNVFX6RS2HbhWLZStDnkJvckXAhN4GXdLdm5FulkXQ7asQTy
|
||
8u1bXWDvRESNuveHuuVyQpfzbnznxUSgf+gJzQ35wbNGZCJDoNlEth6UnIz26LIY
|
||
/3dRt/HcybDLoSIV+PF7m2VZZxwcpRCIgjvhCz0fWdfakPYoCn5l3ZGZnv6vL/ss
|
||
Mt7bh+b9C0u4g9sQxAYsW21EEFcxVjREXQNn9t/9iqwNn+W90Fee3TJGmWQINO29
|
||
zzPgmYyWZQFHCVPuQE/R6cVrRIFte1PjEycsxcTjVv4f71vIWd/54VW7/7TjXYq5
|
||
7CnBxWUlWs8N8GwJLzem5DgJvF85YUbVACfNs8JhZc7osLPxFhnZcKz2dLyJgXOj
|
||
tzZtHJZG7qxR1n9GmERVpk6OSeK0KKYmb+N9u4mGXYTDG6kl+nj1dU/Uh/yoAwG8
|
||
UCEaly81c8sSHjLI3GetK4WxND0cElcSaFY3q22bDay7drhhCMftcbhxoh9ROI5h
|
||
Ldr9eKhzX/iwBRnlcwxVCLSUEP+46oGi8hawrhEUnPxPtftMjPVFTQ==
|
||
-----END X509 CRL-----
|
||
EOF
|
||
def test_revoked_serials
|
||
assert_equal Set.new([2, 4]), @puppet_cert.revoked_serials(CRL_CONTENTS)
|
||
end
|
||
|
||
def test_compute_ca_inventory
|
||
assert_equal({"revoked.my.domain"=>{: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"}},
|
||
@puppet_cert.compute_ca_inventory(INVENTORY_CONTENTS, CRL_CONTENTS))
|
||
end
|
||
|
||
def test_should_clean_host
|
||
#TODO
|
||
assert_respond_to @puppet_cert, :clean
|
||
end
|
||
|
||
def test_should_sign_host
|
||
#TODO
|
||
assert_respond_to @puppet_cert, :sign
|
||
end
|
||
|
||
end
|
test/puppetca/puppetca_test.rb | ||
---|---|---|
require 'test_helper'
|
||
require 'tempfile'
|
||
require 'fileutils'
|
||
|
||
require 'puppetca/puppetca'
|
||
require 'puppetca/puppetca_main'
|
||
|
||
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")
|
||
end
|
||
|
||
def test_which_should_return_a_binary_path
|
||
ENV.stubs(:[]).with('PATH').returns(['/foo', '/bin', '/usr/bin'].join(File::PATH_SEPARATOR))
|
||
{ '/foo' => false, '/bin' => true, '/usr/bin' => false, '/usr/sbin' => false, '/usr/local/bin' => false, '/usr/local/sbin' => false }.each do |p,r|
|
||
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')
|
||
end
|
||
|
||
INVENTORY_CONTENTS =<<EOF
|
||
0x0002 2015-09-01T15:15:57UTC 2020-08-31T15:15:57UTC /CN=revoked.my.domain
|
||
0x0003 2015-09-02T08:34:59UTC 2020-09-01T08:34:59UTC /CN=active.my.domain
|
||
0x0004 2017-01-11T15:04:35UTC 2022-01-11T15:04:35UTC /CN=revoked.my.domain
|
||
0x0005 2017-01-14T12:01:22UTC 2022-01-14T12:01:22UTC /CN=second-active.my.domain/OU=mydepartment
|
||
EOF
|
||
def test_parse_inventory
|
||
assert_equal({"revoked.my.domain" => {: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))
|
||
end
|
||
|
||
CRL_CONTENTS =<<EOF
|
||
-----BEGIN X509 CRL-----
|
||
MIIC9DCB3QIBATANBgkqhkiG9w0BAQUFADA0MTIwMAYDVQQDDClQdXBwZXQgQ0E6
|
||
IGx1Y2lkLW5vbnNlbnNlLmFwcGxpZWRsb2dpYy5jYRcNMTcwMTEyMTUzNjM1WhcN
|
||
MjIwMTExMTUzNjM2WjBEMCACAQIXDTE3MDExMjEzMDEwOVowDDAKBgNVHRUEAwoB
|
||
ATAgAgEEFw0xNzAxMTIxNTM2MzZaMAwwCgYDVR0VBAMKAQGgLzAtMB8GA1UdIwQY
|
||
MBaAFPXwC6fTTZGAEGWebeMJobxzTq0IMAoGA1UdFAQDAgECMA0GCSqGSIb3DQEB
|
||
BQUAA4ICAQBevzkpnkJOelipZsd8GbV5r7b/5Mc/X9fIoNb7wfDGzRWMNDvp/pqd
|
||
3TeXvHKsgFqjgchQlI+dd+K1eouJm3pYSsT5MYVrJYUJ6kzPgC89tgtEDApnYOjx
|
||
rZIWyF6PeWjL8E7ZKNVFX6RS2HbhWLZStDnkJvckXAhN4GXdLdm5FulkXQ7asQTy
|
||
8u1bXWDvRESNuveHuuVyQpfzbnznxUSgf+gJzQ35wbNGZCJDoNlEth6UnIz26LIY
|
||
/3dRt/HcybDLoSIV+PF7m2VZZxwcpRCIgjvhCz0fWdfakPYoCn5l3ZGZnv6vL/ss
|
||
Mt7bh+b9C0u4g9sQxAYsW21EEFcxVjREXQNn9t/9iqwNn+W90Fee3TJGmWQINO29
|
||
zzPgmYyWZQFHCVPuQE/R6cVrRIFte1PjEycsxcTjVv4f71vIWd/54VW7/7TjXYq5
|
||
7CnBxWUlWs8N8GwJLzem5DgJvF85YUbVACfNs8JhZc7osLPxFhnZcKz2dLyJgXOj
|
||
tzZtHJZG7qxR1n9GmERVpk6OSeK0KKYmb+N9u4mGXYTDG6kl+nj1dU/Uh/yoAwG8
|
||
UCEaly81c8sSHjLI3GetK4WxND0cElcSaFY3q22bDay7drhhCMftcbhxoh9ROI5h
|
||
Ldr9eKhzX/iwBRnlcwxVCLSUEP+46oGi8hawrhEUnPxPtftMjPVFTQ==
|
||
-----END X509 CRL-----
|
||
EOF
|
||
def test_revoked_serials
|
||
assert_equal Set.new([2, 4]), ::Proxy::PuppetCa.revoked_serials(CRL_CONTENTS)
|
||
end
|
||
|
||
def test_compute_ca_inventory
|
||
assert_equal({"revoked.my.domain"=>{: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))
|
||
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
|
||
end
|
||
|
||
def test_should_sign_host
|
||
#TODO
|
||
assert_respond_to Proxy::PuppetCa, :sign
|
||
end
|
||
|
||
end
|
test/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_autosigner_test.rb | ||
---|---|---|
require 'test_helper'
|
||
require 'tempfile'
|
||
require 'fileutils'
|
||
|
||
require 'puppetca/puppetca'
|
||
require 'puppetca_hostname_whitelisting/puppetca_hostname_whitelisting'
|
||
require 'puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_autosigner'
|
||
|
||
class PuppetCaHostnameWhitelistingAutosignerTest < 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::HostnameWhitelisting::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
|
test/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_config_test.rb | ||
---|---|---|
require 'test_helper'
|
||
require 'puppetca/puppetca'
|
||
require 'puppetca_hostname_whitelisting/puppetca_hostname_whitelisting'
|
||
require 'puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_plugin'
|
||
|
||
class PuppetCaHostnameWhitelistingConfigTest < Test::Unit::TestCase
|
||
def test_omitted_settings_have_default_values
|
||
Proxy::PuppetCa::HostnameWhitelisting::Plugin.load_test_settings({})
|
||
assert_equal '/etc/puppet/autosign.conf', Proxy::PuppetCa::HostnameWhitelisting::Plugin.settings.autosignfile
|
||
end
|
||
end
|
Also available in: Unified diff
Fixes #23799 - Refactor: Make PuppetCa pluggable