himari-aws 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/README.md +127 -14
  4. data/Rakefile +2 -0
  5. data/lambda/Dockerfile +40 -0
  6. data/lambda/Gemfile +35 -0
  7. data/lambda/Gemfile.lock +374 -0
  8. data/lambda/README.md +42 -0
  9. data/lambda/entrypoint.rb +5 -0
  10. data/lambda/terraform/README.md +92 -0
  11. data/lambda/terraform/functions/aws.tf +2 -0
  12. data/lambda/terraform/functions/dynamodb.tf +18 -0
  13. data/lambda/terraform/functions/lambda_rack.tf +66 -0
  14. data/lambda/terraform/functions/lambda_secrets_rotation.tf +33 -0
  15. data/lambda/terraform/functions/outputs.tf +19 -0
  16. data/lambda/terraform/functions/variables.tf +65 -0
  17. data/lambda/terraform/functions/versions.tf +7 -0
  18. data/lambda/terraform/iam/aws.tf +2 -0
  19. data/lambda/terraform/iam/outputs.tf +7 -0
  20. data/lambda/terraform/iam/role.tf +77 -0
  21. data/lambda/terraform/iam/variables.tf +44 -0
  22. data/lambda/terraform/iam/versions.tf +8 -0
  23. data/lambda/terraform/image/aws.tf +1 -0
  24. data/lambda/terraform/image/copy.tf +45 -0
  25. data/lambda/terraform/image/ecr.tf +42 -0
  26. data/lambda/terraform/image/outputs.tf +9 -0
  27. data/lambda/terraform/image/variables.tf +20 -0
  28. data/lambda/terraform/image/versions.tf +9 -0
  29. data/lambda/terraform/signing_key/aws.tf +1 -0
  30. data/lambda/terraform/signing_key/outputs.tf +3 -0
  31. data/lambda/terraform/signing_key/secret.tf +18 -0
  32. data/lambda/terraform/signing_key/variables.tf +24 -0
  33. data/lambda/terraform/signing_key/versions.tf +7 -0
  34. data/lib/himari/aws/dynamodb_storage.rb +41 -16
  35. data/lib/himari/aws/lambda_handler.rb +76 -0
  36. data/lib/himari/aws/secretsmanager_signing_key_provider.rb +8 -5
  37. data/lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb +36 -9
  38. data/lib/himari/aws/version.rb +1 -1
  39. data/lib/himari-aws.rb +2 -0
  40. metadata +49 -10
  41. data/Gemfile +0 -12
  42. data/Gemfile.lock +0 -171
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'himari/storages/base'
2
4
  require 'aws-sdk-dynamodb'
3
5
 
@@ -8,34 +10,55 @@ module Himari
8
10
 
9
11
  # @param client [Aws::DynamoDB::Client]
10
12
  # @param table_name [String] name of DynamoDB table with hash=pk/range=sk key.
11
- def initialize(client: ::Aws::DynamoDB::Client.new, table_name:)
13
+ # @param consistent_read [Boolean] use consitent read when querying. default to true.
14
+ def initialize(client: ::Aws::DynamoDB::Client.new, table_name:, consistent_read: true)
12
15
  @client = client
13
16
  @table_name = table_name
17
+ @consistent_read = consistent_read
14
18
  end
15
19
 
16
20
  attr_reader :client, :table_name
17
21
 
18
- private def write(kind, key, content, overwrite: false)
22
+ def consistent_read?; !!@consistent_read; end
23
+
24
+ private def write(kind, key, content, overwrite: false, if_version: nil)
19
25
  pk = "storage:#{kind}:#{key}"
