AWS 기술 블로그

AWS Signer를 사용하여 컨테이너 이미지 빌드 파이프라인을 보호하기 위한 모범 사례

이 글은 AWS Security Blog에 게시된 Best Practices to help secure your container image build pipeline by using AWS Signer by Jorge Castillo, Joseph Rodríguez, and Monika Vu Minh을 한국어로 번역 및 편집하였습니다.

AWS Signer는 코드의 신뢰성과 무결성을 보장하는 데 도움이 되는 완전관리형 코드 서명 서비스입니다. 이는 코드가 신뢰할 수 있는 소스에서 왔으며 승인되지 않은 사용자가 코드에 액세스하지 않았는지 확인하는 데 도움이 됩니다. AWS Signer는 코드 서명 인증서와 공개키 및 개인키를 관리하므로 공개 키 기반 구조(PKI) 관리 오버헤드를 줄일 수 있습니다. 또한 사용자가 코드 서명 및 확인에 집중할 수 있도록 키와 인증서의 수명 주기 관리를 단순화하는 기능 세트도 제공합니다.

2023년 6월, AWS는 Amazon Elastic Container Registry(Amazon ECR)에 저장된 컨테이너 이미지 서명 및 확인을 위한 컨테이너 이미지 서명 기능을 발표하였습니다. 이 기능을 사용하면 완전 관리형 코드 서명 서비스인 Signer를 사용하여 승인된 컨테이너 이미지만 Amazon Elastic Kubernetes Service(EKS) 클러스터에 배포되었음을 검증할 수 있습니다.

컨테이너와 AWS Lambda 함수는 클라우드에 구축된 애플리케이션을 위한 널리 사용되는 서버리스 컴퓨팅 솔루션입니다. AWS Signer를 사용하면 이러한 워크로드에서 실행되는 소프트웨어가 신뢰할 수 있는 소스에서 나온 것인지 확인할 수 있습니다.

이 블로그 게시물에서는 소프트웨어 보안, 거버넌스 및 규정 준수 요구 사항에 대한 코드 서명의 이점에 대해 알아봅니다. 유연한 지속적 통합 및 지속적 전달(CI/CD) 통합, 서명 ID 관리, 다른 AWS 서비스와의 네이티브 통합을 통한 자동화로 코드 보안을 단순화할 수 있습니다.

배경

코드 서명은 소프트웨어 공급망의 중요한 부분입니다. 이는 코드가 변조되지 않았고 승인된 출처에서 제공되었는지 확인하는 데 도움이 됩니다.

소프트웨어 개발 워크플로우를 자동화하기 위해 조직에서는 CI/CD 파이프라인을 구현하여 코드를 효과적으로 푸시, 테스트 및 배포하는 경우가 많습니다. 그림 1과 같이 코드 서명을 워크플로우에 통합하면 신뢰할 수 없는 코드가 배포되는 것을 방지할 수 있습니다. 파이프라인에서 코드 서명은 기능 사용 방법에 따라 다양한 유형의 정보를 제공할 수 있습니다. 예를 들어, 코드 서명을 빌드 단계에 통합하여 코드의 취약점을 검사하고, 소프트웨어 구성 명세서 (SBOM)가 내부적으로 승인되었으며 단위 및 통합 테스트를 거쳤음을 증명할 수 있습니다. 코드 서명을 사용하여 개발자, 팀, 조직 등 코드를 푸시했거나 게시한 사람을 확인할 수도 있습니다. 파이프라인에 여러 서명 단계를 포함시켜 이러한 각 단계를 개별적으로 확인할 수 있습니다. 컨테이너 이미지 서명이 제공하는 이점에 대한 자세한 내용은 컨테이너용 암호화 서명을 참조하세요.

그림 1: 파이프라인 내의 보안

다음 섹션에서는 Amazon Elastic Kubernetes Service(Amazon EKS) 배포에 대한 간단한 이미지 서명 구현 및 확인 과정을 안내합니다. 서명은 컨테이너 이미지가 파이프라인을 통과했으며 신뢰할 수 있는 출처에서 왔음을 증명합니다. 다양한 AWS Signer 서명 프로필을 활용하는 여러 AWS CodeBuild 코드 서명 단계를 추가하면 더 복잡한 시나리오에서 이 프로세스를 사용할 수 있습니다.

서비스 및 도구

이 섹션에서는 이 솔루션에 필요한 다양한 AWS 서비스와 타사 도구에 대해 설명합니다.

CI/CD 서비스

CI/CD 파이프라인의 경우 다음 AWS 서비스를 사용합니다.

  • AWS CodePipeline — 빠르고 안정적인 애플리케이션 및 인프라 업데이트를 위해 릴리스 파이프라인을 자동화하는 데 사용할 수 있는 완전 관리형 지속적 전달 서비스입니다.
  • AWS CodeCommit — 안전한 프라이빗 Git 리포지토리를 호스팅하는 완전 관리형 소스 제어 서비스입니다.
  • AWS Signer — 코드의 신뢰성과 무결성을 보장하는 데 사용할 수 있는 완전 관리형 코드 서명 서비스입니다.
  • AWS CodeBuild — 소스 코드를 컴파일하고, 테스트를 실행하고, 배포할 준비가 된 소프트웨어 패키지를 생성하는 완전 관리형 지속적 통합 서비스입니다.

컨테이너 서비스

컨테이너에 대해 다음 AWS 서비스를 사용합니다.

  • Amazon EKS — AWS 클라우드 및 온프레미스 데이터 센터에서 Kubernetes를 실행하는 관리형 Kubernetes 서비스입니다.
  • Amazon ECR — 고성능 호스팅을 위한 완전 관리형 컨테이너 레지스트리로, 애플리케이션 이미지와 아티팩트를 어디에서나 안정적으로 배포할 수 있습니다.

검증 도구

다음은 이 게시물의 파이프라인에 통합된 공개적으로 사용 가능한 서명 확인 도구이지만, 특정 요구 사항을 충족하는 다른 도구를 사용할 수도 있습니다.

  • NotationCloud Native Computing Foundation (CNCF) 내에서 공개적으로 사용 가능한 Notary project입니다. Notary는 AWS 및 기타 업체의 컨트리뷰션을 바탕으로 한 키 관리 및 기타 통합을 위한 공급업체별 플러그인을 허용하는 개방형 표준이자 클라이언트 구현입니다. AWS Signer는 서명 키, 키 순환 및 PKI 관리를 자동으로 관리하며 간단한 클라이언트 기반 워크플로우를 제공하는 선별된 플러그인을 통해 Notation과 통합됩니다.
  • Kyverno — Kubernetes용으로 설계된 공개적으로 사용 가능한 정책 엔진입니다.

솔루션 개요

그림2: 솔루션 아키텍처

그림 2와 같이 솔루션의 작동 방식은 다음과 같습니다.

  1. 개발자는 Dockerfile과 애플리케이션 코드를 CodeCommit에 푸시합니다. CodeCommit에 푸시할 때마다 CodePipeline에서 호스팅되는 파이프라인이 시작됩니다.
  2. CodeBuild는 빌드를 패키징하고, 애플리케이션을 컨테이너화하고, ECR 레지스트리에 이미지를 저장합니다.
  3. CodeBuild는 앞에서 Amazon ECR에 푸시된 이미지의 특정 버전을 검색합니다. AWS Signer와 Notation은 그림 3에 자세히 나와 있는 것처럼, 이전에 설정한 서명 프로필을 사용하여 이미지에 서명합니다.

    그림3: 이미지 서명
  4. AWS Signer 및 Notation은 서명된 이미지 버전을 확인한 다음, 이를 Amazon EKS 클러스터에 배포합니다.
    이전에 이미지가 올바르게 서명되지 않은 경우, CodeBuild 로그에 다음과 유사한 출력이 표시됩니다:

    Error: signature verification failed: no signature is associated with "<AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/hello-server@<DIGEST>" , make sure the artifact was signed successfully

    서명 불일치가 있는 경우, CodeBuild 로그에 다음과 유사한 출력이 표시됩니다:

    Error: signature verification failed for all the signatures associated with <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/hello-server@<DIGEST>
  5. Kyverno는 Amazon EKS 클러스터에서 사용할 컨테이너 이미지 서명을 확인합니다.
    그림 4에서는 4단계와 5단계를 더 자세히 보여줍니다.

