class Krane::EjsonSecretProvisioner
Constants
- EJSON_KEYS_SECRET
- EJSON_SECRETS_FILE
- EJSON_SECRET_KEY
Public Class Methods
new(task_config:, ejson_keys_secret:, ejson_file:, statsd_tags:, selector: nil)
click to toggle source
# File lib/krane/ejson_secret_provisioner.rb, line 21 def initialize(task_config:, ejson_keys_secret:, ejson_file:, statsd_tags:, selector: nil) @ejson_keys_secret = ejson_keys_secret @ejson_file = ejson_file @statsd_tags = statsd_tags @selector = selector @task_config = task_config @kubectl = Kubectl.new( task_config: @task_config, log_failure_by_default: false, output_is_sensitive_default: true # output may contain ejson secrets ) end
Public Instance Methods
resources()
click to toggle source
# File lib/krane/ejson_secret_provisioner.rb, line 34 def resources @resources ||= build_secrets end
Private Instance Methods
build_secrets()
click to toggle source
# File lib/krane/ejson_secret_provisioner.rb, line 40 def build_secrets unless @ejson_keys_secret raise EjsonSecretError, "Secret #{EJSON_KEYS_SECRET} not provided, cannot decrypt secrets" end return [] unless File.exist?(@ejson_file) with_decrypted_ejson do |decrypted| secrets = decrypted[EJSON_SECRET_KEY] unless secrets.present? logger.warn("#{EJSON_SECRETS_FILE} does not have key #{EJSON_SECRET_KEY}."\ "No secrets will be created.") return [] end secrets.map do |secret_name, secret_spec| validate_secret_spec(secret_name, secret_spec) resource = generate_secret_resource(secret_name, secret_spec["_type"], secret_spec["data"]) resource.validate_definition(kubectl: @kubectl) if resource.validation_failed? raise EjsonSecretError, "Resulting resource Secret/#{secret_name} failed validation" end resource end end end
decrypt_ejson(key_dir)
click to toggle source
# File lib/krane/ejson_secret_provisioner.rb, line 135 def decrypt_ejson(key_dir) out, err, st = Open3.capture3({ 'EJSON_KEYDIR' => key_dir.to_s }, 'ejson', 'decrypt', @ejson_file.to_s) unless st.success? # older ejson versions dump some errors to STDOUT msg = err.presence || out raise EjsonSecretError, msg end JSON.parse(out) rescue JSON::ParserError raise EjsonSecretError, "Failed to parse decrypted ejson" end
encrypted_ejson()
click to toggle source
# File lib/krane/ejson_secret_provisioner.rb, line 65 def encrypted_ejson @encrypted_ejson ||= load_ejson_from_file end
fetch_private_key_from_secret()
click to toggle source
# File lib/krane/ejson_secret_provisioner.rb, line 147 def fetch_private_key_from_secret encoded_private_key = @ejson_keys_secret["data"][public_key] unless encoded_private_key raise EjsonSecretError, "Private key for #{public_key} not found in #{EJSON_KEYS_SECRET} secret" end Base64.decode64(encoded_private_key) rescue Kubectl::ResourceNotFoundError raise EjsonSecretError, "Secret/#{EJSON_KEYS_SECRET} is required to decrypt EJSON and could not be found" end
generate_secret_resource(secret_name, secret_type, data)
click to toggle source
# File lib/krane/ejson_secret_provisioner.rb, line 87 def generate_secret_resource(secret_name, secret_type, data) unless data.is_a?(Hash) && data.values.all? { |v| v.is_a?(String) } # Secret data is map[string]string raise EjsonSecretError, "Data for secret #{secret_name} was invalid. Only key-value pairs are permitted." end encoded_data = data.each_with_object({}) do |(key, value), encoded| # Leading underscores in ejson keys are used to skip encryption of the associated value # To support this ejson feature, we need to exclude these leading underscores from the secret's keys secret_key = key.sub(/\A_/, '') encoded[secret_key] = Base64.strict_encode64(value) end labels = { "name" => secret_name } labels.reverse_merge!(@selector.to_h) if @selector secret = { 'kind' => 'Secret', 'apiVersion' => 'v1', 'type' => secret_type, 'metadata' => { "name" => secret_name, "labels" => labels, "namespace" => namespace, }, "data" => encoded_data, } Krane::Secret.build( namespace: namespace, context: context, logger: logger, definition: secret, statsd_tags: @statsd_tags, ) end
load_ejson_from_file()
click to toggle source
# File lib/krane/ejson_secret_provisioner.rb, line 118 def load_ejson_from_file return {} unless File.exist?(@ejson_file) JSON.parse(File.read(@ejson_file)) rescue JSON::ParserError => e raise EjsonSecretError, "Failed to parse encrypted ejson:\n #{e}" end
private_key()
click to toggle source
# File lib/krane/ejson_secret_provisioner.rb, line 73 def private_key @private_key ||= fetch_private_key_from_secret end
public_key()
click to toggle source
# File lib/krane/ejson_secret_provisioner.rb, line 69 def public_key encrypted_ejson["_public_key"] end
validate_secret_spec(secret_name, spec)
click to toggle source
# File lib/krane/ejson_secret_provisioner.rb, line 77 def validate_secret_spec(secret_name, spec) errors = [] errors << "secret type unspecified" if spec["_type"].blank? errors << "no data provided" if spec["data"].blank? unless errors.empty? raise EjsonSecretError, "Ejson incomplete for secret #{secret_name}: #{errors.join(', ')}" end end
with_decrypted_ejson() { |decrypted| ... }
click to toggle source
# File lib/krane/ejson_secret_provisioner.rb, line 125 def with_decrypted_ejson return unless File.exist?(@ejson_file) Dir.mktmpdir("ejson_keydir") do |key_dir| File.write(File.join(key_dir, public_key), private_key) decrypted = decrypt_ejson(key_dir) yield decrypted end end