├── terraform ├── modules │ ├── data-pipeline │ │ ├── outputs.tf │ │ ├── variables.tf │ │ └── main.tf │ └── ecr-pipeline │ │ ├── outputs.tf │ │ ├── variables.tf │ │ └── main.tf ├── outputs.tf ├── providers.tf ├── terraform.tfvars ├── main.tf ├── variables.tf ├── build-spec │ └── buildspec.yaml ├── README.md └── one-off-build-spec │ └── buildspec.yaml ├── assets ├── diagram.png ├── s3_screenshot_01.png ├── s3_screenshot_02.png ├── athena_screenshot_01.jpg ├── athena_screenshot_02.jpg ├── athena_screenshot_03.jpg ├── athena_screenshot_04.jpg ├── codebuild_screenshot_01.png ├── gluecrawler_screenshot_01.png └── gluedatabase_screenshot_01.png ├── eks-image-discovery ├── Dockerfile ├── requirements.txt ├── config │ └── eks-image-discovery.yaml ├── main.py └── README.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── .gitignore ├── CONTRIBUTING.md └── README.md /terraform/modules/data-pipeline/outputs.tf: -------------------------------------------------------------------------------- 1 | output "glue_catalog_database_name" { 2 | value = aws_glue_catalog_database.sbom_db.name 3 | } 4 | -------------------------------------------------------------------------------- /assets/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-use-sbom-to-find-container-image-with-vulnerable-software/HEAD/assets/diagram.png -------------------------------------------------------------------------------- /assets/s3_screenshot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-use-sbom-to-find-container-image-with-vulnerable-software/HEAD/assets/s3_screenshot_01.png -------------------------------------------------------------------------------- /assets/s3_screenshot_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-use-sbom-to-find-container-image-with-vulnerable-software/HEAD/assets/s3_screenshot_02.png -------------------------------------------------------------------------------- /assets/athena_screenshot_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-use-sbom-to-find-container-image-with-vulnerable-software/HEAD/assets/athena_screenshot_01.jpg -------------------------------------------------------------------------------- /assets/athena_screenshot_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-use-sbom-to-find-container-image-with-vulnerable-software/HEAD/assets/athena_screenshot_02.jpg -------------------------------------------------------------------------------- /assets/athena_screenshot_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-use-sbom-to-find-container-image-with-vulnerable-software/HEAD/assets/athena_screenshot_03.jpg -------------------------------------------------------------------------------- /assets/athena_screenshot_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-use-sbom-to-find-container-image-with-vulnerable-software/HEAD/assets/athena_screenshot_04.jpg -------------------------------------------------------------------------------- /assets/codebuild_screenshot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-use-sbom-to-find-container-image-with-vulnerable-software/HEAD/assets/codebuild_screenshot_01.png -------------------------------------------------------------------------------- /assets/gluecrawler_screenshot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-use-sbom-to-find-container-image-with-vulnerable-software/HEAD/assets/gluecrawler_screenshot_01.png -------------------------------------------------------------------------------- /assets/gluedatabase_screenshot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-eks-use-sbom-to-find-container-image-with-vulnerable-software/HEAD/assets/gluedatabase_screenshot_01.png -------------------------------------------------------------------------------- /terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "eks_irsa_policy" { 2 | value = module.ecr-pipeline.eks_irsa_policy 3 | } 4 | 5 | output "s3_bucket_name_unique" { 6 | value = module.ecr-pipeline.s3_bucket_name_unique 7 | } -------------------------------------------------------------------------------- /terraform/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | } 6 | } 7 | } 8 | 9 | provider "aws" { 10 | region = var.aws_region 11 | alias = "aws" 12 | } 13 | -------------------------------------------------------------------------------- /terraform/terraform.tfvars: -------------------------------------------------------------------------------- 1 | aws_region = "us-east-1" 2 | ecr_repo_name = "eks-image-discovery" 3 | codebuild_project_name = "sbom-codebuild-project" 4 | s3_bucket_name = "sbom-bucket" 5 | one_off_scan_repo_settings = "ALL" -------------------------------------------------------------------------------- /terraform/modules/data-pipeline/variables.tf: -------------------------------------------------------------------------------- 1 | variable "s3_bucket_name" { 2 | description = "S3 bucket for storing SBOM file and list of container images running on EKS" 3 | } 4 | 5 | variable "s3_kms_key" { 6 | description = "The ARN of the S3 KMS key" 7 | type = string 8 | } -------------------------------------------------------------------------------- /eks-image-discovery/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/docker/library/python:3.12-rc-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt requirements.txt 6 | 7 | RUN pip install --upgrade pip 8 | RUN pip3 install -r requirements.txt 9 | 10 | COPY . . 11 | 12 | CMD ["python3", "-m" , "main"] -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /terraform/modules/ecr-pipeline/outputs.tf: -------------------------------------------------------------------------------- 1 | output "s3_bucket_name_unique" { 2 | value = aws_s3_bucket.s3_bucket.id 3 | } 4 | 5 | output "eks_irsa_policy" { 6 | value = aws_iam_policy.eks_irsa_policy.arn 7 | } 8 | 9 | output "s3_kms_key_arn" { 10 | description = "The ARN of the KMS key for S3" 11 | value = aws_kms_key.kms_key_s3.arn 12 | } -------------------------------------------------------------------------------- /terraform/main.tf: -------------------------------------------------------------------------------- 1 | module "ecr-pipeline" { 2 | source = "./modules/ecr-pipeline" 3 | s3_bucket_name = var.s3_bucket_name 4 | ecr_repo_name = var.ecr_repo_name 5 | codebuild_project_name = var.codebuild_project_name 6 | } 7 | 8 | module "data-pipeline" { 9 | source = "./modules/data-pipeline" 10 | s3_bucket_name = module.ecr-pipeline.s3_bucket_name_unique 11 | s3_kms_key = module.ecr-pipeline.s3_kms_key_arn 12 | } 13 | -------------------------------------------------------------------------------- /eks-image-discovery/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3==1.26.82 2 | botocore==1.29.82 3 | cachetools==5.3.0 4 | certifi==2023.7.22 5 | charset-normalizer==3.0.1 6 | google-auth==2.16.1 7 | idna==3.4 8 | jmespath==1.0.1 9 | kubernetes==26.1.0 10 | oauthlib==3.2.2 11 | pyasn1==0.4.8 12 | pyasn1-modules==0.2.8 13 | python-dateutil==2.8.2 14 | PyYAML>=6.0 15 | requests==2.31.0 16 | requests-oauthlib==1.3.1 17 | rsa==4.9 18 | s3transfer==0.6.0 19 | six==1.16.0 20 | urllib3==1.26.14 21 | websocket-client==1.5.1 22 | -------------------------------------------------------------------------------- /terraform/modules/ecr-pipeline/variables.tf: -------------------------------------------------------------------------------- 1 | variable "ecr_repo_name" { 2 | description = "Name of ECR repo for cronjob to list running images on EKS cluster" 3 | default = "eks-image-discovery" 4 | } 5 | 6 | variable "codebuild_project_name" { 7 | description = "Name of codebuild project for generating SBOM" 8 | default = "sbom-codebuild-project" 9 | } 10 | 11 | variable "s3_bucket_name" { 12 | description = "S3 bucket for storing SBOM file and list of container images running on EKS" 13 | default = "sbom-bucket" 14 | } 15 | 16 | variable "one_off_scan_repo_settings" { 17 | description = "Settings for what repos to scan on the one-off solution " 18 | default = "ALL" 19 | } 20 | -------------------------------------------------------------------------------- /terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" { 2 | description = "AWS region for deploying the solution components" 3 | } 4 | 5 | variable "ecr_repo_name" { 6 | description = "Name of ECR repo for cronjob to list running images on EKS cluster" 7 | default = "eks-image-discovery" 8 | } 9 | 10 | variable "codebuild_project_name" { 11 | description = "Name of codebuild project for generating SBOM" 12 | default = "sbom-codebuild-project" 13 | } 14 | 15 | variable "s3_bucket_name" { 16 | description = "S3 bucket for storing generated SBOM files" 17 | default = "sbom-bucket" 18 | } 19 | 20 | variable "one_off_scan_repo_settings" { 21 | description = "Settings for what repos to scan on the one-off solution " 22 | default = "ALL" 23 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.tfstate 3 | *.tfstate.backup 4 | *.tfstate.lock.info 5 | 6 | # logs 7 | *.log 8 | 9 | # Directories 10 | .terraform/ 11 | .vagrant/ 12 | 13 | # SSH Keys 14 | *.pem 15 | 16 | # Backup files 17 | *.bak 18 | 19 | # Ignored Terraform files 20 | *gitignore*.tf 21 | 22 | # Ignore Mac .DS_Store files 23 | .DS_Store 24 | 25 | # Ignored vscode files 26 | .vscode/ 27 | 28 | # Ignore Any Generated JSON Files 29 | operations/automation-script/apply.json 30 | operations/automation-script/configversion.json 31 | operations/automation-script/run.template.json 32 | operations/automation-script/run.json 33 | operations/automation-script/variable.template.json 34 | operations/automation-script/variable.json 35 | operations/automation-script/workspace.template.json 36 | operations/automation-script/workspace.json 37 | operations/sentinel-policies-scripts/create-policy.template.json 38 | operations/sentinel-policies-scripts/create-policy.json 39 | operations/variable-scripts/variable.template.json 40 | operations/variable-scripts/variable.json 41 | 42 | # Sentinel runtime directory 43 | .sentinel 44 | 45 | .terraform.lock.hcl 46 | *terraform.lock.hcl -------------------------------------------------------------------------------- /eks-image-discovery/config/eks-image-discovery.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: ClusterRole 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | metadata: 5 | namespace: sbom-image-discovery 6 | name: pods-list 7 | rules: 8 | - apiGroups: [""] 9 | resources: ["pods"] 10 | verbs: ["list"] 11 | --- 12 | kind: ClusterRoleBinding 13 | apiVersion: rbac.authorization.k8s.io/v1 14 | metadata: 15 | name: pods-list 16 | subjects: 17 | - kind: ServiceAccount 18 | name: image-discovery-job 19 | namespace: sbom-image-discovery 20 | roleRef: 21 | kind: ClusterRole 22 | name: pods-list 23 | apiGroup: rbac.authorization.k8s.io 24 | --- 25 | apiVersion: batch/v1 26 | kind: CronJob 27 | metadata: 28 | namespace: sbom-image-discovery 29 | name: eks-image-discovery 30 | spec: 31 | schedule: "*/5 * * * *" 32 | jobTemplate: 33 | spec: 34 | template: 35 | spec: 36 | serviceAccountName: image-discovery-job 37 | containers: 38 | - name: eks-image-discovery 39 | image: 40 | env: 41 | - name: S3_BUCKET_NAME 42 | value: 43 | - name: EKS_CLUSTER_NAME 44 | value: 45 | imagePullPolicy: Always 46 | restartPolicy: Never 47 | -------------------------------------------------------------------------------- /eks-image-discovery/main.py: -------------------------------------------------------------------------------- 1 | from kubernetes import client, config 2 | import boto3 3 | import os 4 | import json 5 | 6 | def main(): 7 | bucket_name = os.environ['S3_BUCKET_NAME'] 8 | cluster_name = os.environ['EKS_CLUSTER_NAME'] 9 | config.load_incluster_config() 10 | # Get list of all images running on cluster 11 | v1 = client.CoreV1Api() 12 | pods = v1.list_pod_for_all_namespaces(watch=False) 13 | images = {} 14 | for pod in pods.items: 15 | for container in pod.spec.containers: 16 | image = container.image 17 | namespace = pod.metadata.namespace 18 | podname = pod.metadata.name 19 | if image not in images: 20 | i = image.split(":") 21 | images[image] = { 22 | 'image_name': image, 23 | 'repo_name': i[0], 24 | 'pods': [] 25 | } 26 | images[image]['pods'].append({ 27 | 'podname': podname, 28 | 'namespace': namespace 29 | }) 30 | 31 | # Upload list of images to S3 32 | s3client = boto3.client('s3') 33 | key = "eks-running-images/" + cluster_name + '-image-list.json' 34 | print('writing image list json to bucket ' + bucket_name) 35 | jsonBody = [] 36 | for item in images.values(): 37 | jsonBody.append(json.dumps(item) + '\n') 38 | s3client.put_object( 39 | Body=''.join(jsonBody), 40 | Bucket=bucket_name, 41 | Key=key 42 | ) 43 | 44 | print('image list written to S3 bucket with key ' + key) 45 | 46 | if __name__ == '__main__': 47 | main() -------------------------------------------------------------------------------- /terraform/build-spec/buildspec.yaml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | pre_build: 5 | commands: 6 | - echo "Logging in to Amazon ECR..." 7 | - aws --version 8 | - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com 9 | - echo "Installing Syft..." 10 | - curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin v0.85.0 11 | - | 12 | if $ECR_PUSH == "true"; then 13 | VERSION="1.0.0" 14 | curl -LO "https://github.com/oras-project/oras/releases/download/v${VERSION}/oras_${VERSION}_linux_amd64.tar.gz" 15 | mkdir -p oras-install/ 16 | tar -zxf oras_${VERSION}_*.tar.gz -C oras-install/ 17 | sudo mv oras-install/oras /usr/local/bin/ 18 | rm -rf oras_${VERSION}_*.tar.gz oras-install/ 19 | fi 20 | 21 | build: 22 | commands: 23 | - echo "REPOSITORY_NAME = $REPOSITORY_NAME" 24 | - echo "IMAGE_TAG = $IMAGE_TAG" 25 | - echo "Pulling Docker image..." 26 | - docker pull $ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_NAME:$IMAGE_TAG 27 | - docker image ls 28 | - export REPOSITORY_FILE_NAME=$(echo "$REPOSITORY_NAME" | tr '/' '-') 29 | - echo "Analyzing Docker image with Syft..." 30 | - syft $ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_NAME:$IMAGE_TAG -o spdx-json | jq -c "." > "$REPOSITORY_FILE_NAME-$IMAGE_TAG.json" 31 | - aws s3 cp "$REPOSITORY_FILE_NAME-$IMAGE_TAG.json" s3://$S3_BUCKET_NAME/sbom/ 32 | - | 33 | if $ECR_PUSH == "true"; then 34 | echo "###STEP 3D. ATTACH SBOM TO ECR REPO" 35 | oras attach --artifact-type "application/spdx+json" $ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_NAME:$IMAGE_TAG "$IMAGE_TAG.json" -v 36 | fi 37 | 38 | artifacts: 39 | files: 40 | - "$IMAGE_TAG.json" 41 | -------------------------------------------------------------------------------- /terraform/README.md: -------------------------------------------------------------------------------- 1 | # SBOM-ECR-BLOG 2 | 3 | 4 | ## Modules 5 | 6 | In this repoistory there are 2 sub modules which are deployed at the same time via the main.tf file located at the root directory. 7 | 8 | 1. Data-pipeline Module 9 | 10 | In this module the tools required to stand up the athena database is deployed. This module deploys an aws glue catalogue database, a glue table, a glue crawler, the required iam role and athena workgroup. 11 | 12 | 13 | 2. ECR-Pipeline Module 14 | 15 | This module is used to deploy the CodeBuild pipeline to generate SBOM whenever a new image is pushed to any ECR repository in the account. The module also creates EventBridge rule that triggers CodeBuild when a new image is pushed. It also creates a CodeBuild pipeline to generate SBOM for existing images stored in any ECR repository in the account. S3 bucket is also created to store the generated SBOM files. 16 | 17 | 18 | ## Deployment steps for Terraform modules 19 | 20 | 21 | 1. Edit terraform.tfvars file inside terraform directory with any text editor of your choice. Make sure you provide the correct value for aws_region variable to specify the AWS region where you want to deploy. You can accept the defaults for other variables or change them if you wish to. 22 | 23 | 2. Make sure the principal being used to run terraform has necessary privileges to deploy all resources. Refer to [terraform documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) for providing AWS authentication credentials. 24 | 25 | 3. Confirm you have configured the default region on your AWS CLI, run: 26 | 27 | ``` 28 | aws configure list 29 | ``` 30 | 31 | The recommended way to provide AWS credentials and default region is to use environment variables. For example, you can use AWS_PROFILE environment variable to provide the AWS CLI profile to use for running terraform. Refer to AWS CLI documentation for configuration details. 32 | 33 | 4. Once these steps are complete please run the following from inside the terraform directory: 34 | - Initialize your working directory 35 | ``` 36 | terraform init 37 | ``` 38 | - Preview the resources that terraform will deploy in your AWS account 39 | 40 | ``` 41 | terraform plan 42 | ``` 43 | - Deploy the resources. By running the following and selecting yes when prompted to approve the creation of resources 44 | 45 | ``` 46 | terraform apply 47 | ``` 48 | -------------------------------------------------------------------------------- /eks-image-discovery/README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | This component can be deployed as a scheduled job on any EKS cluster. The job discovers all of the pods running in all namespaces and creates a JSON file with all container images in the pods. The JSON file is uploaded to an S3 bucket configured as an environment variable. A sample JSON would like below 3 | ``` 4 | {"image_name": ".dkr.ecr.us-east-2.amazonaws.com/eks/coredns:v1.8.7-eksbuild.3", "repo_name": ".dkr.ecr.us-east-2.amazonaws.com/eks/coredns", "pods": [{"podname": "coredns-5c5677bc78-kns5n", "namespace": "kube-system"}, {"podname": "coredns-5c5677bc78-tjg5j", "namespace": "kube-system"}]} 5 | {"image_name": ".dkr.ecr.us-east-2.amazonaws.com/eks/kube-proxy:v1.24.7-minimal-eksbuild.2", "repo_name": ".dkr.ecr.us-east-2.amazonaws.com/eks/kube-proxy", "pods": [{"podname": "kube-proxy-5w5nm", "namespace": "kube-system"}, {"podname": "kube-proxy-nmqgc", "namespace": "kube-system"}]} 6 | 7 | ``` 8 | 9 | ## Deployment steps for EKS cronjob 10 | 11 | We assume that you have an existing EKS cluster and kubernetes CLI installed on your machine to interact with the cluster. Perform the steps below to deploy EKS cronjob for discovering running images on your cluster 12 | 13 | 1. Follow [AWS documentation](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html) to create IAM OIDC provider for the EKS cluster 14 | 15 | 2. Create IAM role for service account using eksctl. This IAM role will be used by the EKS cronjob to write the list of running container images to S3 bucket. Replace cluster name and policy ARN copied from the Terraform output. The policy is one of the resources created by Terraform modules. 16 | 17 | ``` 18 | eksctl create iamserviceaccount --name image-discovery-job --namespace sbom-image-discovery --cluster --attach-policy-arn --approve 19 | ``` 20 | 21 | 3. Terraform would have created an ECR repository ```eks-image-discovery``` in your account to store image of EKS cronjob. Go to ECR repository in AWS console and click on the button for view push commands. Follow those commands to build and push docker image of EKS cronjob to ECR repository. Copy the image URI of the newly pushed image from AWS console. 22 | 23 | 4. Navigate to ```config``` directory inside eks-image-discovery. This contains a Kubernetes manifest file that you need to edit for deploying the cronjob. Open ```eks-image-discovery.yaml``` file in any text editor. 24 | 25 | - Edit image field to change it to image URI that you copied in the previous step. 26 | - Update the name of S3 bucket in environment variable configuration. Use the bucket name copied from Terraform output 27 | - Update the name of EKS cluster in environment variable with your cluster name 28 | - Optionally update the cronjob schedule. By default, the job will run every 5 mins 29 | 30 | 5. Apply the manifest file using kubectl 31 | 32 | ``` 33 | kubectl apply -f eks-image-discovery.yaml 34 | ``` -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /terraform/one-off-build-spec/buildspec.yaml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | pre_build: 5 | commands: 6 | - echo "Logging in to Amazon ECR..." 7 | - aws --version 8 | - echo "Installing Syft..." 9 | - curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin v0.85.0 10 | - export ACCOUNT=$(aws sts get-caller-identity --query 'Account' --output text) 11 | - echo "Installing ORAS CLI" 12 | - | 13 | if $ECR_PUSH == "true"; then 14 | VERSION="1.0.0" 15 | curl -LO "https://github.com/oras-project/oras/releases/download/v${VERSION}/oras_${VERSION}_linux_amd64.tar.gz" 16 | mkdir -p oras-install/ 17 | tar -zxf oras_${VERSION}_*.tar.gz -C oras-install/ 18 | sudo mv oras-install/oras /usr/local/bin/ 19 | rm -rf oras_${VERSION}_*.tar.gz oras-install/ 20 | fi 21 | 22 | build: 23 | commands: 24 | - 25 | - | 26 | if [ "$ONE_OFF_SCAN_SETTINGS" != "ALL" ]; then 27 | export REPO_LIST=$ONE_OFF_SCAN_SETTINGS 28 | else 29 | export REPO_LIST=$(aws ecr describe-repositories --no-paginate --query 'repositories[*].repositoryName' --output text) 30 | fi 31 | - | 32 | for REPO_NAME in $REPO_LIST 33 | do 34 | echo "###STEP 1. GENERATING IMAGE_LIST FOR ALL IMAGES WITHIN A ECR RESPOSITORY" 35 | export IMAGE_LIST=$(aws ecr describe-images --repository-name $REPO_NAME | jq '[.imageDetails[] | select(.imageManifestMediaType == "application/vnd.docker.distribution.manifest.v2+json") | {imageTag: .imageTags[0], imageDigest: .imageDigest}]') 36 | data=$(echo "$IMAGE_LIST" | jq -c '.[]') 37 | 38 | echo "###STEP 2. DOCKER LOGIN TO THAT SPECIFIC ECR REPOSITORY" 39 | aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com 40 | 41 | for row in $data 42 | do 43 | 44 | IMAGETAG=$(echo "$row" | jq -r '.imageTag') 45 | IMAGEDIGEST=$(echo "$row" | jq -r '.imageDigest') 46 | 47 | if [ "$IMAGETAG" = "null" ]; then 48 | IMAGETAG="@${IMAGEDIGEST}" 49 | URI=$ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/$REPO_NAME$IMAGETAG 50 | else 51 | URI=$ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/$REPO_NAME:$IMAGETAG 52 | fi 53 | 54 | echo THIS IS YOUR IMAGETAG = $IMAGETAG 55 | 56 | echo "###STEP 3A. DOCKER PULL IMAGE = $URI" 57 | docker pull $URI 58 | 59 | echo "###STEP 3B. VERIFIES REPOSITORY NAME DOES NOT CONTAIN ILLEGAL / CHAR" 60 | export REPO_FILE_NAME=$(echo "$REPO_NAME" | tr '/' '-') 61 | 62 | echo "###STEP 3C. SYFT SBOM GENERATION OF $URI" 63 | syft $URI -o spdx-json | jq -c "." > "$REPO_FILE_NAME-$IMAGETAG.json" 64 | 65 | echo "###STEP 3D. S3 PUT OBJECT $IMAGETAG.json" 66 | aws s3 cp "$REPO_FILE_NAME-$IMAGETAG.json" s3://$S3_BUCKET_NAME/sbom/ 67 | 68 | if $ECR_PUSH == "true"; then 69 | echo "###STEP 3D. ATTACH SBOM TO ECR REPO" 70 | oras attach --artifact-type "application/spdx+json" $URI "$IMAGETAG.json" 71 | fi 72 | 73 | done 74 | done 75 | 76 | artifacts: 77 | files: 78 | - "$IMAGETAG.json" 79 | 80 | -------------------------------------------------------------------------------- /terraform/modules/data-pipeline/main.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | 3 | data "aws_region" "current" {} 4 | 5 | resource "aws_kms_key" "kms_key" { 6 | description = "KMS key for glue crawler" 7 | deletion_window_in_days = 10 8 | enable_key_rotation = true 9 | } 10 | 11 | resource "aws_kms_key_policy" "kms_key_policy" { 12 | key_id = aws_kms_key.kms_key.key_id 13 | 14 | policy = jsonencode({ 15 | Version = "2012-10-17", 16 | Id = "Enable Logs to use the key", 17 | Statement = [ 18 | { 19 | "Sid": "Enable IAM User Permissions", 20 | "Effect": "Allow", 21 | "Principal": { 22 | "AWS": "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" 23 | }, 24 | "Action": "kms:*", 25 | "Resource": "*" 26 | }, 27 | { 28 | Sid = "Allow use Logs for the use of key", 29 | Effect = "Allow", 30 | Principal = { 31 | Service = "logs.${data.aws_region.current.name}.amazonaws.com" 32 | }, 33 | Action = [ 34 | "kms:Encrypt", 35 | "kms:Decrypt", 36 | "kms:ReEncrypt*", 37 | "kms:GenerateDataKey*", 38 | "kms:DescribeKey" 39 | ], 40 | Resource = "*" 41 | } 42 | ] 43 | }) 44 | } 45 | 46 | 47 | resource "aws_glue_catalog_database" "sbom_db" { 48 | name = "sbom_db" 49 | } 50 | 51 | resource "aws_glue_crawler" "sbom_crawler" { 52 | name = "sbom_crawler" 53 | database_name = aws_glue_catalog_database.sbom_db.name 54 | role = aws_iam_role.glue_crawler_role.arn 55 | security_configuration = aws_glue_security_configuration.sbom_crawler.name 56 | 57 | s3_target { 58 | path = "s3://${var.s3_bucket_name}/sbom" 59 | } 60 | 61 | s3_target { 62 | path = "s3://${var.s3_bucket_name}/eks-running-images" 63 | } 64 | } 65 | 66 | resource "aws_glue_security_configuration" "sbom_crawler" { 67 | name = "sbom_crawler" 68 | 69 | encryption_configuration { 70 | cloudwatch_encryption { 71 | cloudwatch_encryption_mode = "SSE-KMS" 72 | kms_key_arn = aws_kms_key.kms_key.arn 73 | } 74 | 75 | job_bookmarks_encryption { 76 | job_bookmarks_encryption_mode = "CSE-KMS" 77 | kms_key_arn = aws_kms_key.kms_key.arn 78 | } 79 | 80 | s3_encryption { 81 | kms_key_arn = aws_kms_key.kms_key.arn 82 | s3_encryption_mode = "SSE-KMS" 83 | } 84 | } 85 | } 86 | 87 | resource "aws_iam_role" "glue_crawler_role" { 88 | name_prefix = "glue_crawler_role" 89 | 90 | assume_role_policy = jsonencode({ 91 | Version = "2012-10-17" 92 | Statement = [ 93 | { 94 | Effect = "Allow" 95 | Principal = { 96 | Service = "glue.amazonaws.com" 97 | } 98 | Action = "sts:AssumeRole" 99 | } 100 | ] 101 | }) 102 | } 103 | 104 | resource "aws_iam_role_policy_attachment" "glue_service_role" { 105 | role = aws_iam_role.glue_crawler_role.name 106 | policy_arn = "arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole" 107 | } 108 | 109 | resource "aws_iam_role_policy" "glue_s3_policy" { 110 | name = "glue_s3_policy" 111 | role = aws_iam_role.glue_crawler_role.id 112 | policy = jsonencode({ 113 | Version = "2012-10-17" 114 | Statement = [ 115 | { 116 | Action = [ 117 | "s3:GetObject", 118 | "s3:PutObject" 119 | ] 120 | Effect = "Allow" 121 | Resource = "arn:aws:s3:::${var.s3_bucket_name}*" 122 | }, 123 | { 124 | Action = [ 125 | "kms:GenerateDataKey", 126 | "kms:Decrypt" 127 | ] 128 | Effect = "Allow" 129 | Resource = [ 130 | "${var.s3_kms_key}" 131 | ] 132 | } 133 | ] 134 | }) 135 | } 136 | 137 | resource "aws_iam_role_policy" "glue_logs_kms_policy" { 138 | #checkov:skip=CKV_AWS_290:only allowing "logs:AssociateKMSKey" to the loggroup the Glue service role creates 139 | #checkov:skip=CKV_AWS_355:only allowing "logs:AssociateKMSKey" to the loggroup the Glue service role creates 140 | name = "glue_logs_kms_policy" 141 | role = aws_iam_role.glue_crawler_role.id 142 | policy = jsonencode({ 143 | Version = "2012-10-17" 144 | Statement = [ 145 | { 146 | Action = [ 147 | "logs:AssociateKmsKey" 148 | ] 149 | Effect = "Allow" 150 | Resource = "*" 151 | }, 152 | { 153 | Action = [ 154 | "kms:Encrypt", 155 | "kms:Decrypt", 156 | "kms:ReEncrypt*", 157 | "kms:GenerateDataKey*", 158 | "kms:DescribeKey" 159 | ] 160 | Effect = "Allow" 161 | Resource = [ 162 | "${aws_kms_key.kms_key.arn}" 163 | ] 164 | } 165 | ] 166 | }) 167 | } 168 | -------------------------------------------------------------------------------- /terraform/modules/ecr-pipeline/main.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | 3 | data "aws_region" "current" {} 4 | 5 | data "local_file" "buildspec_local" { 6 | filename = "${path.module}/../../build-spec/buildspec.yaml" 7 | } 8 | 9 | data "local_file" "oneoff_buildspec_local" { 10 | filename = "${path.module}/../../one-off-build-spec/buildspec.yaml" 11 | } 12 | 13 | //create kms key for s3 bucket 14 | resource "aws_kms_key" "kms_key_s3" { 15 | #checkov:skip=CKV2_AWS_64:Using default KMS policy 16 | description = "KMS key for s3" 17 | deletion_window_in_days = 10 18 | enable_key_rotation = true 19 | } 20 | 21 | // create ecr repo for EKS image discovery cronjob 22 | 23 | resource "aws_ecr_repository" "eks_cronjob_repo" { 24 | name = var.ecr_repo_name 25 | image_tag_mutability = "IMMUTABLE" 26 | force_delete = true 27 | encryption_configuration { 28 | encryption_type = "KMS" 29 | kms_key = aws_kms_key.kms_key_s3.arn 30 | } 31 | 32 | image_scanning_configuration { 33 | scan_on_push = true 34 | } 35 | } 36 | 37 | // create s3 bucket for storing solution files 38 | resource "aws_s3_bucket" "s3_bucket" { 39 | #checkov:skip=CKV2_AWS_61:lifecycle rule is not required 40 | #checkov:skip=CKV2_AWS_62:event notification does not need to be enabled but can be 41 | #checkov:skip=CKV_AWS_144:cross region replication is not required 42 | #checkov:skip=CKV2_AWS_6:public access block is not required 43 | #checkov:skip=CKV_AWS_18:access logging is not required for this example 44 | #checkov:skip=CKV_AWS_21:versioning is not required as the files on S3 can be easily recreated from ECR and EKS. Also it would incur high costs due to generating new files every 5 mins. 45 | bucket_prefix = "${var.s3_bucket_name}-" 46 | } 47 | 48 | 49 | resource "aws_s3_bucket_server_side_encryption_configuration" "bucket" { 50 | bucket = aws_s3_bucket.s3_bucket.id 51 | 52 | rule { 53 | apply_server_side_encryption_by_default { 54 | kms_master_key_id = aws_kms_key.kms_key_s3.arn 55 | sse_algorithm = "aws:kms" 56 | } 57 | } 58 | } 59 | 60 | // create folder for storing sbom files for container images 61 | resource "aws_s3_object" "sbom-files" { 62 | bucket = aws_s3_bucket.s3_bucket.id 63 | key = "sbom/" 64 | } 65 | 66 | // create folder for storing file with list of images running on EKS cluster 67 | resource "aws_s3_object" "eks-running-images" { 68 | bucket = aws_s3_bucket.s3_bucket.id 69 | key = "eks-running-images/" 70 | } 71 | 72 | # Set up CodeBuild project to generate SBoM file 73 | resource "aws_codebuild_project" "codebuild_project" { 74 | #checkov:skip=CKV_AWS_314:logging is not required for this sample 75 | #checkov:skip=CKV_AWS_316:codebuild needs previlige mode to build containers 76 | name = var.codebuild_project_name 77 | description = "generate sbom for new images" 78 | build_timeout = "5" 79 | service_role = aws_iam_role.codebuild_role.arn 80 | 81 | artifacts { 82 | type = "NO_ARTIFACTS" 83 | } 84 | 85 | environment { 86 | compute_type = "BUILD_GENERAL1_SMALL" 87 | image = "aws/codebuild/amazonlinux2-x86_64-standard:4.0" 88 | type = "LINUX_CONTAINER" 89 | #checkov:skip=CKV_AWS_316:codebuild needs previlige mode to build containers 90 | privileged_mode = true 91 | 92 | environment_variable { 93 | name = "S3_BUCKET_NAME" 94 | value = aws_s3_bucket.s3_bucket.id 95 | } 96 | 97 | environment_variable { 98 | name = "ECR_PUSH" 99 | value = "false" 100 | } 101 | 102 | } 103 | 104 | logs_config { 105 | cloudwatch_logs { 106 | group_name = "log-group" 107 | stream_name = "log-stream" 108 | } 109 | } 110 | 111 | source { 112 | type = "NO_SOURCE" 113 | buildspec = data.local_file.buildspec_local.content 114 | } 115 | 116 | } 117 | 118 | # Set up CodeBuild project to generate SBoM file - oneoff 119 | resource "aws_codebuild_project" "codebuild_project_oneoff" { 120 | name = "${var.codebuild_project_name}-one-off" 121 | description = "generate SBOM for existing images" 122 | build_timeout = "5" 123 | service_role = aws_iam_role.codebuild_role.arn 124 | 125 | artifacts { 126 | type = "NO_ARTIFACTS" 127 | } 128 | 129 | environment { 130 | compute_type = "BUILD_GENERAL1_SMALL" 131 | image = "aws/codebuild/amazonlinux2-x86_64-standard:4.0" 132 | type = "LINUX_CONTAINER" 133 | #checkov:skip=CKV_AWS_316:codebuild needs previlige mode to build containers 134 | privileged_mode = true 135 | 136 | environment_variable { 137 | name = "S3_BUCKET_NAME" 138 | value = aws_s3_bucket.s3_bucket.id 139 | } 140 | 141 | environment_variable { 142 | name = "ECR_PUSH" 143 | value = "false" 144 | } 145 | 146 | environment_variable { 147 | name = "ONE_OFF_SCAN_SETTINGS" 148 | value = var.one_off_scan_repo_settings 149 | } 150 | } 151 | 152 | logs_config { 153 | cloudwatch_logs { 154 | group_name = "log-group" 155 | stream_name = "log-stream" 156 | } 157 | } 158 | 159 | source { 160 | type = "NO_SOURCE" 161 | buildspec = data.local_file.oneoff_buildspec_local.content 162 | } 163 | 164 | } 165 | 166 | # Create EventBridge rule to trigger CodeBuild project when image is pushed to ECR repo 167 | resource "aws_cloudwatch_event_rule" "ecr_push_rule" { 168 | name = "ecr-push-rule" 169 | description = "Event rule for CodeBuild project when image is pushed to ECR repo" 170 | event_pattern = jsonencode({ 171 | source = ["aws.ecr"] 172 | detail-type = ["ECR Image Action"] 173 | detail = { 174 | action-type = ["PUSH"] 175 | result = ["SUCCESS"] 176 | } 177 | }) 178 | } 179 | 180 | # Create IAM role for Eventbridge 181 | resource "aws_iam_role" "eventbridge_role" { 182 | name_prefix = "sbom-eventbridge-role" 183 | 184 | assume_role_policy = jsonencode({ 185 | Version = "2012-10-17" 186 | Statement = [ 187 | { 188 | Action = "sts:AssumeRole" 189 | Effect = "Allow" 190 | Principal = { 191 | Service = "events.amazonaws.com" 192 | } 193 | } 194 | ] 195 | }) 196 | } 197 | 198 | # Grant permissions to eventbridge role 199 | resource "aws_iam_role_policy" "eventbridge_role_policy" { 200 | name = "sbom_eventbridge_policy" 201 | role = aws_iam_role.eventbridge_role.id 202 | 203 | policy = jsonencode({ 204 | Version = "2012-10-17" 205 | Statement = [ 206 | { 207 | Action = [ 208 | "codebuild:StartBuild" 209 | ] 210 | Effect = "Allow" 211 | Resource = "${aws_codebuild_project.codebuild_project.arn}" 212 | } 213 | ] 214 | }) 215 | } 216 | 217 | # Create EventBridge rule target to trigger CodeBuild project 218 | resource "aws_cloudwatch_event_target" "ecr_push_target" { 219 | target_id = "ecr-push-target" 220 | arn = aws_codebuild_project.codebuild_project.arn 221 | role_arn = aws_iam_role.eventbridge_role.arn 222 | rule = aws_cloudwatch_event_rule.ecr_push_rule.name 223 | input_transformer { 224 | input_paths = { 225 | account = "$.account", 226 | hash = "$.detail.image-digest", 227 | image-tag = "$.detail.image-tag", 228 | repository-name = "$.detail.repository-name" 229 | } 230 | input_template = <}, 233 | {"name":"HASH","type":"PLAINTEXT","value":}, 234 | {"name":"REPOSITORY_NAME","type":"PLAINTEXT","value":}, 235 | {"name":"IMAGE_TAG","type":"PLAINTEXT","value":} 236 | ]} 237 | EOF 238 | } 239 | } 240 | 241 | # Create IAM role for CodeBuild project 242 | resource "aws_iam_role" "codebuild_role" { 243 | name_prefix = "sbom-codebuild-role" 244 | 245 | assume_role_policy = jsonencode({ 246 | Version = "2012-10-17" 247 | Statement = [ 248 | { 249 | Action = "sts:AssumeRole" 250 | Effect = "Allow" 251 | Principal = { 252 | Service = "codebuild.amazonaws.com" 253 | } 254 | } 255 | ] 256 | }) 257 | } 258 | 259 | resource "aws_iam_role_policy" "codebuild_role_policy" { 260 | #checkov:skip=CKV_AWS_355:Required "*" for resoucres on ECR since the solution needs to go through ALL ECR repositories in the account 261 | name = "sbom_codebuild_policy" 262 | role = aws_iam_role.codebuild_role.id 263 | 264 | policy = jsonencode({ 265 | Version = "2012-10-17" 266 | Statement = [ 267 | { 268 | Action = [ 269 | "ecr:GetAuthorizationToken", 270 | "ecr:DescribeRepositories" 271 | ] 272 | Effect = "Allow" 273 | Resource = "*" 274 | }, 275 | { 276 | Action = [ 277 | "ecr:BatchCheckLayerAvailability", 278 | "ecr:BatchDeleteImage", 279 | "ecr:BatchGetImage", 280 | "ecr:CompleteLayerUpload", 281 | "ecr:GetDownloadUrlForLayer", 282 | "ecr:InitiateLayerUpload", 283 | "ecr:PutImage", 284 | "ecr:UploadLayerPart", 285 | "ecr:DescribeImages" 286 | ] 287 | Effect = "Allow" 288 | Resource = "arn:aws:ecr:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:repository/*" 289 | }, 290 | { 291 | Action = [ 292 | "logs:CreateLogGroup", 293 | "logs:CreateLogStream", 294 | "logs:PutLogEvents" 295 | ] 296 | Effect = "Allow" 297 | Resource = "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:*" 298 | }, 299 | { 300 | Action = [ 301 | "s3:PutObject", 302 | "s3:ListBucket" 303 | ] 304 | Effect = "Allow" 305 | Resource = [ 306 | "arn:aws:s3:::${aws_s3_bucket.s3_bucket.id}", 307 | "arn:aws:s3:::${aws_s3_bucket.s3_bucket.id}/*" 308 | ] 309 | }, 310 | { 311 | Action = [ 312 | "kms:GenerateDataKey" 313 | ] 314 | Effect = "Allow" 315 | Resource = [ 316 | "${aws_kms_key.kms_key_s3.arn}" 317 | ] 318 | } 319 | ] 320 | }) 321 | } 322 | 323 | # create IAM policy which can be attached to IAM role for service account (IRSA) 324 | # Policy will provide EKS job permission to write file to S3 with list of container images 325 | resource "aws_iam_policy" "eks_irsa_policy" { 326 | name_prefix = "eks_irsa_policy" 327 | description = "Policy to allow EKS job to write list of images to S3" 328 | 329 | policy = jsonencode({ 330 | Version = "2012-10-17" 331 | Statement = [ 332 | { 333 | Action = [ 334 | "s3:PutObject" 335 | ] 336 | Effect = "Allow" 337 | Resource = "arn:aws:s3:::${aws_s3_bucket.s3_bucket.id}*" 338 | }, 339 | { 340 | Action = [ 341 | "kms:GenerateDataKey" 342 | ] 343 | Effect = "Allow" 344 | Resource = [ 345 | "${aws_kms_key.kms_key_s3.arn}" 346 | ] 347 | } 348 | ] 349 | }) 350 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Using SBOM to find vulnerable container images running on Amazon EKS clusters 2 | This repository contains Terraform code and artifacts for a solution to generate Software Bill of Materials (SBOM) for container images in Amazon Elastic Container Registry (Amazon ECR) and analyze SBOM data using Amazon Athena to identify container images running on Amazon Elastic Kubernetes Service (Amazon EKS) clusters that contain vulnerable software components. 3 | 4 | Please check the [Using SBOM to find vulnerable container images running on Amazon EKS clusters AWS Blogpost](https://aws.amazon.com/blogs/containers/using-sbom-to-find-vulnerable-container-images-running-on-amazon-eks-clusters/) 5 | 6 | ## Table of Contents 7 | 1. [Diagram](#diagram) 8 | 2. [Instructions](#instructions) 9 | 1. [Deploy the solution architecture using Terraform](#deploy-the-solution-architecture-using-terraform) 10 | 2. [Deploy the EKS cronjob](#deploy-the-eks-cronjob) 11 | 3. [Populate the Glue catalog](#populate-the-glue-catalog) 12 | 3. [Run sample Athena Query](#run-sample-athena-query) 13 | 1. [Vulnerability in a specific package - Search for specific package (without version)](#vulnerability-in-a-specific-package---search-for-specific-package-without-version) 14 | 2. [Vulnerability in specific package and version - Search for specific package and version](#vulnerability-in-specific-package-and-version---search-for-specific-package-and-version) 15 | 3. [Vulnerability across multiple packages - Search for images with multiple packages installed (Use IN array on SQL)](#vulnerability-across-multiple-packages---search-for-images-with-multiple-packages-installed--use-in-array-on-sql) 16 | 4. [Cleaning up](#cleaning-up) 17 | 5. [Contributing](#contributing) 18 | 6. [License](#license) 19 | 20 | ## Diagram 21 | ![diagram](assets/diagram.png) 22 | 23 | ## Instructions 24 | 25 | This section describes how to deploy the example architecture above which will use SBOM to find container images containing vulnerable software that are running on your EKS cluster. 26 | What we’ll do: 27 | 28 | * Clone the GitHub repository and deploy the solution architecture using Terraform 29 | * Deploy a cronjob on EKS cluster to discover images for running containers 30 | * Populate Glue catalog with with tables for SBOM and running images data 31 | * Run Athena queries to find images found to be using vulnerable libraries and packages 32 | 33 | ### Deploy the solution architecture using Terraform 34 | 35 | * First clone this [GitHub Repositoy](https://github.com/aws-samples/amazon-eks-use-sbom-to-find-container-image-with-vulnerable-software.git): 36 | 37 | * `git clone https://github.com/aws-samples/amazon-eks-use-sbom-to-find-container-image-with-vulnerable-software.git` 38 | 39 | In the root directory of the Git Repository: 40 | 41 | * Navigate to the terraform folder 42 | 43 | * `cd terraform` 44 | 45 | 46 | * Edit `terraform.tfvars` file with any text editor of your choice. Make sure you provide the correct value for aws_region variable to specify the AWS region where you want to deploy. You can accept the defaults for other variables or change them if you wish to. The following variables are available to edit: 47 | * `aws_region` = AWS Region you want to deploy this solution (note that the default CLI AWS region might overwrite this variable) 48 | * `ecr_repo_name` = Name of the ECR repository for the EKS container image to S3 solution 49 | * `codebuild_project_name` = CodeBuild name prefix for the SBOM solutions (event triggered and one-off solution) 50 | * `s3_bucket_name` = S3 Bucket prefix name (it'll add randmon char after this) for SBOM and EKS image files objects storage 51 | * `one_off_scan_repo_settings` = Option to limit the "one-off" CodeBuild project to only scan specific provided ECR repositories. If you want this behaviour, please provide a list (with empty spaces in between) of all ECR repositories names. As an example, if you'd like to have `my-repo-1` and `my-repo-2` as the only ECR repositories names for the one-off solution to run. You'd set this variable as `one_off_scan_repo_settings = my-repo-1 my-repo-2`. 52 | 53 | * Make sure the AWS IAM principal being used to run terraform has necessary privileges to deploy all resources. Refer to terraform documentation for providing AWS authentication credentials. 54 | * Confirm you have configured the default region on your AWS CLI, run: 55 | 56 | * `aws configure list` 57 | 58 | The recommended way to provide AWS credentials and default region is to use environment variables. For example, you can use AWS_PROFILE environment variable to provide the AWS CLI profile to use for running terraform. Refer to AWS CLI documentation for configuration details. 59 | 60 | * By default, the sample solution will store terraform state file locally. If you want to use a remote backend like S3, configure AWS provider accordingly following terraform documentation. 61 | * Initialize your working directory 62 | 63 | * `terraform init ` 64 | 65 | * Preview the resources that terraform will deploy in your AWS account 66 | 67 | * `terraform plan` 68 | 69 | * Deploy the resources. By running the following and selecting yes 70 | 71 | * `terraform apply` 72 | 73 | * If you would like to run this as part of a gitlab pipeline as an example and you need to automatically approve the terraform apply. Run the following: 74 | 75 | * `terraform apply --auto-approve` 76 | 77 | Terraform will output 2 values which you will need to copy for the next step of deploying cronjob on EKS. 78 | 79 | ### Deploy the EKS cronjob 80 | 81 | We assume that you an existing EKS cluster and kubernetes CLI installed on your machine to interact with the cluster. The following steps describe how to deploy the cronjob described in solution overview section on your cluster 82 | 83 | * Follow AWS documentation to create IAM OIDC provider for the cluster using eksctl 84 | * Create IAM role for service account using eksctl. This IAM role will be used by the EKS cronjob to write the list of running container images to S3 bucket. Replace cluster name and policy ARN copied from the Terraform output. 85 | 86 | * `eksctl create iamserviceaccount --name image-discovery-job --namespace sbom-image-discovery --cluster --attach-policy-arn --approve` 87 | 88 | 89 | * Navigate to eks-image-discovery folder in the Git repo 90 | * Terraform would have create an ECR repository in your account to store image of EKS cronjob. Go to ECR repository in AWS console and click on the button for view push commands. Follow those commands to build and push docker image of EKS cronjob to ECR repository. Copy the image URI of the newly pushed image from AWS console. 91 | * Navigate to config folder inside eks-image-discovery. This contains a Kubernetes manifest file that you need to edit for deploying the cronjob. Open eks-image-discovery.yaml file in any text editor. 92 | * Edit image field to change it to image URI that you copied in the previous step. 93 | * Update the name of S3 bucket in environment variable configuration. Use the bucket name copied from Terraform output 94 | * Update the name of EKS cluster in environment variable with your cluster name 95 | * Optionally update the cronjob schedule. By default, the job will run every 5 mins 96 | * Apply the manifest file using kubectl. 97 | 98 | * `kubectl apply -f eks-image-discovery.yaml` 99 | 100 | 101 | ### Populate the Glue catalog 102 | 103 | * Navigate to CodeBuild service in AWS console. Go to on off codebuild project and start a new build 104 | 105 | ![codebuild_01](assets/codebuild_screenshot_01.png) 106 | 107 | * Verify that CodeBuild job finishes successfully. 108 | * Navigate to S3 service in AWS console. Verify that CodeBuild job created SBOM files and EKS cronjob create list of running images file 109 | 110 | ![s3_01](assets/s3_screenshot_01.png) 111 | 112 | ![s3_02](assets/s3_screenshot_02.png) 113 | 114 | * Navigate to Glue service in AWS console. Go to crawlers and run the sbom carwler. 115 | 116 | ![crawler_01](assets/gluecrawler_screenshot_01.png) 117 | 118 | * Wait for crawler to finish successfully. Once finished, navigate to Databases in the AWS Glue service console and select sbom_db. You should see two tables created in the database called sbom and eks_running_images. 119 | 120 | ![database_01](assets/gluedatabase_screenshot_01.png) 121 | 122 | 123 | ### Run sample Athena Query 124 | 125 | We will use Amazon Athena, a serverless query service that allows customers to analyze data stored in Amazon S3 using standard SQL. In this section, we will discuss some scenarios where customers may want to find out vulnerable software packages, and provide Athena queries that can be used to scan through the SBOM files. 126 | 127 | To start querying your tables within the `sbom_db` database using Amazon Athena, follow these steps: 128 | 129 | * Open the Amazon Athena console in your AWS Management Console. 130 | * Confirm your data source is set to `AwsDataCatalog` - this should be the default selection. 131 | * Next, locate and select the `sbom_db` database from the Database dropdown list, typically located on the left-hand side of the Query Editor. 132 | * Before running your first query, configure your query result location by navigating to the Settings tab located at the top right corner of the console. Under 'Query result location', input or choose the desired Amazon S3 location where your query results will be stored. Remember to click 'Save' after setting the location. You can find more information on the Amazon Athena documentation about "Query results location". 133 | * You can now return to the Query Editor and start writing and executing your queries on the `sbom_db` database. 134 | 135 | 136 | Your Athena console should look something similar to the below screenshot: 137 | ![diagram](assets/athena_screenshot_01.jpg) 138 | 139 | Remember that Athena queries data directly from Amazon S3, so ensure your Athena service has the necessary permissions to access the designated S3 buckets. 140 | 141 | Into the next sections, let's delve into the powerful querying capabilities of Amazon Athena applied to Software Bill of Materials (SBOMs). To facilitate a better understanding, we'll walk you through three practical examples: 142 | 143 | * Find a container image(s) with a specific package: This will help you identify container images within your EKS cluster containing a particular package, regardless of its version. 144 | * Identify container image(s) with a certain package and version: This extends the previous example, enabling you to pinpoint container images not just by a package, but also its specific version running in your EKS cluster. 145 | * You can find few more Athena queries (SQL) examples at our GitHub repository. 146 | 147 | Remember, these examples merely scratch the surface. With Amazon Athena's robust SQL capabilities, you're encouraged to craft queries that fit your unique needs, leveraging the available SBOM data. This is not a limiting framework, but a jumping-off point to spark creativity and customization within your software supply chain management. 148 | 149 | 150 | #### **Vulnerability in a specific package - Search for specific package (without version)** 151 | 152 | Identifying container images that contain vulnerable packages is essential to maintaining a secure container environment. By utilizing the query below, security and development teams can quickly pinpoint and address any security issues present in their EKS cluster. This particular query allows users to input a package name with a wildcard character (%) and retrieve a list of all container images on their EKS cluster that contain a package with the specified name. 153 | 154 | Sample Query: 155 | 156 | ``` 157 | SELECT 158 | name AS sbom_image_name, 159 | package.name AS sbom_package_name, 160 | package.sourceInfo AS sbom_sourceInfo, 161 | package.versionInfo AS sbom_version_info, 162 | eks_running_images.image_name AS eks_running_image, 163 | eks_running_images.pods 164 | FROM 165 | sbom 166 | CROSS JOIN UNNEST(sbom.packages) AS t (package) 167 | INNER JOIN eks_running_images 168 | ON sbom.name = eks_running_images.image_name 169 | WHERE package.name LIKE '%%' 170 | ``` 171 | 172 | Sample Output: 173 | ``` 174 | # sbom_image_name sbom_package_name eks_running_image pods sbom_sourceInfo sbom_version_info 175 | 1 176 | ``` 177 | 178 | Sample Screenshot: 179 | ![diagram](assets/athena_screenshot_02.jpg) 180 | 181 | 182 | #### **Vulnerability in specific package and version - Search for specific package and version** 183 | 184 | This sample query allows users to input a package name with a wildcard character (%) and retrieve a list of all container images on their EKS cluster that contain a package with the specified name. Copy the query from the example below, and replace the with a package name that you'd like to find out if any container image running on your EKS cluster exists (or not) in containers that you have generated the SBOM. 185 | 186 | In our example, consider the deployed container "eks-image-discovery" in your EKS cluster, containing a package named "boto3". To locate this package, you would append WHERE package.name LIKE '%boto3%' to the end of your Athena query. This approach enhances the discoverability of any package within your ECR container images. The beauty of this solution is its utilization of SQL language, granting you flexibility to tailor your queries to your needs, based on the available SBOM and running EKS images. This makes it a powerful tool for understanding your software supply chain. 187 | 188 | Sample Query: 189 | 190 | ``` 191 | SELECT 192 | name AS sbom_image_name, 193 | package.name AS sbom_package_name, 194 | package.sourceInfo AS sbom_sourceInfo, 195 | package.versionInfo AS sbom_version_info, 196 | eks_running_images.image_name AS eks_running_image, 197 | eks_running_images.pods 198 | FROM 199 | sbom 200 | CROSS JOIN UNNEST(sbom.packages) AS t (package) 201 | INNER JOIN eks_running_images 202 | ON sbom.name = eks_running_images.image_name 203 | WHERE package.name LIKE '%%' 204 | AND package.versionInfo LIKE '' 205 | ``` 206 | 207 | Incorporating SQL wildcards in your search for package versions can increase the flexibility of your query. For instance, if you need to find all versions that adhere to the format/version "1.26.X", where X can be any value, you may utilize the following AND statement (as depicted in the last line above): 208 | 209 | `AND package.versionInfo LIKE '1.26.%'` 210 | 211 | Sample Output: 212 | ``` 213 | # sbom_image_name sbom_package_name eks_running_image pods sbom_sourceInfo sbom_version_info 214 | 1 215 | ``` 216 | 217 | Sample Screenshot: 218 | ![diagram](assets/athena_screenshot_03.jpg) 219 | 220 | 221 | #### **Vulnerability across multiple packages - Search for images with multiple packages installed (Use IN array on SQL)** 222 | 223 | On occasion, users may need to search for multiple packages simultaneously, such as when a combination of packages creates a vulnerability or scenario that requires mitigation. This solution enables you to query for multiple package names across all the software bill of materials (SBOMs) generated from your container images. By searching for multiple packages at once, you can gain a more comprehensive view of your environment and identify any potential risks or issues. This approach can be particularly useful in large-scale deployments with complex software stacks. 224 | 225 | Sample Query: 226 | ``` 227 | SELECT 228 | name AS sbom_image_name, 229 | package.name AS sbom_package_name, 230 | package.sourceInfo AS sbom_sourceInfo, 231 | package.versionInfo AS sbom_version_info, 232 | eks_running_images.image_name AS eks_running_image, 233 | eks_running_images.pods 234 | FROM 235 | sbom 236 | CROSS JOIN UNNEST(sbom.packages) AS t (package) 237 | INNER JOIN eks_running_images 238 | ON sbom.name = eks_running_images.image_name 239 | WHERE package.name LIKE ('') OR package.name LIKE ('') 240 | ``` 241 | 242 | To match multiple packages simultaneously, you can include multiple OR statements in your query. Similarly, you can use wildcard characters in your LIKE statements to broaden your search, as demonstrated below: 243 | 244 | `WHERE package.name LIKE ('<%PACCKAGE_NAME_1>%') OR package.name LIKE ('<%PACCKAGE_NAME_2%>')` 245 | 246 | 247 | Sample Output: 248 | ``` 249 | # sbom_image_name sbom_package_name eks_running_image pods sbom_sourceInfo sbom_version_info 250 | 1 251 | ``` 252 | 253 | Sample Screenshot: 254 | ![diagram](assets/athena_screenshot_04.jpg) 255 | 256 | 257 | ## Cleaning up 258 | 259 | To avoid incurring future charges, delete the resources created for this solution by running the following commands: 260 | - Navigate to the eks-image-discovery folder from the root of the repository. 261 | 262 | `cd eks-image-discovery` 263 | 264 | - Delete the Amazon EKS CronJob and associated resources 265 | 266 | `kubectl delete -f eks-image-discovery.yaml` 267 | 268 | - Delete the AWS IAM service account 269 | 270 | `eksctl delete iamserviceaccount --name image-discovery-job --namespace sbom-image-discovery --cluster <>` 271 | 272 | - Empty the Amazon S3 bucket created by Terraform for storing SBOM and running images files. If the bucket is not empty, then Terraform fails to delete the Amazon S3 bucket. Replace bucket name with the value from Terraform output. If you haven’t copied the bucket name, run terraform output command to view the outputs again. 273 | 274 | `aws s3 rm s3://<> --recursive` 275 | 276 | - Destroy all resources created by Terraform 277 | 278 | `terraform destroy` 279 | 280 | # Contributing 281 | 282 | See [CONTRIBUTING](CONTRIBUTING.md#contributing-via-pull-requests) for more information. 283 | 284 | # License 285 | This library is licensed under the MIT-0 License. See the LICENSE file. 286 | --------------------------------------------------------------------------------