Skip to content

Instantly share code, notes, and snippets.

@peterellisjones
Last active February 19, 2020 11:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peterellisjones/83cd7448be09c52aa137ec455eab4231 to your computer and use it in GitHub Desktop.
Save peterellisjones/83cd7448be09c52aa137ec455eab4231 to your computer and use it in GitHub Desktop.
BOSH-generated certificate rotation script
#!/usr/bin/env ruby
require 'yaml'
require 'json'
#
# Simple Ruby script to rotate bosh-generated certificates
# Requires a BOSH deployment manifest that uses certificate variables and the original credentials file
# Outputs 4 credentials files that should be deployed one at a time, in order
#
# Allows for zero-downtime certificate rotation by creating an intermediate state where TLS clients trust
# both the old and new CA certificates
#
if ARGV.length != 2
puts "Usage:\n\trotate_certs.rb path_to_manifest.yml path_to_existing_credentials_file.yml"
puts "Requires BOSH CLI 6.1+"
exit 1
end
puts "Loading manifest"
manifest_path = ARGV[0]
manifest = YAML.load_file(manifest_path)
original_creds_path = ARGV[1]
original_creds = YAML.load_file(original_creds_path)
variables = manifest["variables"]
ca_certs = variables.select do |var|
var['type'] == 'certificate' && var['options']['is_ca']
end
leaf_certs = variables.select do |var|
var['type'] == 'certificate' && !var['options']['is_ca']
end
puts "Found CA certs:"
puts ca_certs.map { |cert| "\t#{cert.fetch('name')}" }
puts "Found leaf certs:"
puts leaf_certs.map { |cert| "\t#{cert.fetch('name')}" }
creds_without_certs = original_creds.reject do |key, val|
ca_certs.map { |cert| cert.fetch('name') }.include?(key) || leaf_certs.map { |cert| cert.fetch('name') }.include?(key)
end
puts "Writing old creds file => creds_step_0_old_ca_old_leaf.yml"
File.write("creds_step_0_old_ca_old_leaf.yml", original_creds.to_yaml)
puts "Generating new certs"
new_creds_path = "creds_step_3_new_ca_new_leaf.yml"
File.write(new_creds_path, creds_without_certs.to_yaml)
puts "Writing new creds file => #{new_creds_path}.yml"
interpolate_command = "bosh int --var-errs #{manifest_path} --vars-store #{new_creds_path}"
puts "Running command `#{interpolate_command}`"
system(interpolate_command)
# Load and write it so that we use Ruby YAML library for final version
# this makes it easier to diff
new_creds = YAML.load_file(new_creds_path)
File.write(new_creds_path, new_creds.to_yaml)
# Generate first intermediate creds file with old & new CA certs but old leaf certs
intermediate_creds_1 = YAML.load_file("creds_step_0_old_ca_old_leaf.yml")
# 1. Ensure that all CA certs are replaced
ca_certs.each do |var|
name = var.fetch('name')
intermediate_creds_1.fetch(name)['ca'] += "\n" + new_creds.fetch(name).fetch('ca')
end
# 2. Ensure that "CA" component of all leaf certs is replaced
leaf_certs.each do |var|
name = var.fetch('name')
intermediate_creds_1.fetch(name)['ca'] += "\n" + new_creds.fetch(name).fetch('ca')
end
# 3. Write file
puts "Writing intermediate creds file => creds_step_1_new_and_old_ca_old_leaf.yml"
File.write('creds_step_1_new_and_old_ca_old_leaf.yml', intermediate_creds_1.to_yaml)
# Generate second intermediate creds file with old & new CA certs but new leaf certs
intermediate_creds_2 = YAML.load_file("creds_step_3_new_ca_new_leaf.yml")
# 1. Ensure that all CA certs are replaced
ca_certs.each do |var|
name = var.fetch('name')
intermediate_creds_2.fetch(name)['ca'] = original_creds.fetch(name).fetch('ca') + "\n" + intermediate_creds_2.fetch(name)['ca']
end
# 2. Ensure that "CA" component of all leaf certs is replaced
leaf_certs.each do |var|
name = var.fetch('name')
intermediate_creds_2.fetch(name)['ca'] = original_creds.fetch(name).fetch('ca') + "\n" + intermediate_creds_2.fetch(name)['ca']
end
puts "Writing intermediate creds file => creds_step_2_new_and_old_ca_new_leaf.yml"
File.write('creds_step_2_new_and_old_ca_new_leaf.yml', intermediate_creds_2.to_yaml)
puts "DEPLOYMENT INSTRUCTIONS:"
puts "Deploy #{manifest_path} using these 4 creds files, one at a time"
puts "\tcreds_step_0_old_ca_old_leaf.yml\n\tcreds_step_1_new_and_old_ca_old_leaf.yml\n\tcreds_step_2_new_and_old_ca_new_leaf.yml\n\tcreds_step_3_new_ca_new_leaf.yml"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment