GitHub Actions에서 1Password 시크릿 사용하기
GitHub Actions로 배포나 테스트를 자동화하다 보면 API 키, 데이터베이스 비밀번호, 배포 토큰 같은 시크릿을 다루게 됩니다. 처음에는 GitHub Secrets에 하나씩 넣어두면 충분해 보이는데요. 프로젝트가 늘어나고, 시크릿을 여러 저장소에서 공유하고, 키를 주기적으로 교체해야 하는 순간부터 관리가 조금씩 복잡해집니다.
이럴 때 1Password를 시크릿의 원천 저장소로 두고, GitHub Actions에서는 실행 시점에 필요한 값만 읽어오게 만들 수 있습니다. GitHub에는 1Password에 접근하기 위한 서비스 계정 토큰 하나만 저장하고, 실제 API 키나 비밀번호는 1Password 볼트에서 관리하는 방식이죠.
이번 글에서는 GitHub Actions에서 1Password 시크릿을 불러오는 흐름을 처음부터 끝까지 구성해보겠습니다.
1Password CLI 사용법을 이미 알고 있다면 op://볼트/항목/필드 형식의 시크릿 참조가 익숙할 텐데요.
GitHub Actions에서도 같은 참조 문법을 사용할 수 있습니다.
왜 1Password를 함께 쓸까?
GitHub Secrets는 GitHub Actions와 아주 자연스럽게 붙어 있어서 편합니다.
저장소의 Settings > Secrets and variables > Actions에서 값을 넣고, 워크플로우에서는 ${{ secrets.API_KEY }}처럼 읽으면 되니까요.
하지만 시크릿 관리의 중심이 GitHub로 흩어지는 단점도 있습니다. 같은 데이터베이스 비밀번호를 여러 저장소에서 써야 한다면 저장소마다 같은 값을 복사해야 하고, 비밀번호를 교체할 때도 여러 곳을 찾아다니며 수정해야 합니다. 팀에서 이미 1Password를 쓰고 있다면 시크릿의 실제 소유권은 1Password에 두는 편이 더 자연스러울 수 있어요.
1Password와 GitHub Actions를 연결하면 GitHub에는 OP_SERVICE_ACCOUNT_TOKEN 하나만 저장합니다.
워크플로우가 실행될 때 이 토큰으로 1Password에 인증하고, 필요한 시크릿을 op://Production/API Key/credential 같은 참조로 읽어옵니다.
실제 비밀 값은 1Password에 남아 있고, GitHub Actions에는 실행 중인 잡에 필요한 값만 주입되는 구조입니다.
이 방식은 GitHub Actions를 안전하게 사용하는 방법에서 이야기한 최소 권한 원칙과도 잘 맞습니다. GitHub에는 모든 시크릿을 복사하지 않고, 1Password 서비스 계정도 필요한 볼트와 권한만 갖게 만들 수 있으니까요.
전체 흐름
먼저 1Password에 CI/CD용 볼트를 준비합니다.
그 볼트 안에 GitHub Actions에서 읽을 API 키나 비밀번호를 저장하고, 각 필드를 op:// 시크릿 참조로 가리킬 수 있게 해둡니다.
그다음 1Password에서 서비스 계정을 만듭니다. 서비스 계정은 사람이 직접 로그인하지 않아도 1Password API에 접근할 수 있게 해주는 자동화용 계정입니다. 이 계정에는 특정 볼트에 대한 읽기 권한만 주고, 마지막 단계에서 발급되는 서비스 계정 토큰을 저장합니다.
마지막으로 GitHub 저장소의 Secrets에 이 토큰을 OP_SERVICE_ACCOUNT_TOKEN이라는 이름으로 등록합니다.
워크플로우에서는 1Password의 load-secrets-action을 사용해서 op:// 참조를 실제 값으로 바꿔 사용합니다.
정리하면 구조는 이렇습니다.
1Password 볼트
└─ API Key, DB Password, Deploy Token
↑
│ op://Production/API Key/credential
│
GitHub Actions
└─ OP_SERVICE_ACCOUNT_TOKEN으로 인증
└─ load-secrets-action으로 시크릿 로드
CI/CD용 볼트 만들기
가장 먼저 할 일은 GitHub Actions에서 사용할 시크릿을 담을 볼트를 정하는 것입니다.
여기서 중요한 점이 하나 있는데요.
1Password 서비스 계정은 기본 제공 Personal, Private, Employee 볼트나 기본 Shared 볼트에 접근할 수 없습니다.
따라서 개인 계정에서 Personal 볼트가 보이지 않는 것은 정상입니다.
CI/CD에서 사용할 시크릿은 별도의 볼트를 만들거나, 서비스 계정 접근이 가능한 기존 볼트에 넣어야 합니다.
예를 들어 Production, GitHub Actions, CI Secrets 같은 이름의 볼트를 따로 만들어두면 목적이 분명해져요.
볼트 안에는 실제 시크릿을 항목으로 저장합니다. 예를 들어 배포에 필요한 값을 다음처럼 나눠둘 수 있습니다.
Vault: Production
Item: Database
Field: url
Item: API Key
Field: credential
Item: Deploy Token
Field: password
그러면 GitHub Actions에서 사용할 시크릿 참조는 다음처럼 됩니다.
op://Production/Database/url
op://Production/API Key/credential
op://Production/Deploy Token/password
시크릿 참조 문자열 자체는 비밀 값이 아닙니다. 다만 볼트, 항목, 필드 이름이 드러나므로 공개 저장소에 넣을 때는 이름에 너무 많은 내부 정보를 담지 않는 편이 좋습니다.
서비스 계정 만들기
이제 1Password 웹사이트에서 서비스 계정을 만듭니다. 개인 계정이라면 보통 https://my.1password.com/에 로그인하면 되고, 팀이나 비즈니스 계정이라면 회사별 로그인 주소를 사용하면 됩니다. 참고로 이 글은 GitHub Actions에서 쓸 토큰을 웹 콘솔에서 만드는 흐름인데요. 내 노트북에서 도는 MCP 서버나 cron 같은 로컬 자동화에 쓸 거라면 CLI로 만들어 키체인에 두는 1Password 서비스 계정으로 로컬 시크릿 헤드리스로 관리하기가 더 잘 맞습니다.
로그인한 뒤 Developer > Directory로 이동합니다. 화면 구성은 계정 종류나 1Password UI 업데이트에 따라 조금 달라질 수 있는데요. 범용 서비스 계정 토큰을 만들려면 Access Tokens 영역의 Service Account를 선택하면 됩니다. GitHub Actions 전용 안내를 따라가고 싶다면 Infrastructure Secrets Management 영역의 GitHub Actions를 선택해도 됩니다.
서비스 계정 생성 화면에서는 먼저 이름을 정합니다.
예를 들어 github-actions-blog-deploy처럼 어디에서 쓰는 계정인지 드러나게 짓는 것이 좋습니다.
나중에 토큰을 회전하거나 폐기할 때 어떤 자동화가 영향을 받는지 쉽게 알 수 있거든요.
다음 단계에서는 접근할 볼트를 선택합니다.
앞에서 만든 Production 또는 CI Secrets 볼트를 선택하고, 오른쪽 설정 버튼에서 권한을 정합니다.
대부분의 GitHub Actions 워크플로우는 시크릿을 읽기만 하면 되므로 읽기 권한만 주면 충분합니다.
워크플로우가 1Password 항목을 만들거나 수정해야 하는 특별한 경우가 아니라면 쓰기 권한은 주지 않는 편이 안전합니다.
여기서 조심해야 할 점은 서비스 계정의 볼트 접근 권한과 환경 접근 권한이 생성 후 바뀌지 않는다는 것입니다. 접근할 볼트를 빠뜨렸거나 권한을 잘못 줬다면 기존 서비스 계정을 수정하는 대신 새 서비스 계정을 만들어야 합니다. 그래서 처음 만들 때 어떤 볼트에 어떤 권한이 필요한지 잠깐 멈춰서 확인하는 게 좋아요.
마지막 단계에서 서비스 계정 토큰이 표시됩니다. 이 토큰은 한 번만 볼 수 있으므로 바로 1Password에 저장해야 합니다. 평문 파일이나 메모 앱, 코드 저장소에 잠깐 붙여두는 식으로 다루면 안 됩니다. GitHub Actions에서는 이 토큰 하나로 해당 볼트의 시크릿을 읽을 수 있기 때문입니다.
GitHub Secrets에 토큰 저장하기
서비스 계정 토큰을 받았다면 GitHub 저장소로 이동합니다. 저장소의 Settings > Secrets and variables > Actions로 들어간 뒤 New repository secret을 누릅니다.
이름은 다음처럼 정확히 입력합니다.
OP_SERVICE_ACCOUNT_TOKEN
값에는 방금 발급받은 1Password 서비스 계정 토큰을 넣습니다.
이제 워크플로우에서는 ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}으로 이 토큰을 읽을 수 있습니다.
조직 단위로 여러 저장소에서 같은 1Password 볼트를 사용한다면 organization secret으로 등록할 수도 있습니다. 다만 처음에는 저장소 하나에만 repository secret으로 넣고 동작을 확인하는 편이 실수 범위가 작습니다. GitHub Secrets의 저장 위치와 권한 범위는 배포 구조에 맞춰 천천히 넓히면 됩니다.
워크플로우에서 시크릿 불러오기
이제 실제 워크플로우를 작성해보겠습니다.
1Password 공식 액션은 1password/load-secrets-action입니다.
이 액션은 환경 변수에 적힌 op:// 참조를 읽어서 실제 값으로 바꿔줍니다.
가장 안전한 기본 형태는 시크릿을 step output으로 받는 방식입니다.
필요한 단계에서만 ${{ steps.load_secrets.outputs.DATABASE_URL }}처럼 꺼내 쓰기 때문에 값의 범위가 비교적 좁습니다.
name: Deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Load secrets from 1Password
id: load_secrets
uses: 1password/load-secrets-action@v4
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
DATABASE_URL: op://Production/Database/url
API_KEY: op://Production/API Key/credential
- name: Deploy
env:
DATABASE_URL: ${{ steps.load_secrets.outputs.DATABASE_URL }}
API_KEY: ${{ steps.load_secrets.outputs.API_KEY }}
run: |
./scripts/deploy.sh
이 예제에서 GitHub Secrets에는 OP_SERVICE_ACCOUNT_TOKEN만 들어갑니다.
DATABASE_URL과 API_KEY의 실제 값은 1Password에서 읽어오고, 워크플로우 파일에는 참조만 남습니다.
액션이 로드한 시크릿은 로그에 출력되더라도 ***로 마스킹됩니다.
그래도 시크릿을 일부러 echo로 찍어보는 습관은 피하는 게 좋습니다.
마스킹은 안전망이지 디버깅 도구가 아니니까요.
환경 변수로 바로 내보내기
여러 단계에서 같은 시크릿을 반복해서 써야 한다면 export-env: true 옵션을 사용할 수 있습니다.
이 옵션을 켜면 로드한 값이 이후 단계의 환경 변수로 노출됩니다.
- name: Load secrets from 1Password
uses: 1password/load-secrets-action@v4
with:
export-env: true
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
DATABASE_URL: op://Production/Database/url
API_KEY: op://Production/API Key/credential
- name: Run migration
run: bun run db:migrate
- name: Deploy
run: bun run deploy
export-env: true는 편하지만 시크릿이 잡 안의 여러 단계에서 보이게 됩니다.
단일 단계에서만 필요한 값이라면 output 방식으로 좁게 전달하는 편이 좋습니다.
반대로 마이그레이션, 빌드, 배포 단계에서 같은 환경 변수가 모두 필요하다면 export-env: true가 훨씬 간단합니다.
어떤 방식을 쓰든 GitHub Actions의 권한 설정처럼 워크플로우의 permissions도 함께 줄여두는 것이 좋습니다.
1Password 시크릿을 안전하게 불러와도 GITHUB_TOKEN 권한이 과하면 다른 위험이 남을 수 있거든요.
자주 헷갈리는 부분
서비스 계정을 만들 때 Personal 볼트가 보이지 않는 경우가 있습니다.
앞에서 이야기한 것처럼 서비스 계정은 기본 제공 개인 볼트에 접근할 수 없습니다.
CI/CD용 별도 볼트를 만들고 그 볼트에 필요한 시크릿만 복사하거나 이동해야 합니다.
서비스 계정 권한을 나중에 바꾸고 싶을 수도 있는데요. 1Password는 서비스 계정의 볼트 접근과 환경 접근을 생성 후 수정할 수 없도록 제한합니다. 권한을 잘못 골랐다면 토큰만 바꾸는 것이 아니라 서비스 계정을 새로 만드는 흐름으로 생각하는 편이 편합니다.
워크플로우에서 op:// 참조가 제대로 동작하지 않는다면 볼트 이름, 항목 이름, 필드 이름을 먼저 확인하세요.
이름에 공백이 있어도 참조 자체는 사용할 수 있지만, 오타가 있으면 값을 찾지 못합니다.
처음에는 1Password 앱에서 항목을 열고 필드 이름을 그대로 복사하는 게 안전합니다.
마지막으로, OP_SERVICE_ACCOUNT_TOKEN은 GitHub Secrets에만 넣어야 합니다.
워크플로우 파일의 env에 직접 토큰 값을 적거나, 저장소에 .env 파일로 커밋하면 1Password를 쓰는 의미가 사라집니다.
로컬 개발용 .env 파일을 다룬다면 .gitignore 파일에 반드시 제외해두세요.
마치며
GitHub Actions와 1Password를 함께 쓰면 시크릿 관리의 중심을 1Password에 두면서도 CI/CD 자동화를 깔끔하게 유지할 수 있습니다. GitHub에는 서비스 계정 토큰 하나만 저장하고, 실제 시크릿은 1Password 볼트에서 읽어오는 구조라 시크릿 교체와 권한 관리가 훨씬 단순해집니다.
핵심은 CI/CD용 볼트를 따로 만들고, 서비스 계정에는 필요한 볼트의 읽기 권한만 주고, GitHub Actions에서는 1password/load-secrets-action으로 op:// 참조를 불러오는 것입니다.
처음 설정할 때는 단계가 조금 많아 보이지만, 한 번 만들어두면 이후에는 시크릿을 1Password에서만 관리하면 되니 운영 부담이 줄어듭니다.
더 자세한 내용은 1Password의 서비스 계정 시작하기 문서와 load-secrets-action 공식 저장소를 참고하세요. GitHub 쪽 설정은 GitHub Actions Secrets 공식 문서를 함께 보면 좋습니다.
This work is licensed under
CC BY 4.0