Last active
August 19, 2020 15:42
-
-
Save justinstoller/9724d9dd24454d13089dd8bbc24163e0 to your computer and use it in GitHub Desktop.
Revoke and Clean Certs in Certdir
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'optparse' | |
require 'openssl' | |
require 'fileutils' | |
options = { | |
crl: '/etc/puppetlabs/puppet/ssl/ca/ca_crl.pem', | |
signeddir: '/etc/puppetlabs/puppet/ssl/ca/signed', | |
ca_key_path: '/etc/puppetlabs/puppet/ssl/ca/ca_key.pem', | |
certs_to_skip: ['ca.pem', "`hostname -f`.pem"], | |
timing: false, | |
trace: false | |
} | |
parser = OptionParser.new do |opts| | |
opts.on('--crl PATH', 'Full path to CRL') { |path| options[:crl] = path } | |
opts.on('--signed-dir PATH', 'Full path to CA\'s signeddir') { |path| options[:signeddir] = path } | |
opts.on('--ca-key PATH', 'Full path to CA\'s private key') { |path| options[:ca_key_path] = path } | |
opts.on('--block LIST', 'comma separated list of certs filenmaes not to revoke & clean') do |names| | |
options[:certs_to_skip] = names.split(',') | |
end | |
opts.on('--timing', 'Display timing information for major sections of work') { |bool| options[:timing] = bool } | |
opts.on('--trace', 'Log each action taken, very verbose') { |bool| options[:trace] = bool } | |
end | |
parser.parse(ARGV) | |
start = Time.now | |
# Load the CRL and create a uniqued list of revocations | |
crl = OpenSSL::X509::CRL.new(File.read(options[:crl])) | |
old_version = crl.version | |
revoked_by_serial = Hash.new | |
crl.revoked.each do |revoked| | |
if revoked_by_serial[revoked.serial] | |
puts("Found duplicated serial: #{revoked.serial}") if options[:trace] | |
else | |
revoked_by_serial[revoked.serial] = revoked | |
end | |
end | |
finish = Time.now | |
puts "Deduplicated existing CRL in memory" | |
puts("Deduplication took #{finish - start} seconds") if options[:timing] | |
unique_existing_revocations = revoked_by_serial.length | |
puts "Searching for certs to revoke" | |
start = Time.now | |
full_paths_to_all_certs = Dir[options[:signeddir] + '/*.pem'] | |
full_paths_to_certs_to_delete = full_paths_to_all_certs.reject do |path| | |
if options[:certs_to_skip].include?(File.basename(path)) | |
puts("Skipping revocation and cleaning of #{path}") if options[:trace] | |
true | |
end | |
end | |
finish = Time.now | |
puts("Filtering certs in signeddir took #{finish - start} seconds") if options[:timing] | |
puts "Found and preparing to revoke up to #{full_paths_to_certs_to_delete.length} certs" | |
start = Time.now | |
full_paths_to_certs_to_delete.each do |path| | |
cert = OpenSSL::X509::Certificate.new(File.read(path)) | |
serial = cert.serial | |
unless revoked_by_serial[serial] | |
revoked = OpenSSL::X509::Revoked.new | |
revoked.serial = serial | |
revoked.time = Time.now | |
revoked.add_extension( | |
OpenSSL::X509::Extension.new( | |
"CRLReason", | |
OpenSSL::ASN1::Enumerated(OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE))) | |
revoked_by_serial[serial] = revoked | |
# Just revoke, don't delete untile we know we've successfully written the CRL to disk | |
puts("Revoked serial #{serial}") if options[:trace] | |
end | |
end | |
finish = Time.now | |
puts("Revocation took #{finish - start} seconds") if options[:timing] | |
total_revoked = revoked_by_serial.length | |
unique_newly_revoked = total_revoked - unique_existing_revocations | |
start = Time.now | |
crl.revoked = revoked_by_serial.values | |
# Increment the version by the number of unique additions | |
crl.version = old_version + unique_newly_revoked | |
ca_key = OpenSSL::PKey.read(File.read(options[:ca_key_path])) | |
crl.sign(ca_key, OpenSSL::Digest::SHA256.new) | |
File.write(options[:crl], crl.to_pem) | |
finish = Time.now | |
puts "Revoked #{unique_newly_revoked} certs, now cleaning certdir" | |
puts("Writing crl took #{finish - start} seconds") if options[:timing] | |
start = Time.now | |
full_paths_to_certs_to_delete.each do |path| | |
puts("Removing cert at #{path}") if options[:trace] | |
FileUtils.rm_rf(path) | |
end | |
finish = Time.now | |
puts "Cleaned #{full_paths_to_certs_to_delete.length} stale certs" | |
puts("Cleaning certs took #{finish - start} seconds") if options[:timing] | |
exit 0 |
The use case has been updated to:
Bulk-clear a known set of certificate names which is not equal to the full set of issued certificates. This could be a short list of tens, or a long list of thousands. Individual teams responsible for applications deployments will initiate this as a self-serve action, so it should be accessible via an API call—a Bolt task, for example.
As a Bolt task, this would be the user interface.
{
"description": "Bulk delete and/or revoke Puppet certificates through direct file operations",
"input_method": "stdin",
"parameters": {
"certnames": {
"description": "List of certnames to clear",
"type": "Array[String[1]]"
},
"revoke": {
"description": "Add cleared certificates to the CRL?",
"type": "Boolean",
"default": true
},
"shutdown_puppet_server": {
"description": "Shut down Puppet Server during clearing operation?",
"type": "Boolean",
"default": false
}
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
output