Blog

AWS Lambda Security: Automated Vulnerability Checks for ECR Repositories with EventBridge and SNS

Picture of Iveta Paneva
Iveta Paneva
DevOps and Cloud Engineer
15.02.2024
Reading time: 9 mins.
Last Updated: 29.07.2024

Table of Contents

In the dynamic world of serverless computing, securing your AWS Lambda function is crucial. However, one often neglected area is the security of containerized applications in Amazon Elastic Container Registry (ECR). This blog explores a powerful solution for using AWS Lambda, EventBridge, and Simple Notification Service (SNS) to automate vulnerability checks for ECR repositories. 

By the end, you’ll have a deeper understanding of how to secure your serverless architecture against potential risks.

We will navigate you through the three most important components, to show you how these pieces fit together. 

Firstly, let’s check out the Lambda function and see how it plays a part in our security solution.

In this section, we’ll explore the heart of our security setup – AWS Lambda. This tool helps check automatically for vulnerabilities in Docker images stored in Amazon Elastic Container Registry (ECR). We’ll break down the essential parts and how our Lambda function works with the needed code written in Python, providing you with a clear understanding of its crucial role in enhancing your containerized security.

 Let’s break down the key components and logic of our Lambda function.

To begin, we have to create a Lambda function in the AWS Management Console. Then we will specify the necessary IAM role with permissions of the Lambda so to interact with ECR, S3, and SNS. The S3 bucket is to store the logs of the reported vulnerabilities.

The diagram is at the end of the topic with a short summary.

When you create a new AWS Lambda function, you have the option to either choose an existing IAM role or create a new role for the function. If you choose to create a new role, AWS Lambda will automatically create an IAM role with the necessary permissions for your function. 

We will create the Lambda with a new IAM role and update the role permissions later.

  • Start creating the lambda:

import boto3
from datetime import datetime
today = datetime.now()
import os

ECR_NAME = os.environ['ECR_NAME']
SNS_ARN = os.environ['SNS_ARN']
S3_BUCKET = os.environ['S3_BUCKET']

def get_latest_ecr_image(repository_name):
    """
    Get details about the latest pushed image in the specified ECR repository.
    """
    ecr_client = boto3.client('ecr')
    image_details = ecr_client.describe_images(repositoryName=repository_name, filter={'tagStatus': 'TAGGED'})['imageDetails']

    # Sort images based on push timestamp in descending order
    sorted_images = sorted(image_details, key=lambda x: x['imagePushedAt'], reverse=True)

    return sorted_images[0] if sorted_images else None

def send_sns_alert(topic_arn, message):
    """
    Send an alert message to the specified SNS topic.
    """
    sns_client = boto3.client('sns')
    sns_client.publish(TopicArn=topic_arn, Message=message, Subject="Vulnerability Alert from un1te-wl-dev")

def lambda_handler(event, context):
    # Fetch details about the latest pushed image in the repository
    
    ECRS = ECR_NAME.split(",")
    for ecr in ECRS:
        latest_image_detail = get_latest_ecr_image(ecr)
        
        # Check if there is a latest pushed image in the repository
        if latest_image_detail:
            image_id = latest_image_detail['imageTags'][0]  # Use the first tag as the image identifier
    
            # Check if 'imageScanFindings' key is present in the image details
            if 'imageScanFindingsSummary' in latest_image_detail:
                # Get the actual vulnerability findings
                scan_findings = latest_image_detail['imageScanFindingsSummary']['findingSeverityCounts']
                print (scan_findings)
                #Severity = 'High'
                # Check if there are 'High' or 'Critical' vulnerabilities
                
                if ( 'CRITICAL' in scan_findings ):
                    critical = scan_findings['CRITICAL']
                else:
                    critical = 0
                if ( 'HIGH' in scan_findings ):
                    high = scan_findings['HIGH']
                else:
                    high = 0
                    
                if (critical + high >= 0): 
                    # Report vulnerabilities to S3
                    s3_client = boto3.client('s3')
                    report_content = f"Vulnerabilities found in Latest Pushed Image ID: {image_id}\n"
                    report_content += str(scan_findings)
                    key = f'vulnerability_report_'+ecr+'_'+today.strftime("%Y%m%d_%H%M")+'.txt'
                    s3_client.put_object(Bucket=S3_BUCKET, Key=key, Body=report_content)
    
                    # Send an alert via SNS
                    alert_message = f"Vulnerabilities found in ECR {ecr} Latest Pushed Image ID: {image_id}"
                    send_sns_alert(SNS_ARN, alert_message)
                else:
                    print ("-- ok --")
    
    return "Vulnerability check completed."

With this Lambda function in place, automated vulnerability checks become an integral part of your ECR repository security strategy.

