1Password Service Account로 로컬 자동화 시크릿을 Touch ID 없이 관리하기

1Password Service Account로 로컬 자동화 시크릿을 Touch ID 없이 관리하기

얼마 전에 Discord MCP 서버를 클로드 코드에 붙여서 쓰고 있었는데요. 봇 토큰을 설정 파일에 평문으로 박아두기는 찜찜해서, 1Password CLI로 토큰을 주입하도록 바꿔뒀습니다. 그런데 그날부터 클로드 코드를 켤 때마다 화면 한가운데 “1Password Access Requested” 창이 뜨면서 Touch ID를 요구하기 시작했어요. 😅

하루에도 몇 번씩 터미널을 새로 여는데, 그때마다 손가락을 갖다 대는 건 생각보다 거슬립니다. 게다가 cron으로 돌리는 스크립트라면 아예 사람이 없으니 인증 자체가 불가능하죠.

이 글에서는 왜 이런 인증 창이 매번 뜨는지 원인을 짚어보고, 1Password 서비스 계정(Service Account)과 macOS 키체인을 조합해서 로컬 MCP 서버나 자동화 스크립트가 시크릿을 사람 손 없이 불러오게 만드는 방법을 정리해 보겠습니다. 참고로 이 글은 1Password CLI(op) 2.34.0 버전을 기준으로 합니다.

왜 매번 인증 창이 뜰까?

먼저 범인부터 찾아야 합니다. 제 Discord MCP 설정은 이런 모양이었어요.

export DISCORD_TOKEN="$(op read op://dev/DISCORD_DALEBOT/credential)"; exec npx -y @pasympa/discord-mcp

op read로 볼트에서 토큰을 꺼내 환경 변수에 넣고 서버를 띄우는 방식인데요. 문제는 이 명령이 실행되는 시점입니다. MCP 서버는 클라이언트가 도구를 쓸 때 새 프로세스로 떠서 stdio로 통신하는 구조라, 클로드 코드를 새로 켤 때마다 서버도 새로 시작됩니다. 즉 시작할 때마다 위 명령이 다시 실행되고, 그 안의 op read도 매번 호출되는 거죠. MCP 서버를 클로드 코드에 연결하는 기본 구조가 궁금하다면 클로드 코드에서 MCP 서버 연동하기를 먼저 보고 오셔도 좋습니다.

여기서 핵심은 op가 어떻게 인증하느냐입니다. 우리가 평소에 터미널에서 op를 편하게 쓸 수 있는 건 데스크톱 앱과 연동(Integrate with 1Password CLI)해 뒀기 때문인데요. 이 연동 모드에서 op는 명령을 실행할 때 데스크톱 앱에 “나 좀 잠금 해제해 줘”라고 요청하고, 앱은 사람에게 Touch ID나 비밀번호로 본인 확인을 받습니다.

이 방식은 사람이 직접 터미널을 두드릴 때는 완벽합니다. 그런데 MCP 서버처럼 자동으로, 그것도 자주 떴다 사라지는 프로세스에는 최악이에요. 매번 새 프로세스가 op를 부르고, op는 매번 사람에게 생체 인증을 요구하니까요. 한마디로 데스크톱 앱 연동은 애초에 “사람이 직접 쓰는” 상황을 위한 인증 방식이라는 거죠.

데스크톱 앱 연동과 서비스 계정의 차이

그럼 자동화에는 어떤 인증을 써야 할까요? 바로 이런 상황을 위해 만들어진 게 서비스 계정입니다.

서비스 계정은 사람이 아니라 프로그램을 위한 1Password 계정이라고 생각하면 됩니다. 생성하면 ops_로 시작하는 긴 토큰을 하나 받는데요. 이 토큰을 OP_SERVICE_ACCOUNT_TOKEN 환경 변수에 넣어두면, op는 데스크톱 앱에 묻지 않고 그 토큰만으로 곧장 인증합니다. 생체 인증도, 잠금 해제도 필요 없죠. 헤드리스 환경을 위해 설계된 방식이라 GitHub Actions에서 1Password 시크릿 사용하기처럼 CI/CD에서 흔히 쓰이는데, 이번엔 그걸 내 노트북의 로컬 자동화에 적용해 보는 셈입니다.

한 가지 미리 알아둘 점이 있습니다. 서비스 계정은 1Password Teams나 Business 플랜에서만 만들 수 있어요. 개인(Individual)이나 가족(Families) 플랜에는 이 기능이 없습니다. 본인 계정에서 만들 수 있는지는 뒤에 나오는 생성 명령을 실행해 보면 바로 알 수 있습니다.

서비스 계정 만들기

서비스 계정은 op service-account create 명령으로 만듭니다. 이때 가장 중요한 원칙은 최소 권한이에요. 이 토큰이 접근할 수 있는 볼트와 권한을 딱 필요한 만큼만 열어줘야 합니다.

저는 Discord 토큰이 dev 볼트에 있으니, 그 볼트에 대한 읽기 권한만 주겠습니다.

op service-account create discord-mcp --vault dev:read_items

--vault 플래그는 볼트이름:권한 형식인데요. 권한에는 read_items, write_items, share_items가 있습니다. 우리는 토큰을 꺼내 읽기만 하면 되니 read_items 하나면 충분합니다. 권한을 생략하면 기본값으로 read_items가 적용돼요. 쓰기 권한까지 주는 건 그만큼 사고 위험을 키우는 일이라, 정말 필요할 때만 추가하세요.

명령을 실행하면 데스크톱 앱이 한 번 인증을 요구합니다(서비스 계정을 만드는 것도 계정 변경이니까요). 인증을 마치면 ops_로 시작하는 토큰이 출력되는데, 이 토큰은 딱 한 번만 보여줍니다. 화면을 닫으면 다시는 볼 수 없으니 바로 안전한 곳에 저장해야 합니다.

여기서 또 하나 기억할 점이 있어요. 서비스 계정에는 본인의 Personal이나 Private 볼트 접근 권한을 줄 수 없습니다. 그래서 자동화에 쓸 시크릿은 처음부터 dev 같은 별도 볼트에 두는 편이 깔끔합니다. op CLI의 기본 사용법이 낯설다면 1Password CLI 사용법에서 설치부터 볼트 조회까지 차근차근 다뤘으니 참고하세요.

토큰을 어디에 둘까: 평문은 금물

이제 받은 토큰을 저장할 차례입니다. 그런데 잠깐, 가장 흔한 실수를 짚고 가야 해요.

토큰을 .zshrc.env 파일에 export OP_SERVICE_ACCOUNT_TOKEN=ops_...처럼 평문으로 적어두고 싶은 유혹이 듭니다. 하지만 이러면 안 됩니다. 생각해 보세요. 우리가 애초에 Discord 토큰을 평문으로 두기 싫어서 1Password에 넣었는데, 그 1Password 금고를 통째로 여는 마스터키 격인 서비스 계정 토큰을 평문 파일에 적어버리면 보안이 오히려 후퇴합니다. 1Password 공식 안내도 이 토큰을 평문으로 저장하지 말라고 못 박고 있어요.

그래서 macOS라면 운영체제가 제공하는 키체인(Keychain)에 넣는 게 좋습니다. 로그인 키체인은 우리가 맥에 로그인하는 순간 자동으로 잠금이 풀리기 때문에, 사람의 추가 인증 없이도 프로그램이 값을 꺼내 쓸 수 있거든요.

security 명령으로 토큰을 키체인에 저장합니다.

security add-generic-password \
  -U \
  -a discord-mcp \
  -s op-discord-mcp \
  -w "ops_여기에_받은_토큰" \
  -T /usr/bin/security

