Module: Himari::Aws::SecretsmanagerSigningKeyRotationHandler

Defined in:
lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb

Defined Under Namespace

Classes: RotationRequest

Class Method Summary collapse

Class Method Details

.create_secret(req) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb', line 50

def self.create_secret(req)
  current = begin
    @secretsmanager.get_secret_value(secret_id: req.id, version_stage: 'AWSCURRENT')
  rescue ::Aws::SecretsManager::Errors::ResourceNotFoundException
    nil
  end
  puts "createSecret: current version is: #{current.version_id} @ #{current.arn}" if current

  begin
    @secretsmanager.get_secret_value(secret_id: req.id, version_id: req.token, version_stage: 'AWSPENDING')
  rescue ::Aws::SecretsManager::Errors::ResourceNotFoundException
    puts "createSecret: generating for #{req.token} @ #{req.id}"

    @secretsmanager.put_secret_value(
      secret_id: req.id,
      client_request_token: req.token,
      secret_string: generate_secret(req, current),
    )
  else
    puts "createSecret: do nothing for #{req.token} @ #{req.id}"
  end
end

.finish_secret(req) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb', line 128

def self.finish_secret(req)
  current_version = req.secret.version_ids_to_stages.find { |k,v| v.include?('AWSCURRENT') }.first
  if current_version == req.token
    puts "finishSecret: #{current_version} on #{req.id} is on AWSCURRENT, do nothing"
    return
  end

  puts "finishSecret: update_secret_version_stage AWSCURRENT to #{req.token} from #{current_version} for #{req.id}"
  @secretsmanager.update_secret_version_stage(
    secret_id: req.id,
    version_stage: 'AWSCURRENT',
    move_to_version_id: req.token,
    remove_from_version_id: current_version,
  )
end

.generate_secret(req, _current) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb', line 73

def self.generate_secret(req, _current)
  param_raw = req.secret.tags&.find { |t| t.key == ENV.fetch('HIMARI_KEYGEN_PARAM_TAG_KEY', 'HimariKey') }&.value || ENV.fetch('HIMARI_KEYGEN_PARAM_DEFAULT', '{"kty": "rsa", "len": 2048}')
  param = parse_keygen_param(param_raw)
  puts "createSecret: generate_secret with #{param.inspect}"

  case param.fetch(:kty, 'rsa').downcase
  when 'rsa'
    rsa = OpenSSL::PKey::RSA.generate(param.fetch(:len, 2048).to_i)
    JSON.pretty_generate({kind: 'himari.signing_key', kty: 'rsa', rsa: {pem: rsa.to_pem}})
  when 'ec'
    curve = case param.fetch(:len, 256).to_i
    when 256; 'prime256v1'
    when 384; 'secp384r1'
    when 521; 'secp521r1'
    else
      raise ArgumentError, "unknown len: #{param.inspect}"
    end
    ec = OpenSSL::PKey::EC.generate(curve)
    JSON.pretty_generate({kind: 'himari.signing_key', kty: 'ec', ec: {pem: ec.to_pem}})
  else
    raise ArgumentError, "unknown kty: #{param.inspect}"
  end
end

.handler(event:, context:) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb', line 13

def self.handler(event:, context:)
  @secretsmanager ||= ::Aws::SecretsManager::Client.new()

  secret = prerequisite_check!(event)

  req = RotationRequest.new(
    step: event.fetch('Step'),
    token: event.fetch('ClientRequestToken'),
    id: event.fetch('SecretId'),
    secret: secret,
  )
  puts JSON.generate(plan: {step: req.step, token: req.token, id: req.id})

  case req.step
  when 'createSecret'
    create_secret(req)
  when 'setSecret'
    set_secret(req)
  when 'testSecret'
    test_secret(req)
  when 'finishSecret'
    finish_secret(req)
  else
    raise "Unknown Step: #{req.step}"
  end
end

.parse_keygen_param(str) ⇒ Object

Scan k=v,k2=v2



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb', line 98

def self.parse_keygen_param(str)
  begin
    ary = str.scan(/(.+?)=(.+?)(?:,|$)/)
    unless ary.empty?
      return ary.to_h.transform_keys(&:to_sym)
    end
  end

  if str.start_with?('eyJ')
    return JSON.parse(Base64.decode64(str), symbolize_names: true)
  end

  begin
    return JSON.parse(str, symbolize_names: true)
  rescue JSON::ParserError
  end

  raise "cannot parse keygen param #{str.inspect}"
end

.prerequisite_check!(event) ⇒ Object



40
41
42
43
44
45
46
47
48
# File 'lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb', line 40

def self.prerequisite_check!(event)
  secret = @secretsmanager.describe_secret(secret_id: event.fetch('SecretId'))
  raise "secret #{secret.arn.inspect} have not enabled rotation" unless secret.rotation_enabled
  stages = secret.version_ids_to_stages[event.fetch('ClientRequestToken')]
  raise "Secret version #{event.fetch('ClientRequestToken').inspect} has no stage for secret #{secret.arn.inspect}" unless stages
  raise "Secret version #{event.fetch('ClientRequestToken').inspect} is on AWSCURRENT for secret #{secret.arn.inspect}" if stages.include?('AWSCURRENT') && !stages.include?('AWSPENDING')
  raise "Secret version #{event.fetch('ClientRequestToken').inspect} is not on AWSPENDING for secret #{secret.arn.inspect}" unless stages.include?('AWSPENDING')
  secret
end

.set_secret(req) ⇒ Object



118
119
120
121
# File 'lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb', line 118

def self.set_secret(req)
  _check = @secretsmanager.get_secret_value(secret_id: req.id, version_id: req.token, version_stage: 'AWSPENDING')
  puts "setSecret: do nothing for #{req.token} @ #{req.id}"
end

.test_secret(req) ⇒ Object



123
124
125
126
# File 'lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb', line 123

def self.test_secret(req)
  _check = @secretsmanager.get_secret_value(secret_id: req.id, version_id: req.token, version_stage: 'AWSPENDING')
  puts "testSecret: do nothing for #{req.token} @ #{req.id}"
end