20
- payload = {
26
+ # version and updated_at are mirrored to top-level attributes (besides living inside
27
+ # content_json) so the refresh-token compare-and-swap can reference them in a condition.
28
+ attrs = {
21
29
  content_json: JSON.pretty_generate(content),
22
- ttl: content[:ttl] || content[:expiry],
23
- }
30
+ version: content[:version],
31
+ updated_at: content[:updated_at],
32
+ }.compact
33
+ ttl = content[:ttl] || content[:expiry]
34
+ attrs[:ttl] = ttl if ttl
35
+
36
+ update_expression = +"SET #{attrs.keys.map { |k| "##{k} = :#{k}" }.join(", ")}"
37
+ update_expression << "\nREMOVE #ttl" unless attrs.key?(:ttl)
38
+
39
+ expression_attribute_names = attrs.keys.to_h { |k| ["##{k}", k.to_s] }
40
+ expression_attribute_names['#ttl'] = 'ttl' unless attrs.key?(:ttl)
41
+ expression_attribute_values = attrs.transform_keys { ":#{_1}" }
42
+
43
+ condition_expression =
44
+ if if_version
45
+ expression_attribute_names['#version'] = 'version'
46
+ expression_attribute_values[':expected_version'] = if_version
47
+ 'attribute_exists(pk) AND #version = :expected_version'
48
+ elsif !overwrite
49
+ 'attribute_not_exists(pk)'
50
+ end
51
+
24
52
  @client.update_item(
25
53
  table_name: @table_name,
26
54
  key: {
27
55
  'pk' => pk,
28
56
  'sk' => pk,
29
57
  },
30
- # #{payload.each_key.map { |k| "##{k} = :#{k}" }.join(', ')}
31
- update_expression: <<~EOS,
32
- SET
33
- #content_json = :content_json
34
- #{payload[:ttl] ? ", #ttl = :ttl" : "REMOVE #ttl"}
35
- EOS
36
- condition_expression: overwrite ? nil : 'attribute_not_exists(pk)',
37
- expression_attribute_names: payload.each_key.map { |k| ["##{k}", k] }.to_h,
38
- expression_attribute_values: payload.transform_keys { ":#{_1}" },
58
+ update_expression: update_expression,
59
+ condition_expression: condition_expression,
60
+ expression_attribute_names: expression_attribute_names,
61
+ expression_attribute_values: expression_attribute_values,
39
62
  )
40
63
  nil
41
64
  rescue ::Aws::DynamoDB::Errors::ConditionalCheckFailedException
@@ -50,9 +73,11 @@ module Himari
50
73
  limit: 1,
51
74
  key_condition_expression: 'pk = :pk AND sk = :sk',
52
75
  expression_attribute_values: {":pk" => pk, ":sk" => pk},
76
+ consistent_read: consistent_read?,
53
77
  ).items.first
54
78
 
55
- return nil unless item
79
+ return unless item
80
+
56
81
  JSON.parse(item.fetch('content_json'), symbolize_names: true)
57
82
  end
58
83
 
@@ -60,7 +85,7 @@ module Himari
60
85
  pk = "storage:#{kind}:#{key}"
61
86
  @client.delete_item(
62
87
  table_name: @table_name,
63
- key: {'pk' => pk, 'sk' => pk}
88
+ key: {'pk' => pk, 'sk' => pk},
64
89
  )
65
90
  nil
66
91
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'himari'
4
+ require 'himari/aws/secretsmanager_signing_key_rotation_handler'
5
+
6
+ require 'digest/sha2'
7
+ require 'aws-sdk-dynamodb'
8
+ require 'apigatewayv2_rack'
9
+
10
+ $stdout.sync = true
11
+
12
+ module Himari
13
+ module Aws
14
+ module LambdaHandler
15
+ def self.app
16
+ @app ||= make_app
17
+ end
18
+
19
+ def self.config_ru
20
+ a = Time.now
21
+ retval = config_ru_from_task_root || config_ru_from_dynamodb
22
+ b = Time.now
23
+ $stdout.puts(JSON.generate(config_ru: {ts: b, elapsed_time: b - a}))
24
+ retval
25
+ end
26
+
27
+ def self.config_ru_from_task_root
28
+ return unless ENV['LAMBDA_TASK_ROOT']
29
+
30
+ File.read(File.join(ENV['LAMBDA_TASK_ROOT'], 'config.ru'))
31
+ rescue Errno::ENOENT, Errno::EPERM
32
+ nil
33
+ end
34
+
35
+ def self.config_ru_from_dynamodb
36
+ dgst = ENV.fetch('HIMARI_RACK_DIGEST')
37
+ table_name = ENV.fetch('HIMARI_RACK_DYNAMODB_TABLE')
38
+ pk = "rack"
39
+ sk = "rack:#{dgst}"
40
+
41
+ ddb = ::Aws::DynamoDB::Client.new
42
+ item = ddb.query(
43
+ table_name: table_name,
44
+ select: 'ALL_ATTRIBUTES',
45
+ limit: 1,
46
+ key_condition_expression: 'pk = :pk AND sk = :sk',
47
+ expression_attribute_values: {":pk" => pk, ":sk" => sk},
48
+ ).items.first
49
+
50
+ unless item
51
+ raise "item not found (pk=#{pk.inspect}, sk=#{sk.inspect}) on dynamodb table #{table_name} for config.ru"
52
+ end
53
+
54
+ content = item.fetch('file')
55
+ content_dgst = Digest::SHA256.digest(content)
56
+ raise "config.ru item content digest mismatch" if content_dgst != Base64.decode64(dgst)
57
+
58
+ content
59
+ end
60
+
61
+ def self.make_app
62
+ require 'rack'
63
+ require 'rack/builder'
64
+ Rack::Builder.new_from_string(config_ru)
65
+ end
66
+
67
+ def self.rack_handler(event:, context:)
68
+ Apigatewayv2Rack.handle_request(event: event, context: context, app: app)
69
+ end
70
+
71
+ def self.secrets_rotation_handler(event:, context:)
72
+ Himari::Aws::SecretsmanagerSigningKeyRotationHandler.handler(event: event, context: context)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'aws-sdk-secretsmanager'
2
4
  require 'himari/signing_key'