플래그를 하나씩 풀어보면 이렇습니다.

  • -a — 항목을 구분하는 계정(account) 이름입니다. 여기서는 discord-mcp로 줬어요.
  • -s — 항목을 구분하는 서비스(service) 이름입니다. 나중에 이 값으로 토큰을 찾습니다.
  • -w — 저장할 비밀번호, 즉 서비스 계정 토큰입니다.
  • -U — 같은 항목이 이미 있으면 덮어씁니다. 토큰을 회전했을 때 다시 저장하기 편합니다.
  • -T /usr/bin/securitysecurity 명령에 이 항목을 읽을 권한을 줍니다. 이게 있어야 나중에 GUI 팝업 없이 값을 꺼낼 수 있어요.

다만 위처럼 -w에 토큰을 직접 적으면 셸 히스토리에 토큰이 남습니다. 이게 신경 쓰인다면 -w 뒤의 값을 빼고 실행해서 프롬프트가 뜨면 거기에 붙여넣거나, 저장 후 history -d로 해당 줄을 지우는 것도 방법입니다.

제대로 저장됐는지는 다음 명령으로 확인합니다.

security find-generic-password -a discord-mcp -s op-discord-mcp -w

ops_로 시작하는 토큰이 그대로 출력되면 성공입니다. 그리고 이때 Touch ID 창이 뜨지 않는다는 점에 주목하세요. 키체인에서 헤드리스로 값을 꺼낸 거예요.

명령 스코프로만 토큰 주입하기

토큰을 키체인에 넣었으니 이제 op가 이걸 쓰게 만들면 됩니다. 가장 단순한 방법은 OP_SERVICE_ACCOUNT_TOKEN을 셸 전역에 export하는 것이지만, 추천하지 않습니다.

이 환경 변수가 셸 전체에 깔려 있으면, 그 셸에서 실행하는 모든 op 명령이 데스크톱 앱 연동 대신 서비스 계정으로 동작하게 됩니다. 평소 op로 개인 볼트를 조회하던 것이나, 1Password와 연동해 둔 다른 CLI 도구의 동작까지 바뀌어 버리죠. 자동화 하나 편하게 하려다 멀쩡하던 흐름을 깨는 셈입니다.

그래서 토큰은 딱 그 명령에서만 살아 있도록 범위를 좁히는 게 좋습니다. 키체인에서 토큰을 꺼내 환경 변수에 넣는 일을, MCP 서버를 띄우는 그 한 줄 안에서 처리하면 됩니다.

export OP_SERVICE_ACCOUNT_TOKEN="$(security find-generic-password -a discord-mcp -s op-discord-mcp -w)"; export DISCORD_TOKEN="$(op read op://dev/DISCORD_DALEBOT/credential)"; exec npx -y @pasympa/discord-mcp

흐름을 따라가 보면 이렇습니다. 먼저 키체인에서 서비스 계정 토큰을 꺼내 OP_SERVICE_ACCOUNT_TOKEN에 넣습니다. 그 상태에서 op read가 실행되니, 이번엔 데스크톱 앱에 묻지 않고 서비스 계정으로 곧장 인증해서 Discord 토큰을 가져옵니다. 마지막으로 exec로 MCP 서버를 띄우는 거죠. 이 모든 과정이 이 명령을 실행하는 동안에만 환경 변수가 살아 있어서, 다른 곳의 op는 여전히 데스크톱 앱 연동으로 잘 동작합니다.

MCP 서버에 적용하기

원리를 알았으니 실제 설정에 반영해 봅시다. 클로드 코드라면 claude mcp CLI로 기존 서버를 교체하면 됩니다. 설정 파일을 직접 손대는 것보다 안전한데, 클로드 코드의 설정 파일은 앱이 종료될 때 다시 쓰는 경우가 있어 직접 편집한 내용이 덮어써질 수 있기 때문입니다.

먼저 기존 등록을 지웁니다.

claude mcp remove discord -s user

그다음 서비스 계정 방식으로 다시 등록합니다. 명령 안에 작은따옴표 충돌이 없도록 전체를 작은따옴표로 감싸 sh -c에 넘깁니다.