그림4: Kubernetes의 이미지 서명 검증

전제조건

시작하기 전에 다음 필수 구성 요소가 갖추어져 있는지 확인하세요.

  • 프로비저닝된 Amazon EKS 클러스터
  • 컨테이너 이미지용 Amazon ECR 리포지토리
  • 애플리케이션 코드가 포함된 CodeCommit 리포지토리. 자세한 내용은 AWS CodeCommit 리포지토리 만들기를 참조하십시오.
  • CodeCommit 리포지토리를 코드 소스로 사용하고 4가지 CodeBuild 단계(Build, ApplicationSigning, ApplicationDeployment 및 verifyContainerSign)를 사용하여 배포된 CodePipeline 파이프라인. CI/CD 파이프라인은 그림 5와 같아야 합니다.

그림5: CodePipeline을 사용한 CI/CD 파이프라인

단계별 절차

AWS 명령줄 인터페이스(AWS CLI), AWS Management 콘솔 또는 AWS Signer API를 사용하여 서명 프로필을 생성할 수 있습니다. 이 섹션에서는 AWS CLI를 사용하여 이미지에 서명하는 방법을 안내합니다.

이미지에 서명하기(AWS CLI)

  1. 각 ID에 대한 서명 프로필을 만듭니다.
    # Create an AWS Signer signing profile with default validity period
    $ aws signer put-signing-profile \
        --profile-name build_signer \
        --platform-id Notation-OCI-SHA384-ECDSA
  2. CodeBuild build에서 이미지에 서명합니다. buildspec.yaml 구성 파일은 다음과 같아야 합니다:
    version: 0.2
    
    phases:
      pre_build:
        commands:
          - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
          - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr. $AWS_REGION.amazonaws.com/hello-server
          - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
          - IMAGE_TAG=${COMMIT_HASH:=latest}
          - DIGEST=$(docker manifest inspect $AWS_ACCOUNT_ID.dkr.ecr. $AWS_REGION.amazonaws.com/hello-server:$IMAGE_TAG -v | jq -r '.Descriptor.digest')
          - echo $DIGEST
          
          - wget https://d2hvyiie56hcat.cloudfront.net/linux/amd64/installer/rpm/latest/aws-signer-notation-cli_amd64.rpm
          - sudo rpm -U aws-signer-notation-cli_amd64.rpm
          - notation version
          - notation plugin ls
      build:
        commands:
          - notation sign $REPOSITORY_URI@$DIGEST --plugin com.amazonaws.signer.notation.plugin --id arn:aws:signer: $AWS_REGION:$AWS_ACCOUNT_ID:/signing-profiles/notation_container_signing
          - notation inspect $AWS_ACCOUNT_ID.dkr.ecr. $AWS_REGION.amazonaws.com/hello-server@$DIGEST
          - notation verify $AWS_ACCOUNT_ID.dkr.ecr. $AWS_REGION.amazonaws.com/hello-server@$DIGEST
      post_build:
        commands:
          - printf '[{"name":"hello-server","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
    artifacts:
        files: imagedefinitions.json

    buildspec.yaml 구성 파일의 명령은 다음을 수행합니다.
    a. Docker 이미지 작업을 위해 Amazon ECR에 로그인합니다.
    b. repository URI를 ECR 이미지로 설정하고 소스 Git commit ID의 처음 7자로 이미지 태그를 추가합니다. 해당 컨테이너 이미지의 다이제스트를 얻습니다.
    c. Notation CLI를 설치합니다. 이 예에서는 Linux용 설치 프로그램을 사용합니다. 다양한 운영 체제에 대한 설치 프로그램 목록은 AWS Signer 개발자 안내서를 참조하십시오.
    d. notation sign 명령을 사용하여 컨테이너 이미지에 서명합니다. 이 명령은 이미지 태그 대신 컨테이너 이미지 다이제스트를 사용합니다.
    e. notation inspect 명령을 사용하여 서명된 이미지를 검사하여 성공적으로 서명되었는지 확인합니다.
    f. notation verify 명령을 사용하여 서명된 이미지를 확인합니다. 출력은 다음과 유사해야 합니다.

    Successfully verified signature for <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/hello-server@<DIGEST>
  3. (선택 사항) 문제 해결을 위해 파이프라인 자체에서 notation policy를 print하는 notation policy show 명령을 실행하여 예상대로 작동하는지 확인합니다:
    notation policy show

    이를 위해 buildspec.yaml 구성 파일에서 pre_build 단계에 있는 notation version 명령 다음에 해당 명령을 포함시킵니다.
    notation policy show 명령이 실행된 후 CodeBuild 로그에 다음과 유사한 출력이 표시되어야 합니다:

    {
      "version": "1.0",
      "trustPolicies": [
        {
          "name": "aws-signer-tp",
          "registryScopes": [
          "<AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/hello-server"
          ],
          "signatureVerification": {
            "level": "strict"
          },
          "trustStores": [
            "signingAuthority:aws-signer-ts"
          ],
          "trustedIdentities": [
            "arn:aws:signer:<AWS_REGION>:<AWS_ACCOUNT_ID>:/signing-profiles/notation_test"
          ]
        }
      ]
    }
  4. Kubernetes에서 이미지를 검증하려면 EKS 클러스터에 Kyverno와 Kyverno-notation-AWS Signer를 모두 설정하십시오. Kyverno 및 Kyverno-notation-AWS Signer 솔루션을 시작하려면 설치 지침을 참조하십시오.
  5. Kyverno 및 Kyverno-notation-AWS Signer를 설치한 후 컨트롤러가 실행 중인지 확인합니다. STATUS에 Running이 표시되어야 합니다:
    $ kubectl get pods -n kyverno-notation-aws -w
    
    NAME                                    READY   STATUS    RESTARTS   AGE
    kyverno-notation-aws-75b7ddbcfc-kxwjh   1/1     Running   0          6h58m
  6. CodeBuild buildspec.yaml 구성 파일을 구성하여 클러스터에 배포된 이미지가 사전에 서명되었는지 확인합니다. 다음 코드를 사용하여 buildspec.yaml 파일을 구성할 수 있습니다.
    version: 0.2
    
    phases:
      pre_build:
        commands:
          - echo Logging in to Amazon ECR...
          - aws --version
          - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/hello-server
          - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
          - IMAGE_TAG=${COMMIT_HASH:=latest}
          - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
          - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl.sha256"
          - echo "$(cat kubectl.sha256)  kubectl" | sha256sum --check
          - chmod +x kubectl
          - mv ./kubectl /usr/local/bin/kubectl
          - kubectl version --client
      build:
        commands:
          - echo Build started on `date`
          - aws eks update-kubeconfig --name ${EKS_NAME} --region ${AWS_REGION}
          - echo Deploying Application
          - sed -i.bak 's#IMAGE_TAG#'"$IMAGE_TAG"'#' deployment.yaml
          - kubectl apply -f ./yaml/deployment.yaml
          - STATUS=$(kubectl logs --tail=1 deploy/kyverno-notation-aws -n kyverno-notation-aws | grep $IMAGE_TAG | awk '{ if ($2 ~ "ERROR") { print } }')
          - |
            if [[ $STATUS ]]; then
              echo "There is an error"
              exit 1
            else
              echo "No Error"
            fi
      post_build:
        commands:
          - printf '[{"name":"hello-server","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
    artifacts:
        files: imagedefinitions.json

    buildspec.yaml 구성 파일의 명령은 다음을 수행합니다.
    a. ECR repository URI 및 commit hash와 같은 환경 변수를 설정하여 이미지 태그를 빌드합니다. kubectl 도구는 나중에 이를 사용하여 Kubernetes 개체와 함께 배포될 컨테이너 이미지를 참조합니다.
    b. kubectl을 사용하여 EKS 클러스터에 연결하고 deplyment.yaml 파일에 배포 대상 컨테이너 이미지를 추가합니다.
    c. 컨테이너가 배포된 후 kyverno-notation-aws 컨트롤러를 관찰하고 해당 로그에 액세스할 수 있습니다. 배포된 이미지가 서명되었는지 확인할 수 있습니다. 로그에 오류가 포함된 경우 오류 코드를 사용하여 파이프라인 실행을 중지하거나, 이전 버전으로 롤백하거나, 이미지가 서명되지 않은 것을 감지하면 배포된 deployment를 삭제하세요.

AWS 리소스 정리

이 게시물에 대해 프로비저닝한 리소스가 더 이상 필요하지 않은 경우 다음 단계를 완료하여 삭제하세요.

리소스 정리하기

  1. EKS 클러스터를 삭제하고 ECR 이미지를 삭제합니다.
  2. IAM roles for service accounts (IRSA) 구성에 사용한 IAM 역할 및 정책을 삭제합니다.
  3. AWS CLI에서 다음 명령을 실행하여 서명 프로세스를 위해 생성하고 사용한 AWS Signer 서명 프로필을 취소합니다:
    $ aws signer revoke-signing-profile
  4. Amazon ECR 리포지토리에서 서명을 삭제합니다. <AWS_ACCOUNT_ID> 및 <AWS_REGION>을 자신의 정보로 바꿔야 합니다.
    # Use oras CLI, with Amazon ECR Docker Credential Helper, to delete signature
    $ oras manifest delete <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/pause@sha256:ca78e5f730f9a789ef8c63bb55275ac12dfb9e8099e6a0a64375d8a95ed501c4

    참고: ORAS 프로젝트의 oras 클라이언트를 사용하면 서명 및 기타 참조 유형 아티팩트를 삭제할 수 있습니다. 먼저 인덱스에서 참조를 제거한 다음 매니페스트를 삭제합니다.

결론

이 게시물에서는 CodePipeline, CodeBuild, Amazon ECR 및 AWS Signer와 같은 AWS 서비스와 Notary 및 Kyverno와 같은 공개적으로 사용 가능한 도구를 사용하여 CI/CD 파이프라인에서 컨테이너 이미지 서명을 구현하는 방법을 배웠습니다. 파이프라인에 필수 이미지 서명을 구현하면 검증되고 승인된 컨테이너 이미지만 프로덕션에 배포되는지 확인할 수 있습니다. 컨테이너를 대규모로 안전하게 배포하려면 서명 프로세스와 서명 확인을 자동화하는 것이 중요합니다. 또한 배포 도중 또는 배포 후에 서명된 이미지를 확인하는 방법도 배웠습니다. 이 게시물은 공급망 보안 강화을 제공하기 위해 AWS의 CI/CD 파이프라인에 이미지 서명 기능을 추가하려는 모든 사람에게 귀중한 통찰력을 제공합니다. AWS 관리형 서비스와 공개적으로 사용 가능한 도구의 조합으로 이러한 강력한 환경을 구현할 수 있습니다.

이 게시물에 대한 피드백이 있는 경우 아래 댓글 섹션에 댓글을 남겨주세요. 이 게시물에 대해 질문이 있는 경우 AWS Support에 문의하세요.

Cho Sungchul

Cho Sungchul

아마존웹서비스 코리아의 Solutions Architect로서 고객의 클라우드 여정을 위해 기술적으로 돕는 역할을 담당하고 있습니다. 새로운 분야나 기술에 대한 배움과 핸즈온을 즐기는 엔지니어로서 기술 관련 공유와 커뮤니케이션을 좋아합니다. 현재 AWS AOD (area of depth ) member of container TFC (technical field community)로 활동하고 있습니다. 주요 관심사는 오픈 소스, 컨테이너, 보안, 스토리지, 그리고 Software-Defined Data Center (SDDC) 및 서버 가상화입니다.