클라우드 GitHub Actions 시크릿 유출 트러블슈팅 - 100-hours-a-week/16-Hot6-wiki GitHub Wiki
본 문서는 아래 두 건의 GitHub Actions 실행에서 발생한 시크릿 유출 사고를 트러블슈팅한 결과입니다.
FE CI/CD GitHub Actions 워크플로우에서 민감한 시크릿이 GitHub Actions 로그에 출력되어 두 차례 유출 사고가 발생했습니다. 두 경우 모두 환경 변수 설정 후 외부 명령 실행 중 발생한 env:
블럭 로그 출력이 원인이며, 코드 내부에서 echo
를 통해 출력한 것은 아니었습니다.
- name: Load secrets from GCP and export as env
run: |
gcloud secrets list --filter="labels.env=frontend_prod" --format="value(name)" | while read SECRET_NAME; do
SECRET_VALUE=$(gcloud secrets versions access latest --secret="$SECRET_NAME")
IFS='-' read -r SERVICE KEY ENV <<< "$SECRET_NAME"
echo "export $KEY='$SECRET_VALUE'" >> $GITHUB_ENV
done
- name: Deploy to S3
run: |
aws s3 sync ott/dist/ s3://onthe-top/frontend/prod/blue --delete
이때 AWS 설정을 위한 환경 변수가 다음과 같이 GITHUB_ENV에 설정되어 있었고, 워크플로우 로그에서 자동으로 다음처럼 노출되었습니다.
env:
S3_ACCESS_KEY: AKIAXPYLOAYRVANCGQS7
S3_SECRET_KEY: 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
즉, echo
명령이 문제라기보다는 aws
명령 실행 전 자동으로 출력되는 env:
블럭이 민감정보를 포함하고 있었던 것이 유출의 직접적인 원인이었습니다.
배포 명령이 실패하며 fatal error: AuthorizationHeaderMalformed
등의 에러 메시지와 함께 env:
블럭 전체가 출력되었고, 여기에 모든 키 값이 포함되었습니다.
이전 사고 이후 echo
대신 printf
를 사용하여 key=value 형태로 GITHUB_ENV에 기록하도록 개선하였습니다.
- name: Load secrets from GCP
run: |
printf "%s=%s\n" "$KEY" "$SECRET_VALUE" >> $GITHUB_ENV
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation \
--distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" \
--paths "/*"
이 명령도 실패하면서 GitHub Actions가 자동으로 모든 env:
값을 출력했고, 다시 한 번 다음과 같이 민감 정보가 포함된 상태로 노출되었습니다.
env:
AWS_ACCESS_KEY_ID: AKIAXPYLOAYRRHLZLVHR
AWS_SECRET_ACCESS_KEY: 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
- 백엔드의 경우 시크릿을 환경 변수로 설정하지 않고
secrets.properties
파일로 생성하여 서버에scp
로 복사하는 구조였습니다. -
aws
,gcloud
CLI 명령을 실행할 필요 없이 시스템 내부에서 Java 애플리케이션이 로컬 파일 기반으로 시크릿을 참조하였습니다. - GitHub Actions의
env:
블럭에 민감 정보가 포함될 일이 없었습니다.
echo "$KEY=$SECRET_VALUE" >> ./secrets/secrets.properties
scp secrets.properties ...
이 방식을 사용하면 GitHub Actions가 자동 출력하는 env:
블럭에 민감 정보가 포함되지 않으므로 안전합니다.
aws configure set aws_access_key_id "$KEY" --profile deploy
aws configure set aws_secret_access_key "$SECRET" --profile deploy
aws s3 sync ... --profile deploy
또는 다음과 같이도 설정할 수 있습니다:
mkdir -p ~/.aws
cat <<EOF > ~/.aws/credentials
[default]
aws_access_key_id=$KEY
aws_secret_access_key=$SECRET
EOF
이 방식은 환경변수 없이도 AWS CLI가 인증에 성공할 수 있게 해줍니다. 따라서 민감 정보가 env:
블럭을 통해 유출되지 않습니다.
# 잘못된 예시 (유출 발생 가능)
echo "export AWS_ACCESS_KEY_ID=..." >> $GITHUB_ENV
# 올바른 예시
echo "AWS_ACCESS_KEY_ID=..." >> $GITHUB_ENV
GitHub Actions는 GITHUB_ENV
파일에 export KEY=...
형식으로 기록된 값을 실행 시점에 출력하지 않지만, 이후 외부 명령 실패 시 전체 env:
블럭을 노출하며 유출로 이어질 수 있습니다.
GCP Secret Manager를 사용하는 구조 대신, 아래처럼 GitHub Actions의 Secrets 기능을 사용해 간단하게 해결할 수 있습니다.
env:
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }}
이 방식은 설정이 간편하며, GitHub이 자체적으로 시크릿 값을 로그에 노출하지 않도록 보호해줍니다.
단점은 시크릿 수가 많거나 복잡한 계층 구조가 필요한 경우 관리가 어렵다는 점입니다.
- 두 차례 유출 모두 명시적으로 출력하지 않았음에도, GitHub Actions의
env:
블럭 자동 출력 정책으로 인해 발생하였습니다. - 민감 정보는 환경변수 기반 전달이 아닌 파일 기반 전달이 가장 안전합니다.
- 또는 GitHub Actions 시크릿 기능을 직접 사용하여 출력 억제를 GitHub에 위임하는 것도 현실적인 대안이 될 수 있습니다.
- AWS CLI 사용 시에는
--profile
기반 인증 또는 명시적credentials
파일 사용을 고려해야 합니다.