Project

General

Profile

Download (6.21 KB) Statistics
| Branch: | Tag: | Revision:
require 'proxy/log'
require 'openssl'

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.exists?(autosign_file)

found = false
entries = open(autosign_file, File::RDONLY).readlines.collect do |l|
if l.chomp != certname
l
else
found = true
nil
end
end.uniq.compact
if found
autosign = open(autosign_file, File::TRUNC|File::RDWR)
autosign.write entries
autosign.close
logger.info "Removed #{certname} from autosign"
else
logger.info "Attempt to remove nonexistant client autosign for #{certname}"
raise NotPresent, "Attempt to remove nonexistant 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)

autosign = open(autosign_file, File::RDWR)
# Check that we don't have that host already
found = false
autosign.each_line { |line| found = true if line.chomp == certname }
autosign.puts certname if found == false
autosign.close
logger.info "Added #{certname} to autosign"
end

# list of hosts which are now allowed to be installed via autosign
def autosign_list
File.exist?(autosign_file) ? File.read(autosign_file).split : []
end

# list of all certificates and their state/fingerprint
def list
find_puppetca
command = "#{@sudo} -S #{@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

private

# 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"
raise "SSL/CA unavailable on this machine"
end

# puppetca is the old method of using puppet cert which is new in puppet 2.6
default_path = ["/usr/sbin","/opt/puppet/bin"]
@puppetca = which("puppetca", default_path) || which("puppet", default_path)
unless File.exists?("#{@puppetca}")
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")

@sudo = which("sudo", "/usr/bin")
unless File.exists?("#{@sudo}")
logger.warn "unable to find sudo binary"
raise "Unable to find sudo"
end
logger.debug "Found sudo at #{@sudo}"

end

def ssldir
SETTINGS.ssldir || "/var/lib/puppet/ssl"
end

def puppetdir
SETTINGS.puppetdir || "/etc/puppet"
end

def autosign_file
"#{puppetdir}/autosign.conf"
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.exists?(inventory)
hash = {}
# 0x005a 2011-04-16T07:12:46GMT 2016-04-14T07:12:46GMT /CN=uuid
File.read(inventory).each_line do |cert|
if cert =~ /(0(x|X)(\d|[a-f]|[A-F])+)\s+(\d+\S+)\s+(\d+\S+)\s+\/CN=(\S+)/
hash[$6] = {:serial => $1.to_i(16), :not_before => $4, :not_after => $5}
end
end
crl = revoked_serials
hash.each do |cert,values|
values[:state] = "revoked" if crl.include?(values[:serial])
end
hash
end

def revoked_serials
crl = Pathname.new(ssldir).join("ca","ca_crl.pem")
raise "Unable to find CRL" unless File.exists?(crl)

crl = OpenSSL::X509::CRL.new(File.read(crl))
crl.revoked.collect {|r| r.serial}
end

def puppetca mode, certname
raise "Invalid mode #{mode}" unless mode =~ /^(clean|sign)$/
find_puppetca
certname.downcase!
command = "#{@sudo} -S #{@puppetca} --#{mode} #{certname}"
logger.debug "Executing #{command}"
response = `#{command} 2>&1`
if $?.success?
logger.info "#{mode}ed puppet certificate for #{certname}"
else
# Later versions of puppetca return OK even if the certificate is not present
# However we can report this condition for 0.24 and not flag an error to foreman
if response =~ /Could not find client certificate/
logger.info "Attempt to remove nonexistant client certificate for #{certname}"
raise NotPresent, "Attempt to remove nonexistant client certificate for #{certname}"
else
logger.warn "Failed to run puppetca: #{response}"
raise "Execution of puppetca failed, check log files"
end
end
$?.success?
end
end
end
(5-5/9)