3
5
  require 'himari/middlewares/signing_key'
@@ -26,10 +28,12 @@ module Himari
26
28
 
27
29
  def collect(id: nil, active: nil, group: nil, **_remainder)
28
30
  return [] if group && group != @group
31
+
29
32
  case
30
33
  when id
31
34
  return [] unless id.start_with?("#{@kid_prefix}_")
32
- version_id = id[(@kid_prefix.size+1)..-1] || ''
35
+
36
+ version_id = id[(@kid_prefix.size + 1)..-1] || ''
33
37
  [secret_value_to_signing_key(@client.get_secret_value(secret_id: @secret_id, version_id: version_id))].compact
34
38
 
35
39
  when active
@@ -50,10 +54,10 @@ module Himari
50
54
  JSON.parse(value.secret_string)
51
55
  rescue JSON::ParserError
52
56
  warn "JSON::ParserError while parsing #{value.arn} #{value.version_id}"
53
- return nil
57
+ return
54
58
  end
55
-
56
- return nil unless json['kind'] == 'himari.signing_key'
59
+
60
+ return unless json['kind'] == 'himari.signing_key'
57
61
 
58
62
  pkey = case json.fetch('kty')
59
63
  when 'rsa'
@@ -76,4 +80,3 @@ module Himari
76
80
  end
77
81
  end
78
82
  end
79
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotate-secrets_how.html
2
4
  require 'openssl'
3
5
  require 'json'
@@ -11,7 +13,7 @@ module Himari
11
13
  RotationRequest = Struct.new(:step, :id, :token, :secret, keyword_init: true)
12
14
 
13
15
  def self.handler(event:, context:)
14
- @secretsmanager ||= ::Aws::SecretsManager::Client.new()
16
+ @secretsmanager ||= ::Aws::SecretsManager::Client.new
15
17
 
16
18
  secret = prerequisite_check!(event)
17
19
 
@@ -40,10 +42,12 @@ module Himari
40
42
  def self.prerequisite_check!(event)
41
43
  secret = @secretsmanager.describe_secret(secret_id: event.fetch('SecretId'))
42
44
  raise "secret #{secret.arn.inspect} have not enabled rotation" unless secret.rotation_enabled
45
+
43
46
  stages = secret.version_ids_to_stages[event.fetch('ClientRequestToken')]
44
- raise "Secret version #{event.fetch('ClientRequestToken').inspect} has no stage for secret #{secret.arn.inspect}" unless stages
45
- raise "Secret version #{event.fetch('ClientRequestToken').inspect} is on AWSCURRENT for secret #{secret.arn.inspect}" if stages.include?('AWSCURRENT') && !stages.include?('AWSPENDING')
46
- raise "Secret version #{event.fetch('ClientRequestToken').inspect} is not on AWSPENDING for secret #{secret.arn.inspect}" unless stages.include?('AWSPENDING')
47
+ raise "Secret version #{event.fetch("ClientRequestToken").inspect} has no stage for secret #{secret.arn.inspect}" unless stages
48
+ raise "Secret version #{event.fetch("ClientRequestToken").inspect} is on AWSCURRENT for secret #{secret.arn.inspect}" if stages.include?('AWSCURRENT') && !stages.include?('AWSPENDING')
49
+ raise "Secret version #{event.fetch("ClientRequestToken").inspect} is not on AWSPENDING for secret #{secret.arn.inspect}" unless stages.include?('AWSPENDING')
50
+
47
51
  secret
48
52
  end
49
53
 
@@ -71,7 +75,8 @@ module Himari
71
75
  end
72
76
 
