DEV Community

Cover image for How I Set Up Cross-Account ECR Access for Lambda Functions
Liran Aknin
Liran Aknin

Posted on

How I Set Up Cross-Account ECR Access for Lambda Functions

The Use Case

Recently, I took on a project to deploy a new Lambda function in each of my AWS accounts to serve as a filter for Guard Duty alerts. Based on internal logic, this function would determine whether it requires immediate handling or can be addressed later. Because this function needed to be deployed across multiple accounts and regions (basically in each of my accounts), I faced the challenge of ensuring it could access a container image stored in a central Elastic Container Registry (ECR) repository in one of my AWS accounts.
This blog will focus on setting up cross-account access to ECR with region replication, rather than the specifics of my GuardDuty setup, which I might cover in a future post.

Reading the AWS documentation gave me a good sense of what configurations are necessary. Nonetheless, in this blog I will show you how I did it - the terraform way, while providing some tips and tricks to achieve the solution for my use case.
Terraform, an Infrastructure as Code (IaC) tool, provides a consistent and automated way to manage resources across multiple AWS accounts and regions. While this blog won't dive into all the details of Terraform, you can learn more about it here.

It is worth mentioning, that this blog requires a basic understanding of Terraform.

Overview

Many organizations in today’s cloud environments centralize container images within a single AWS account to streamline management, versioning, and security. However, additional configurations are required when Lambda functions across different accounts and regions need access to these images.

Following AWS’s same-region access requirement, each Lambda function must be able to access the image in the same region as its deployment. For that reason, I used the ECR’s region replication feature. This allowed the container image to be replicated to the necessary region.

A High-Level Overview of the Solution

<br>
The image illustrates an AWS architecture for cross-account and cross-region ECR (Elastic Container Registry) image replication. Account 111111111111 hosts an ECR registry in us-east-2, replicating images to eu-west-1. Lambda functions in Account 123456789012 (us-east-2) and Account 222222222222 (eu-west-1) pull images from the closest ECR registry.

Now that we have a high-level idea of what must be done, let's jump into the setup.

Some Facts to Consider

I manage multiple repositories in Account ID 111111111111 region us-east-2. Some of my repositories will require Lambda access to pull their images, but not all of my repositories' images will require a replication. However, all repositories replicated to another region will also need lambda access.

In short:

  • Lambda access ≠ replicating an image
  • Replicating an image = Lambda access

The Solution

The Terraform module iterates over a list of repository names (var.repository_names) using for_each.

Each repository is tagged with: Lambda-Access = true/false

The tag value is determined dynamically using the lookup function on a map (var.ecr_lambda_access) that specifies which repositories should have Lambda access.

ecr_lambda_access = {
        "repo_name_1"  = "true"
        "repo_name_2"  = "true"
        "repo_name_3"  = "true"
}    
Enter fullscreen mode Exit fullscreen mode

Here’s the implementation:

resource "aws_ecr_repository" "example_registry" {
        for_each        = toset(var.repository_names)
        name            = each.value

        tags = {
          Lambda-Access = lookup(var.ecr_lambda_access, each.value, false)
        }
}
Enter fullscreen mode Exit fullscreen mode

To grant cross-account and same-account access to ECR repositories, along with Lambda-specific access, we add three statements to the aws_ecr_repository_policy resource:

Cross-account access grants general access to all repositories for specific external accounts.
Cross-account Lambda access grants Lambda functions in external accounts access to repositories tagged with Lambda-Access = true.
Same-account Lambda access grants Lambda functions in the same account access to repositories tagged with Lambda-Access = true.

Here’s the complete policy:

{
    "Version": "2008-10-17",
    "Statement": [
        {
        "Sid": "CrossAccountAccess",
        "Effect": "Allow",
        "Principal": {
            "AWS": [
            "arn:aws:iam::123456789012:root",
            "arn:aws:iam::222222222222:root"
            ]
        },
        "Action": [
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "ecr:BatchCheckLayerAvailability",
        "ecr:ListImages"
        ]
    },
    {
        "Sid": "LambdaECRImageCrossAccountRetrievalPolicy",
        "Effect": "Allow",
        "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer",
        "ecr:ListImages"
        ],
        "Principal": {
        "Service": "lambda.amazonaws.com"
        },
        "Condition": {
        "StringLike": {
            "aws:sourceArn": "arn:aws:lambda:us-east-2:123456789012:function:*"
        },
        "StringEquals": {
            "aws:ResourceTag/Lambda-Access": "true"
        }
        }
    },
    {
        "Sid": "LambdaECRSameAccountImageRetrievalPolicy",
        "Effect": "Allow",
        "Principal": {
        "Service": "lambda.amazonaws.com"
        },
        "Action": [
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer"
        ],
        "Condition": {
        "StringEquals": {
            "aws:ResourceTag/Lambda-Access": "true"
        }
        }
    }
    ]
}
Enter fullscreen mode Exit fullscreen mode

After solving cross-account and same-account access for the default region, we address cross-account, same-region access using the aws_ecr_replication_configuration resource.

We replicate only to specific regions where the Lambda function resides. In our case, it is eu-west-1 in account ID 222222222222. The replication configuration includes a rule specifying destination regions and repository filters.

The dynamic block is used to iterate over var.repository_to_replicate, a list of repository prefixes to include in replication. It avoids hardcoding multiple repository_filter blocks, making the configuration scalable and easier to manage as the list of repositories grows.

For each prefix, it dynamically generates a repository_filter block with:

  • filter: The prefix for repositories to match.
  • filter_type: Set to PREFIX_MATCH

This filter will control which repositories will be replicated.

Here’s the implementation:

resource "aws_ecr_replication_configuration" "example" {
        replication_configuration {
          rule {
            destination {
              region      = "eu-west-1"
              registry_id = data.aws_caller_identity.current.account_id
            }
            dynamic "repository_filter" {
              for_each = toset(var.repository_to_replicate)
              content {
                filter      = repository_filter.value
                filter_type = "PREFIX_MATCH"
              }
            }
          }
        }
}
Enter fullscreen mode Exit fullscreen mode

Region-Specific Repository Policies

We also need to define region-specific repository policies for the replicated repositories. This ensures that Lambda, in those regions, can access the replicated repositories.

To achieve this, we leverage the aws provider's alias for the replicated region (e.g. eu-west-1) and create repository policies for the replicated repositories.

Here's how it looks:

provider "aws" {
        alias  = "ireland_region"
        region = "eu-west-1"
}

resource "aws_ecr_repository_policy" "elastic_container_registry_policy_ireland" {
        for_each   = toset(var.repository_to_replicate)
        provider   = aws.ireland_region
        repository = each.value
        depends_on = [module.elastic_container_registry]
        policy     = <<EOF
        {
        "Version": "2012-10-17",
            "Statement": [
            {
                "Sid": "repositoryPerRegionStatement",
                "Effect": "Allow",
                "Principal": {
                "Service": "lambda.amazonaws.com",
                "AWS": "arn:aws:iam::222222222222:root"
                },
                "Action": [
                "ecr:BatchCheckLayerAvailability",
                "ecr:BatchGetImage",
                "ecr:GetDownloadUrlForLayer",
                "ecr:ListImages"
                ]
            }
            ]
        }
        EOF
}
Enter fullscreen mode Exit fullscreen mode

This approach keeps our configuration modular and ensures seamless access to replicated resources across regions.

Conclusion

That’s how I tackled the challenge of setting up cross-account and cross-region access for Lambda functions to pull container images from a centralized ECR repository, using a scalable and modular solution with Terraform.

I hope this blog provided some insights or inspiration for your own projects. Feel free to share your thoughts or questions—I’d be happy to hear from you!

Thank you for reading!

Top comments (0)