【AWS】Amazon ECR ~ boto3 ~

■ はじめに

https://dk521123.hatenablog.com/entry/2024/01/05/000000
https://dk521123.hatenablog.com/entry/2024/01/22/210831

で、Amazon ECR の Dockerイメージのスキャンについて触れたが、
エラーを解消できても、時間が経つと別のエラーが検知されたりと
結構、頻繁に更新されるので、せめてレポートを半自動化したい。

そこで、今回は、Amazon ECR の boto3 API について触れる。

後日談

* 同じようなことを考えている方がいらっしゃった、、、

https://made.livesense.co.jp/entry/2023/05/31/080000

目次

【1】boto3 API
 1)list_images
 2)describe_image_scan_findings
【2】サンプル
 例1:list_images / describe_image_scan_findings
 例2:+ Inspector2.list_findings

【1】boto3 API

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecr.html

1)list_images

* リポジトリ内のイメージ一覧表示

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecr/client/list_images.html

2)describe_image_scan_findings

* スキャン結果取得

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecr/client/describe_image_scan_findings.html

【2】サンプル

例1:list_images / describe_image_scan_findings

import boto3


ecr_client = boto3.client('ecr', region_name="us-west-2")

target_repository_name = "your_repository_name"
target_image_tag = "latest"

response_for_list_images = ecr_client.list_images(
    repositoryName=target_repository_name
)
print(f"response_for_list_images={response_for_list_images}")

# find target imageDigest
target_ids = next(filter(lambda x: x.get("imageTag") == target_image_tag, response_for_list_images.get("imageIds")), None)
if target_ids is None:
  print("target_ids is None")
  exit
target_image_digest = target_ids.get("imageDigest")

response = ecr_client.describe_image_scan_findings(
  repositoryName=target_repository_name,
  imageId={
    'imageDigest': target_image_digest,
    'imageTag': target_image_tag
  }
)

print(f"response = {response}")

例2:+ Inspector2.list_findings

上記「describe_image_scan_findings」の戻り値のremediationだと
AWS Management コンソールと比べると、異なる値(使えない値)が
返ってくるので、以下のAPIを使ってみる

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/inspector2/client/list_findings.html

report.py

import sys
import boto3

class EcrScanReporter:
  def __init__(self, args, aws_region: str = "us-west-2"):
    self.repository_name = args[1]
    self.image_tag = args[2]
    self.aws_region = aws_region

    self.ecr_client = boto3.client('ecr', region_name=aws_region)
    self.inspector2_client = boto3.client('inspector2', region_name=aws_region)
    self.s3_client = boto3.client('s3', region_name=aws_region)
    self.sns_client = boto3.client('sns', region_name=aws_region)

  def execute(self):
    print(f"Your inputs: {self.repository_name}, {self.image_tag}")
    image_digest = self.get_image_digest()
    if image_digest is None:
      print("[WARN] Please make sure your inputs")
      return
    self.get_scan_results(image_digest)

  def get_image_digest(self):
    response = self.ecr_client.list_images(
      repositoryName=self.repository_name
    )
    print(f"[DEBUG] response_for_list_images={response}")
    # find target imageDigest
    target_ids = next(filter(lambda x: x.get("imageTag") == self.image_tag, response.get("imageIds")), None)
    if target_ids is None:
      print("[WARN] target_ids is None")
      return None
    return target_ids.get("imageDigest")

  def get_scan_results(self, target_image_digest):
    response = self.ecr_client.describe_image_scan_findings(
      repositoryName=self.repository_name,
      imageId={
        'imageDigest': target_image_digest,
        'imageTag': self.image_tag
      }
    )
    print(f"[DEBUG] response = {response}")

    image_scan_status = response.get("imageScanStatus")
    print(f"image_scan_status = {image_scan_status}")
    if (image_scan_status.get("status") != "COMPLETE"):
      print("[WARN] Not finish yet")
      return

    image_scan_findings = response.get("imageScanFindings")
    for index, finding in enumerate(image_scan_findings.get("enhancedFindings")):
      vulnerability_id = finding.get("packageVulnerabilityDetails").get("vulnerabilityId")
      print(f"[{index}] vulnerability_id={vulnerability_id}")
      severity = finding.get("severity")
      print(f"[{index}] severity={severity}")
      finding_arn = finding.get("findingArn")
      print(f"[{index}] finding_arn={finding_arn}")

      describe_response = self.inspector2_client.list_findings(filterCriteria={
        'findingArn': [
          {
            'comparison': 'EQUALS',
            'value': finding_arn
          },
        ]
      })
      print(f"[{index}] describe_response={describe_response}")


if __name__ == "__main__":
    args = sys.argv
    report = EcrScanReporter(args)
    report.execute()

参考文献

https://made.livesense.co.jp/entry/2023/05/31/080000

関連記事

Amazon ECR ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2020/05/22/165711
Amazon ECR ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2020/05/26/142645
Amazon ECR ~ AWS CLI
https://dk521123.hatenablog.com/entry/2024/01/05/000000
Amazon ECR ~ Dockerイメージを Pull & Push ~
https://dk521123.hatenablog.com/entry/2023/12/02/024631
Amazon ECR ~ 脆弱性診断 / Amazon Inspector ~
https://dk521123.hatenablog.com/entry/2024/01/22/210831
Terraform ~ AWS ECR ~
https://dk521123.hatenablog.com/entry/2023/05/23/002314
Amazon ECR でのトラブルシューティング
https://dk521123.hatenablog.com/entry/2020/05/24/000000