Let’s break down the logic:

  • The Lambda function fetches details about all images in the specified ECR repository.

It iterates through each image, checking for vulnerabilities based on “Severity” criteria.

If vulnerabilities are found (with severity ‘High’ or ‘Critical’), the function creates a report containing the vulnerabilities found in the image.

  • The report is then uploaded to an S3 bucket named my-bucket-name.
  • Alerts via SNS:

It sends an immediate alert message to an SNS topic with information about the vulnerabilities, including the image ID and severity level.

The SNS topic has subscribers, which could include email addresses, that will receive notifications about the vulnerabilities.

  • Go to AWS Lambda
  • Select “Configuration”
  • On the left panel choose “Permissions”
  • Look for the “Execution role” (check the name) and click on it.

Or navigate to IAM and select the Lambda Role (check for the already created lambda) and expand the permissions. Choose edit and paste the content below:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:region:MY-ACCOUNT-ID:*"
        },
        {
            "Effect": "Allow",
            "Action": "events:*",
            "Resource": "arn:aws:lambda:region:MY-ACCOUNT-ID:function:my-lambda-name"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ecr:DescribeRepositories",
                "ecr:StartImageScan",
                "ecr:DescribeImageScanFindings",
                "ecr:ListImages",
                "ecr:DescribeImages"
            ],
            "Resource": [
                "arn:aws:ecr:region:MY-ACCOUNT-ID:repository/my-repo-name"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "sns:Publish"
            ],
            "Resource": "arn:aws:sns:region:MY-ACCOUNT-ID:my-sns-topic-name"
        },
        {
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::my-bucket-name/*",
                "arn:aws:s3:::my-bucket-name"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:region:MY-ACCOUNT-ID:log-group:/aws/lambda/my-lambda-name:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": [
                "arn:aws:lambda:region:MY-ACCOUNT-ID:function:my-lambda-name:*",
                "arn:aws:lambda:"arn:aws:lambda:region:MY-ACCOUNT-ID:function:my-lambda-name"
            ]
        }
    ]
}

Let’s break down the policy logic and what are the permission used for:
1. logs:CreateLogGroup
The purpose of this permission is to allow the Lambda function to create log groups in AWS CloudWatch Logs. Log groups are used to organize and store logs generated by the Lambda function.
2. events:*
It grants permissions for EventBridge operations related to the specified Lambda function. This is essential for managing events and triggers associated with the Lambda function.
3. ECR Permissions:
These permissions allow the Lambda function to interact with the specified Amazon ECR repository.
4. SNS Permissions:
Its purpose is to enable the Lambda function to publish messages to the specified SNS topic. This is crucial for sending alerts or notifications.
5. S3 Permissions:
Grants full access to the specified S3 bucket and its contents. This allows the Lambda function to store, retrieve, and manage objects in the S3 bucket.
6. CloudWatch Logs Permissions:
These permissions enable the Lambda function to create log streams and put log events into the specified CloudWatch Logs log group.
7. Lambda Permissions:
Authorizes the Lambda function to invoke itself and other Lambda functions. This is necessary for event-driven execution and invoking other functions.

These permissions collectively ensure that the Lambda function has the necessary access rights to perform its designated tasks, covering aspects such as logging, event handling, ECR interactions, alerting, S3 access, and Lambda function invocation.

Next to “Permissions” tab, go to “Trust relationships” tab of the IAM Role and paste the content:
The trust relationships are used for specifying which services are allowed to assume the role:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "events.amazonaws.com",
                    "lambda.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "scheduler.amazonaws.com"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": "MY-ACCOUNT-ID"
                }
            }
        }
    ]
}

In summary: We created the Lambda function and the IAM role with Trust relationship attached to the Lambda function.

Let’s continue with creation of the S3 bucket with the necessary permissions where the Lambda will send the logs.

  • Navigate to the S3
  • Choose “Create bucket”
  • Paste the below permissions content in the bucket policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::my-bucket-name/*"
        }
    ]
}

The bucket policy grants the necessary permissions of our AWS Lambda function, which allows it to upload objects to the specified S3 bucket. 

The next step is to create a schedule via EventBridge so the Lambda to be triggered whenever we want.

EventBridge plays a pivotal role in orchestrating the automated vulnerability checks. Scheduled events ensure regular security assessments. Here’s how you can configure a schedule in the AWS Management Console:

  • Navigate to the EventBridge console.
  • Create a new schedule.
  • Choose the event source (Schedule) and configure the schedule expression (e.g., cron(0 8 ? * MON *))

In this case the Lambda will be triggered every Monday at 8 am.

Set the target to the Lambda function created in page 1 as well as the Execution role of the Lambda.

As a final step we will create an SNS topic for alert.

The Simple Notification Service (SNS) is used for alerting or notifying relevant stakeholders or systems. In our case it will notify us when high or critical vulnerabilities are detected in our (ECR) repositories. Let’s break down its role: The SNS topic is configured to receive the alerts and acts as a communication channel. Subsequently, the alerts are published by the Lambda function using the AWS SDK (Boto3 in the case of Python) to send a message to the configured SNS topic. (An example of the email is provided below – page 9)

The subscribers to the SNS topic, which could be email addresses, other AWS Lambda functions, or other systems, receive the alerts.

SNS ensures timely and scalable delivery of messages to all subscribed endpoints, making it suitable for immediate alerting.

Now let’s proceed with creating the SNS topic.

  • Create a “Standard” topic.
  • Access policy

Ensure the Access policy has the below permission sets:

{
  "Version": "2008-10-17",
  "Id": "__default_policy_ID",
  "Statement": [
    {
      "Sid": "__default_statement_ID",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": [
        "SNS:Publish",
        "SNS:RemovePermission",
        "SNS:SetTopicAttributes",
        "SNS:DeleteTopic",
        "SNS:ListSubscriptionsByTopic",
        "SNS:GetTopicAttributes",
        "SNS:AddPermission",
        "SNS:Subscribe"
      ],
      "Resource": "arn:aws:sns:region:MY-ACCOUNT-ID:my-sns-topic-name",
      "Condition": {
        "StringEquals": {
          "AWS:SourceOwner": "MY-ACCOUNT-ID"
        }
      }
    }
  ]
}
  • Create subscription:
  • Topic ARN – the topic you created 
  • Protocol: Email (add your email address, so you can receive the email notification)

Then you will receive email and select subscribe.


Example of the email you will receive if High or Critical vulnerabilities are found:

Subject: Vulnerability Alert


“Vulnerabilities found in ECR my-repo-name Image ID: XXX – Severity: High

If you wish to stop receiving notifications from this topic, please click or visit the link below to unsubscribe:

https://sns.region.amazonaws.com/unsubscribe.html?SubscriptionArn=arn:aws:sns:region:MY-ACCOUNT-ID:my-sns-topic-name:myemail@.com

Please do not reply directly to this email. If you have any questions or comments regarding this email, please contact us at https://aws.amazon.com/support

Now, let’s bring together the components into a cohesive solution. The Lambda function is triggered by scheduled events through EventBridge. When vulnerabilities are detected, immediate alerts are sent via SNS and send the logs to the S3.

EventBridge is configured with a schedule expression (e.g., cron) triggering the Lambda function at specified intervals.

The Lambda function, responsible for vulnerability assessments, is invoked by EventBridge based on the schedule.

The Lambda function scans ECR repositories for vulnerabilities. If high or critical vulnerabilities are detected, it proceeds to the next step.

An immediate alert is sent via SNS to subscribed endpoints (e.g., email addresses) notifying stakeholders about the detected vulnerabilities.

The logs are being written with the name “vulnerability_report_imageID.txt” (it is described in the Lambda function, you can change it as per your needs)  in the S3 bucket.

In conclusion, we can see the integration of AWS Lambda, EventBridge, and SNS creates a robust and secure solution for automating vulnerability checks in ECR repositories. This approach ensures timely assessments, immediate alerts, and observes the best practices in serverless security. Moreover, the efficient use of serverless architecture, such as AWS Lambda, can contribute to potential cost savings, especially when leveraging the pay-as-you-go model. As demonstrated, implementing Lambda functions for ECR vulnerability checks not only enhances security but also offers an economical and scalable solution for organizations. By adopting this serverless strategy, businesses can achieve both operational efficiency and cost-effectiveness in managing container security within their AWS environment.

We hope the topic was helpful to you, and you have successfully integrated your Lambda. We remain at your disposal for any questions and advice. Wishing you continued success in your serverless endeavors. Until our next interaction, happy coding! 🙂

2 Responses

Leave a Reply

Your email address will not be published. Required fields are marked *

More Posts

Introduction Integrating Alertmanager with Microsoft Teams enables you to receive alerts directly in Teams channels, facilitating swift collaboration with your team to address issues promptly. This guide will use Prometheus-MSTeams,...
Reading
Managing Terraform locals efficiently is crucial for creating clean, maintainable, and reusable configurations. This guide will cover what Terraform locals are, how to implement them, and best practices for their...
Reading
Get In Touch
ITGix provides you with expert consultancy and tailored DevOps services to accelerate your business growth.
Newsletter for
Tech Experts
Join 12,000+ business leaders and engineers who receive blogs, e-Books, and case studies on emerging technology.