Amazon Web Services 한국 블로그

테스트 주도 보안으로 DevSecOps 도입 가속화 하기

많은 기업이 애자일과 DevOps를 핵심 전략으로 채택하여 빠르게 비즈니스를 성장시키고 있습니다. 대부분의 경우 DevOps는 비즈니스의 핵심 역량이 되었습니다. 하지만 속도와 자동화를 달성한 DevOps 조직의 발전과 비교할 때 기존의 보안 제어 요소들은 DevOps 파이프라인과의 통합에 어려움을 겪거나 혹은 DevOps와 보안을 개별적으로 여기기도 합니다. 이러한 상황에서 기존의 보안 접근 방법은 병목현상을 만들기도 합니다.

DevOps와 기존의 보안을 어떻게 통합할 수 있을까에 대한 고민으로 DevSecOps가 널리 이야기되기 시작한 지도 많은 시간이 지났습니다. 하지만 여전히 DevSecOps를 DevOps의 문화에 어떻게 적용하여 구현하고 개선할 수 있는지에 대한 실질적인 관행을 찾는 것에 많은 기업이 여전히 어려움을 느끼고 있습니다. 이러한 고객여러분의 고민에 도움을 드리기 위해 AWS 솔루션즈 아키텍트팀에서는 AWS Builders 프로그램 Industry Edition : 보안 현대화 라는 주제로 5회차에 걸쳐서 온라인 세미나를 진행하기도 하였습니다.

이 글을 통해서 DevSecOps를 시작할 수 있는 여러 방법 중 DevOps와 보안을 연결해주는 출발점이 될 수 있는 중요 프랙티스인 테스트 주도 보안(Test Driven Security, TDS)에 대해서 살펴보고 관련된 예시를 AWS 환경에서 구현해 보도록 하겠습니다.

DevSecOps 소개

DevOps는 2009년 오라일리 주최 벨로시티 컨퍼런스에서 처음 이야기되기 시작하였습니다. DevOps는 애플리케이션과 서비스를 빠른 속도로 제공할 수 있도록 조직의 역량을 향상하는 조직문화, 방식, 환경 및 도구의 조합으로, 기존 방식을 사용하는 조직보다 제품을 더 빠르게 혁신하고 개선하는 것을 목표로 하고 있습니다. DevOps는 애자일 방법론과 마이크로 서비스 아키텍처 그리고 클라우드 기술과 결합하여 더욱 강력한 모습으로 나아가고 있습니다.

DevSecOps는 2012년 말 가트너에서 기존의 DevOps보다 발전된 모습의 새로운 개념으로 처음 소개되었습니다. 이것은 DevOps와 보안을 결합한 개념으로 DevOps의 속도를 떨어뜨리지 않으면서 보안을 확보하는 것을 의미합니다. 즉, DevSecOps는 DevOps 환경의 보안을 높이기 위한 개념입니다. DevSecOps는 보안의 여러 기술 요소들을 코드로 표현하는 것으로부터 출발하며 DevOps의 핵심 구성 요소인 CI/CD 파이프라인의 보안 및 파이프라인을 타고 흘러가는 아티팩트들의 보안 그리고 보안 운영의 자동화까지를 포함하는 상당히 폭넓고 광범위한 주제를 다루고 있습니다.

DevSecOps의 주요 개념 중에는 “Shift Left”라는 개념이 있습니다. Shift Left는 소프트웨어 개발 수명 주기에서 가능한 한 빨리 보안 제어를 도입하여 보안 위협 요소를 제거하는 방법을 말합니다. 이번 포스팅에서 이야기할 테스트 주도 개발은 이러한 Shift Left 개념에서 중요한 역할을 하게 됩니다.

테스트 주도 보안

테스트 주도 보안은 크게 보면 중요성이 나날이 강조되고 있는 지속적인 보안(Continuous Security) 활동의 중요한 예시로 볼 수 있습니다. 지속적인 보안은 DevOps 환경의 베스트 프랙티스들이 보안 영역과 통합되어 DevOps의 가치를 해치지 않으면서 시스템의 안전을 확보해 나가는 활동으로 볼 수 있으며 이러한 활동은 규정 준수 자동화로 알려진 Compliance as Code로 확장될 수 있습니다. Compliance as Code는 보안 위반사항들을 자동으로 예방하고 탐지하며, 더 나아가서는 복구 작업까지도 자동화해서 한층 더 높은 보안 수준을 유지할 수 있도록 해주는 개념으로 확장되고 있습니다. 최근 Compliance as Code는 Infra as Code만큼이나 큰 관심을 받는 분야입니다. AWS에서는 AWS Config 서비스와 AWS Systems Manager를 통해 이러한 부분을 쉽게 확장 할 수 있도록 지원합니다.

테스트 주도 보안은 보안 제어 요소를 마치 원하는 동작에 해당하는 테스트를 먼저 작성한 다음, 해당 테스트를 구현하는 코드를 작성하도록 권장하는 테스트 주도 개발(TDD)과 유사한 접근 방식입니다. 테스트 주도 보안에서 중요한 부분은 보안을 마치 제품 및 서비스의 기능의 일부처럼 다루도록 하는 것입니다. 코드를 통해 제품의 시스템에 직접 보안 제어를 구현함으로써 이것을 가능하게 합니다. 보안 담당자는 별도의 보안 인프라를 구축하는 대신 DevOps 환경에서 보안 제어를 구축하고 테스트하도록 합니다. 테스트 주도 보안의 각 테스트 들은 DevOps 환경에서 항상 테스트 되고 우리는 그 결과를 확인 할 수 있습니다.

제대로 작동하는 DevOps 환경에서는 수동 테스트는 규칙이 아니라 항상 예외가 되어야 합니다. 보안 테스트는 모든 애플리케이션 테스트가 CI/CD 파이프라인에서 처리되는 것과 같은 방식으로 항상 자동으로 처리되어야 합니다. 테스트 주도 보안은 DevOps와 DevSecOps를 연결하는 중요한 연결 고리로 인식되고 있습니다. 또한 테스트 주도 보안은 그 조직이 얼마나 안전한지에 대한 지속적인 그림을 보여주는 주요한 활동으로 볼 수 있습니다. 또한 클라우드 환경의 모든 서비스는 API를 제공하며 IaC(Infrastructure as Code)를 지원하므로 사실상 우리가 원하는 모든 보안 테스트를 코드로 구현할 수 있습니다.

테스트 주도 보안 접근 방식의 이점은 다음과 같습니다.

  • 테스트 작성을 통해 문제를 명확히 예상할 수 있고 필수 컨트롤에 대한 충분한 지식을 갖게 된다.
  • 보안 요소를 테스트하기 쉽고 작은 단위로 구성하여 모두가 이해할 수 있다.
  • 제품 및 서비스와 같은 인프라를 공유하므로 테스트의 재사용성이 높다.
  • 일련의 기본 테스트가 작성되면 보안팀은 더욱 복잡한 작업에 집중할 수 있다.
  • 개발자와 운영자가 고객을 위험에 빠트리기 전에 문제를 해결할 기회를 제공한다.

테스트 주도 보안 시작하기

테스트 주도 보안을 시작하기 위해서는 어떻게 접근하는 것이 좋을까요? 모질라 재단의 파이어폭스 프로젝트에서 보안 리드로 일했던 줄리언 베헨트(Julien Vehent)는 ‘Test Driven Security in Continuous Integration’ 이라는 발표에서 다음과 같은 몇가지 방안을 제시하였습니다.

  1. 베이스라인 정의하기
  2. 테스트 작성하기
  3. 베이스라인 테스트하기
  4. 지속적인 테스트 실행
  5. 배포를 위해서는 테스트를 반드시 통과해야 함

테스트 주도 보안을 시작하려고 할때 가장 중요한 부분은 베이스라인을 수립하는 것입니다. 베이스라인 수립은 개발팀, 운영팀, 보안팀에서 각각 생각하고 있는 보안 관점에서의 최우선 순위의 액션 아이템으로 볼 수 있습니다. 베이스라인은 다음과 같은 용어로 다시 재정리 할 수 있습니다.

  • 모든 사람이 구현하기를 원하는 보안 컨트롤
  • 최소 보안 요구 사항의 집합
  • 합의된 최우선 순위의 보안

베이스라인의 예를 들면 개발팀, 운영팀, 보안팀의 협의를 통해 다음과 같은 것들이 도출될 수 있습니다.

  • 자격증명은 애플리케이션 코드와 함께 저장될 수 없다.
  • SSH 루트 로그인은 모든 시스템에서 비활성화되어야 한다.
  • 웹 애플리케이션은 HTTP가 아닌 HTTPS를 사용해야만 한다.
  • 관리자 콘솔은 VPN을 통해서만 접근 할 수 있어야 한다.
  • 모든 AWS IAM 유저는 MFA를 활성해야만 한다.
  • 컨테이너 이미지 스캐닝을 통해 High 레벨 이상의 취약점이 발견되면 배포를 중지해야만 한다.
  • 배스천 호스트의 보안그룹은 사내망에서만 SSH 접속을 허용해야만 한다.

개발팀, 운영팀, 보안팀에서 베이스라인을 수립하였다면 이것은 가장 우선순위가 높은 보안 요소가 식별되었다는 것을 뜻합니다. 보안에 대한 우선순위는 각 조직에 처한 상황마다 다를 수 있습니다.

베이스라인이 식별되면 테스트를 작성할 수 있습니다. 테스트는 기존의 테스트 주도 개발 방식과 마찬가지로 선호하는 언어에서 지원하는 단위테스트 프레임워크를 사용하여 개발할 수도 있으며, 원하는 목적에 특화된 기능을 제공하는 도구들을 사용하여 구현할 수도 있습니다. 또한 작성된 테스트는 기존의 Jenkins와 같은 CI 도구들에 통합하여 그대로 사용할 수 있습니다. AWS에서는 AWS CodeBuildAWS CodePipeline 그리고 AWS Config를 통해서 이러한 테스트를 지속해서 실행할 수 있습니다.

AWS 환경에 적용해 보기

AWS에 배포되고 있는 예제 애플리케이션을 통해서 ‘자격증명은 애플리케이션 코드와 함께 저장될 수 없다’ 에 대한 베이스라인을 만족하는 테스트 주도 보안을 도입해 보도록 하겠습니다. 이 예제에서는 컨테이너 기반의 애플리케이션 배포 환경구성을 위해 Amazon ECS 환경 구성을 손쉽게 할 수 있도록 도와주는 도구인 AWS Copilot 를 사용하여 구성하도록 하겠습니다.  공식문서의 설치 방법에 따라 미리 설치합니다.

다음과 같이 애플리케이션을 위한 AWS CodeCommit Git 리포지토리를 생성하고 클론 합니다.

repo_https_url=$(aws codecommit create-repository \
--repository-name "myapp" \
--repository-description "myapp for test driven security" | jq -r '.repositoryMetadata.cloneUrlHttp')

생성된 AWS CodeCommit 리포지토리를 클론하고 해당 디렉토리인 myapp으로 이동한 후 main 브랜치를 생성합니다.

git config --global credential.helper '!aws codecommit credential-helper $@'
git config --global credential.UseHttpPath true

예제를 위한 애플리케이션을 구성하기 위해 다음의 파일들을 touch 명령어로 생성하고 각 파일을 다음의 내용으로 저장합니다.

touch app.py requirements.txt Dockerfile

app.py의 내용은 아래와 같습니다.

from flask import Flask

app = Flask(__name__)
accesskey = "AKIAXXXXYYYYYYYYZZZZZZ1234"


@app.route("/")
def hello():
    return "Hello World!"


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=int("5000"), debug=True)

requirements.txt의 내용은 아래와 같습니다.

Flask==2.0.2

Dockerfile의 내용은 아래와 같습니다.

# Use ECR public-repo Due to DockerHub pull rate limit
FROM public.ecr.aws/bitnami/python:3.10.0-prod

RUN apt-get -y update \
    && apt-get -y upgrade \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* \
    && groupadd -g 999 appuser \ 
    && useradd -r -u 999 -g appuser appuser 

COPY . /app
WORKDIR /app
RUN pip3 install -r requirements.txt --no-cache-dir
EXPOSE 5000
USER appuser
CMD flask run -h 0.0.0.0 -p 5000

이제 애플리케이션이 준비되었습니다. Dockerfile 을 참고하여 애플리케이션을 미리 실행해 보실 수도 있습니다. 이제 AWS Copilot을 이용하여 애플리케이션을 배포해 보도록 하겠습니다. 아래의 명령을 실행하면 AWS Copilot은 myapp 이라는 이름의 애플리케이션을 Amazon ECS 클러스터에 AWS Fargate를 이용하여 배포하고 AWS ALB를 통해 서비스를 제공합니다.

copilot init --app myapp                     \
  --name api                                 \
  --type 'Load Balanced Web Service'         \
  --dockerfile './Dockerfile'                \
  --port 5000                                \
  --deploy

만약 실행 중 ‘pull access denied for public.ecr.aws’ 와 같은 오류를 경험했다면 ‘docker logout public.ecr.aws’ 를 실행한 후 진행 할 수 있습니다. 실행이 완료되면 ‘You can access your service at http://myapp-xxxxxxxxxxx.us-west-2.elb.amazonaws.com over the internet.’ 과 같은 메시지를 확인 할 수 있으며 curl 혹은 httpie 등의 CLI 명령어나 웹 브라우저를 통해 애플리케이션이 잘 작동하는 것을 다음과 같이 확인할 수 있습니다.

하지만 우리는 소스 코드 app.py에 AWS 보안 자격증명이 포함되어 있다는 것을 알고 있습니다. 이것은 앞에서 정의한 베이스라인 ‘자격증명은 애플리케이션 코드와 함께 저장될 수 없다.’와 어긋납니다. 이 문제를 어떻게 해결 할 수 있을까요? 이 부분을 검증하기 위해 CI/CD 파이프라인에 보안 테스트를 통합해 보겠습니다.

먼저 변경된 내용을 저장소에 모두 커밋합니다.

git add *
git commit -m 'Initial commit'
git push origin main

AWS Copilot을 이용하여 CI/CD 파이프라인을 생성합니다. copilot pipeline init 을 실행합니다.


copilot pipeline update 명령을 실행하면 CICD 파이프라인이 프로비저닝됩니다. 파이프라인이 성공적으로 프로비저닝되면 AWS Copilot에서 자동으로 생성해준 ./copilot/buildspec.yml 파일을 오픈합니다.

phase 항목의 install 섹션의 commands 부분에아래의 부분을 추가하고,

- pip3 install 'truffleHog>=2.1.0,<3.0'

build 섹션의 commands 부분에 다음 내용을 추가합니다.

- export REPO_URL=`cat repo_https_url.env` 
- git config --global credential.helper '!aws codecommit credential-helper $@'
- git config --global credential.UseHttpPath true - trufflehog --regex --rules secrets_config.json --entropy=False "${REPO_URL}"

위에서 살펴본 내용을 반영하면 buildspec.yml 파일은 다음과 같습니다.

# Buildspec runs in the build stage of your pipeline.
version: 0.2
phases:
  install:
    runtime-versions:
      docker: 18
      ruby: 2.6
    commands:
      - echo "cd into $CODEBUILD_SRC_DIR"
      - cd $CODEBUILD_SRC_DIR
      # Download the copilot linux binary.
      - wget https://ecs-cli-v2-release.s3.amazonaws.com/copilot-linux-v1.11.0
      - mv ./copilot-linux-v1.11.0 ./copilot-linux
      - chmod +x ./copilot-linux
      - pip3 install 'truffleHog>=2.1.0,<3.0'
  build:
    commands:
      - echo "Run your tests"
      - echo "TDS: Scanning with truffleHog..."
      - export REPO_URL=`cat repo_https_url.env`
      - git config --global credential.helper '!aws codecommit credential-helper $@'
      - git config --global credential.UseHttpPath true
      - trufflehog --regex --rules secrets_config.json --entropy=False "${REPO_URL}"      
  ....(중략)....

그리고 소스코드가 위치한 디렉토리에 아래와 같은 truffleHog 가 참고할 보안정보 패턴인 secrets_config.json 파일을 아래와 같은 내용으로 추가합니다. 제일 윗줄에 AWS 보안 자격증명에 대한 정규식 패턴을 확일 하실 수 있으며, 참고 할 수 있는 여러가지 유용한 패턴들도 살펴 보실 수 있습니다.

{
    "AWS API Key": "AKIA[0-9A-Z]{16}",
    "Slack Token": "(xox[p|b|o|a]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32})",
    "RSA private key": "-----BEGIN RSA PRIVATE KEY-----",
    "SSH (OPENSSH) private key": "-----BEGIN OPENSSH PRIVATE KEY-----",
    "SSH (DSA) private key": "-----BEGIN DSA PRIVATE KEY-----",
    "SSH (EC) private key": "-----BEGIN EC PRIVATE KEY-----",
    "PGP private key block": "-----BEGIN PGP PRIVATE KEY BLOCK-----",
    "Facebook Oauth": "[f|F][a|A][c|C][e|E][b|B][o|O][o|O][k|K].*['|\"][0-9a-f]{32}['|\"]",
    "Twitter Oauth": "[t|T][w|W][i|I][t|T][t|T][e|E][r|R].*['|\"][0-9a-zA-Z]{35,44}['|\"]",
    "GitHub": "[g|G][i|I][t|T][h|H][u|U][b|B].*['|\"][0-9a-zA-Z]{35,40}['|\"]",
    "Google Oauth": "(\"client_secret\":\"[a-zA-Z0-9-_]{24}\")",
    "Heroku API Key": "[h|H][e|E][r|R][o|O][k|K][u|U].*[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}",
    "Generic Secret": "[s|S][e|E][c|C][r|R][e|E][t|T].*['|\"][0-9a-zA-Z]{32,45}['|\"]",
    "Generic API Key": "[a|A][p|P][i|I][_]?[k|K][e|E][y|Y].*['|\"][0-9a-zA-Z]{32,45}['|\"]",
    "Slack Webhook": "https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}",
    "Google (GCP) Service-account": "\"type\": \"service_account\"",
    "Twilio API Key": "SK[a-z0-9]{32}",
    "Password in URL": "[a-zA-Z]{3,10}://[^/\\s:@]{3,20}:[^/\\s:@]{3,20}@.{1,100}[\"'\\s]"
}

truffelHog는 Git 리포지토리를 통해 Credential과 같은 비밀정보를 검색하고 커밋 기록 및 브랜치를 자세히 조사합니다. 실수로 범한 비밀정보 유출을 찾는 데 효과적인 오픈소스 도구입니다. 물론 때에 따라서는 위에서 언급한 것처럼 이러한 기능을 직접 프로그래밍을 통하여 개발하고 유닛테스트 프레임워크에 통합하여 사용할 수도 있습니다.

이제 다음의 명령어를 실행하면, 잠시 후 CI/CD 파이프라인의 Source 단계가 실행되고 이어서 Build 스테이지에서 AWS 보안 자격증명을 발견하고 배포를 하기 전에 파이프라인 가동을 중단시킬 것입니다. truffelHog는 Git 리포지토리를 클론하여 검사를 수행하므로 AWS CodeCommit에 대한 접근 권한이 필요합니다. AWS Copilot에서 자동으로 생성된 AWS CodeBuild 프로젝트에는 AWS CodeCommit에 대한 접근 권한이 포함되어 있지 않음으로 “pipeline-myapp-myapp-BuildProjectRole” 문자열을 포함한 IAM 역할에 AWS CodeCommit 에 대한 읽기 권한을 미리 추가해 줍니다.

echo $repo_https_url > repo_https_url.env
git add *
git commit -m 'Add secret scanning test'
git push origin main

아래와 같이 CI/CD 파이프라인이 실패로 중단된 것을 AWS CodePipeline 콘솔에서 확인 할 수 있습니다. Build 스테이지의 ‘Details’ 링크를 클릭하면 truffelHog 가 발견한 AWS 보안 자격증명에 대한 경고메시지를 확인할 수 있습니다.

이제 app.py 에서 accesskey 항목이 포함된 네 번째 줄을 제거하면 파이프라인은 정상적으로 작동할 것입니다.

결론

이 글에서 DevOps 환경에서 DevSecOps를 도입하고자 하는 경우 그 시작점이 될 수 있는 모범 사례인 테스트 주도 보안에 대해서 알아보고 AWS 환경에서는 어떻게 적용 할 수 있을지 살펴봤습니다. AWS는 고객분들의 다양한 보안 요구사항들을 충분히 만족할 수 있도록 다양한 보안 서비스들을 제공해드리고 있습니다. 단편적인 예를 들면 우리가 추가한 자격증명에 대한 보안 테스트에 대해서도 손쉽게 AWS Security Hub에 통합하여 AWS ConfigAmazon GuardDuty 등의 다양한 소스로부터의 경고 알림을 받을 수 있습니다.

앞에서 설명한 것처럼, DevSecOps는 보안의 여러 기술 요소들을 코드로 표현하는 것으로부터 출발하며 DevOps의 핵심 구성 요소인 CI/CD 파이프라인의 보안 및 CI/CD 파이프라인을 타고 흘러가는 아티팩트들의 보안 그리고 보안 운영의 자동화까지를 포함하는 상당히 폭넓고 광범위한 주제를 다루고 있습니다. 클라우드 환경에서 실행되는 DevOps 시대에서는 비지니스의 성장 속도가 점점 빨라지고 있습니다. DevSecOps의 도입을 통해 더욱 안전한 IT 환경으로 전환하고 AWS와 함께 비지니스의 안전성을 확보하실 수 있습니다.

AWS에서 구현되는 DevSecOps에 대해서 자세히 알아보려면 다음 문서를 참고하실 수 있습니다.

– 주성식, AWS 솔루션즈 아키텍트