Skip to content

Commit

Permalink
feat(containers): create ecr-repos Terraform module (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
p5 authored Sep 1, 2024
1 parent a3419bb commit bf5513f
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 0 deletions.
20 changes: 20 additions & 0 deletions examples/aws/containers/ecr-repos/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module "ecr_repos" {
source = "../../../../modules/aws/containers/ecr-repos"

repositories = {
example = {}
another-example = {
image_tag_mutability = "IMMUTABLE"
external_account_ids_with_read_access = ["586861619874"]
external_account_ids_with_write_access = ["586861619874"]
external_account_ids_with_lambda_access = ["586861619874"]
tags = {
Hello = "World"
}
}
}

tags_all = {
Hi = "There"
}
}
19 changes: 19 additions & 0 deletions examples/aws/containers/ecr-repos/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
output "ecr_repo_arns" {
description = "A map of repository name to its ECR ARN."
value = module.ecr_repos.ecr_repo_arns
}

output "ecr_repo_urls" {
description = "A map of repository name to its URL."
value = module.ecr_repos.ecr_repo_urls
}

output "ecr_read_policy_actions" {
description = "A list of IAM policy actions necessary for ECR read access."
value = module.ecr_repos.ecr_read_policy_actions
}

output "ecr_write_policy_actions" {
description = "A list of IAM policy actions necessary for ECR write access."
value = module.ecr_repos.ecr_write_policy_actions
}
103 changes: 103 additions & 0 deletions modules/aws/containers/ecr-repos/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
data "aws_partition" "current" {}

locals {
# Construct the configuration of ECR repositories that combine the raw user input with the configured defaults.
repositories_with_defaults = {
for repo_name, user_config in var.repositories :
repo_name => {
external_account_ids_with_read_access = lookup(user_config, "external_account_ids_with_read_access", var.default_external_account_ids_with_read_access)
external_account_ids_with_write_access = lookup(user_config, "external_account_ids_with_write_access", var.default_external_account_ids_with_write_access)
external_account_ids_with_lambda_access = lookup(user_config, "external_account_ids_with_lambda_access", var.default_external_account_ids_with_lambda_access)
enable_automatic_image_scanning = lookup(user_config, "enable_automatic_image_scanning", var.default_automatic_image_scanning)
encryption_config = lookup(user_config, "encryption_config", var.default_encryption_config)
image_tag_mutability = lookup(user_config, "image_tag_mutability", var.default_image_tag_mutability)
lifecycle_policy_rules = lookup(user_config, "lifecycle_policy_rules", var.default_lifecycle_policy_rules)
tags = merge(
lookup(user_config, "tags", {}),
var.tags_all,
)
}
}

repositories_with_lifecycle_rules = {
for repo_name, repo in local.repositories_with_defaults :
repo_name => repo if length(repo.lifecycle_policy_rules) > 0
}
repositories_with_external_access = {
for repo_name, repo in local.repositories_with_defaults :
repo_name => repo
if(
length(repo.external_account_ids_with_read_access) > 0
|| length(repo.external_account_ids_with_write_access) > 0
|| length(repo.external_account_ids_with_lambda_access) > 0
)
}

# The list of IAM policy actions for write access
iam_write_access_policies = [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:DescribeRepositories",
"ecr:ListImages",
"ecr:DescribeImages",
"ecr:BatchGetImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
]

# The list of IAM policy actions for read access
iam_read_access_policies = [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:ListImages",
]
}

resource "aws_ecr_repository" "repos" {
for_each = local.repositories_with_defaults

name = each.key
image_tag_mutability = each.value.image_tag_mutability
tags = each.value.tags

image_scanning_configuration {
scan_on_push = each.value.enable_automatic_image_scanning
}

dynamic "encryption_configuration" {
for_each = each.value.encryption_config != null ? ["once"] : []
content {
encryption_type = each.value.encryption_config.encryption_type
kms_key = each.value.encryption_config.kms_key
}
}
}

resource "aws_ecr_lifecycle_policy" "this" {
for_each = local.repositories_with_lifecycle_rules
repository = aws_ecr_repository.repos[each.key].name
policy = jsonencode(each.value.lifecycle_policy_rules)
}

resource "aws_ecr_replication_configuration" "this" {
count = length(var.replication_regions) > 0 ? 1 : 0
replication_configuration {
rule {

dynamic "destination" {
for_each = var.replication_regions
content {
region = destination.value
registry_id = data.aws_caller_identity.current.account_id
}
}
}
}
}
19 changes: 19 additions & 0 deletions modules/aws/containers/ecr-repos/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
output "ecr_repo_arns" {
description = "A map of repository name to its ECR ARN."
value = { for repo_name, repo in aws_ecr_repository.repos : repo_name => repo.arn }
}

output "ecr_repo_urls" {
description = "A map of repository name to its URL."
value = { for repo_name, repo in aws_ecr_repository.repos : repo_name => repo.repository_url }
}

output "ecr_read_policy_actions" {
description = "A list of IAM policy actions necessary for ECR read access."
value = local.iam_read_access_policies
}

output "ecr_write_policy_actions" {
description = "A list of IAM policy actions necessary for ECR write access."
value = local.iam_write_access_policies
}
65 changes: 65 additions & 0 deletions modules/aws/containers/ecr-repos/policy.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
resource "aws_ecr_repository_policy" "external_account_access" {
for_each = local.repositories_with_external_access
repository = aws_ecr_repository.repos[each.key].name
policy = data.aws_iam_policy_document.external_account_access[each.key].json
}

data "aws_iam_policy_document" "external_account_access" {
for_each = local.repositories_with_external_access

dynamic "statement" {
for_each = length(each.value.external_account_ids_with_read_access) > 0 ? ["noop"] : []

content {
effect = "Allow"

principals {
type = "AWS"
identifiers = formatlist("arn:${data.aws_partition.current.partition}:iam::%s:root", each.value.external_account_ids_with_read_access)
}

actions = local.iam_read_access_policies
}
}

dynamic "statement" {
for_each = length(each.value.external_account_ids_with_write_access) > 0 ? ["noop"] : []

content {
effect = "Allow"

principals {
type = "AWS"
identifiers = formatlist("arn:${data.aws_partition.current.partition}:iam::%s:root", each.value.external_account_ids_with_write_access)
}

actions = local.iam_write_access_policies
}
}

dynamic "statement" {
for_each = each.value.external_account_ids_with_lambda_access

content {
effect = "Allow"

principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}

condition {
test = "StringLike"
variable = "aws:sourceARN"

# Allow lambda function access in any of the regions that the ECR repo is created for each account.
values = [
for region in concat([data.aws_region.current.name], var.replication_regions) :
"arn:${data.aws_partition.current.partition}:lambda:${region}:${statement.value}:function:*"
]
}

actions = local.iam_read_access_policies
}
}
}
64 changes: 64 additions & 0 deletions modules/aws/containers/ecr-repos/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
variable "repositories" {
description = "A map of repo names to configurations for that repository."
type = any
}