73
77
  def self.generate_secret(req, _current)
74
- param = JSON.parse(req.secret.tags.find { |t| t.name == ENV.fetch('HIMARI_KEYGEN_PARAM_TAG_KEY', 'HimariKey') }&.value || ENV.fetch('HIMARI_KEYGEN_PARAM_DEFAULT', '{"kty": "rsa", "len": 2048}'), symbolize_names: true)
78
+ 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}')
79
+ param = parse_keygen_param(param_raw)
75
80
  puts "createSecret: generate_secret with #{param.inspect}"
76
81
 
77
82
  case param.fetch(:kty, 'rsa').downcase
@@ -80,9 +85,9 @@ module Himari
80
85
  JSON.pretty_generate({kind: 'himari.signing_key', kty: 'rsa', rsa: {pem: rsa.to_pem}})
81
86
  when 'ec'
82
87
  curve = case param.fetch(:len, 256).to_i
83
- when 256; 'prime256v1'
84
- when 384; 'secp384r1'
85
- when 521; 'secp521r1'
88
+ when 256 then 'prime256v1'
89
+ when 384 then 'secp384r1'
90
+ when 521 then 'secp521r1'
86
91
  else
87
92
  raise ArgumentError, "unknown len: #{param.inspect}"
88
93
  end
@@ -93,6 +98,28 @@ module Himari
93
98
  end
94
99
  end
95
100
 
101
+ # Scan k=v,k2=v2
102
+ def self.parse_keygen_param(str)
103
+ begin
104
+ ary = str.scan(/(.+?)=(.+?)(?:,|$)/)
105
+ unless ary.empty?
106
+ return ary.to_h.transform_keys(&:to_sym)
107
+ end
108
+ end
109
+
110
+ if str.start_with?('eyJ')
111
+ return JSON.parse(Base64.decode64(str), symbolize_names: true)
112
+ end
113
+
114
+ begin
115
+ return JSON.parse(str, symbolize_names: true)
116
+ rescue JSON::ParserError
117
+ # fall through to raise below
118
+ end
119
+
120
+ raise "cannot parse keygen param #{str.inspect}"
121
+ end
122
+
96
123
  def self.set_secret(req)
97
124
  _check = @secretsmanager.get_secret_value(secret_id: req.id, version_id: req.token, version_stage: 'AWSPENDING')
98
125
  puts "setSecret: do nothing for #{req.token} @ #{req.id}"
@@ -104,7 +131,7 @@ module Himari
104
131
  end
105
132
 
106
133
  def self.finish_secret(req)
107
- current_version = req.secret.version_ids_to_stages.find { |k,v| v.include?('AWSCURRENT') }.first
134
+ current_version = req.secret.version_ids_to_stages.find { |_k, v| v.include?('AWSCURRENT') }.first
108
135
  if current_version == req.token
109
136
  puts "finishSecret: #{current_version} on #{req.id} is on AWSCURRENT, do nothing"
110
137
  return
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Himari
4
4
  module Aws
5
- VERSION = "0.1.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
data/lib/himari-aws.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'himari/aws'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: himari-aws
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sorah Fukumori
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2023-03-20 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: himari
@@ -25,7 +24,7 @@ dependencies:
25
24
  - !ruby/object:Gem::Version
26
25
  version: '0'
27
26
  - !ruby/object:Gem::Dependency
28
- name: aws-sdk-secretsmanager
27
+ name: apigatewayv2_rack
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - ">="
@@ -52,7 +51,20 @@ dependencies:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
53
  version: '0'
55
- description:
54
+ - !ruby/object:Gem::Dependency
55
+ name: aws-sdk-secretsmanager
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
56
68
  email:
57
69
  - her@sorah.jp
58
70
  executables: []
@@ -60,14 +72,43 @@ extensions: []
60
72
  extra_rdoc_files: []
61
73
  files:
62
74
  - ".rspec"
63
- - Gemfile
64
- - Gemfile.lock
75
+ - CHANGELOG.md
65
76
  - LICENSE.txt
66
77
  - README.md
67
78
  - Rakefile
79
+ - lambda/Dockerfile
80
+ - lambda/Gemfile
81
+ - lambda/Gemfile.lock
82
+ - lambda/README.md
83
+ - lambda/entrypoint.rb
84
+ - lambda/terraform/README.md
85
+ - lambda/terraform/functions/aws.tf
86
+ - lambda/terraform/functions/dynamodb.tf
87
+ - lambda/terraform/functions/lambda_rack.tf
88
+ - lambda/terraform/functions/lambda_secrets_rotation.tf
89
+ - lambda/terraform/functions/outputs.tf
90
+ - lambda/terraform/functions/variables.tf
91
+ - lambda/terraform/functions/versions.tf
92
+ - lambda/terraform/iam/aws.tf
93
+ - lambda/terraform/iam/outputs.tf
94
+ - lambda/terraform/iam/role.tf
95
+ - lambda/terraform/iam/variables.tf
96
+ - lambda/terraform/iam/versions.tf
97
+ - lambda/terraform/image/aws.tf
98
+ - lambda/terraform/image/copy.tf
99
+ - lambda/terraform/image/ecr.tf
100
+ - lambda/terraform/image/outputs.tf
101
+ - lambda/terraform/image/variables.tf
102
+ - lambda/terraform/image/versions.tf
103
+ - lambda/terraform/signing_key/aws.tf
104
+ - lambda/terraform/signing_key/outputs.tf
105
+ - lambda/terraform/signing_key/secret.tf
106
+ - lambda/terraform/signing_key/variables.tf
107
+ - lambda/terraform/signing_key/versions.tf
68
108
  - lib/himari-aws.rb
69
109
  - lib/himari/aws.rb
70
110
  - lib/himari/aws/dynamodb_storage.rb
111
+ - lib/himari/aws/lambda_handler.rb
71
112
  - lib/himari/aws/secretsmanager_signing_key_provider.rb
72
113
  - lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb
73
114
  - lib/himari/aws/version.rb
@@ -78,7 +119,6 @@ licenses:
78
119
  metadata:
79
120
  homepage_uri: https://github.com/sorah/himari
80
121
  source_code_uri: https://github.com/sorah/himari
81
- post_install_message:
82
122
  rdoc_options: []
83
123
  require_paths:
84
124
  - lib
@@ -93,8 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
133
  - !ruby/object:Gem::Version
94
134
  version: '0'
95
135
  requirements: []
96
- rubygems_version: 3.4.0.dev
97
- signing_key:
136
+ rubygems_version: 4.0.10
98
137
  specification_version: 4
99
138
  summary: AWS related plugins for Himari
100
139
  test_files: []
data/Gemfile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- gemspec path: '../himari'
6
- # Specify your gem's dependencies in himari-aws.gemspec
7
- gemspec
8
-
9
- gem 'nokogiri'
10
-
11
- gem "rake", "~> 13.0"
12
- gem "rspec", "~> 3.0"
data/Gemfile.lock DELETED
@@ -1,171 +0,0 @@
1
- PATH
2
- remote: ../himari
3
- specs:
4
- himari (0.1.0)
5
- addressable
6
- omniauth (>= 2.0)
7
- openid_connect
8
- rack-oauth2
9
- rack-protection
10
- sinatra (>= 3.0)
11
-
12
- PATH
13
- remote: .
14
- specs:
15
- himari-aws (0.1.0)
16
- aws-sdk-dynamodb
17
- aws-sdk-secretsmanager
18
- himari
19
-
20
- GEM
21
- remote: https://rubygems.org/
22
- specs:
23
- activemodel (7.0.4.3)
24
- activesupport (= 7.0.4.3)
25
- activesupport (7.0.4.3)
26
- concurrent-ruby (~> 1.0, >= 1.0.2)
27
- i18n (>= 1.6, < 2)
28
- minitest (>= 5.1)
29
- tzinfo (~> 2.0)
30
- addressable (2.8.1)
31
- public_suffix (>= 2.0.2, < 6.0)
32
- aes_key_wrap (1.1.0)
33
- attr_required (1.0.1)
34
- aws-eventstream (1.2.0)
35
- aws-partitions (1.730.0)
36
- aws-sdk-core (3.170.1)
37
- aws-eventstream (~> 1, >= 1.0.2)
38
- aws-partitions (~> 1, >= 1.651.0)
39
- aws-sigv4 (~> 1.5)
40
- jmespath (~> 1, >= 1.6.1)
41
- aws-sdk-dynamodb (1.83.0)
42
- aws-sdk-core (~> 3, >= 3.165.0)
43
- aws-sigv4 (~> 1.1)
44
- aws-sdk-secretsmanager (1.73.0)
45
- aws-sdk-core (~> 3, >= 3.165.0)
46
- aws-sigv4 (~> 1.1)
47
- aws-sigv4 (1.5.2)
48
- aws-eventstream (~> 1, >= 1.0.2)
49
- bindata (2.4.15)
50
- concurrent-ruby (1.2.2)
51
- date (3.3.3)
52
- diff-lcs (1.5.0)
53
- faraday (2.7.4)
54
- faraday-net_http (>= 2.0, < 3.1)
55
- ruby2_keywords (>= 0.0.4)
56
- faraday-follow_redirects (0.3.0)
57
- faraday (>= 1, < 3)
58
- faraday-net_http (3.0.2)
59
- hashie (5.0.0)
60
- i18n (1.12.0)
61
- concurrent-ruby (~> 1.0)
62
- jmespath (1.6.2)
63
- json-jwt (1.16.3)
64
- activesupport (>= 4.2)
65
- aes_key_wrap
66
- bindata
67
- faraday (~> 2.0)
68
- faraday-follow_redirects
69
- mail (2.8.1)
70
- mini_mime (>= 0.1.1)
71
- net-imap
72
- net-pop
73
- net-smtp
74
- mini_mime (1.1.2)
75
- mini_portile2 (2.8.1)
76
- minitest (5.18.0)
77
- mustermann (3.0.0)
78
- ruby2_keywords (~> 0.0.1)
79
- net-imap (0.3.4)
80
- date
81
- net-protocol
82
- net-pop (0.1.2)
83
- net-protocol
84
- net-protocol (0.2.1)
85
- timeout
86
- net-smtp (0.3.3)
87
- net-protocol
88
- nokogiri (1.14.2)
89
- mini_portile2 (~> 2.8.0)
90
- racc (~> 1.4)
91
- omniauth (2.1.1)
92
- hashie (>= 3.4.6)
93
- rack (>= 2.2.3)
94
- rack-protection
95
- openid_connect (2.2.0)
96
- activemodel
97
- attr_required (>= 1.0.0)
98
- faraday (~> 2.0)
99
- faraday-follow_redirects
100
- json-jwt (>= 1.16)
101
- net-smtp
102
- rack-oauth2 (~> 2.2)
103
- swd (~> 2.0)
104
- tzinfo
105
- validate_email
106
- validate_url
107
- webfinger (~> 2.0)
108
- public_suffix (5.0.1)
109
- racc (1.6.2)
110
- rack (2.2.6.4)
111
- rack-oauth2 (2.2.0)
112
- activesupport
113
- attr_required
114
- faraday (~> 2.0)
115
- faraday-follow_redirects
116
- json-jwt (>= 1.11.0)
117
- rack (>= 2.1.0)
118
- rack-protection (3.0.5)
119
- rack
120
- rake (13.0.6)
121
- rspec (3.12.0)
122
- rspec-core (~> 3.12.0)
123
- rspec-expectations (~> 3.12.0)
124
- rspec-mocks (~> 3.12.0)
125
- rspec-core (3.12.1)
126
- rspec-support (~> 3.12.0)
127
- rspec-expectations (3.12.2)
128
- diff-lcs (>= 1.2.0, < 2.0)
129
- rspec-support (~> 3.12.0)
130
- rspec-mocks (3.12.4)
131
- diff-lcs (>= 1.2.0, < 2.0)
132
- rspec-support (~> 3.12.0)
133
- rspec-support (3.12.0)
134
- ruby2_keywords (0.0.5)
135
- sinatra (3.0.5)
136
- mustermann (~> 3.0)
137
- rack (~> 2.2, >= 2.2.4)
138
- rack-protection (= 3.0.5)
139
- tilt (~> 2.0)
140
- swd (2.0.2)
141
- activesupport (>= 3)
142
- attr_required (>= 0.0.5)
143
- faraday (~> 2.0)
144
- faraday-follow_redirects
145
- tilt (2.1.0)
146
- timeout (0.3.2)
147
- tzinfo (2.0.6)
148
- concurrent-ruby (~> 1.0)
149
- validate_email (0.1.6)
150
- activemodel (>= 3.0)
151
- mail (>= 2.2.5)
152
- validate_url (1.0.15)
153
- activemodel (>= 3.0.0)
154
- public_suffix
155
- webfinger (2.1.2)
156
- activesupport
157
- faraday (~> 2.0)
158
- faraday-follow_redirects
159
-
160
- PLATFORMS
161
- ruby
162
-
163
- DEPENDENCIES
164
- himari!
165
- himari-aws!
166
- nokogiri
167
- rake (~> 13.0)
168
- rspec (~> 3.0)
169
-
170
- BUNDLED WITH
171
- 2.4.8