Terraform Study - w07

wdb·2023년 8월 19일

01. 테라폼의 격리 구조

테라폼의 격리 구조는 각각 분리된 하위 모듈들을 루트 모듈을에서 통합하고 하나의 State를 관리하는 방식으로 이루어집니다. 루트 모듈에서 코드를 모놀리식과 MSA 아키텍처와 마찬가지로 테라폼으로 배포하고자 하는 리소스가 적고 단순한 구조를 가질 경우 State를 모놀리식으로 관리하는 방법이 작업 속도 면에서나 관리 포인트 면에서나 유리합니다. 테라폼의 모놀리식 방식이란 하나의 main.tf 파일에 리소스 배포 코드를 통합하는 방식으로 설명할 수 있습니다. 반대로 규모가 큰 워크플로우의 경우 소규모 단위로 분리하여 단위별로 관리가 가능하게 하는 방법이 유리합니다.
환경의 격리가 필요한 경우 Git의 브랜치를 활용할 수 있습니다. 개발, 테스트, 검증, 운영 각각의 환경이 필요한 경우에 Git 브랜치를 분리하여 각 환경 별로 작업을 구분하여 진행할 수 있습니다. 각 브랜치에 대해서 모든 환경에 대한 디렉터리는 그대로 가져가도록 하여 환경 별 리소스 구성이 동일하게 유지될 수 있도록 하는 방식을 권장합니다.

02. 프로비저닝 파이프라인

소스코드

프로덕션 환경에서의 배포는 단순 개발 환경에서의 배포보다 신중해야 할 필요가 있습니다. 배포 실수 혹은 오류로 인한 서비스 중단을 막기 위해서는 Plan과 Apply 과정 사이에 코드 검증, 실행 계획 검증, 실행 후 결과 확인과 같은 추가 동작을 자동화해야 할 필요가 있습니다.

이번 실습에서는 Github Action를 이용하여 테라폼 코드를 검증하고, 테라폼을 실행하여 리소스를 배포하는 과정을 진행해보았습니다.
Github Action은 yml 파일을 기반으로 동작합니다. 실습에서 사용한 파일은 다음과 같습니다.

#action.yml

name: Terraform DEV

on:
  push:
    branches:
      - add-env-variable
  pull_request:

env:
  MY_PREFIX: DEV
  TF_VERSION: 1.2.6

jobs:
  SCAN:
    name: SCAN
    runs-on: ubuntu-latest
    # env:
    #   working-directory: terraform
    #   TF_WORKSPACE: my-workspace
    steps:
      # - name: Configure AWS credentials
      #   uses: aws-actions/configure-aws-credentials@v1
      #   with:
      #     aws-region: eu-west-1

      - name: Check out code
        uses: actions/checkout@v3
        
      - name: Run Terrascan
        id: terrascan
        uses: tenable/terrascan-action@main
        with:
          iac_type: 'terraform'
          iac_version: 'v14'
          policy_type: 'aws'
          only_warn: true
          sarif_upload: true

      - name: Upload SARIF file
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: terrascan.sarif  
  Terraform:
    needs: SCAN
    name: Terraform
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v3

      - uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: $TF_VERSION
          cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

      - name: Terraform Fmt
        id: fmt
        run: terraform fmt -recursive -check
        continue-on-error: true

      - name: Terraform init
        id: init
        run: terraform init -upgrade
        # working-directory: ${{ env.working-directory }}

      - name: Terraform validate
        id: validate
        run: terraform validate -no-color

      - name: Terraform plan
        id: plan
        run: terraform plan -no-color -var=prefix="$MY_PREFIX"
        # working-directory: ${{ env.working-directory }}
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          TF_LOG: info

      - name: Plan output
        id: output
        uses: actions/github-script@v3
        if: github.event_name == 'pull_request'
        env:
          PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
            #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
            #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
            <details><summary>Show Plan</summary>
            \`\`\`hcl
            ${process.env.PLAN}
            \`\`\`
            </details>
            **Pusher**: @${{ github.actor }}
            **Action**: ${{ github.event_name }}
            `;
            github.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })

      - name: Terraform apply
        id: apply
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        run: terraform apply -auto-approve -var=prefix="$MY_PREFIX" -input=false
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Github Actions에서 수행하는 Job은 SCAN과 Terraform입니다. SCAN에서는 terrascan을 이용하여 테라폼 코드를 검증합니다. Terraform에서는 fmt, init, validate, plan, output, apply 순으로 테라폼 명령어를 실행합니다.
Github Action의 컨테이너 내부에서 action.yml에 정의된 명령어들이 실행됩니다. 명령어들을 수행하기 위해서는 TFC와 AWS에 대해서 인증 정보가 필요합니다. Github Action의 Repository Secrets에 TFC Token과 AWS Credential 키 값을 저장하면 Github Action에서 해당 변수를 가져와 컨테이너 안에서 이용할 수 있습니다.

add-env-variable 브랜치에 푸시하면 Github Action이 트리거되도록 설정되어 있습니다. 코드의 유효성과 인증 정보에 이상이 없다면 푸시 시 Github Action이 트리거되어 리소스 배포가 이루어지는 것을 확인할 수 있었습니다.

다음으로 배포한 리소스를 삭제하는 실습을 진행하였습니다. action.yml을 다음과 같이 수정하였습니다.

name: Terraform DEV Destroy

on:
  workflow_dispatch:

env:
  MY_PREFIX: DEV
  TF_VERSION: 1.2.6

jobs:
  Terraform:
    name: Terraform
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v3

      - uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: $TF_VERSION
          cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

      - name: Terraform init
        id: init
        run: terraform init -upgrade
        # working-directory: ${{ env.working-directory }}

      - name: Terraform validate
        id: validate
        run: terraform validate -no-color

      - name: Terraform destroy
        id: Destory
        run: terraform destroy -auto-approve -var=prefix="$MY_PREFIX"
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

워크플로우가 실행되면 리소스가 정상적으로 삭제되는 것을 확인할 수 있었습니다.

03. Terrascan으로 검증 테스트 진행

이전 실습에서 Terrascan을 이용해 테라폼 코드를 검증한 다음 배포하는 과정을 진행하였습니다. Terrascan은 IaC를 위한 정적 코드 분석기로 클라우드 인프라 전반의 취약성 및 컴플라이언스 위반을 검사합니다. 테라폼 뿐만 아니라 쿠버네티스, 헬름 등에 사용되는 구성 파일 역시 스캐닝할 수 있습니다.

맥 환경에서는 brew를 통해 Terrascan을 설치할 수 있습니다.

brew install terrascan

코드를 스캔하는 명령어는 다음과 같습니다.

terrascan scan

실습 코드에 대하여 Terrascan으로 스캔 명령을 실행한 결과는 아래와 같이 나타납니다.

어떤 스크립트에서 어떤 취약점이 발생했는지 알려주기 때문에 취약점을 인지하고 수정하는 과정이 용이해지며, 취약점의 심각성을 Low, Medium, High 등급으로 분류하여 우선적으로 수정해야 할 부분을 구분할 수도 있습니다.

0개의 댓글