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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +127 -14
- data/Rakefile +2 -0
- data/lambda/Dockerfile +40 -0
- data/lambda/Gemfile +35 -0
- data/lambda/Gemfile.lock +374 -0
- data/lambda/README.md +42 -0
- data/lambda/entrypoint.rb +5 -0
- data/lambda/terraform/README.md +92 -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 +7 -0
- data/lambda/terraform/iam/role.tf +77 -0
- data/lambda/terraform/iam/variables.tf +44 -0
- data/lambda/terraform/iam/versions.tf +8 -0
- data/lambda/terraform/image/aws.tf +1 -0
- data/lambda/terraform/image/copy.tf +45 -0
- data/lambda/terraform/image/ecr.tf +42 -0
- data/lambda/terraform/image/outputs.tf +9 -0
- data/lambda/terraform/image/variables.tf +20 -0
- data/lambda/terraform/image/versions.tf +9 -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/dynamodb_storage.rb +41 -16
- data/lib/himari/aws/lambda_handler.rb +76 -0
- data/lib/himari/aws/secretsmanager_signing_key_provider.rb +8 -5
- data/lib/himari/aws/secretsmanager_signing_key_rotation_handler.rb +36 -9
- data/lib/himari/aws/version.rb +1 -1
- data/lib/himari-aws.rb +2 -0
- metadata +49 -10
- data/Gemfile +0 -12
- data/Gemfile.lock +0 -171
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
architecture = "x86_64" # or arm64; must match the Lambda architecture
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- Uses null_resource with `skopeo` command to copy the image to ECR private locally (requires `skopeo` on the machine running Terraform)
|
|
44
|
+
- `architecture` selects which platform to copy out of the multi-arch source image (defaults to `x86_64`); set it to match the `architecture` you pass to the functions module
|
|
45
|
+
- Requires Terraform >= 1.10 and AWS provider >= 5.83 (ephemeral resource for ECR credentials)
|
|
46
|
+
- Prebuilt image tag is based on git commit SHA: https://github.com/sorah/himari/commits/main
|
|
47
|
+
|
|
48
|
+
## functions
|
|
49
|
+
|
|
50
|
+
Deploy lambda functions and DynamoDB table
|
|
51
|
+
|
|
52
|
+
```terraform
|
|
53
|
+
module "himari_functions" {
|
|
54
|
+
source = "github.com/sorah/himari//himari-aws/lambda/terraform/functions"
|
|
55
|
+
|
|
56
|
+
iam_role_arn = module.himari_iam.role_arn
|
|
57
|
+
image_url = module.himari_image.image.url
|
|
58
|
+
|
|
59
|
+
dynamodb_table_name = "himari"
|
|
60
|
+
function_name_prefix = "himari"
|
|
61
|
+
|
|
62
|
+
config_ru = file("${path.module}/config.ru")
|
|
63
|
+
|
|
64
|
+
environment = {
|
|
65
|
+
HIMARI_SIGNING_KEY_ARN = module.himari_signing_key.secret_arn
|
|
66
|
+
HIMARI_SECRET_PARAMS_ARN = aws_secretsmanager_secret.params.arn
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## signing_key
|
|
72
|
+
|
|
73
|
+
```terraform
|
|
74
|
+
module "himari_signing_key" {
|
|
75
|
+
source = "github.com/sorah/himari//himari-aws/lambda/terraform/signing_key"
|
|
76
|
+
|
|
77
|
+
secret_name = "himari-prd-signing-key"
|
|
78
|
+
|
|
79
|
+
rotation_function_arn = module.himari_functions.secrets_rotation_function_arn
|
|
80
|
+
rotate_automatically_after_days = 20
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## misc (not modules)
|
|
85
|
+
|
|
86
|
+
Use secrets manager to store additional secrets like upstream client secrets and SECRET_KEY_BASE...
|
|
87
|
+
|
|
88
|
+
```terraform
|
|
89
|
+
resource "aws_secretsmanager_secret" "params" {
|
|
90
|
+
name = "himari-secret-params"
|
|
91
|
+
}
|
|
92
|
+
```
|
|
@@ -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.region}:${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,45 @@
|
|
|
1
|
+
ephemeral "aws_ecr_authorization_token" "repo" {}
|
|
2
|
+
|
|
3
|
+
locals {
|
|
4
|
+
# Map Lambda's architecture notation to the source image's OS/arch so skopeo
|
|
5
|
+
# picks the matching image out of the multi-arch index deterministically,
|
|
6
|
+
# regardless of the host running Terraform.
|
|
7
|
+
skopeo_platform = {
|
|
8
|
+
x86_64 = { os = "linux", arch = "amd64" }
|
|
9
|
+
arm64 = { os = "linux", arch = "arm64" }
|
|
10
|
+
}[var.architecture]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
resource "null_resource" "copy-image" {
|
|
14
|
+
triggers = {
|
|
15
|
+
region = data.aws_region.current.region
|
|
16
|
+
repository_url = aws_ecr_repository.repo.repository_url
|
|
17
|
+
source_image_tag = var.source_image_tag
|
|
18
|
+
architecture = var.architecture
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
provisioner "local-exec" {
|
|
22
|
+
interpreter = ["/bin/bash", "-c"]
|
|
23
|
+
# public.ecr.aws is pulled anonymously; only the destination ECR needs credentials.
|
|
24
|
+
# The password is fed over stdin into a temporary authfile rather than passed as a
|
|
25
|
+
# command-line argument, so it never appears in the process argument list.
|
|
26
|
+
command = <<-EOT
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
authfile="$(mktemp)"
|
|
29
|
+
trap 'rm -f "$authfile"' EXIT
|
|
30
|
+
# skopeo login reads the authfile before merging the new credential into it,
|
|
31
|
+
# so seed it with an empty JSON object; mktemp's 0-byte file is invalid JSON.
|
|
32
|
+
printf '{}' > "$authfile"
|
|
33
|
+
printf '%s' "$DEST_PASSWORD" | skopeo login --username AWS --password-stdin --authfile "$authfile" "$${REPOSITORY_URL%%/*}"
|
|
34
|
+
skopeo copy --authfile "$authfile" --override-os "$OVERRIDE_OS" --override-arch "$OVERRIDE_ARCH" "docker://public.ecr.aws/sorah/himari-lambda:$SOURCE_IMAGE_TAG" "docker://$REPOSITORY_URL:$SOURCE_IMAGE_TAG"
|
|
35
|
+
EOT
|
|
36
|
+
|
|
37
|
+
environment = {
|
|
38
|
+
DEST_PASSWORD = ephemeral.aws_ecr_authorization_token.repo.password
|
|
39
|
+
REPOSITORY_URL = aws_ecr_repository.repo.repository_url
|
|
40
|
+
SOURCE_IMAGE_TAG = var.source_image_tag
|
|
41
|
+
OVERRIDE_OS = local.skopeo_platform.os
|
|
42
|
+
OVERRIDE_ARCH = local.skopeo_platform.arch
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -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,20 @@
|
|
|
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
|
+
}
|
|
10
|
+
|
|
11
|
+
variable "architecture" {
|
|
12
|
+
type = string
|
|
13
|
+
default = "x86_64"
|
|
14
|
+
description = "Lambda CPU architecture to copy from the multi-arch source image (x86_64 or arm64)"
|
|
15
|
+
|
|
16
|
+
validation {
|
|
17
|
+
condition = contains(["x86_64", "arm64"], var.architecture)
|
|
18
|
+
error_message = "architecture must be one of: x86_64, arm64."
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -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
|
+
}
|