himari-aws 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +127 -14
- data/Rakefile +2 -0
- data/lambda/Dockerfile +33 -0
- data/lambda/Gemfile +24 -0
- data/{Gemfile.lock → lambda/Gemfile.lock} +61 -23
- data/lambda/README.md +42 -0
- data/lambda/entrypoint.rb +3 -0
- data/lambda/terraform/README.md +89 -0
- data/lambda/terraform/functions/aws.tf +2 -0
- data/lambda/terraform/functions/dynamodb.tf +18 -0
- data/lambda/terraform/functions/lambda_rack.tf +66 -0
- data/lambda/terraform/functions/lambda_secrets_rotation.tf +33 -0
- data/lambda/terraform/functions/outputs.tf +19 -0
- data/lambda/terraform/functions/variables.tf +65 -0
- data/lambda/terraform/functions/versions.tf +7 -0
- data/lambda/terraform/iam/aws.tf +2 -0
- data/lambda/terraform/iam/outputs.tf +3 -0
- data/lambda/terraform/iam/role.tf +77 -0
- data/lambda/terraform/iam/variables.tf +44 -0
- data/lambda/terraform/iam/versions.tf +7 -0
- data/lambda/terraform/image/aws.tf +1 -0
- data/lambda/terraform/image/copy.sh +5 -0
- data/lambda/terraform/image/copy.tf +17 -0
- data/lambda/terraform/image/ecr.tf +42 -0
- data/lambda/terraform/image/outputs.tf +9 -0
- data/lambda/terraform/image/variables.tf +9 -0
- data/lambda/terraform/image/versions.tf +7 -0
- data/lambda/terraform/signing_key/aws.tf +1 -0
- data/lambda/terraform/signing_key/outputs.tf +3 -0
- data/lambda/terraform/signing_key/secret.tf +18 -0
- data/lambda/terraform/signing_key/variables.tf +24 -0
- data/lambda/terraform/signing_key/versions.tf +7 -0
- data/lib/himari/aws/lambda_handler.rb +72 -0
- data/lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb +23 -1
- data/lib/himari/aws/version.rb +1 -1
- metadata +48 -5
- data/Gemfile +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2cc2ae7b20fd4a41872281249587bacae7170a411651458cfab901afc08a9bfe
|
4
|
+
data.tar.gz: dc2cd99bad600b636b817c1534b5ea8c17e59bb49a9bf667b4ee7f0d41f17acd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f0fdb41114958bf5330173fd12de1db680f77ef8719d84e0fc1724ea9bb0311c8d7d63363111ec9490ee4a39227d91acff8224f601a5ae055e1199a239a7f64
|
7
|
+
data.tar.gz: 2ee76e5b556d477ee37d51a92a5a4fb03d8c751c5e877d295c46970bb073cfb6bf6b7f4a70611b4ade5890caec4e15f42a98a6d94fb81a02553df6cf2815a8ff
|
data/README.md
CHANGED
@@ -1,24 +1,137 @@
|
|
1
|
-
# Himari
|
1
|
+
# himari-aws: AWS related plugins for Himari
|
2
2
|
|
3
|
-
|
3
|
+
- DynamoDB storage backend
|
4
|
+
- Secrets Manager automatic rotation Lambda function for signing keys
|
5
|
+
- Secrets Manager signing key provider
|
6
|
+
- Lambda container image to host Himari itself (TODO)
|
4
7
|
|
5
|
-
|
8
|
+
## Deploy on Lambda with Terraform
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
10
|
-
|
11
|
-
Install the gem and add to the application's Gemfile by executing:
|
10
|
+
- See [./lambda/terraform/](./lambda/terraform/) for quick deployment using Terraform modules.
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
If bundler is not being used to manage dependencies, install the gem by executing:
|
12
|
+
## Installation
|
16
13
|
|
17
|
-
|
14
|
+
```ruby
|
15
|
+
gem 'himari'
|
16
|
+
gem 'himari-aws'
|
17
|
+
gem 'nokogiri'
|
18
|
+
```
|
19
|
+
|
20
|
+
### IAM policy
|
21
|
+
|
22
|
+
```json
|
23
|
+
{
|
24
|
+
"Version": "2012-10-17",
|
25
|
+
"Statement": [
|
26
|
+
{
|
27
|
+
"Effect": "Allow",
|
28
|
+
"Action": [
|
29
|
+
"dynamodb:DeleteItem",
|
30
|
+
"dynamodb:Query",
|
31
|
+
"dynamodb:UpdateItem"
|
32
|
+
],
|
33
|
+
"Resource": "arn:aws:dynamodb:[REGION]:[ACCOUNTID]:table/himari_*"
|
34
|
+
},
|
35
|
+
{
|
36
|
+
"Effect": "Allow",
|
37
|
+
"Action": [
|
38
|
+
"secretsmanager:DescribeSecret",
|
39
|
+
"secretsmanager:GetSecretValue",
|
40
|
+
"secretsmanager:PutSecretValue",
|
41
|
+
"secretsmanager:UpdateSecretVersionStage"
|
42
|
+
],
|
43
|
+
"Resource": "arn:aws:secretsmanager:[REGION]:[ACCOUNTID]:secret:himari_*"
|
44
|
+
}
|
45
|
+
]
|
46
|
+
}
|
47
|
+
```
|
18
48
|
|
19
49
|
## Usage
|
20
50
|
|
21
|
-
|
51
|
+
### Secrets Manager Rotation Handler
|
52
|
+
|
53
|
+
1. Deploy [./lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb]() as a Lambda function. This file works standalone.
|
54
|
+
|
55
|
+
- Refer to the [./lambda](./lambda) for prebuilt container image
|
56
|
+
|
57
|
+
2. Grant secrets manager a `lambda:InvokeFunction` to the function.
|
58
|
+
3. Create a secrets manager secret and set up rotation.
|
59
|
+
|
60
|
+
You can tag a secret with `HimariKey` key and the following value to customize key types:
|
61
|
+
|
62
|
+
- RSA 2048-bit: `kty=rsa,len=2048`
|
63
|
+
- RSA 4096-bit: `kty=rsa,len=4096`
|
64
|
+
- EC P-256: `kty=ec,len=256`
|
65
|
+
|
66
|
+
_you may also specify in base64'd json_
|
67
|
+
|
68
|
+
### config.ru
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# config.ru
|
72
|
+
require 'himari'
|
73
|
+
require 'himari/aws'
|
74
|
+
require 'json'
|
75
|
+
require 'omniauth'
|
76
|
+
require 'open-uri'
|
77
|
+
require 'rack/session/cookie'
|
78
|
+
|
79
|
+
use(Rack::Session::Cookie,
|
80
|
+
path: '/',
|
81
|
+
expire_after: 3600,
|
82
|
+
secure: true,
|
83
|
+
secret: ENV.fetch('SECRET_KEY_BASE'),
|
84
|
+
)
|
85
|
+
|
86
|
+
use OmniAuth::Builder do
|
87
|
+
provider :developer, fields: %i(login), uid_field: :login
|
88
|
+
end
|
89
|
+
|
90
|
+
use(Himari::Middlewares::Config,
|
91
|
+
issuer: 'https://idp.example.net',
|
92
|
+
providers: [
|
93
|
+
{ name: :github, button: 'Log in with GitHub' },
|
94
|
+
],
|
95
|
+
storage: Himari::Aws::DynamodbStorage.new(table_name: 'himari'),
|
96
|
+
)
|
97
|
+
|
98
|
+
# Signing key from Secrets Manager. For rotation deployment, read
|
99
|
+
use(Himari::Aws::SecretsmanagerSigningKeyProvider,
|
100
|
+
secret_id: 'arn:aws:secretsmanager:ap-northeast-1:...:secret:himari-xxx',
|
101
|
+
group: nil,
|
102
|
+
kid_prefix: 'asm1',
|
103
|
+
)
|
104
|
+
|
105
|
+
# Add clients as many as you need
|
106
|
+
use(Himari::Middlewares::Client,
|
107
|
+
name: 'awsalb',
|
108
|
+
id: '...',
|
109
|
+
secret_hash: '...', # sha384 hexdigest of secret
|
110
|
+
# secret: '...' # or in cleartext
|
111
|
+
redirect_uris: %w(https://app.example.net/oauth2/idpresponse),
|
112
|
+
)
|
113
|
+
|
114
|
+
use(Himari::Middlewares::ClaimsRule, name: 'developer-initialize') do |context, decision|
|
115
|
+
next decision.skip!("provider not in scope") unless context.provider == 'developer'
|
116
|
+
decision.initialize_claims!(
|
117
|
+
sub: "dev_#{Digest::SHA256.hexdigest(context.auth[:uid])}",
|
118
|
+
name: context.auth[:info][:login],
|
119
|
+
preferred_username: context.auth[:info][:login],
|
120
|
+
)
|
121
|
+
decision.continue!
|
122
|
+
end
|
123
|
+
|
124
|
+
use(Himari::Middlewares::AuthenticationRule, name: 'always-allow') do |context, decision|
|
125
|
+
next decision.skip!("provider not in scope") unless context.provider == 'developer'
|
126
|
+
decision.allow!
|
127
|
+
end
|
128
|
+
|
129
|
+
use(Himari::Middlewares::AuthorizationRule, name: 'always-allow') do |context, decision|
|
130
|
+
decision.allow!
|
131
|
+
end
|
132
|
+
|
133
|
+
run Himari::App
|
134
|
+
```
|
22
135
|
|
23
136
|
## Development
|
24
137
|
|
@@ -28,7 +141,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
28
141
|
|
29
142
|
## Contributing
|
30
143
|
|
31
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
144
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sorah/himari.
|
32
145
|
|
33
146
|
## License
|
34
147
|
|
data/Rakefile
CHANGED
data/lambda/Dockerfile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# context must be repository root
|
2
|
+
FROM public.ecr.aws/lambda/ruby:2.7 as builder
|
3
|
+
RUN yum install -y gcc gcc-c++ make pkg-config git
|
4
|
+
|
5
|
+
COPY ./himari/himari.gemspec ${LAMBDA_TASK_ROOT}/app/himari/himari.gemspec
|
6
|
+
COPY ./himari/lib/himari/version.rb ${LAMBDA_TASK_ROOT}/app/himari/lib/himari/version.rb
|
7
|
+
COPY ./himari-aws/himari-aws.gemspec ${LAMBDA_TASK_ROOT}/app/himari-aws/himari-aws.gemspec
|
8
|
+
COPY ./himari-aws/lib/himari/aws/version.rb ${LAMBDA_TASK_ROOT}/app/himari-aws/lib/himari/aws/version.rb
|
9
|
+
COPY ./himari-aws/lambda/Gemfile* ${LAMBDA_TASK_ROOT}/app/himari-aws/lambda/
|
10
|
+
WORKDIR ${LAMBDA_TASK_ROOT}/app
|
11
|
+
|
12
|
+
ENV BUNDLE_GEMFILE ${LAMBDA_TASK_ROOT}/app/himari-aws/lambda/Gemfile
|
13
|
+
ENV BUNDLE_PATH ${LAMBDA_TASK_ROOT}/vendor/bundle
|
14
|
+
ENV BUNDLE_DEPLOYMENT 1
|
15
|
+
ENV BUNDLE_JOBS 16
|
16
|
+
ENV HIMARI_LAMBDA_IMAGE 1
|
17
|
+
RUN bundle install
|
18
|
+
|
19
|
+
COPY . ${LAMBDA_TASK_ROOT}/app
|
20
|
+
|
21
|
+
FROM public.ecr.aws/lambda/ruby:2.7
|
22
|
+
|
23
|
+
COPY --from=builder ${LAMBDA_TASK_ROOT}/vendor ${LAMBDA_TASK_ROOT}/vendor
|
24
|
+
COPY . ${LAMBDA_TASK_ROOT}/app
|
25
|
+
|
26
|
+
COPY ./himari-aws/lambda/entrypoint.rb ${LAMBDA_TASK_ROOT}/himari_lambda_entrypoint.rb
|
27
|
+
|
28
|
+
WORKDIR ${LAMBDA_TASK_ROOT}/app
|
29
|
+
ENV BUNDLE_GEMFILE ${LAMBDA_TASK_ROOT}/app/himari-aws/lambda/Gemfile
|
30
|
+
ENV BUNDLE_PATH ${LAMBDA_TASK_ROOT}/vendor/bundle
|
31
|
+
ENV BUNDLE_DEPLOYMENT 1
|
32
|
+
ENV HIMARI_LAMBDA_IMAGE 1
|
33
|
+
CMD [ "himari_lambda_entrypoint.Himari::Aws::LambdaHandler.rack_handler" ]
|
data/lambda/Gemfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
root = File.join('..', '..')
|
4
|
+
|
5
|
+
gem 'himari', path: File.join(root, 'himari')
|
6
|
+
gem 'himari-aws', path: File.join(root, 'himari-aws')
|
7
|
+
gem 'nokogiri'
|
8
|
+
#gem 'apigatewayv2_rack', git: 'https://github.com/sorah/apigatewayv2_rack'
|
9
|
+
gem 'apigatewayv2_rack', '>= 0.1.3'
|
10
|
+
|
11
|
+
# contribs
|
12
|
+
gem 'omniauth-oauth2'
|
13
|
+
gem 'omniauth-saml'
|
14
|
+
#gem 'omniauth-twitter'
|
15
|
+
gem 'omniauth-github'
|
16
|
+
gem 'omniauth-auth0'
|
17
|
+
#gem 'omniauth-shibboleth'
|
18
|
+
gem 'omniauth-gitlab'
|
19
|
+
#gem 'omniauth-kerberos'
|
20
|
+
gem 'omniauth-google-oauth2'
|
21
|
+
gem 'omniauth-discord'
|
22
|
+
gem 'omniauth-apple'
|
23
|
+
# gem 'omniauth-ldap' # omniauth < 2
|
24
|
+
#gem 'omniauth-slack'# omniauth-oauth2 version constraints does not match with omniauth-github
|
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
|
-
remote:
|
2
|
+
remote: ../../himari
|
3
3
|
specs:
|
4
|
-
himari (0.
|
4
|
+
himari (0.2.0)
|
5
5
|
addressable
|
6
6
|
omniauth (>= 2.0)
|
7
7
|
openid_connect
|
@@ -10,9 +10,10 @@ PATH
|
|
10
10
|
sinatra (>= 3.0)
|
11
11
|
|
12
12
|
PATH
|
13
|
-
remote:
|
13
|
+
remote: ..
|
14
14
|
specs:
|
15
|
-
himari-aws (0.
|
15
|
+
himari-aws (0.2.0)
|
16
|
+
apigatewayv2_rack
|
16
17
|
aws-sdk-dynamodb
|
17
18
|
aws-sdk-secretsmanager
|
18
19
|
himari
|
@@ -30,9 +31,11 @@ GEM
|
|
30
31
|
addressable (2.8.1)
|
31
32
|
public_suffix (>= 2.0.2, < 6.0)
|
32
33
|
aes_key_wrap (1.1.0)
|
34
|
+
apigatewayv2_rack (0.1.3)
|
35
|
+
rack
|
33
36
|
attr_required (1.0.1)
|
34
37
|
aws-eventstream (1.2.0)
|
35
|
-
aws-partitions (1.
|
38
|
+
aws-partitions (1.732.0)
|
36
39
|
aws-sdk-core (3.170.1)
|
37
40
|
aws-eventstream (~> 1, >= 1.0.2)
|
38
41
|
aws-partitions (~> 1, >= 1.651.0)
|
@@ -49,7 +52,6 @@ GEM
|
|
49
52
|
bindata (2.4.15)
|
50
53
|
concurrent-ruby (1.2.2)
|
51
54
|
date (3.3.3)
|
52
|
-
diff-lcs (1.5.0)
|
53
55
|
faraday (2.7.4)
|
54
56
|
faraday-net_http (>= 2.0, < 3.1)
|
55
57
|
ruby2_keywords (>= 0.0.4)
|
@@ -66,6 +68,7 @@ GEM
|
|
66
68
|
bindata
|
67
69
|
faraday (~> 2.0)
|
68
70
|
faraday-follow_redirects
|
71
|
+
jwt (2.7.0)
|
69
72
|
mail (2.8.1)
|
70
73
|
mini_mime (>= 0.1.1)
|
71
74
|
net-imap
|
@@ -74,6 +77,7 @@ GEM
|
|
74
77
|
mini_mime (1.1.2)
|
75
78
|
mini_portile2 (2.8.1)
|
76
79
|
minitest (5.18.0)
|
80
|
+
multi_xml (0.6.0)
|
77
81
|
mustermann (3.0.0)
|
78
82
|
ruby2_keywords (~> 0.0.1)
|
79
83
|
net-imap (0.3.4)
|
@@ -88,10 +92,43 @@ GEM
|
|
88
92
|
nokogiri (1.14.2)
|
89
93
|
mini_portile2 (~> 2.8.0)
|
90
94
|
racc (~> 1.4)
|
95
|
+
oauth2 (2.0.9)
|
96
|
+
faraday (>= 0.17.3, < 3.0)
|
97
|
+
jwt (>= 1.0, < 3.0)
|
98
|
+
multi_xml (~> 0.5)
|
99
|
+
rack (>= 1.2, < 4)
|
100
|
+
snaky_hash (~> 2.0)
|
101
|
+
version_gem (~> 1.1)
|
91
102
|
omniauth (2.1.1)
|
92
103
|
hashie (>= 3.4.6)
|
93
104
|
rack (>= 2.2.3)
|
94
105
|
rack-protection
|
106
|
+
omniauth-apple (1.3.0)
|
107
|
+
json-jwt
|
108
|
+
omniauth-oauth2
|
109
|
+
omniauth-auth0 (3.1.0)
|
110
|
+
omniauth (~> 2)
|
111
|
+
omniauth-oauth2 (~> 1)
|
112
|
+
omniauth-discord (1.0.0)
|
113
|
+
omniauth
|
114
|
+
omniauth-oauth2
|
115
|
+
omniauth-github (2.0.1)
|
116
|
+
omniauth (~> 2.0)
|
117
|
+
omniauth-oauth2 (~> 1.8)
|
118
|
+
omniauth-gitlab (4.1.0)
|
119
|
+
omniauth (~> 2.0)
|
120
|
+
omniauth-oauth2 (~> 1.8.0)
|
121
|
+
omniauth-google-oauth2 (1.1.1)
|
122
|
+
jwt (>= 2.0)
|
123
|
+
oauth2 (~> 2.0.6)
|
124
|
+
omniauth (~> 2.0)
|
125
|
+
omniauth-oauth2 (~> 1.8.0)
|
126
|
+
omniauth-oauth2 (1.8.0)
|
127
|
+
oauth2 (>= 1.4, < 3)
|
128
|
+
omniauth (~> 2.0)
|
129
|
+
omniauth-saml (2.1.0)
|
130
|
+
omniauth (~> 2.0)
|
131
|
+
ruby-saml (~> 1.12)
|
95
132
|
openid_connect (2.2.0)
|
96
133
|
activemodel
|
97
134
|
attr_required (>= 1.0.0)
|
@@ -117,26 +154,19 @@ GEM
|
|
117
154
|
rack (>= 2.1.0)
|
118
155
|
rack-protection (3.0.5)
|
119
156
|
rack
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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)
|
157
|
+
rexml (3.2.5)
|
158
|
+
ruby-saml (1.15.0)
|
159
|
+
nokogiri (>= 1.13.10)
|
160
|
+
rexml
|
134
161
|
ruby2_keywords (0.0.5)
|
135
162
|
sinatra (3.0.5)
|
136
163
|
mustermann (~> 3.0)
|
137
164
|
rack (~> 2.2, >= 2.2.4)
|
138
165
|
rack-protection (= 3.0.5)
|
139
166
|
tilt (~> 2.0)
|
167
|
+
snaky_hash (2.0.1)
|
168
|
+
hashie
|
169
|
+
version_gem (~> 1.1, >= 1.1.1)
|
140
170
|
swd (2.0.2)
|
141
171
|
activesupport (>= 3)
|
142
172
|
attr_required (>= 0.0.5)
|
@@ -152,6 +182,7 @@ GEM
|
|
152
182
|
validate_url (1.0.15)
|
153
183
|
activemodel (>= 3.0.0)
|
154
184
|
public_suffix
|
185
|
+
version_gem (1.1.2)
|
155
186
|
webfinger (2.1.2)
|
156
187
|
activesupport
|
157
188
|
faraday (~> 2.0)
|
@@ -161,11 +192,18 @@ PLATFORMS
|
|
161
192
|
ruby
|
162
193
|
|
163
194
|
DEPENDENCIES
|
195
|
+
apigatewayv2_rack (>= 0.1.3)
|
164
196
|
himari!
|
165
197
|
himari-aws!
|
166
198
|
nokogiri
|
167
|
-
|
168
|
-
|
199
|
+
omniauth-apple
|
200
|
+
omniauth-auth0
|
201
|
+
omniauth-discord
|
202
|
+
omniauth-github
|
203
|
+
omniauth-gitlab
|
204
|
+
omniauth-google-oauth2
|
205
|
+
omniauth-oauth2
|
206
|
+
omniauth-saml
|
169
207
|
|
170
208
|
BUNDLED WITH
|
171
|
-
2.
|
209
|
+
2.3.21
|
data/lambda/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# Himari Lambda Container Image
|
2
|
+
|
3
|
+
## Deploy
|
4
|
+
|
5
|
+
- See [./terraform/](./terraform/) for quick deployment using Terraform modules.
|
6
|
+
|
7
|
+
## Image
|
8
|
+
|
9
|
+
### Prebuilt image
|
10
|
+
|
11
|
+
- https://gallery.ecr.aws/sorah/himari-lambda
|
12
|
+
- `public.ecr.aws/sorah/himari-lambda`
|
13
|
+
|
14
|
+
Images are tagged with commit SHA.
|
15
|
+
|
16
|
+
### Build an image
|
17
|
+
|
18
|
+
Run the following at the repository root:
|
19
|
+
|
20
|
+
```
|
21
|
+
docker build -f himari-aws/lambda/Dockerfile .
|
22
|
+
```
|
23
|
+
|
24
|
+
### Usage
|
25
|
+
|
26
|
+
The same container image supports multiple handlers:
|
27
|
+
|
28
|
+
#### Rack app for API Gateway v2, Function URL, ALB target
|
29
|
+
|
30
|
+
- Handler: `himari_lambda_entrypoint.Himari::Aws::LambdaHandler.rack_handler`
|
31
|
+
|
32
|
+
Served through [apigatewayv2_rack](https://github.com/sorah/apigatewayv2_rack).
|
33
|
+
|
34
|
+
This handler reads `config.ru` from:
|
35
|
+
|
36
|
+
- `${LAMBDA_TASK_ROOT}/config.ru` in a container image
|
37
|
+
- DynamoDB Table item (pk=`rack`, sk=`rack:${HIMARI_RACK_DIGEST}`, file=config.ru content) on table `$HIMARI_RACK_DYNAMODB_TABLE`
|
38
|
+
- where HIMARI_RACK_DIGEST must be [base64'd sha256 hash](https://developer.hashicorp.com/terraform/language/functions/base64sha256) of `file` attribute
|
39
|
+
|
40
|
+
#### Secrets Manager automatic rotation handler
|
41
|
+
|
42
|
+
- Handler: `himari_lambda_entrypoint.Himari::Aws::LambdaHandler.secrets_rotation_handler`
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Himari Terraform modules for AWS Lambda
|
2
|
+
|
3
|
+
himari-aws/lambda/terraform provides the following modules:
|
4
|
+
|
5
|
+
- **iam:** to establish IAM role
|
6
|
+
- **image:** to copy prebuilt container image for Lambda from ECR Public
|
7
|
+
- **functions:** to deploy Lambda function
|
8
|
+
- **signing_key:** to create a secret with auto rotation enabled on Secrets Manager
|
9
|
+
|
10
|
+
## iam
|
11
|
+
|
12
|
+
Provisions IAM role for Lambda functions.
|
13
|
+
|
14
|
+
```terraform
|
15
|
+
module "himari_iam" {
|
16
|
+
source = "github.com/sorah/himari//himari-aws/lambda/terraform/iam"
|
17
|
+
|
18
|
+
role_name = "HimariRole"
|
19
|
+
|
20
|
+
# for policy hardening
|
21
|
+
secrets_rotation_function_arn = module.himari_functions.secrets_rotation_function_arn
|
22
|
+
|
23
|
+
# Add grants
|
24
|
+
dynamodb_table_arn = module.himari_functions.dynamodb_table_arn
|
25
|
+
secret_arns = toset([module.himari_signing_key.secret_arn])
|
26
|
+
}
|
27
|
+
```
|
28
|
+
|
29
|
+
## image
|
30
|
+
|
31
|
+
Create ECR private repository then mirror specified image tag from https://gallery.ecr.aws/sorah/himari-lambda
|
32
|
+
|
33
|
+
```terraform
|
34
|
+
module "himari_image" {
|
35
|
+
source = "github.com/sorah/himari//himari-aws/lambda/terraform/image"
|
36
|
+
|
37
|
+
repository_name = "himari-lambda"
|
38
|
+
source_image_tag = "" # Replace with image tag
|
39
|
+
}
|
40
|
+
```
|
41
|
+
|
42
|
+
- Uses null_resource with `docker` command to perform pull, retag and push to ECR private locally
|
43
|
+
- Prebuilt image tag is based on git commit SHA: https://github.com/sorah/himari/commits/main
|
44
|
+
|
45
|
+
## functions
|
46
|
+
|
47
|
+
Deploy lambda functions and DynamoDB table
|
48
|
+
|
49
|
+
```terraform
|
50
|
+
module "himari_functions" {
|
51
|
+
source = "github.com/sorah/himari//himari-aws/lambda/terraform/functions"
|
52
|
+
|
53
|
+
iam_role_arn = module.himari_iam.role_arn
|
54
|
+
image_url = module.himari_image.image.url
|
55
|
+
|
56
|
+
dynamodb_table_name = "himari"
|
57
|
+
function_name_prefix = "himari"
|
58
|
+
|
59
|
+
config_ru = file("${path.module}/config.ru")
|
60
|
+
|
61
|
+
environment = {
|
62
|
+
HIMARI_SIGNING_KEY_ARN = module.himari_signing_key.secret_arn
|
63
|
+
HIMARI_SECRET_PARAMS_ARN = aws_secretsmanager_secret.params.arn
|
64
|
+
}
|
65
|
+
}
|
66
|
+
```
|
67
|
+
|
68
|
+
## signing_key
|
69
|
+
|
70
|
+
```terraform
|
71
|
+
module "himari_signing_key" {
|
72
|
+
source = "github.com/sorah/himari//himari-aws/lambda/terraform/signing_key"
|
73
|
+
|
74
|
+
secret_name = "himari-prd-signing-key"
|
75
|
+
|
76
|
+
rotation_function_arn = module.himari_functions.secrets_rotation_function_arn
|
77
|
+
rotate_automatically_after_days = 20
|
78
|
+
}
|
79
|
+
```
|
80
|
+
|
81
|
+
## misc (not modules)
|
82
|
+
|
83
|
+
Use secrets manager to store additional secrets like upstream client secrets and SECRET_KEY_BASE...
|
84
|
+
|
85
|
+
```terraform
|
86
|
+
resource "aws_secretsmanager_secret" "params" {
|
87
|
+
name = "himari-secret-params"
|
88
|
+
}
|
89
|
+
```
|
@@ -0,0 +1,18 @@
|
|
1
|
+
resource "aws_dynamodb_table" "table" {
|
2
|
+
count = var.create_dynamodb_table ? 1 : 0
|
3
|
+
|
4
|
+
name = var.dynamodb_table_name
|
5
|
+
billing_mode = "PAY_PER_REQUEST"
|
6
|
+
hash_key = "pk"
|
7
|
+
range_key = "sk"
|
8
|
+
|
9
|
+
attribute {
|
10
|
+
name = "pk"
|
11
|
+
type = "S"
|
12
|
+
}
|
13
|
+
|
14
|
+
attribute {
|
15
|
+
name = "sk"
|
16
|
+
type = "S"
|
17
|
+
}
|
18
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
resource "aws_lambda_function" "rack" {
|
2
|
+
count = var.deploy_rack ? 1 : 0
|
3
|
+
|
4
|
+
function_name = "${var.function_name_prefix}-rack"
|
5
|
+
|
6
|
+
package_type = "Image"
|
7
|
+
image_uri = var.image_url
|
8
|
+
architectures = var.architectures
|
9
|
+
|
10
|
+
image_config {
|
11
|
+
command = ["himari_lambda_entrypoint.Himari::Aws::LambdaHandler.rack_handler"]
|
12
|
+
}
|
13
|
+
|
14
|
+
role = var.iam_role_arn
|
15
|
+
|
16
|
+
memory_size = var.rack_memory_size
|
17
|
+
timeout = 20
|
18
|
+
|
19
|
+
environment {
|
20
|
+
variables = merge({
|
21
|
+
HIMARI_RACK_DYNAMODB_TABLE = var.dynamodb_table_name
|
22
|
+
HIMARI_DYNAMODB_TABLE = var.dynamodb_table_name
|
23
|
+
|
24
|
+
# dependency trick. see below
|
25
|
+
HIMARI_RACK_DIGEST = nonsensitive(jsondecode(aws_dynamodb_table_item.config_ru[local.config_ru_dgst].item)["dgst"]["S"])
|
26
|
+
|
27
|
+
RACK_ENV = "production"
|
28
|
+
}, var.environment)
|
29
|
+
}
|
30
|
+
|
31
|
+
depends_on = [aws_dynamodb_table_item.config_ru]
|
32
|
+
}
|
33
|
+
|
34
|
+
resource "aws_lambda_function_url" "rack" {
|
35
|
+
count = (var.deploy_rack && var.enable_function_url) ? 1 : 0
|
36
|
+
function_name = aws_lambda_function.rack[0].function_name
|
37
|
+
authorization_type = "NONE"
|
38
|
+
}
|
39
|
+
|
40
|
+
resource "aws_dynamodb_table_item" "config_ru" {
|
41
|
+
# Employing dependency trick to make sure a previous config_ru item removed after the function environment value update.
|
42
|
+
# By using for_each every updates will be a new resource and is referred by aws_lambda_function as a dependency, old item will be removed after updating the function completes.
|
43
|
+
for_each = { "${local.config_ru_dgst}" = var.config_ru }
|
44
|
+
|
45
|
+
table_name = var.dynamodb_table_name
|
46
|
+
hash_key = "pk"
|
47
|
+
range_key = "sk"
|
48
|
+
|
49
|
+
# using sensitive to supress unwanted diff, and diff is useful as we use for_each here
|
50
|
+
item = sensitive(jsonencode({
|
51
|
+
"pk" = { "S" = "rack" },
|
52
|
+
"sk" = { "S" = "rack:${each.key}" },
|
53
|
+
"dgst" = { "S" = each.key },
|
54
|
+
"file" = { "S" = each.value },
|
55
|
+
}))
|
56
|
+
|
57
|
+
lifecycle {
|
58
|
+
create_before_destroy = true
|
59
|
+
}
|
60
|
+
|
61
|
+
depends_on = [aws_dynamodb_table.table]
|
62
|
+
}
|
63
|
+
|
64
|
+
locals {
|
65
|
+
config_ru_dgst = base64sha256(var.config_ru)
|
66
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
resource "aws_lambda_function" "secrets_rotation" {
|
2
|
+
count = var.deploy_secrets_rotation ? 1 : 0
|
3
|
+
|
4
|
+
function_name = "${var.function_name_prefix}-secrets-rotation"
|
5
|
+
|
6
|
+
package_type = "Image"
|
7
|
+
image_uri = var.image_url
|
8
|
+
architectures = var.architectures
|
9
|
+
|
10
|
+
image_config {
|
11
|
+
command = ["himari_lambda_entrypoint.Himari::Aws::LambdaHandler.secrets_rotation_handler"]
|
12
|
+
}
|
13
|
+
|
14
|
+
role = var.iam_role_arn
|
15
|
+
|
16
|
+
memory_size = 128
|
17
|
+
timeout = 20
|
18
|
+
|
19
|
+
environment {
|
20
|
+
variables = merge({
|
21
|
+
}, var.environment)
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
resource "aws_lambda_permission" "secrets_rotation_secretsmanager" {
|
26
|
+
count = var.deploy_secrets_rotation ? 1 : 0
|
27
|
+
|
28
|
+
statement_id = "secretsmanager"
|
29
|
+
action = "lambda:InvokeFunction"
|
30
|
+
function_name = aws_lambda_function.secrets_rotation[0].function_name
|
31
|
+
principal = "secretsmanager.amazonaws.com"
|
32
|
+
source_account = data.aws_caller_identity.current.account_id
|
33
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
output "dynamodb_table_name" {
|
2
|
+
value = var.dynamodb_table_name
|
3
|
+
}
|
4
|
+
|
5
|
+
output "dynamodb_table_arn" {
|
6
|
+
value = var.create_dynamodb_table ? aws_dynamodb_table.table[0].arn : null
|
7
|
+
}
|
8
|
+
|
9
|
+
output "function_url" {
|
10
|
+
value = (var.deploy_rack && var.enable_function_url) ? aws_lambda_function_url.rack[0].function_url : null
|
11
|
+
}
|
12
|
+
|
13
|
+
output "rack_function_arn" {
|
14
|
+
value = var.deploy_rack ? aws_lambda_function.rack[0].arn : null
|
15
|
+
}
|
16
|
+
|
17
|
+
output "secrets_rotation_function_arn" {
|
18
|
+
value = var.deploy_secrets_rotation ? aws_lambda_function.secrets_rotation[0].arn : null
|
19
|
+
}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
variable "iam_role_arn" {
|
2
|
+
type = string
|
3
|
+
description = "IAM role arn for functions"
|
4
|
+
}
|
5
|
+
|
6
|
+
variable "image_url" {
|
7
|
+
type = string
|
8
|
+
description = "Image url for deploy"
|
9
|
+
}
|
10
|
+
|
11
|
+
variable "dynamodb_table_name" {
|
12
|
+
type = string
|
13
|
+
description = "dynamodb table name to use"
|
14
|
+
}
|
15
|
+
|
16
|
+
variable "create_dynamodb_table" {
|
17
|
+
type = bool
|
18
|
+
description = "Create dynamodb table"
|
19
|
+
default = true
|
20
|
+
}
|
21
|
+
|
22
|
+
variable "function_name_prefix" {
|
23
|
+
type = string
|
24
|
+
description = "function name prefix"
|
25
|
+
}
|
26
|
+
|
27
|
+
variable "deploy_rack" {
|
28
|
+
type = bool
|
29
|
+
description = "Deploy rack function"
|
30
|
+
default = true
|
31
|
+
}
|
32
|
+
|
33
|
+
variable "rack_memory_size" {
|
34
|
+
type = number
|
35
|
+
default = 256
|
36
|
+
}
|
37
|
+
|
38
|
+
variable "deploy_secrets_rotation" {
|
39
|
+
type = bool
|
40
|
+
description = "Deploy secrets rotation function"
|
41
|
+
default = true
|
42
|
+
}
|
43
|
+
|
44
|
+
variable "enable_function_url" {
|
45
|
+
type = bool
|
46
|
+
description = "Enable function URL"
|
47
|
+
default = true
|
48
|
+
}
|
49
|
+
|
50
|
+
variable "config_ru" {
|
51
|
+
type = string
|
52
|
+
description = "File content of config.ru"
|
53
|
+
default = "raise 'empty config_ru'\n"
|
54
|
+
}
|
55
|
+
|
56
|
+
variable "environment" {
|
57
|
+
type = map(any)
|
58
|
+
description = "Additional environment variables"
|
59
|
+
default = {}
|
60
|
+
}
|
61
|
+
|
62
|
+
variable "architectures" {
|
63
|
+
type = list(string)
|
64
|
+
default = ["x86_64"]
|
65
|
+
}
|
@@ -0,0 +1,77 @@
|
|
1
|
+
resource "aws_iam_role" "role" {
|
2
|
+
name = var.role_name
|
3
|
+
description = var.role_description
|
4
|
+
assume_role_policy = data.aws_iam_policy_document.role-trust.json
|
5
|
+
permissions_boundary = var.role_permissions_boundary
|
6
|
+
}
|
7
|
+
|
8
|
+
data "aws_iam_policy_document" "role-trust" {
|
9
|
+
statement {
|
10
|
+
effect = "Allow"
|
11
|
+
actions = ["sts:AssumeRole"]
|
12
|
+
principals {
|
13
|
+
type = "Service"
|
14
|
+
identifiers = [
|
15
|
+
"lambda.amazonaws.com"
|
16
|
+
]
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
resource "aws_iam_role_policy_attachment" "role-AWSLambdaBasicExecutionRole" {
|
22
|
+
role = aws_iam_role.role.name
|
23
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
24
|
+
}
|
25
|
+
|
26
|
+
resource "aws_iam_role_policy" "role-dynamodb" {
|
27
|
+
count = local.dynamodb_table_arn != null ? 1 : 0
|
28
|
+
role = aws_iam_role.role.name
|
29
|
+
policy = data.aws_iam_policy_document.role-dynamodb.json
|
30
|
+
}
|
31
|
+
|
32
|
+
data "aws_iam_policy_document" "role-dynamodb" {
|
33
|
+
statement {
|
34
|
+
effect = "Allow"
|
35
|
+
actions = [
|
36
|
+
"dynamodb:DeleteItem",
|
37
|
+
"dynamodb:Query",
|
38
|
+
"dynamodb:UpdateItem",
|
39
|
+
]
|
40
|
+
resources = [local.dynamodb_table_arn]
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
resource "aws_iam_role_policy" "role-secretsmanager" {
|
45
|
+
count = length(var.secret_arns) > 0 ? 1 : 0
|
46
|
+
role = aws_iam_role.role.name
|
47
|
+
policy = data.aws_iam_policy_document.role-secretsmanager.json
|
48
|
+
}
|
49
|
+
|
50
|
+
data "aws_iam_policy_document" "role-secretsmanager" {
|
51
|
+
statement {
|
52
|
+
effect = "Allow"
|
53
|
+
actions = [
|
54
|
+
"secretsmanager:DescribeSecret",
|
55
|
+
"secretsmanager:GetSecretValue",
|
56
|
+
|
57
|
+
]
|
58
|
+
resources = toset(var.secret_arns)
|
59
|
+
}
|
60
|
+
statement {
|
61
|
+
effect = "Allow"
|
62
|
+
actions = [
|
63
|
+
"secretsmanager:PutSecretValue",
|
64
|
+
"secretsmanager:UpdateSecretVersionStage",
|
65
|
+
]
|
66
|
+
dynamic "condition" {
|
67
|
+
for_each = var.secrets_rotation_function_arn != null ? { hardening = true } : {}
|
68
|
+
content {
|
69
|
+
test = "StringEquals"
|
70
|
+
variable = "lambda:SourceFunctionArn"
|
71
|
+
values = [var.secrets_rotation_function_arn]
|
72
|
+
}
|
73
|
+
}
|
74
|
+
resources = toset(var.secret_arns)
|
75
|
+
}
|
76
|
+
|
77
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
variable "role_name" {
|
2
|
+
type = string
|
3
|
+
description = "IAM Role name to create"
|
4
|
+
}
|
5
|
+
|
6
|
+
variable "role_description" {
|
7
|
+
type = string
|
8
|
+
description = "IAM Role description to specify"
|
9
|
+
default = "sorah/himari lambda function role"
|
10
|
+
}
|
11
|
+
|
12
|
+
variable "role_permissions_boundary" {
|
13
|
+
type = string
|
14
|
+
description = "IAM Role permissions boundary to specify"
|
15
|
+
default = null
|
16
|
+
}
|
17
|
+
|
18
|
+
variable "dynamodb_table_arn" {
|
19
|
+
type = string
|
20
|
+
description = "DynamoDB Table ARN"
|
21
|
+
default = null
|
22
|
+
}
|
23
|
+
|
24
|
+
variable "dynamodb_table_name" {
|
25
|
+
type = string
|
26
|
+
description = "DynamoDB Table name"
|
27
|
+
default = null
|
28
|
+
}
|
29
|
+
|
30
|
+
variable "secret_arns" {
|
31
|
+
type = set(string)
|
32
|
+
description = "Secrets Manager secret ARNs"
|
33
|
+
default = null
|
34
|
+
}
|
35
|
+
|
36
|
+
variable "secrets_rotation_function_arn" {
|
37
|
+
type = string
|
38
|
+
description = "ARN of rotation function. If set, it will be used to harden the write action policy"
|
39
|
+
default = null
|
40
|
+
}
|
41
|
+
|
42
|
+
locals {
|
43
|
+
dynamodb_table_arn = coalesce(var.dynamodb_table_arn, var.dynamodb_table_name != null ? "arn:aws:dynamodb:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:table/${var.dynamodb_table_name}" : null)
|
44
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
data "aws_region" "current" {}
|
@@ -0,0 +1,5 @@
|
|
1
|
+
#!/bin/bash -xe
|
2
|
+
aws ecr get-login-password | docker login --username AWS --password-stdin "${REPOSITORY_URL}"
|
3
|
+
docker pull "public.ecr.aws/sorah/himari-lambda:${SOURCE_IMAGE_TAG}"
|
4
|
+
docker tag "public.ecr.aws/sorah/himari-lambda:${SOURCE_IMAGE_TAG}" "${REPOSITORY_URL}:${SOURCE_IMAGE_TAG}"
|
5
|
+
docker push "${REPOSITORY_URL}:${SOURCE_IMAGE_TAG}"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
resource "null_resource" "copy-image" {
|
2
|
+
triggers = {
|
3
|
+
region = data.aws_region.current.name
|
4
|
+
repository_url = aws_ecr_repository.repo.repository_url
|
5
|
+
source_image_tag = var.source_image_tag
|
6
|
+
}
|
7
|
+
provisioner "local-exec" {
|
8
|
+
command = "cd ${path.module} && ./copy.sh"
|
9
|
+
environment = {
|
10
|
+
AWS_REGION = data.aws_region.current.name
|
11
|
+
AWS_DEFAULT_REGION = data.aws_region.current.name
|
12
|
+
|
13
|
+
REPOSITORY_URL = aws_ecr_repository.repo.repository_url
|
14
|
+
SOURCE_IMAGE_TAG = var.source_image_tag
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
resource "aws_ecr_repository" "repo" {
|
2
|
+
name = var.repository_name
|
3
|
+
}
|
4
|
+
|
5
|
+
resource "aws_ecr_repository_policy" "repo-lambda" {
|
6
|
+
repository = aws_ecr_repository.repo.name
|
7
|
+
policy = data.aws_iam_policy_document.repo-lambda.json
|
8
|
+
}
|
9
|
+
|
10
|
+
data "aws_iam_policy_document" "repo-lambda" {
|
11
|
+
statement {
|
12
|
+
effect = "Allow"
|
13
|
+
principals {
|
14
|
+
type = "Service"
|
15
|
+
identifiers = ["lambda.amazonaws.com"]
|
16
|
+
}
|
17
|
+
actions = [
|
18
|
+
"ecr:BatchGetImage",
|
19
|
+
"ecr:GetDownloadUrlForLayer"
|
20
|
+
]
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
resource "aws_ecr_lifecycle_policy" "repo" {
|
25
|
+
repository = aws_ecr_repository.repo.name
|
26
|
+
policy = jsonencode({
|
27
|
+
rules = [
|
28
|
+
{
|
29
|
+
rulePriority = 10
|
30
|
+
description = "expire old images"
|
31
|
+
selection = {
|
32
|
+
tagStatus = "any"
|
33
|
+
countType = "imageCountMoreThan"
|
34
|
+
countNumber = 10
|
35
|
+
}
|
36
|
+
action = {
|
37
|
+
type = "expire"
|
38
|
+
}
|
39
|
+
}
|
40
|
+
]
|
41
|
+
})
|
42
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
variable "repository_name" {
|
2
|
+
type = string
|
3
|
+
description = "Repository name to create on your AWS account"
|
4
|
+
}
|
5
|
+
|
6
|
+
variable "source_image_tag" {
|
7
|
+
type = string
|
8
|
+
description = "Image tag for public.ecr.aws/sorah/himari-lambda. You can use Git commit hash on https://github.com/sorah/himari"
|
9
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
data "aws_region" "current" {}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
resource "aws_secretsmanager_secret" "signing_key" {
|
2
|
+
name = var.secret_name
|
3
|
+
|
4
|
+
tags = {
|
5
|
+
HimariKey = base64encode(jsonencode(var.keygen_params))
|
6
|
+
}
|
7
|
+
}
|
8
|
+
|
9
|
+
resource "aws_secretsmanager_secret_rotation" "signing_key" {
|
10
|
+
secret_id = aws_secretsmanager_secret.signing_key.id
|
11
|
+
rotation_lambda_arn = var.rotation_function_arn
|
12
|
+
|
13
|
+
# XXX: https://github.com/hashicorp/terraform-provider-aws/issues/22969
|
14
|
+
rotation_rules {
|
15
|
+
automatically_after_days = 16
|
16
|
+
}
|
17
|
+
|
18
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
variable "secret_name" {
|
2
|
+
type = string
|
3
|
+
description = "Secret name to create"
|
4
|
+
}
|
5
|
+
|
6
|
+
variable "rotation_function_arn" {
|
7
|
+
type = string
|
8
|
+
description = "Lambda function ARN to handle secret rotation"
|
9
|
+
}
|
10
|
+
|
11
|
+
variable "rotate_automatically_after_days" {
|
12
|
+
type = number
|
13
|
+
description = "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_rotation#automatically_after_days"
|
14
|
+
default = 16
|
15
|
+
}
|
16
|
+
|
17
|
+
variable "keygen_params" {
|
18
|
+
type = object({ kty = string, len = string })
|
19
|
+
description = "keygen params"
|
20
|
+
default = {
|
21
|
+
"kty" = "rsa"
|
22
|
+
"len" = 2048
|
23
|
+
}
|
24
|
+
}
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'himari'
|
2
|
+
require 'himari/aws/secretsmanager_signing_key_rotation_handler'
|
3
|
+
|
4
|
+
require 'digest/sha2'
|
5
|
+
require 'aws-sdk-dynamodb'
|
6
|
+
require 'apigatewayv2_rack'
|
7
|
+
|
8
|
+
$stdout.sync = true
|
9
|
+
|
10
|
+
module Himari
|
11
|
+
module Aws
|
12
|
+
module LambdaHandler
|
13
|
+
def self.app
|
14
|
+
@app ||= make_app()
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.config_ru
|
18
|
+
a = Time.now
|
19
|
+
retval = config_ru_from_task_root || config_ru_from_dynamodb
|
20
|
+
b = Time.now
|
21
|
+
$stdout.puts(JSON.generate(config_ru: {ts: b, elapsed_time: b-a}))
|
22
|
+
retval
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.config_ru_from_task_root
|
26
|
+
return nil unless ENV['LAMBDA_TASK_ROOT']
|
27
|
+
File.read(File.join(ENV['LAMBDA_TASK_ROOT'], 'config.ru'))
|
28
|
+
rescue Errno::ENOENT, Errno::EPERM
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.config_ru_from_dynamodb
|
33
|
+
dgst = ENV.fetch('HIMARI_RACK_DIGEST')
|
34
|
+
table_name = ENV.fetch('HIMARI_RACK_DYNAMODB_TABLE')
|
35
|
+
pk, sk = "rack", "rack:#{dgst}"
|
36
|
+
|
37
|
+
ddb = ::Aws::DynamoDB::Client.new()
|
38
|
+
item = ddb.query(
|
39
|
+
table_name: table_name,
|
40
|
+
select: 'ALL_ATTRIBUTES',
|
41
|
+
limit: 1,
|
42
|
+
key_condition_expression: 'pk = :pk AND sk = :sk',
|
43
|
+
expression_attribute_values: {":pk" => pk, ":sk" => sk},
|
44
|
+
).items.first
|
45
|
+
|
46
|
+
unless item
|
47
|
+
raise "item not found (pk=#{pk.inspect}, sk=#{sk.inspect}) on dynamodb table #{table_name} for config.ru"
|
48
|
+
end
|
49
|
+
|
50
|
+
content = item.fetch('file')
|
51
|
+
content_dgst = Digest::SHA256.digest(content)
|
52
|
+
raise "config.ru item content digest mismatch" if content_dgst != Base64.decode64(dgst)
|
53
|
+
|
54
|
+
content
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.make_app
|
58
|
+
require 'rack'
|
59
|
+
require 'rack/builder'
|
60
|
+
Rack::Builder.new_from_string(config_ru)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.rack_handler(event:, context:)
|
64
|
+
Apigatewayv2Rack.handle_request(event: event, context: context, app: app)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.secrets_rotation_handler(event:, context:)
|
68
|
+
Himari::Aws::SecretsmanagerSigningKeyRotationHandler.handler(event: event, context: context)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -71,7 +71,8 @@ module Himari
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def self.generate_secret(req, _current)
|
74
|
-
|
74
|
+
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}')
|
75
|
+
param = parse_keygen_param(param_raw)
|
75
76
|
puts "createSecret: generate_secret with #{param.inspect}"
|
76
77
|
|
77
78
|
case param.fetch(:kty, 'rsa').downcase
|
@@ -93,6 +94,27 @@ module Himari
|
|
93
94
|
end
|
94
95
|
end
|
95
96
|
|
97
|
+
# Scan k=v,k2=v2
|
98
|
+
def self.parse_keygen_param(str)
|
99
|
+
begin
|
100
|
+
ary = str.scan(/(.+?)=(.+?)(?:,|$)/)
|
101
|
+
unless ary.empty?
|
102
|
+
return ary.to_h.transform_keys(&:to_sym)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if str.start_with?('eyJ')
|
107
|
+
return JSON.parse(Base64.decode64(str), symbolize_names: true)
|
108
|
+
end
|
109
|
+
|
110
|
+
begin
|
111
|
+
return JSON.parse(str, symbolize_names: true)
|
112
|
+
rescue JSON::ParserError
|
113
|
+
end
|
114
|
+
|
115
|
+
raise "cannot parse keygen param #{str.inspect}"
|
116
|
+
end
|
117
|
+
|
96
118
|
def self.set_secret(req)
|
97
119
|
_check = @secretsmanager.get_secret_value(secret_id: req.id, version_id: req.token, version_stage: 'AWSPENDING')
|
98
120
|
puts "setSecret: do nothing for #{req.token} @ #{req.id}"
|
data/lib/himari/aws/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: himari-aws
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sorah Fukumori
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-03-
|
11
|
+
date: 2023-03-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: himari
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: apigatewayv2_rack
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description:
|
56
70
|
email:
|
57
71
|
- her@sorah.jp
|
@@ -60,14 +74,43 @@ extensions: []
|
|
60
74
|
extra_rdoc_files: []
|
61
75
|
files:
|
62
76
|
- ".rspec"
|
63
|
-
- Gemfile
|
64
|
-
- Gemfile.lock
|
65
77
|
- LICENSE.txt
|
66
78
|
- README.md
|
67
79
|
- Rakefile
|
80
|
+
- lambda/Dockerfile
|
81
|
+
- lambda/Gemfile
|
82
|
+
- lambda/Gemfile.lock
|
83
|
+
- lambda/README.md
|
84
|
+
- lambda/entrypoint.rb
|
85
|
+
- lambda/terraform/README.md
|
86
|
+
- lambda/terraform/functions/aws.tf
|
87
|
+
- lambda/terraform/functions/dynamodb.tf
|
88
|
+
- lambda/terraform/functions/lambda_rack.tf
|
89
|
+
- lambda/terraform/functions/lambda_secrets_rotation.tf
|
90
|
+
- lambda/terraform/functions/outputs.tf
|
91
|
+
- lambda/terraform/functions/variables.tf
|
92
|
+
- lambda/terraform/functions/versions.tf
|
93
|
+
- lambda/terraform/iam/aws.tf
|
94
|
+
- lambda/terraform/iam/outputs.tf
|
95
|
+
- lambda/terraform/iam/role.tf
|
96
|
+
- lambda/terraform/iam/variables.tf
|
97
|
+
- lambda/terraform/iam/versions.tf
|
98
|
+
- lambda/terraform/image/aws.tf
|
99
|
+
- lambda/terraform/image/copy.sh
|
100
|
+
- lambda/terraform/image/copy.tf
|
101
|
+
- lambda/terraform/image/ecr.tf
|
102
|
+
- lambda/terraform/image/outputs.tf
|
103
|
+
- lambda/terraform/image/variables.tf
|
104
|
+
- lambda/terraform/image/versions.tf
|
105
|
+
- lambda/terraform/signing_key/aws.tf
|
106
|
+
- lambda/terraform/signing_key/outputs.tf
|
107
|
+
- lambda/terraform/signing_key/secret.tf
|
108
|
+
- lambda/terraform/signing_key/variables.tf
|
109
|
+
- lambda/terraform/signing_key/versions.tf
|
68
110
|
- lib/himari-aws.rb
|
69
111
|
- lib/himari/aws.rb
|
70
112
|
- lib/himari/aws/dynamodb_storage.rb
|
113
|
+
- lib/himari/aws/lambda_handler.rb
|
71
114
|
- lib/himari/aws/secretsmanager_signing_key_provider.rb
|
72
115
|
- lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb
|
73
116
|
- lib/himari/aws/version.rb
|
@@ -93,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
136
|
- !ruby/object:Gem::Version
|
94
137
|
version: '0'
|
95
138
|
requirements: []
|
96
|
-
rubygems_version: 3.
|
139
|
+
rubygems_version: 3.1.6
|
97
140
|
signing_key:
|
98
141
|
specification_version: 4
|
99
142
|
summary: AWS related plugins for Himari
|