GitHub에서 AWS 엑세스 키 대신 OIDC를 쓰는게 좋다는 건 알고 있었지만 직접 해보지 않아서 흐름도 이해할 겸 테스트를 해봤다.
GitHub Actions에서 AWS 엑세스 키 사용하기
AWS를 사용할 때 AWS CLI를 사용하던 AWS SDK를 사용하던 가장 일반적으로는 사용자의 AWS_ACCESS_KEY_ID
와 AWS_SECRET_ACCESS_KEY
를 환경변수 등에 지정해 두고 사용하는 방식이다.
GitHub Actions에서 사용할 AWS IAM user를 생성했다면 여기에 S3 권한을 주고 Access Key를 생성한다.
그리고 이 엑세스키를 액션에서 사용하기 위해 저장소의 시크릿으로 AWS_ACCESS_KEY_ID
와 AWS_SECRET_ACCESS_KEY
를 등록한다.
그러면 다음과 같은 액션에서 시크릿을 가져다가 사용할 수 있다.
name: OIDC Test
on:
push:
workflow_dispatch:
jobs:
access-s3:
runs-on: ubuntu-latest
steps:
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: List files in S3 bucket
run: aws s3 ls outsider-oidc-demo-bucket
AWS에서 GitHub Actions에서 인증하는 configure-aws-credentials 액션을 제공하고 있기 때문에 이 액션을 이용해서 엑세스키를 제공하면 인증받아서 AWS CLI를 사용할 수 있다.
가장 쉽고 일반적인 사용 방법이지만 이 방법은 보안상 좋지 않다. 엑세스키가 GitHub에 시크릿으로 등록해서 사용해야 하므로 개인적으로 사용할 때는 큰 문제가 없지만 회사나 조직에서 사용할 때는 키 유출에 대해 걱정을 해야하고 문제가 생겼을 때 키를 교체하기도 쉽지 않다.(물론 잘 관리한다면 관리 방법이 아예 없는 것은 아니다.)
여기서는 환경변수를 step 단위에서 with
로 제공했지만, 환경변수는 워크플로우 전역으로도 설정할 수 있고 job 단위로도 설정할 수 있기 때문에 편의상 워크플로우에 AWS 환경변수를 너무 넓게 지정한다면 실제로는 다른 작업에서도 이 환경변수를 읽을 수 있게 된다. 여기서는 aws s3 ls
를 사용했지만, 만약 AWS CLI가 손상되어서 악성 코드가 들어간다거나 어떤 오픈소스 라이브러리나 액션을 사용하는데 그 라이브러리나 액션이 악의를 가진 공격자에게 탈취당해서 환경변수를 읽는 코드가 삽입되어 있다면 시크릿을 빼앗길 수도 있다.
또한, configure-aws-credentials의 경우 워크플로우로 보면 환경변수를 configure-aws-credentials
액션에만 제공한 것처럼 보이지만 실제로는 configure-aws-credentials
내부에서 전달받은 AWS_ACCESS_KEY_ID
와 AWS_SECRET_ACCESS_KEY
를 환경변수로 설정하기 때문에 이어진 step에서도 해당 환경변수에 접근할 수 있다.
이는 아마 AWS CLI나 SDK의 동작 때문에 의도된 동작으로 보이지만 보안상 아쉬운 부분이긴 하다.
GitHub Actions에서 OIDC 사용하기
시크릿은 직접 저장해서 사용하거나 오랫동안 유지할수록 보안상 좋지 않기 때문에 최근에는 엑세스 토큰을 직접 사용하는 대신 OIDC를 사용하는 것을 권장하고 있다.
GitHub이 잘 지켜줄 거라고 믿기는 하지만 위의 예시에서 사용자와 AWS 외에 GitHub에도 인증을 위해서 엑세스키를 제공해야 한다는 부분도 문제이다. 이러한 부분을 해결하기 위해 나온 것이 OIDC인데 OIDC는 OpenID Connect의 약자로 OAuth 2.0에 기반한 인증 프로토콜이라고 할 수 있다.
OIDC를 사용하면 긴 수명의 AWS 엑세스키를 GitHub에 제공할 필요가 없고 사용할 때만 유효한 짧은 생명의 엑세스 토큰을 사용할 수 있기 때문에 탈취당하더라도 보안상 훨씬 안전하다.
OIDC를 사용하려면 AWS IAM에서 Identity provider를 먼저 설정해야 한다.
GitHub 문서에 따라 프로바이더는 OpenID Connect를 선택하고 Provider URL과 Audience를 https://token.actions.githubusercontent.com
와 sts.amazonaws.com
로 각각 설정했다. 여기서 Audience는 configure-aws-credentials을 사용할 예정이라 이 값을 지정한 것이다.
Identity provider를 만들었으면 GitHub Actions에서 사용할 IAM Role을 생성한다.
뭐로 만들던 다 가능하긴 하지만 GitHub Actions에서 사용할 것이므로 Custom trust policy로 선택해서 만들면 바로 Custom trust policy를 입력할 수 있다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::252807701206: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:outsideris/actions-test:*"
}
}
}
]
}
이 Trust relationships가 결국 GitHub Actions에서 인증할 수 있게 하는 부분이므로 중요하다. Principal.Federated
에는 좀 전에 만든 Identity provider의 ARN을 넣어준다. Condition은 다양하게 지정할 수 있는데 Audience 부분인 token.actions.githubusercontent.com:aud
는 sts.amazonaws.com
로 지정하고, 이는 configure-aws-credentials의 기본값이다.
Subject인 token.actions.githubusercontent.com:sub
에는 테스트할 저장소에서는 모두 사용할 수 있도록 StringLike
로 지정했다. sub
는 다양한 패턴으로 사용하면서 필요한 대로 제약을 걸 수 있다.
repo:ORG-NAME/*
: ORG 내에서는 모두 사용 가능repo:ORG-NAME/REPO-NAME:*
저장소 내의 브랜치는 모두 사용 가능repo:ORG-NAME/REPO-NAME:ref:refs/heads/BRANCH-NAME
: 특정 브랜치에서만 사용 가능repo:ORG-NAME/REPO-NAME:environment:ENVIRONMENT-NAME
특정 환경에서만 사용 가능
OIDC 토큰의 각 클레임에 대해서 알고 싶다면 Understanding the OIDC token을 참고하면 된다.
이렇게 생성한 Role에 GitHub Actions에서 사용할 AWS 권한(이 데모에서는 S3 버킷의 읽기 권한)을 추가하면 된다. 이 Role에서는 세션의 기간도 설정할 수 있는데 기본은 1시간이다.
이제 OIDC를 사용할 준비가 되었으므로 GitHub Actions에서 이를 사용하면 된다.
name: OIDC Test
on:
push:
workflow_dispatch:
permissions:
id-token: write
jobs:
access-s3:
runs-on: ubuntu-latest
steps:
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ap-northeast-2
role-to-assume: arn:aws:iam::252807701206:role/oidc-demo-role
- name: List files in S3 bucket
run: aws s3 ls outsider-oidc-demo-bucket
여기서 중요한 부분은 permissions
에 id-token: write
를 지정하는 것이다. id-token은 OIDC 토큰을 가져오기 위한 권한이므로 이 권한을 주지 않으면 Credentials could not be loaded
오류가 발생하면서 실패한다.
configure-aws-credentials
를 지정하는 부분에서는 role-to-assume
에 아까 생성한 role의 ARN을 지정해 주었다. 이렇게 사용하면 저장소에 AWS 엑세스키에 대한 시크릿을 지정하지 않더라도 인증이 제대로 수행되어 버킷의 내용을 조회할 수 있다.
아까처럼 환경변수를 다음 step에서 출력해 보면 AWS_ACCESS_KEY_ID
와 AWS_SECRET_ACCESS_KEY
가 설정되어 있고 AWS_SESSION_TOKEN
도 함께 설정된 것을 볼 수 있다. 이렇게 보면 아까와 비슷해 보일 수 있지만 실제로 이 엑세스키는 짧은 시간 동안만 유지되는 키이고 GitHub 측에 길게 유지되는 엑세스 키를 제공하지 않았기 때문에 훨씬 안전하다.
Comments