Skip to content

Instantly share code, notes, and snippets.

@bmhatfield
Created March 29, 2016 00:45
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 bmhatfield/53b863dd17df844a11de to your computer and use it in GitHub Desktop.
Save bmhatfield/53b863dd17df844a11de to your computer and use it in GitHub Desktop.
Encrypt, Edit and key-rotate databags.
require 'chef/knife'
module LocalDatabags
class Encrypt < Chef::Knife
deps do
require 'chef/encrypted_data_bag_item'
end
banner "knife encrypt BAGNAME ITEM KEYFILE"
def validate!
unless name_args.length == 3
ui.fatal "Missing Arguments"
show_usage
exit 1
end
@bagname = name_args[0]
@itemname = name_args[1]
@keyfile = name_args[2]
@databag_file = "data_bags/#{@bagname}/#{@itemname}.json"
unless Dir.exists? 'data_bags'
ui.fatal "Could not find 'data_bags' directory"
exit 1
end
begin
unless File.exists? @keyfile
raise
end
@secret = Chef::EncryptedDataBagItem.load_secret(@keyfile)
rescue
ui.fatal "Unable to load key '#{@keyfile}'"
exit 1
end
end
def run
validate!
# Force config to chef-solo for our current purposes, which allows
# loading of databags from the :data_bag_path, allowing us to edit
# existing databags.
Chef::Config[:solo] = true
Chef::Config[:data_bag_path] = 'data_bags'
if File.exists? @databag_file
data = Chef::EncryptedDataBagItem.load(@bagname, @itemname, @secret).to_hash
else
data = {"id" => @itemname}
end
edited = ui.edit_data(data, parse_output=true)
if edited != data
ui.msg "Saving updated databag..."
encrypted_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(edited, @secret)
FileUtils.mkpath("data_bags/#{@bagname}")
File.open(@databag_file, 'w') do |f|
f.print encrypted_data.to_json
end
end
end
end
class EncryptRotate < Chef::Knife
deps do
require 'chef/encrypted_data_bag_item'
end
banner "knife encrypt rotate KEYFILE_OLD KEYFILE_NEW"
def validate!
unless name_args.length == 2
ui.fatal "Missing Arguments"
show_usage
exit 1
end
@old_key_path = name_args[0]
@new_key_path = name_args[1]
unless Dir.exists? 'data_bags'
ui.fatal "Could not find 'data_bags' directory"
exit 1
end
begin
unless File.exists? @old_key_path
raise
end
@old_secret = Chef::EncryptedDataBagItem.load_secret(@old_key_path)
rescue
ui.fatal "Unable to load key '#{old_key_path}'"
exit 1
end
begin
unless File.exists? @new_key_path
raise
end
@new_secret = Chef::EncryptedDataBagItem.load_secret(@new_key_path)
rescue
ui.fatal "Unable to load key '#{@new_key_path}'"
exit 1
end
end
def rotate(directory)
bags = Dir.entries(directory)
bags.each do |bag|
if ['README.md', '.', '..'].include?(bag)
next
end
if File.directory?("data_bags/#{bag}")
items = Dir.entries("data_bags/#{bag}")
items.each do |item_file_name|
if ['README.md', '.', '..'].include?(item_file_name)
next
end
item_name = item_file_name[0..item_file_name.rindex('.')-1]
begin
data = Chef::EncryptedDataBagItem.load(bag, item_name, @old_secret).to_hash
encrypted_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(data, @new_secret)
ui.msg "Writing data_bags/#{bag}/#{item_file_name}"
File.open("data_bags/#{bag}/#{item_file_name}", 'w') do |f|
f.print encrypted_data.to_json
end
rescue
ui.msg "Skipping #{bag} #{item_name} - not encrypted?"
end
end
end
end
end
def run
validate!
# Force config to chef-solo for our current purposes, which allows
# loading of databags from the :data_bag_path, allowing us to edit
# existing databags.
Chef::Config[:solo] = true
Chef::Config[:data_bag_path] = 'data_bags'
rotate('data_bags')
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment