Skip to content

Commit

Permalink
Add parameter encryption plugin. (#185)
Browse files Browse the repository at this point in the history
  • Loading branch information
askreet committed Nov 26, 2016
1 parent 3fe83ca commit 84527a2
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 0 deletions.
111 changes: 111 additions & 0 deletions lib/plugins/encrypted_parameters.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# This plugin encrypts parameters of the stack using a KMS Key,
# storing and passing the key used to the stack as a parameter as
# well. The resources in the stack can then use that KMS Key to
# decrypt those values.
#
# Example:
#
# Moonshot.config do |s|
# # .. mechanism config, etc. ..
#
# # The user will be prompted for values for SecretParameter1 and
# # SecretParameter2, which will then be encrypted by this plugins
# # pre_create and pre_update hooks.
# c.plugins << Moonshot::Plugins::EncryptedParameters.new(
# 'KMSKey1', %w(SecretParameter1 SecretParameter2)
#
# # Don't prompt the user for a KMS Key, since the default of 'Auto'
# # will generate a new key. They can override it with an answer
# # file or command line parameter if needed.
# c.parameter_sources['KMSKey1'] = Moonshot::AlwaysUseDefaultSource.new
# end
module Moonshot
class EncryptedParameters
# @param [String] kms_key_parameter_name
# The parameter name to store the KMS Key ARN as.
# @param [Array<String>] parameters
# Names of parameters to encrypt, if they are not already set.
def initialize(kms_key_parameter_name, parameters)
@kms_key_parameter_name = kms_key_parameter_name
@parameters = parameters
@delete_key = true
end

def pre_create(res)
@ilog = res.ilog

key_arn = find_or_create_kms_key
pe = ParameterEncrypter.new(key_arn)

@parameters.each do |parameter_name|
sp = Moonshot.config.parameters[parameter_name]
raise "No such parameter #{parameter_name}" unless sp

@ilog.start_threaded "Handling encrypted parameter #{parameter_name.blue}..." do |s|
if sp.use_previous?
# TODO: Remove this and the one below when the upstream race is fixed.
# See https://github.com/askreet/interactive-logger/issues/7
sleep 0.05
s.success "Using previous encrypted value for #{parameter_name.blue}."
elsif !sp.set? && !sp.default?
# If the parameter isn't set, we can't encrypt it. Doing
# nothing means we will give the user a friendly error message
# about unset parameters when the controller resumes.
sleep 0.05
s.failure "No value to encrypt for #{parameter_name.blue}!"
else
s.continue "Encrypting new value for parameter #{parameter_name.blue}..."
Moonshot.config.parameters[sp.name].set(pe.encrypt(sp.value))
s.success "Encrypted new value for parameter #{parameter_name.blue}!"
end
end
end
end
alias pre_update pre_create

def post_delete(res)
key_arn = Moonshot.config.parameters[@kms_key_parameter_name].value

res.ilog.start_threaded "Cleaning up KMS Key #{@kms_key_parameter_name.blue}..." do |s|
if @delete_key
KmsKey.new(key_arn).delete
s.success "Deleted KMS Key #{@kms_key_parameter_name.blue}!"
else
# TODO: See above.
sleep 0.05
s.success "Retained KMS Key #{@kms_key_parameter_name.blue}."
end
end
end

def delete_cli_hook(parser)
parser.on('--retain-kms-key', TrueClass, 'Do not delete the KMS Key for this environment.') do
@delete_key = false
end
end

private

def find_or_create_kms_key
key_arn = nil

@ilog.start_threaded "Checking for KMS Key #{@kms_key_parameter_name}" do |s|
if Moonshot.config.parameters.key?(@kms_key_parameter_name)
if 'Auto' == Moonshot.config.parameters[@kms_key_parameter_name].value
s.continue "Auto-generating KMS Key for #{@kms_key_parameter_name.blue}... "
key_arn = KmsKey.create.arn
Moonshot.config.parameters[@kms_key_parameter_name].set(key_arn)
s.success "Created a new KMS Key for #{@kms_key_parameter_name.blue}!"
else
key_arn = KmsKey.new(Moonshot.config.parameters[@kms_key_parameter_name].value).arn
s.success "Using existing KMS Key for #{@kms_key_parameter_name.blue}!"
end
end
end

raise "No such Stack Parameter #{@kms_key_parameter_name}!" unless key_arn

key_arn
end
end
end
22 changes: 22 additions & 0 deletions lib/plugins/encrypted_parameters/kms_key.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class EncryptedParameters
# Class that manages KMS keys in AWS.
class KmsKey
attr_reader :arn

def initialize(arn)
@arn = arn
@kms_client = Aws::KMS::Client.new
end

def self.create
resp = Aws::KMS::Client.new.create_key
arn = resp.key_metadata.arn

new(arn)
end

def delete
@kms_client.schedule_key_deletion(key_id: @arn, pending_window_in_days: 7)
end
end
end
23 changes: 23 additions & 0 deletions lib/plugins/encrypted_parameters/parameter_encrypter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require 'base64'

class EncryptedParameters
# Class that can encrypt and decrypt parameters using KMS.
class ParameterEncrypter
# @param [String] key_arn The ARN for the KMS key.
def initialize(key_arn)
@kms_client = Aws::KMS::Client.new
@key_arn = key_arn
end

# Encrypt and base64 encode the parameter value.
#
# @param [String] param_value The parameter to encrypt.
# @return [String] base64 encoded encrypted ciphertext.
def encrypt(param_value)
resp = @kms_client.encrypt(key_id: @key_arn, plaintext: param_value)

# Use strict here to avoid newlines which cause issues with parameters.
Base64.strict_encode64(resp.ciphertext_blob)
end
end
end

0 comments on commit 84527a2

Please sign in to comment.