GitHub Actions의 권한(Permissions) 설정
GitHub Actions 워크플로우를 작성하다 보면 GitHub Pages에 배포하거나, PR에 댓글을 달거나, 이슈에 라벨을 붙이는 등 단순히 코드를 빌드하고 테스트하는 것 이상의 작업을 하게 되는 경우가 많죠? 이때 워크플로우가 GitHub API에 접근하려면 적절한 권한이 필요한데요.
이번 포스팅에서는 GitHub Actions에서 워크플로우의 권한을 제어하는 permissions 키워드에 대해 자세히 알아보겠습니다.
GITHUB_TOKEN이란?
GitHub Actions에서 워크플로우가 실행될 때마다 GitHub은 자동으로 GITHUB_TOKEN이라는 특수한 토큰을 생성해줍니다.
이 토큰은 해당 워크플로우가 속한 저장소에 대해 다양한 API 작업을 수행할 수 있는 일종의 출입증이라고 보시면 됩니다.
GITHUB_TOKEN은 워크플로우가 시작될 때 자동으로 만들어지고, 워크플로우가 끝나면 자동으로 폐기되기 때문에 별도로 토큰을 만들거나 관리할 필요가 없습니다.
워크플로우 안에서는 secrets.GITHUB_TOKEN 또는 github.token으로 접근할 수 있는데요.
jobs:
example:
runs-on: ubuntu-latest
steps:
- name: GitHub API 호출
run: |
curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
https://api.github.com/repos/${{ github.repository }}/issues
이 토큰이 할 수 있는 일의 범위, 즉 어떤 API에 읽기 또는 쓰기가 가능한지를 결정하는 것이 바로 permissions 설정입니다.
저장소 기본 권한 설정
permissions를 워크플로우 파일에서 명시적으로 설정하기 전에 먼저 알아두어야 할 것이 있는데요.
GitHub 저장소 자체에 GITHUB_TOKEN의 기본 권한을 결정하는 설정이 있습니다.
저장소의 Settings > Actions > General 페이지에서 Workflow permissions 항목을 보시면 두 가지 옵션을 선택할 수 있습니다.
첫 번째 옵션은 Read and write permissions인데요.
이 옵션을 선택하면 GITHUB_TOKEN이 저장소의 대부분의 스코프에 대해 읽기와 쓰기 권한을 모두 갖게 됩니다.
편리하지만 보안 측면에서는 과도한 권한을 부여하는 셈이 되죠.
두 번째 옵션은 Read repository contents and packages permissions입니다.
이 옵션을 선택하면 GITHUB_TOKEN이 contents와 packages 스코프에 대해서만 읽기 권한을 가지게 됩니다.
2023년 2월 이후에 생성된 GitHub 저장소는 이 옵션이 기본으로 설정되어 있어서 더 안전한 기본 상태를 유지하게 되었습니다.
워크플로우 파일에서 permissions를 명시하면 이 저장소 기본 설정을 덮어쓰게 되므로, 워크플로우에 정확히 필요한 만큼의 권한만 부여하는 것이 가능합니다.
워크플로우 수준 설정
워크플로우 파일에서 permissions 키워드를 사용하면 GITHUB_TOKEN에 부여되는 권한을 세밀하게 제어할 수 있는데요.
크게 워크플로우 수준과 작업(job) 수준에서 설정할 수 있습니다.
워크플로우의 최상위에 permissions를 설정하면 해당 워크플로우 내의 모든 작업(job)에 동일한 권한이 적용됩니다.
name: CI
on: pull_request
permissions:
contents: read
pull-requests: write
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: npm test
위 예제에서는 워크플로우 수준에서 contents에 읽기, pull-requests에 쓰기 권한을 설정하고 있습니다.
이 워크플로우에 속한 모든 작업은 이 권한을 물려받게 되죠.
작업(job) 수준 설정
워크플로우 수준이 아닌 개별 작업 수준에서도 permissions를 설정할 수 있는데요.
작업 수준에서 설정하면 워크플로우 수준의 설정을 완전히 덮어쓰게 됩니다.
name: Build and Deploy
on:
push:
branches: [main]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: npm run build
deploy:
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
steps:
- name: Deploy to GitHub Pages
uses: actions/deploy-pages@v4
여기서 build 작업은 워크플로우 수준의 contents: read 권한을 그대로 사용하지만, deploy 작업은 자체적으로 pages: write와 id-token: write 권한을 설정하고 있습니다.
이때 중요한 점은 deploy 작업에서 contents: read를 따로 명시하지 않았기 때문에 contents 스코프는 none으로 처리된다는 것인데요.
작업 수준의 permissions는 워크플로우 수준의 설정을 “합치는” 게 아니라 완전히 “대체”하는 방식으로 동작하기 때문입니다.
접근 수준
permissions에서 각 스코프에 설정할 수 있는 접근 수준은 세 가지입니다.
read는 해당 스코프의 API를 읽기만 할 수 있는 수준입니다.
예를 들어 contents: read를 설정하면 저장소의 코드를 체크아웃 받거나 파일 내용을 조회할 수 있지만, 새로운 커밋을 푸시하거나 브랜치를 생성하는 것은 불가능합니다.
permissions:
contents: read # 코드 읽기만 가능
write는 해당 스코프의 API에 읽기와 쓰기를 모두 할 수 있는 수준인데요.
write를 설정하면 read 권한도 함께 포함됩니다.
permissions:
issues: write # 이슈 읽기 + 생성/수정/라벨링 가능
none은 해당 스코프에 대한 접근을 완전히 차단하는데요.
명시적으로 차단하고 싶은 스코프가 있을 때 사용합니다.
permissions:
actions: none # Actions API 접근 차단
여기서 한 가지 주의할 점이 있습니다.
permissions를 명시하는 순간, 나열하지 않은 모든 스코프는 자동으로 none으로 설정됩니다.
따라서 필요한 스코프를 빠뜨리면 워크플로우가 권한 부족으로 실패할 수 있으니 신경 써야 합니다.
permissions:
contents: read
# 이 외의 pull-requests, issues, pages 등 나머지 스코프는 전부 none
주요 권한 스코프
GITHUB_TOKEN이 접근할 수 있는 API 범위는 스코프(scope)별로 나뉘어져 있는데요.
워크플로우에서 자주 사용되는 주요 스코프를 살펴보겠습니다.
actions— GitHub Actions 자체의 API에 대한 접근을 제어합니다. 워크플로우 실행 로그 조회, 아티팩트(artifact) 다운로드, 캐시 관리 등에 필요합니다.contents— 아마도 가장 많이 사용하게 될 스코프입니다. 저장소의 코드, 브랜치, 커밋, 태그 등에 대한 접근을 제어하는데요.actions/checkout으로 코드를 내려받으려면 최소한contents: read가 필요하고, 자동 커밋이나 릴리즈 생성에는contents: write가 필요합니다.pull-requests— PR에 대한 접근을 제어합니다. PR에 댓글을 달거나, 리뷰를 등록하거나, 라벨을 붙이려면pull-requests: write가 필요합니다.issues— 이슈에 대한 접근을 제어합니다. 이슈 생성, 라벨 관리, 댓글 작성 등에 사용합니다.pages— GitHub Pages 배포에 필요한 스코프입니다. 보통pages: write와 함께id-token: write가 필요합니다.id-token— OpenID Connect(OIDC) 토큰을 요청할 때 사용됩니다. GitHub Pages 배포나 클라우드 서비스(AWS, Azure, GCP) 인증에 자주 쓰입니다.packages— GitHub Packages에 대한 접근을 제어합니다. npm, Docker, Maven 등의 패키지를 발행하거나 설치할 때 필요합니다.security-events— 코드 스캐닝 및 Dependabot 관련 API에 접근할 때 사용됩니다. 코드 스캐닝 결과 업로드나 보안 알림 관리에 필요합니다.statuses— 커밋 상태(status)를 관리할 때 사용됩니다. 외부 CI 서비스와 연동하여 빌드 성공/실패 표시를 할 때 유용합니다.checks— 체크 런(check run)과 체크 스위트(check suite)를 관리할 때 사용됩니다. 커밋에 대한 상세한 검사 결과를 생성하거나 업데이트할 때 필요합니다.deployments— 배포(deployment) 관련 API에 접근할 때 사용됩니다. 배포 상태를 생성하거나 업데이트하는 작업에 필요합니다.
편의 설정
매번 필요한 스코프를 하나하나 나열하는 게 번거로울 수 있는데요. GitHub Actions에서는 몇 가지 편의 설정을 제공합니다.
write-all을 사용하면 모든 스코프에 대해 쓰기 권한을 부여할 수 있습니다.
permissions: write-all
반대로 read-all을 사용하면 모든 스코프에 대해 읽기 권한을 부여합니다.
permissions: read-all
빈 객체 {}를 사용하면 모든 스코프에 대한 접근을 차단할 수 있는데요.
외부 API만 호출하고 GitHub API에는 전혀 접근하지 않는 워크플로우에 유용합니다.
permissions: {}
다만 write-all이나 read-all은 편리하지만 보안 측면에서는 필요한 스코프만 명시하는 것이 더 바람직합니다.
특별한 이유가 없다면 필요한 스코프를 직접 나열해주는 게 좋겠죠?
CI 테스트
지금까지 permissions의 개념과 사용법을 알아보았는데요.
이제 실제 프로젝트에서 많이 마주치는 상황별 워크플로우 설정을 살펴보겠습니다.
가장 기본적인 CI 워크플로우입니다.
코드를 체크아웃 받아서 테스트를 실행하기만 하면 되므로 contents: read 권한이면 충분합니다.
name: CI
on: pull_request
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: npm ci
- run: npm test
GitHub Pages 배포
GitHub Pages에 사이트를 배포하려면 빌드 작업과 배포 작업을 나누는 것이 일반적인데요.
빌드 작업은 코드를 읽기만 하면 되지만 배포 작업에는 pages: write와 id-token: write 권한이 필요합니다.
name: Deploy to GitHub Pages
on:
push:
branches: [main]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: npm ci
- run: npm run build
- uses: actions/upload-pages-artifact@v3
with:
path: ./dist
deploy:
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
runs-on: ubuntu-latest
steps:
- uses: actions/deploy-pages@v4
자동 이슈 관리
새로운 이슈가 등록되면 자동으로 라벨을 붙이는 워크플로우입니다.
이슈를 읽고 수정해야 하므로 issues: write 권한이 필요합니다.
name: Auto Label Issues
on:
issues:
types: [opened]
permissions:
issues: write
jobs:
label:
runs-on: ubuntu-latest
steps:
- name: 버그 리포트에 라벨 추가
if: contains(github.event.issue.title, 'bug')
run: |
gh issue edit ${{ github.event.issue.number }} \
--add-label "bug" \
--repo ${{ github.repository }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR 자동 리뷰 댓글
PR이 열리면 자동으로 댓글을 남기는 워크플로우입니다.
PR의 내용을 읽고 댓글을 작성해야 하므로 pull-requests: write 권한이 필요하고, 코드를 체크아웃 받으려면 contents: read도 필요합니다.
name: PR Comment
on:
pull_request:
types: [opened]
permissions:
contents: read
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: PR에 환영 댓글 작성
run: |
gh pr comment ${{ github.event.pull_request.number }} \
--body "PR을 제출해주셔서 감사합니다! 리뷰어가 곧 확인할 예정입니다." \
--repo ${{ github.repository }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
릴리즈 자동화
태그가 푸시되면 자동으로 GitHub 릴리즈를 생성하는 워크플로우입니다.
릴리즈를 만들려면 contents: write 권한이 필요합니다.
name: Release
on:
push:
tags:
- "v*"
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: 릴리즈 생성
run: |
gh release create ${{ github.ref_name }} \
--title "${{ github.ref_name }}" \
--generate-notes \
--repo ${{ github.repository }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
포크된 저장소에서의 권한
오픈 소스 프로젝트를 유지보수하다 보면 외부 기여자가 포크(fork)한 저장소에서 PR을 보내는 경우가 빈번한데요.
이때 GITHUB_TOKEN의 권한이 어떻게 되는지 알아두면 당황하지 않을 수 있습니다.
포크된 저장소에서 원본 저장소로 보낸 PR에서 실행되는 워크플로우에서는 GITHUB_TOKEN이 기본적으로 읽기 전용 권한만 가지게 됩니다.
워크플로우 파일에서 permissions에 write를 아무리 설정해도 포크 PR에서는 쓰기 권한이 부여되지 않는 것이죠.
이런 제한이 있는 이유는 보안 때문입니다. 만약 외부 기여자가 워크플로우 파일을 변경하여 쓰기 권한으로 저장소의 코드를 조작하거나 민감한 정보에 접근할 수 있다면 심각한 보안 문제가 될 수 있겠죠? GitHub은 이를 방지하기 위해 포크에서 실행되는 워크플로우의 권한을 의도적으로 제한하고 있습니다.
이로 인해 포크 PR에서 코드 스캐닝 결과 업로드나 PR 댓글 작성이 실패하는 경우가 생길 수 있는데요.
이런 상황을 처리하려면 pull_request_target 이벤트를 사용하거나, 두 단계로 나누어 별도 워크플로우에서 처리하는 방법을 고려해야 합니다.
보안 모범 사례
permissions를 사용할 때 보안을 강화하기 위한 몇 가지 모범 사례를 알아보겠습니다.
가장 중요한 원칙은 최소 권한 원칙(Principle of Least Privilege)입니다.
워크플로우가 실제로 필요로 하는 권한만 부여하는 것인데요.
예를 들어 코드를 빌드하고 테스트하는 CI 워크플로우에 write-all을 부여하는 것은 과도합니다.
contents: read면 충분하죠.
워크플로우에 여러 작업이 있고 각 작업이 필요로 하는 권한이 다르다면 작업 수준에서 permissions를 분리하는 것이 좋습니다. 앞서 살펴본 GitHub Pages 배포 예제처럼 빌드 작업과 배포 작업의 권한을 분리하면, 빌드 과정에서 실행되는 써드 파티 액션이 불필요한 쓰기 권한을 갖지 않게 됩니다.
저장소의 기본 토큰 권한을 Read repository contents and packages permissions로 설정하는 것도 권장합니다.
이렇게 하면 permissions를 명시하지 않은 워크플로우도 최소한의 권한만 갖게 되므로, 실수로 과도한 권한이 부여되는 것을 방지할 수 있습니다.
마지막으로 주기적으로 워크플로우를 점검하면서 더 이상 필요 없는 권한이 있으면 제거하는 것이 좋습니다. 프로젝트가 발전하면서 워크플로우도 변경되기 마련이니까요.
마치며
이상으로 GitHub Actions에서 permissions 키워드를 활용하여 GITHUB_TOKEN의 권한을 세밀하게 설정하는 방법에 대해서 알아보았습니다.
간단히 정리해보면, GITHUB_TOKEN은 워크플로우 실행 시 자동으로 생성되는 인증 토큰이며, permissions 키워드를 통해 스코프별로 read, write, none 접근 수준을 설정할 수 있습니다.
워크플로우 수준과 작업 수준에서 설정할 수 있되, 작업 수준의 설정이 워크플로우 수준을 완전히 대체한다는 점을 기억해두시면 좋겠습니다.
특히 최소 권한 원칙을 지키면서 각 작업에 필요한 만큼만 권한을 부여하는 습관을 들이면, 보안 사고를 예방하면서도 워크플로우를 안전하게 운영할 수 있을 것입니다.
GitHub Actions 관련 포스팅은 GitHub Actions 태그를 통해서 쉽게 만나보세요!
This work is licensed under
CC BY 4.0