How to leverage image vulnerability scanning on AWS ECR using a fully automated solution

AWS ECR supports vulnerability scanning of Docker images. This article describes a quick and simple approach to use and automate this feature and combine it with alerting notifications sent to a Slack channel in case security risks are found.
24.08.2021
Tags

This article covers:

  • Description and details of the AWS ECR image scan feature
  • Outline and details of an automated usage proposal
  • The single parts of the proposed approach shown in detail

The AWS feature

AWS Elastic Container Registry has been able to support the scanning of images for vulnerabilities using the open source project Clair for quite some time now. Clair is an open source project used for the static analysis of vulnerabilities in application containers (currently including OCI and Docker). Made available by AWS directly and implemented into ECR, it is a very useful feature to minimize the risk of using endangered software - and stay compliant. The scanning for vulnerabilities should be a good standard in any Dockerized scenario as public images and their heirs can contain many security risks (Top-ten-docker-images) - which might be overlooked while developing applications that are constantly changed and improved - and new versions of images are pushed to your ECR many times a day.

AWS ECR supports automatically scanning any pushed image per repository or to trigger a scan manually - which can be configured any time in the settings of an individual repository:

aws-image-scan-settings




Scanning and the list of results can accessed via the management console:

repo screenshot




Findings are shown in the overview with a short summary:

repo-scanned2 screenshot




Details explain exactly what threat or risk was found:

finding-high screenshot




For more details about the feature, please see the documentation.

So much for the manual usage of the feature. Nobody really wants to scan and check in the console every time a new container is built and pushed 😃

Using the automated image scanning in AWS ECR is a good start to automate, but if nobody looks into it it doesn’t help much. Getting notified with the results automatically will greatly enhance your awareness for security risks that could be implemented in your application by using insecure images.

Outline and details of the proposed solution

The quick description:

  • Enable “scan on push” - for all repositories in your AWS account (manually enabling is shown in the section above).
  • This will trigger an image scan, each time an image is pushed to one of your repositories.
  • An event of the type “ECR Image Scan” is published to the AWS event bridge when the scan is finished. The event details contain data about the image and number of findings
  • Define an event rule that transforms the data from the event to readable data and forwards it to an SNS topic
  • From the SNS the next target



    The data of a typical event used in this example looks like this:
{
    "imageDetails": [
        {
            "registryId": "123456789012",
            "repositoryName": "demo-repo",
            "imageDigest": "sha256:7f5b2640fe6fb4f46592dfd3410c4a79dac4f89e4782432e0378abcd1234",
            "imageTags": [
                "2.0.20190115"
            ],
            "imageSizeInBytes": 61283455,
            "imagePushedAt": 1572489492.0,
            "imageScanStatus": {
                "status": "COMPLETE",
                "description": "The scan was completed successfully."
            },
            "imageScanFindingsSummary": {
                "imageScanCompletedAt": 1572489494.0,
                "vulnerabilitySourceUpdatedAt": 1572454026.0,
                "findingSeverityCounts": {
                    "HIGH": 9,
                    "LOW": 5,
                    "MEDIUM": 18
                }
            }
        }
    ]
}



The tiny parts:

For demonstration of the parts needed and the deployment, terraform was used, resources step by step:


  1. ECR Repository with “scan on push” enabled:
resource "aws_ecr_repository" "ecr_repo" {
  name = "ecr_repo"
  image_scanning_configuration {
    scan_on_push = true
  }
}



  1. An AWS Event Bridge rule (formerly Cloudwatch rule, in terraform still Cloudwatch) to receive and handle events:
resource "aws_cloudwatch_event_rule" "ecr_scan_event" {
  name          = "ecr_scan_event"
  description   = "Triggered when image scan was completed."
  event_pattern = <<EOF
  {
  "detail-type": ["ECR Image Scan"],
  "source": ["aws.ecr"],
  "detail": {
    "repository-name": [{
      "prefix": "${aws_ecr_repository.ecr_repo.name}"
    }]
  }
}
  EOF
  role_arn = aws_iam_role.ecr_scan_role.arn
}



  1. A target for the event rule… an SNS topic:

resource "aws_cloudwatch_event_target" "ecr_scan_event_target" {
  rule = aws_cloudwatch_event_rule.ecr_scan_event.name
  arn  = aws_sns_topic.ecr_scan_sns_topic.arn
  input_transformer {
    input_paths    = { "findings" : "$.detail.finding-severity-counts", "repo" : "$.detail.repository-name", "digest" : "$.detail.image-digest", "time" : "$.time", "status" : "$.detail.scanstatus", "tags" : "$.detail.image-tags", "account" : "$.account", "region" : "$.region" }
    input_template = <<EOF
"ECR Image scan results:"
"Time: <time>"
"Acc : <account>"
"Repo: <repo>"
"SHA : <digest>"
"Tags: <tags>"
"Find: <findings>"
EOF
  }
}



  1. A SNS subscription which forwards to to lambda:
resource "aws_sns_topic_subscription" "ecr_scan_sns_topic_subscription" {
  topic_arn = aws_sns_topic.ecr_scan_sns_topic.id
  protocol  = "lambda"
  endpoint  = aws_lambda_function.ecr_scan_notification_lambda.arn
}



  1. The target lambda function:
resource "aws_lambda_function" "ecr_scan_notification_lambda" {
  function_name = "ecr_scan_notification_lambda"
  filename      = "${path.module}/slackify.zip"
  role          = aws_iam_role.ecr_scan_notification_lambda_role.arn
  runtime       = "python3.8"
  handler       = "slackify.lambda_handler"
  depends_on = [data.archive_file.slackify-zip]
  environment {
    variables = {
      SLACK_WEBHOOK = var.slack_webhook      
    }
  }
}




The lambda function itself is a simple Python script posting the message in the correct format to the http webhook of the slack channel:

import urllib3 
import json
import os

http = urllib3.PoolManager() 

def lambda_handler(event, context): 
    slackWebhook = os.environ.get('SLACK_WEBHOOK')
    msg = {
        "text": event['Records'][0]['Sns']['Message']
    }
    encoded_msg = json.dumps(msg).encode('utf-8')
    resp = http.request('POST',slackWebhook, body=encoded_msg)
    print(
        {
            "message": event['Records'][0]['Sns']['Message'], 
            "status_code": resp.status, 
            "response": resp.data
        }
    )




The sample code can be found here, please feel free to use.

Conlusion

A notification in Slack could look like this:

webhook

It contains all information needed to act upon - the details of the security risks found in the image can be retrieved from the console (as shown earlier in the article).

The approach shown here is easy to use, easy to implement, easy to to adapt to more options - and definitely worth the while to enhance security for any containerized solution running on AWS.