소개
GitHub Actions에서 AWS 리소스에 배포할 때, 기존 방법은 IAM 사용자 액세스 키를 GitHub Secrets에 저장하는 것이었습니다. 그러나 이 방법에는 몇 가지 과제가 있습니다:
- 액세스 키 유출 위험
- 정기적인 교체 작업 필요
- 관리해야 할 시크릿 증가
OIDC(OpenID Connect) 인증은 이러한 과제를 해결합니다. 이 글에서는 GitHub Actions와 AWS 간 OIDC 연동 설정 방법을 자세히 설명합니다.
OIDC란?
OIDC는 OAuth 2.0을 기반으로 한 인증 프로토콜입니다. GitHub Actions가 "이 저장소의 워크플로우입니다"라고 증명하는 토큰을 AWS에 발행하면, AWS가 이를 검증하고 임시 자격 증명을 제공합니다.
OIDC의 장점
- 시크릿 불필요: GitHub에 액세스 키를 저장할 필요 없음
- 보안 강화: 임시 자격 증명만 사용
- 세밀한 권한 제어: 저장소나 브랜치별로 권한 제한 가능
- 관리 비용 감소: 키 교체 작업 불필요
AWS 설정
1. IAM Identity Provider 생성
먼저 AWS Management Console에서 IAM Identity Provider를 생성합니다.
- IAM 콘솔 열기
- "Identity providers" → "Add provider" 클릭
- 다음 정보 입력:
Provider type: OpenID Connect
Provider URL: https://token.actions.githubusercontent.com
Audience: sts.amazonaws.com
Terraform 예시
resource "aws_iam_openid_connect_provider" "github_actions" {
url = "https://token.actions.githubusercontent.com"
client_id_list = [
"sts.amazonaws.com",
]
thumbprint_list = [
"6938fd4d98bab03faadb97b34396831e3780aea1"
]
}
AWS SAM 예시
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: GitHub Actions OIDC Provider
Resources:
GitHubOIDCProvider:
Type: AWS::IAM::OIDCProvider
Properties:
Url: https://token.actions.githubusercontent.com
ClientIdList:
- sts.amazonaws.com
ThumbprintList:
- 6938fd4d98bab03faadb97b34396831e3780aea1
2. IAM 역할 생성
다음으로 GitHub Actions가 수임할 IAM 역할을 생성합니다.
신뢰 정책 설정
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:*"
}
}
}
]
}
조건 세부사항
token.actions.githubusercontent.com:sub 값으로 접근을 제한할 수 있습니다.
# 특정 저장소 전체
repo:your-org/your-repo:*
# 특정 브랜치만
repo:your-org/your-repo:ref:refs/heads/main
# 특정 환경만
repo:your-org/your-repo:environment:production
# 풀 리퀘스트만
repo:your-org/your-repo:pull_request
Terraform 예시
data "aws_iam_policy_document" "github_actions_assume_role" {
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.github_actions.arn]
}
condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:aud"
values = ["sts.amazonaws.com"]
}
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = ["repo:your-org/your-repo:*"]
}
}
}
resource "aws_iam_role" "github_actions" {
name = "github-actions-deploy-role"
assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role.json
}
# 필요한 권한 정책 연결
resource "aws_iam_role_policy_attachment" "deploy_policy" {
role = aws_iam_role.github_actions.name
policy_arn = "arn:aws:iam::aws:policy/AmazonECS_FullAccess"
}
AWS SAM 예시
Resources:
GitHubActionsRole:
Type: AWS::IAM::Role
Properties:
RoleName: github-actions-deploy-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Federated: !GetAtt GitHubOIDCProvider.Arn
Action: sts:AssumeRoleWithWebIdentity
Condition:
StringEquals:
token.actions.githubusercontent.com:aud: sts.amazonaws.com
StringLike:
token.actions.githubusercontent.com:sub: repo:your-org/your-repo:*
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonECS_FullAccess
Outputs:
GitHubActionsRoleArn:
Description: ARN of the GitHub Actions IAM Role
Value: !GetAtt GitHubActionsRole.Arn
Export:
Name: GitHubActionsRoleArn
SAM으로 배포하려면:
sam build
sam deploy --guided
초기 배포 후 출력된 역할 ARN을 GitHub Secrets에 AWS_ROLE_ARN으로 등록합니다.
3. 권한 정책 연결
역할에 배포에 필요한 권한을 연결합니다. 예를 들어:
- S3 배포:
AmazonS3FullAccess - ECS 배포:
AmazonECS_FullAccess - Lambda:
AWSLambda_FullAccess
프로덕션 환경에서는 최소 권한 원칙에 따라 커스텀 정책을 만들 것을 권장합니다.
GitHub Actions 설정
워크플로우 파일 생성
.github/workflows/deploy.yml을 생성합니다.
name: Deploy to AWS
on:
push:
branches:
- main
# OIDC 토큰 취득을 위한 권한 설정 (중요!)
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy-role
aws-region: ap-northeast-1
- name: Deploy to S3
run: |
aws s3 sync ./dist s3://your-bucket-name --delete
- name: Verify deployment
run: |
aws s3 ls s3://your-bucket-name
중요 포인트
1. permissions 설정
permissions:
id-token: write # OIDC 토큰 취득에 필요
contents: read # 저장소 체크아웃에 필요
이 설정이 없으면 OIDC 토큰을 취득할 수 없어 오류가 발생합니다.
2. configure-aws-credentials 액션 버전
v4 이상을 사용합니다. 이전 버전은 OIDC를 지원하지 않을 수 있습니다.
uses: aws-actions/configure-aws-credentials@v4
실용 예시: SAM 애플리케이션 배포
AWS SAM을 사용한 서버리스 애플리케이션 배포 예시입니다.
name: Deploy SAM Application
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
env:
AWS_REGION: ap-northeast-1
SAM_STACK_NAME: my-sam-app
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Setup SAM CLI
uses: aws-actions/setup-sam@v2
with:
use-installer: true
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: SAM Build
run: sam build --use-container
- name: SAM Deploy
run: |
sam deploy \
--stack-name ${{ env.SAM_STACK_NAME }} \
--capabilities CAPABILITY_IAM \
--resolve-s3 \
--no-fail-on-empty-changeset \
--no-confirm-changeset
- name: Get Stack Outputs
run: |
aws cloudformation describe-stacks \
--stack-name ${{ env.SAM_STACK_NAME }} \
--query 'Stacks[0].Outputs' \
--output table
실용 예시: ECS 배포
더 실용적인 예시로 ECS 배포 워크플로우입니다.
name: Deploy to ECS
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
env:
AWS_REGION: ap-northeast-1
ECR_REPOSITORY: my-app
ECS_SERVICE: my-service
ECS_CLUSTER: my-cluster
CONTAINER_NAME: my-container
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push image to ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Download task definition
run: |
aws ecs describe-task-definition \
--task-definition my-task \
--query taskDefinition > task-definition.json
- name: Update task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ steps.build-image.outputs.image }}
- name: Deploy to ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
트러블슈팅
오류: "Not authorized to perform sts:AssumeRoleWithWebIdentity"
원인: 신뢰 정책 조건이 일치하지 않음
해결책:
- IAM 역할의 신뢰 정책에서
token.actions.githubusercontent.com:sub값 확인 - 저장소 이름과 브랜치 이름이 올바른지 확인
- GitHub Actions 로그에서 실제
sub클레임 값 확인
- name: Debug OIDC token
run: |
curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | \
jq -R 'split(".") | .[1] | @base64d | fromjson'
오류: "Error: Credentials could not be loaded"
원인: permissions 설정이 누락됨
해결책: 워크플로우 파일에 다음을 추가
permissions:
id-token: write
contents: read
인증은 성공했지만 권한 오류 발생
원인: IAM 역할에 필요한 권한 정책이 연결되어 있지 않음
해결책: IAM 역할에 적절한 정책 연결
aws iam attach-role-policy \
--role-name github-actions-deploy-role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
보안 모범 사례
1. 최소 권한 원칙
최소한의 필요한 권한만 부여합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-specific-bucket",
"arn:aws:s3:::your-specific-bucket/*"
]
}
]
}
2. 브랜치 또는 태그로 제한
프로덕션 배포를 특정 브랜치에만 제한합니다.
{
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
}
}
}
3. 환경별 역할 분리
개발, 스테이징, 프로덕션에서 서로 다른 역할을 사용합니다.
- name: Configure AWS credentials (Production)
if: github.ref == 'refs/heads/main'
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN_PROD }}
aws-region: ap-northeast-1
- name: Configure AWS credentials (Development)
if: github.ref == 'refs/heads/develop'
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN_DEV }}
aws-region: ap-northeast-1
4. CloudTrail로 감사
모든 API 호출을 기록하고 정기적으로 검토합니다.
# CloudTrail용 S3 버킷
resource "aws_s3_bucket" "cloudtrail" {
bucket = "my-cloudtrail-logs-bucket"
}
resource "aws_s3_bucket_policy" "cloudtrail" {
bucket = aws_s3_bucket.cloudtrail.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AWSCloudTrailAclCheck"
Effect = "Allow"
Principal = {
Service = "cloudtrail.amazonaws.com"
}
Action = "s3:GetBucketAcl"
Resource = aws_s3_bucket.cloudtrail.arn
},
{
Sid = "AWSCloudTrailWrite"
Effect = "Allow"
Principal = {
Service = "cloudtrail.amazonaws.com"
}
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.cloudtrail.arn}/*"
Condition = {
StringEquals = {
"s3:x-amz-acl" = "bucket-owner-full-control"
}
}
}
]
})
}
# CloudTrail
resource "aws_cloudtrail" "github_actions_audit" {
name = "github-actions-audit"
s3_bucket_name = aws_s3_bucket.cloudtrail.id
include_global_service_events = true
is_multi_region_trail = true
enable_logging = true
depends_on = [aws_s3_bucket_policy.cloudtrail]
}
결론
GitHub Actions와 AWS 간의 OIDC 연동은 다음과 같은 장점을 제공합니다:
- 액세스 키 없는 안전한 배포
- 임시 자격 증명만 사용
- 저장소나 브랜치별 세밀한 권한 제어
- 관리 비용 감소
초기 설정이 다소 복잡하지만, 일단 구성하면 장기적으로 보안과 운영성이 향상됩니다. 적극적인 도입을 권장합니다.