claude mcp add discord -s user -- sh -c 'export OP_SERVICE_ACCOUNT_TOKEN="$(security find-generic-password -a discord-mcp -s op-discord-mcp -w)"; export DISCORD_TOKEN="$(op read op://dev/DISCORD_DALEBOT/credential)"; exec npx -y @pasympa/discord-mcp'

등록이 끝나면 클로드 코드에서 /mcp 명령으로 상태를 확인합니다. discordconnected로 표시되면, 새 명령이 키체인에서 토큰을 꺼내 op read까지 헤드리스로 통과했다는 뜻이에요. 이제 클로드 코드를 껐다 켜도 그 거슬리던 Touch ID 창은 더 이상 뜨지 않습니다. 🎉

cron이나 스크립트에도 똑같이

이 패턴이 좋은 건 Discord MCP에만 쓸 수 있는 게 아니라는 점입니다. 시크릿이 필요한 어떤 로컬 자동화든 똑같이 적용됩니다.

예를 들어 매일 새벽에 API를 호출하는 cron 스크립트가 있다고 해볼게요. 사람이 없는 시간에 도는 작업이라 데스크톱 앱 연동으로는 인증이 막혀 버립니다. 하지만 같은 방식이면 문제없어요.

daily-report.sh
#!/bin/bash
export OP_SERVICE_ACCOUNT_TOKEN="$(security find-generic-password -a discord-mcp -s op-discord-mcp -w)"
API_KEY="$(op read op://dev/SOME_API/credential)"

curl -H "Authorization: Bearer $API_KEY" https://api.example.com/report

스크립트 안에서만 토큰을 꺼내 쓰고, 끝나면 프로세스와 함께 사라집니다. cron이 사람 없이 돌려도 인증이 걸리지 않죠. 원리는 똑같아요. 시크릿은 1Password 볼트에 두고, 볼트를 여는 서비스 계정 토큰은 키체인에 두고, 그 토큰은 명령이 실행되는 순간에만 환경 변수로 잠깐 등장하는 거죠.

알아두면 좋은 점들

마지막으로 운영하면서 챙기면 좋은 것들을 정리해 둡니다.

  • Teams나 Business 플랜이 필요합니다 — 개인이나 가족 플랜에는 서비스 계정 기능이 없습니다. 생성 명령이 실패하면 플랜부터 확인하세요.
  • Personal·Private 볼트는 접근 불가 — 자동화용 시크릿은 처음부터 별도 볼트에 모아두는 게 좋습니다.
  • 토큰은 한 번만 출력됩니다 — 생성 직후 키체인에 바로 저장하고, 놓쳤다면 깔끔하게 지우고 새로 만드세요.
  • 만료 시간을 걸 수 있습니다op service-account create--expires-in 90d처럼 주면 토큰이 자동으로 만료됩니다. 단 만료되면 자동화가 멈추니, 회전 일정을 함께 관리해야 합니다.
  • 권한은 최소로read_items만으로 충분하다면 굳이 쓰기 권한을 주지 마세요.
  • 서비스 계정 관리는 웹 콘솔에서op CLI로는 생성만 가능하고, 목록 조회나 삭제는 1Password.com의 Developer 메뉴에서 합니다.

마치며

정리하면, 로컬에서 시크릿을 다루는 자동화가 매번 Touch ID를 띄운다면 그건 데스크톱 앱 연동이 “사람이 직접 쓰는” 인증 방식이기 때문입니다. 자동화에는 자동화에 맞는 인증, 즉 서비스 계정을 써야 하죠. 여기에 토큰을 평문 대신 macOS 키체인에 두고, 그 토큰을 필요한 명령의 범위 안에서만 주입하면 보안과 편의를 동시에 챙길 수 있습니다.

같은 1Password 토큰을 GitHub CLI나 Wrangler 같은 도구에 생체 인증으로 연결하는 방법이 궁금하다면 1Password Shell Plugins로 CLI 인증 관리하기도 함께 보면 좋습니다. 서비스 계정의 더 자세한 옵션과 권한 모델은 1Password 서비스 계정 공식 문서에서 확인할 수 있습니다.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord