IaC 보안
IaC 보안이란
Terraform, CloudFormation, Kubernetes YAML, Dockerfile 등 인프라를 코드로 정의하는 IaC 파일의 보안 설정 오류(퍼블릭 S3 버킷·암호화 미적용·과도한 권한 등)를 배포 전에 탐지하는 검사입니다. 잘못된 인프라 설정은 애플리케이션 취약점보다 더 넓은 범위의 피해를 유발할 수 있어 코드 리뷰 단계에서의 차단이 중요합니다.
이 페이지의 YAML·명령은 핵심을 보여주는 예시입니다. 복사해 바로 쓸 수 있는 전체 파이프라인(정책 파일·샘플 앱 포함)은 Best Practice 저장소에서 확인하세요.
아래 예시는 읽기 쉽도록 @v7 같은 태그를 그대로 썼습니다. 태그는 나중에 다른 커밋을 가리키도록 바뀔 수 있으므로, 실제 운영 워크플로에서는 액션을 커밋 SHA로 고정하고 permissions: 로 잡마다 필요한 권한만 부여하세요. 이유와 방법은 파이프라인 자체 보안에서 다룹니다.
도구 비교
| 도구 | 특징 | 지원 대상 | 라이선스 |
|---|---|---|---|
| Checkov | 광범위한 커버리지·커스텀 정책 지원 | Terraform·K8s·CF·Dockerfile·ARM | Apache-2.0 |
| tfsec | Terraform 전용·빠른 속도 | Terraform | MIT |
| Trivy | IaC 스캔 포함 (컨테이너와 통합) | Terraform·K8s·Dockerfile | Apache-2.0 |
| Kubesec | Kubernetes 전용 보안 점수 | Kubernetes YAML | Apache-2.0 |
멀티 IaC 환경에는 Checkov, 컨테이너 보안과 통합하려면 Trivy를 권장합니다. tfsec 은 유지보수가 Trivy 로 이관되고 있어, Terraform 전용 검사가 필요하면 trivy config 사용을 우선 검토하세요.
Checkov 설정
Checkov는 500개 이상의 내장 정책을 제공하며 별도 서버 없이 로컬과 CI 모두에서 실행됩니다. SARIF 포맷 출력을 지원해 GitHub Security 탭과 연동하면 PR에서 바로 결과를 확인할 수 있습니다.
기본 사용법
# 현재 디렉토리 전체 스캔
checkov -d .
# 특정 프레임워크만 스캔
checkov -d . --framework terraform
checkov -d . --framework kubernetes
# 특정 검사 항목만 실행
checkov -d . --check CKV_AWS_18,CKV_AWS_19
# 결과를 JSON으로 출력
checkov -d . -o json > checkov-report.json
GitHub Actions
# .github/workflows/iac-security.yml
name: IaC Security — Checkov
on:
pull_request:
branches: [main, develop]
jobs:
checkov:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # SARIF 업로드에 필요합니다
steps:
- uses: actions/checkout@v7
- name: Run Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: .
framework: terraform,kubernetes,dockerfile
soft_fail: false
output_format: cli,sarif
output_file_path: console,checkov-results.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: checkov-results.sarif
GitLab CI
# .gitlab-ci.yml (iac-security 잡 부분)
iac-security:
stage: test
image: bridgecrew/checkov:latest
script:
# 위반 발견 시 exit 1(하드 실패)이 기본 동작
- checkov -d .
--framework terraform,kubernetes,dockerfile
--output cli
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
tfsec 설정 (Terraform 전용)
tfsec은 Terraform에 특화된 도구로 실행 속도가 빠르고 AWS·Azure·GCP 등 주요 클라우드 프로바이더의 보안 규칙이 내장돼 있습니다. 다만 유지보수가 Trivy 로 이관되고 있으므로 신규 도입은 trivy config 를 우선 검토하세요. Checkov와 병행해 Terraform 전용 심층 검사를 추가할 때도 유용합니다.
GitHub Actions
# .github/workflows/iac-security-tfsec.yml (Terraform 전용)
name: IaC Security — tfsec
on:
pull_request:
branches: [main]
jobs:
tfsec:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1.0.3
with:
soft_fail: false
예외 처리
불가피하게 특정 검사를 건너뛰어야 할 경우 인프라 코드에 인라인 주석으로 이유를 명시합니다.
# Terraform 인라인 예외 예시
resource "aws_s3_bucket" "logs" {
bucket = "my-log-bucket"
# checkov:skip=CKV_AWS_18:접근 로그 버킷은 자체 로깅 불필요
# checkov:skip=CKV_AWS_144:로그 버킷은 교차 리전 복제 대상이 아님
}
# Kubernetes 인라인 예외 예시
metadata:
annotations:
checkov.io/skip1: 'CKV_K8S_14=테스트 환경 전용 파드'
초록은 무엇을 근거로 한 초록인가
IaC 스캔을 붙이고 나면 배지가 초록으로 바뀝니다. 그런데 그 초록이 "위반이 없다"는 뜻인지 "검사가 이뤄지지 않았다"는 뜻인지는 배지만 봐서는 구분되지 않습니다. 아래는 TRUSCA 에 스캔과 차단 게이트를 붙이면서(iac-security.yml, Checkov 대신 Trivy config) 실제로 겪은 네 가지입니다. 앞의 셋은 스캔 결과가, 넷째는 설정 자체가 의도와 달랐습니다.
렌더되지 않은 차트를 검사하고 통과하는 경우
Helm 차트를 trivy config 로 스캔할 때, 템플릿이 필수 값을 요구하면 렌더가 실패합니다. 이때 Trivy는 렌더 오류를 경고로만 남기고 결과 0건으로 정상 종료합니다. 종료 코드가 0이므로 잡은 통과합니다. 검사 대상이 하나도 없는데 초록이 되는 형태입니다.
대응은 두 가지를 같이 해야 합니다. 스캔 전용 값 파일을 만들어 --helm-values 로 넘겨 템플릿이 렌더되게 하고, 그것과 별개로 검사한 파일 수를 세는 단계를 둡니다.
count=$(jq '[.Results[]?] | length' trivy-chart.json)
echo "config files inspected: ${count}"
if [ "${count}" -eq 0 ]; then
echo "::error::Trivy inspected 0 files. The chart failed to render."
exit 1
fi
값 파일만 넣고 끝내면, 나중에 템플릿이 바뀌어 렌더가 다시 깨졌을 때 같은 자리로 돌아옵니다. 파일 수를 세는 단계가 있어야 그 회귀가 실패로 드러납니다.
배포되지 않는 것을 검사하고 실패하는 경우
반대 방향도 있습니다. 컨테이너 이미지 스캔이 몇 주 전에 이미 고쳐진 패키지를 계속 취약하다고 보고했습니다. 원인은 캐시된 이미지 레이어였습니다. 캐시된 레이어는 다시 실행되지 않으므로 그 안의 apt-get upgrade 도 실행되지 않고, 레이어는 처음 빌드됐을 때의 패키지 버전을 계속 내놓습니다. 실제로 릴리스되는 이미지는 패치된 버전을 담고 있었는데 스캔만 옛 것을 보고 있었습니다.
빨간 게이트는 눈에 띄므로 언젠가는 발견됩니다. 문제는 그때까지 리뷰어가 빨강에 익숙해진다는 것입니다. 대응은 캐시 키에 시간을 넣어 어긋나는 기간을 제한하는 것입니다. ISO 주차를 키에 넣으면 최대 한 주까지만 벌어지고, 주에 한 번 캐시 없이 빌드하는 것으로 그 비용을 치릅니다.
차단 수준을 올렸는데 차단이 사라지는 경우
관측 단계에서 차단 단계로 넘어갈 때입니다. 게이트가 이렇게 세고 있었다고 하겠습니다.
n=$(jq --arg s "${BLOCKING_SEVERITY}" \
'[.Results[]?.Misconfigurations[]? | select(.Severity == $s)] | length' "$file")
BLOCKING_SEVERITY 를 CRITICAL 에서 CRITICAL,HIGH 로 바꾸면 차단 범위가 넓어질 것 같습니다. 그런데 어떤 항목의 Severity 도 "CRITICAL,HIGH" 라는 문자열과 같지 않습니다. 비교가 하나도 맞지 않아 개수는 항상 0이 되고, 게이트는 매번 통과합니다. 차단을 강화했다고 믿는 시점에 차단이 사라집니다.
값과 비교 조건은 함께 바꿔야 합니다.
sevs=$(printf '%s' "${BLOCKING_SEVERITY}" | jq -Rc 'split(",")')
n=$(jq --argjson sevs "$sevs" \
'[.Results[]?.Misconfigurations[]? | select(.Severity as $s | $sevs | index($s))] | length' "$file")
그리고 고쳤다는 것으로 끝내지 않습니다. 위반이 0건인 상태에서 게이트가 통과하는 모습은, 비교가 깨져서 통과하는 모습과 똑같습니다. 위반을 일부러 하나 되살린 브랜치로 게이트가 실제로 막는지 확인해야 두 경우가 구분됩니다. TRUSCA에서는 차트의 securityContext를 뺀 상태로 돌려 새 조건이 3건을 세는 것과 옛 조건이 같은 입력에서 0건을 세는 것을 나란히 확인했습니다.
껐다고 생각한 설정이 켜져 있는 경우
Helm 차트에서 기본값으로 준 설정을 사용자가 끄려 할 때입니다. 값 파일에 빈 맵을 주면 블록이 빠질 것 같지만 그렇지 않습니다.
# 지워지지 않는다. Helm 은 맵을 깊은 병합하므로 기본값이 그대로 남는다
worker:
containerSecurityContext: {}
# 이렇게 해야 지워진다
worker:
containerSecurityContext: null
이 차이가 위험한 이유는 방향 때문입니다. 끄려던 사람은 껐다고 생각하고, 실제로는 켜져 있습니다. 보안 설정이라면 다행이지만 반대 상황도 성립합니다. 값 파일에 안내 주석을 쓸 때 {} 로 지워진다고 적어 두면, 그 문서를 믿은 사람이 의도와 다른 상태로 배포하게 됩니다.
공통점
앞의 세 사례는 결과 화면이 정상이었습니다. 초록이거나, 빨강이더라도 이유가 그럴듯했습니다. 네 번째는 화면조차 없습니다. 설정을 바꿨다고 믿은 사람에게만 존재하는 어긋남입니다. 네 경우 모두 잘못된 것은 판정이 아니라 판정의 근거였습니다.
그래서 게이트를 붙일 때는 판정과 함께 근거를 남기게 만듭니다. 몇 개 파일을 검사했는지, 어떤 심각도를 몇 건 세었는지, 어떤 대상을 읽었는지를 로그나 잡 요약에 찍습니다. 그리고 차단으로 올린 뒤에는 위반을 일부러 만들어 한 번 막혀 봅니다. 검사 결과가 0건이라는 것과 검사기가 0건을 셌다는 것은 다른 문장이고, 그 둘을 구분하는 장치를 두지 않으면 파이프라인은 조용히 관측 단계로 되돌아갑니다.
주요 검사 항목
처음 도입 시 아래 항목부터 우선 검사를 활성화하면 실질적인 리스크를 빠르게 줄일 수 있습니다. 팀이 결과에 익숙해진 뒤 전체 정책으로 확대하는 것을 권장합니다.
| 항목 | Checkov ID | 설명 |
|---|---|---|
| S3 퍼블릭 접근 차단 | CKV_AWS_53 | 버킷 퍼블릭 접근 차단 설정 |
| S3 암호화 | CKV_AWS_19 | 서버 사이드 암호화 활성화 |
| 보안 그룹 0.0.0.0 | CKV_AWS_24 | SSH 포트(22) 전체 개방 금지 |
| K8s 루트 실행 금지 | CKV_K8S_23 | 컨테이너 루트 실행 차단 |
| K8s 리소스 제한 | CKV_K8S_11·CKV_K8S_13 | CPU limits·메모리 limits 설정 |
| K8s 시크릿 관리 | CKV_K8S_35 | 시크릿을 환경변수 대신 파일로 주입 |
IaC 보안 수정기
Checkov 결과 파일을 업로드하면 위반 항목별 수정된 코드를 자동으로 생성합니다. 리포트가 아닌 바로 적용할 수 있는 수정 파일을 제공합니다.
먼저 샘플로 미리보기 (API 키 불필요)
API 키 없이 미리 만든 샘플 스캔 결과의 수정 코드를 바로 확인할 수 있습니다. 도구가 어떤 결과를 만들어 주는지 먼저 체감한 뒤, 아래에서 본인 결과로 실제 분석을 진행하세요.
내 결과로 실제 수정하기
브라우저에서 직접 Anthropic API를 호출합니다. 본인의 Anthropic API 키를 입력하면 바로 사용할 수 있으며, 키와 입력 내용은 브라우저에서 Anthropic으로만 전송됩니다(trustedoss 서버를 거치지 않습니다). 사용량은 본인 Anthropic 계정에 과금됩니다.
셀프 스터디
위 수정기는 브라우저에서 바로 사용 가능합니다. 원본 .tf 파일에 수정 내용을 직접 반영한 전체 파일 생성이 필요하면 아래 agent를 사용하세요.
사전 조건: Trusted OSS 저장소 클론 필요
cd agents/iac-fixer
claude
agent가 아래를 자동으로 수행합니다.
- Checkov 결과 파일(JSON) 자동 파싱
- 수정 가능 항목 → 수정 코드 직접 생성
- 수정 불가 항목 → checkov:skip 주석 자동 삽입
- 원본 파일 제공 시 전체 수정 파일 생성