variable "default_external_account_ids_with_read_access" {
description = "The default list of AWS account IDs for external AWS accounts that should be able to pull images from these ECR repos. Can be overridden on a per repo basis by the external_account_ids_with_read_access property in the repositories map."
type = list(string)
default = []
}

variable "default_external_account_ids_with_write_access" {
description = "The default list of AWS account IDs for external AWS accounts that should be able to pull and push images to these ECR repos. Can be overridden on a per repo basis by the external_account_ids_with_write_access property in the repositories map."
type = list(string)
default = []
}

variable "default_external_account_ids_with_lambda_access" {
description = "The default list of AWS account IDs for external AWS accounts that should be able to create Lambda functions based on container images in these ECR repos. Can be overridden on a per repo basis by the external_account_ids_with_lambda_access property in the repositories map."
type = list(string)
default = []
}

variable "default_automatic_image_scanning" {
description = "Whether or not to enable image scanning on all the repos. Can be overridden on a per repo basis by the enable_automatic_image_scanning property in the repositories map."
type = bool
default = true
}

variable "default_encryption_config" {
description = "The default encryption configuration to apply to the created ECR repository. When null, the images in the ECR repo will not be encrypted at rest. Can be overridden on a per repo basis by the encryption_config property in the repositories map."
type = object({
encryption_type = string
kms_key = string
})
default = {
encryption_type = "AES256"
kms_key = null
}
}

variable "default_image_tag_mutability" {
description = "The tag mutability setting for all the repos. Must be one of: MUTABLE or IMMUTABLE. Can be overridden on a per repo basis by the image_tag_mutability property in the repositories map."
type = string
default = "MUTABLE"
}

variable "tags_all" {
description = "A map of tags (where the key and value correspond to tag keys and values) that should be assigned to all ECR repositories."
type = map(string)
default = {}
}

variable "default_lifecycle_policy_rules" {
description = "Add lifecycle policy to ECR repo."
type = any
default = []
}

variable "replication_regions" {
description = "List of regions (e.g., us-east-1) to replicate the ECR repository to."
type = list(string)
default = []
}
10 changes: 10 additions & 0 deletions modules/aws/containers/ecr-repos/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">=1.3"

required_providers {
aws = {
source = "hashicorp/aws"
version = ">=4.0"
}
}
}

0 comments on commit bf5513f

Please sign in to comment.