Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Cross account ECR for lambda functions #88

Merged
merged 11 commits into from
Mar 21, 2022
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,9 @@ Available targets:
| [aws_iam_policy_document.empty](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.resource](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.resource_full_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.resource_full_lambda_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.resource_readonly_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.resource_readonly_lambda_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |

## Inputs

Expand All @@ -204,6 +206,7 @@ Available targets:
| <a name="input_name"></a> [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.<br>This is the only ID element not also included as a `tag`.<br>The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no |
| <a name="input_namespace"></a> [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no |
| <a name="input_principals_full_access"></a> [principals\_full\_access](#input\_principals\_full\_access) | Principal ARNs to provide with full access to the ECR | `list(string)` | `[]` | no |
| <a name="input_principals_lambda"></a> [principals\_lambda](#input\_principals\_lambda) | Principal account IDs of Lambdas allowed to consume ECR | `list(string)` | `[]` | no |
| <a name="input_principals_readonly_access"></a> [principals\_readonly\_access](#input\_principals\_readonly\_access) | Principal ARNs to provide with readonly access to the ECR | `list(string)` | `[]` | no |
| <a name="input_protected_tags"></a> [protected\_tags](#input\_protected\_tags) | Name of image tags prefixes that should not be destroyed. Useful if you tag images with names like `dev`, `staging`, and `prod` | `set(string)` | `[]` | no |
| <a name="input_regex_replace_chars"></a> [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.<br>Characters matching the regex will be removed from the ID elements.<br>If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
Expand Down
3 changes: 3 additions & 0 deletions docs/terraform.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
| [aws_iam_policy_document.empty](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.resource](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.resource_full_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.resource_full_lambda_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.resource_readonly_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.resource_readonly_lambda_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |

## Inputs

Expand All @@ -54,6 +56,7 @@
| <a name="input_name"></a> [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.<br>This is the only ID element not also included as a `tag`.<br>The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no |
| <a name="input_namespace"></a> [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no |
| <a name="input_principals_full_access"></a> [principals\_full\_access](#input\_principals\_full\_access) | Principal ARNs to provide with full access to the ECR | `list(string)` | `[]` | no |
| <a name="input_principals_lambda"></a> [principals\_lambda](#input\_principals\_lambda) | Principal account IDs of Lambdas allowed to consume ECR | `list(string)` | `[]` | no |
| <a name="input_principals_readonly_access"></a> [principals\_readonly\_access](#input\_principals\_readonly\_access) | Principal ARNs to provide with readonly access to the ECR | `list(string)` | `[]` | no |
| <a name="input_protected_tags"></a> [protected\_tags](#input\_protected\_tags) | Name of image tags prefixes that should not be destroyed. Useful if you tag images with names like `dev`, `staging`, and `prod` | `set(string)` | `[]` | no |
| <a name="input_regex_replace_chars"></a> [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.<br>Characters matching the regex will be removed from the ID elements.<br>If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
Expand Down
128 changes: 126 additions & 2 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
locals {
principals_readonly_access_non_empty = length(var.principals_readonly_access) > 0 ? true : false
principals_readonly_access_lambda = length(var.principals_lambda) > 0 ? true : false
principals_full_access_non_empty = length(var.principals_full_access) > 0 ? true : false
principals_full_access_lambda = length(var.principals_lambda) > 0 ? true : false
dsme94 marked this conversation as resolved.
Show resolved Hide resolved
ecr_need_policy = length(var.principals_full_access) + length(var.principals_readonly_access) > 0 ? true : false
}

Expand Down Expand Up @@ -116,6 +118,73 @@ data "aws_iam_policy_document" "resource_readonly_access" {
}
}

data "aws_iam_policy_document" "resource_readonly_lambda_access" {
dsme94 marked this conversation as resolved.
Show resolved Hide resolved
count = module.this.enabled ? 1 : 0

statement {
sid = "ReadonlyAccess"
effect = "Allow"

principals {
type = "AWS"

identifiers = var.principals_readonly_access
}

actions = [
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:DescribeImageScanFindings",
"ecr:DescribeImages",
"ecr:DescribeRepositories",
"ecr:GetDownloadUrlForLayer",
"ecr:GetLifecyclePolicy",
"ecr:GetLifecyclePolicyPreview",
"ecr:GetRepositoryPolicy",
"ecr:ListImages",
"ecr:ListTagsForResource",
]
}

statement {
sid = "LambdaECRImageCrossAccountRetrievalPolicy"
effect = "Allow"

principals {
type = "Service"

identifiers = ["lambda.amazonaws.com"]
}

actions = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]

condition {
test = "StringLike"
variable = "aws:sourceArn"
values = formatlist("arn:aws:lambda:*:%s:function:*", var.principals_lambda)
dsme94 marked this conversation as resolved.
Show resolved Hide resolved
}
}

statement {
sid = "CrossAccountPermission"
effect = "Allow"

principals {
type = "AWS"

identifiers = var.principals_lambda
}

actions = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
}
}

data "aws_iam_policy_document" "resource_full_access" {
count = module.this.enabled ? 1 : 0

Expand All @@ -133,10 +202,65 @@ data "aws_iam_policy_document" "resource_full_access" {
}
}

data "aws_iam_policy_document" "resource_full_lambda_access" {
dsme94 marked this conversation as resolved.
Show resolved Hide resolved
count = module.this.enabled ? 1 : 0

statement {
sid = "FullAccess"
effect = "Allow"

principals {
type = "AWS"

identifiers = var.principals_full_access
}

actions = ["ecr:*"]
}

statement {
sid = "LambdaECRImageCrossAccountRetrievalPolicy"
effect = "Allow"

principals {
type = "Service"

identifiers = ["lambda.amazonaws.com"]
}

actions = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]

condition {
test = "StringLike"
variable = "aws:sourceArn"
values = formatlist("arn:aws:lambda:*:%s:function:*", var.principals_lambda)
dsme94 marked this conversation as resolved.
Show resolved Hide resolved
}
}

statement {
sid = "CrossAccountPermission"
effect = "Allow"

principals {
type = "AWS"

identifiers = var.principals_lambda
}

actions = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
}
}

data "aws_iam_policy_document" "resource" {
count = module.this.enabled ? 1 : 0
source_json = local.principals_readonly_access_non_empty ? join("", [data.aws_iam_policy_document.resource_readonly_access[0].json]) : join("", [data.aws_iam_policy_document.empty[0].json])
override_json = local.principals_full_access_non_empty ? join("", [data.aws_iam_policy_document.resource_full_access[0].json]) : join("", [data.aws_iam_policy_document.empty[0].json])
source_json = local.principals_readonly_access_lambda && local.principals_readonly_access_non_empty ? join("", [data.aws_iam_policy_document.resource_readonly_lambda_access[0].json]) : local.principals_readonly_access_non_empty ? join("", [data.aws_iam_policy_document.resource_readonly_access[0].json]) : join("", [data.aws_iam_policy_document.empty[0].json])
override_json = local.principals_readonly_access_lambda && local.principals_full_access_non_empty ? join("", [data.aws_iam_policy_document.resource_full_lambda_access[0].json]) : local.principals_full_access_non_empty ? join("", [data.aws_iam_policy_document.resource_full_access[0].json]) : join("", [data.aws_iam_policy_document.empty[0].json])
}

resource "aws_ecr_repository_policy" "name" {
Expand Down
6 changes: 6 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ variable "principals_readonly_access" {
default = []
}

variable "principals_lambda" {
type = list(string)
description = "Principal account IDs of Lambdas allowed to consume ECR"
default = []
}

variable "scan_images_on_push" {
type = bool
description = "Indicates whether images are scanned after being pushed to the repository (true) or not (false)"
Expand Down