class Hippo::SecretManager

Constants

CIPHER

Attributes

stage[R]

Public Class Methods

new(stage) click to toggle source
# File lib/hippo/secret_manager.rb, line 11
def initialize(stage)
  @stage = stage
end

Public Instance Methods

all() click to toggle source
# File lib/hippo/secret_manager.rb, line 148
def all
  @all ||= begin
    return {} unless exists?

    unless key_available?
      raise Error, 'No encryption key is available to decrypt secrets'
    end

    YAML.safe_load(decrypt(File.read(path)))
  end
rescue Psych::SyntaxError => e
  raise Error, "Could not parse secrets file: #{e.message}"
end
create() click to toggle source

Create an empty encrypted example secret file

@return [void]

# File lib/hippo/secret_manager.rb, line 115
def create
  unless key_available?
    raise Error, 'Cannot create secret file because no key is available for encryption'
  end

  return if exists?

  yaml = { 'example' => 'This is an example secret2!' }.to_yaml
  FileUtils.mkdir_p(File.dirname(path))
  File.open(path, 'w') { |f| f.write(encrypt(yaml)) }
end
create_key() click to toggle source

Generate and publish a new secret key to the Kubernetes API.

@return [void]

# File lib/hippo/secret_manager.rb, line 59
def create_key
  if key_available?
    raise Hippo::Error, 'A key already exists on Kubernetes. Remove this first.'
  end

  CIPHER.encrypt
  secret_key = CIPHER.random_key
  secret_key64 = Base64.encode64(secret_key).gsub("\n", '').strip
  od = ObjectDefinition.new({
                              'apiVersion' => 'v1',
                              'kind' => 'Secret',
                              'type' => 'hippo.adam.ac/secret-encryption-key',
                              'metadata' => { 'name' => 'hippo-secret-key' },
                              'data' => { 'key' => secret_key64 }
                            }, @stage)
  @stage.apply([od])
  @key = secret_key
end
decrypt(value) click to toggle source

Decrypt the given value value and return it

@param value [String] @return [String]

# File lib/hippo/secret_manager.rb, line 99
def decrypt(value)
  value = Base64.decode64(value.to_s)
  encrypted_value, salt, iv = value.split('---', 3).map { |s| Base64.decode64(s) }
  Encryptor.decrypt(value: encrypted_value, key: @key, iv: iv, salt: salt).to_s
end
download_key() click to toggle source

Download the current key from the Kubernetes API and set it as the key for this instance

@return [void]

# File lib/hippo/secret_manager.rb, line 25
def download_key
  return if @key

  Util.action 'Downloading secret encryption key...' do |state|
    begin
      value = @stage.get('secret', 'hippo-secret-key').first

      if value.nil? || value.dig('data', 'key').nil?
        state.call('not found')
        return
      end

      @key = Base64.decode64(Base64.decode64(value['data']['key']))
    rescue Hippo::Error => e
      if e.message =~ /not found/
        state.call('not found')
      else
        raise
      end
    end
  end
end
edit() click to toggle source
# File lib/hippo/secret_manager.rb, line 127
def edit
  create unless exists?

  unless key_available?
    raise Error, 'Cannot create edit file because no key is available for decryption'
  end

  old_contents = decrypt(File.read(path))
  new_contents = Util.open_in_editor('secret', old_contents)
  if old_contents != new_contents
    write_file(new_contents)
  else
    puts 'No changes detected. Not re-encrypting secret file.'
  end
end
encrypt(value) click to toggle source

Encrypt a given value?

# File lib/hippo/secret_manager.rb, line 79
def encrypt(value)
  unless key_available?
    raise Error, 'Cannot encrypt values because there is no key'
  end

  CIPHER.encrypt
  iv = CIPHER.random_iv
  salt = SecureRandom.random_bytes(16)
  encrypted_value = Encryptor.encrypt(value: value.to_s, key: @key, iv: iv, salt: salt)
  Base64.encode64([
    Base64.encode64(encrypted_value),
    Base64.encode64(salt),
    Base64.encode64(iv)
  ].join('---'))
end
exists?() click to toggle source

Does a secrets file exist for this application.

@return [Boolean]

# File lib/hippo/secret_manager.rb, line 108
def exists?
  File.file?(path)
end
key_available?() click to toggle source

Is there a key availale in this manager?

@return [Boolean]

# File lib/hippo/secret_manager.rb, line 51
def key_available?
  download_key
  !@key.nil?
end
path() click to toggle source
# File lib/hippo/secret_manager.rb, line 17
def path
  File.join(@stage.config_root, 'secrets.yaml')
end
write_file(contents) click to toggle source
# File lib/hippo/secret_manager.rb, line 143
def write_file(contents)
  FileUtils.mkdir_p(File.dirname(path))
  File.open(path, 'w') { |f| f.write(encrypt(contents)) }
end