문서로 건너뛰기
API

스코프

키가 무엇을 할 수 있는가.

어휘

resource:action 형태의 닫힌 집합입니다. 체크박스 목록으로 사람에게 보여줄 만큼 작고, 저장된 권한이 1년 뒤에도 같은 뜻이 될 만큼 안정적입니다. 워크스페이스 역할이 쓰이는 어휘도, MCP 도구를 통제하는 어휘도 같으므로, 읽기 전용 클라이언트에는 발송 도구가 보이지도 않습니다. 하나의 알파벳, 세 개의 표면.

스코프부여하는 권한
emails:send이메일 발송
emails:read보낸 메시지와 그 전달 상태 읽기
drafts:read초안 읽기
drafts:write초안 생성과 편집
threads:read스레드와 메시지 읽기
threads:write스레드에 레이블 붙이기, 읽음 표시, 보관
labels:read레이블 읽기
labels:write레이블 생성과 편집
contacts:read연락처 읽기
contacts:write연락처 추가, 편집, 제거
audiences:read오디언스와 그 구성원 읽기
audiences:write오디언스 생성과 편집, 구성원 변경
calendar:read캘린더 일정과 초대 읽기
calendar:write캘린더 일정 생성, 변경, 응답
templates:read이메일 템플릿 읽기와 미리보기
templates:write이메일 템플릿 생성, 편집, 템플릿으로 발송
domains:read도메인과 그 DNS 상태 읽기
domains:write도메인 검증과 설정
webhooks:read웹훅 엔드포인트와 전달 내역 읽기
webhooks:write웹훅 생성, 편집, 테스트
rules:read메일 규칙 읽기와 테스트
rules:write메일 규칙 생성, 편집, 순서 변경
connections:read어떤 메일함이 연결되어 있는지 읽기
members:read워크스페이스에 누가 있고 무엇을 갖고 있는지 보기
members:write사람 추가와 제거, 접근 범위 변경
roles:read이 워크스페이스가 정의한 역할 읽기
roles:write역할 생성, 편집, 삭제
settings:read서명을 포함한 메일함 설정 읽기
settings:write메일함 설정과 서명 변경
keys:write콘솔을 열지 않고 키 자신의 시크릿 교체

고민해서 고른 스코프 없이 만든 키는 emails:send만 받고 그 외에는 아무것도 받지 않습니다. 자격 증명의 안전한 기본값은 쓸모를 유지하는 가장 좁은 것입니다.

키는 뒤에 있는 역할로 제한된다

키는 역할에 대해 발급될 수 있고, 역할은 두 번째 권한 부여가 아니라 상한입니다. 키가 실제로 할 수 있는 일은 자신의 스코프와 그 역할의 권한의 교집합(key.scopes ∩ role.permissions)이며, 어떤 엔드포인트에 닿기도 전에 요청마다 경계에서 한 번 계산됩니다. 그 아래로는 역할의 존재를 아무도 모릅니다. 역할이 갖지 않은 스코프는 스코프 검사가 읽는 목록에 그냥 없을 뿐입니다.

그래서 두 목록은 함께 읽히며 어느 쪽도 혼자 이기지 않습니다. emails:send를 가진 키라도 그것을 갖지 않은 역할 아래에서는 보낼 수 없고, emails:send를 가진 역할도 그것을 요청하지 않은 키에는 아무것도 주지 않습니다. 스코프를 체크하는 것은 권한을 요청하는 것이고, 요청한 것 중 얼마를 받을지는 역할이 정합니다.

역할이 없는 키에는 상한이 없으므로, 발급된 워크스페이스만큼 넓습니다. 역할이 생기기 전에 만들어진 모든 키가 그런 상태이고, 소유자가 그 칸을 비워 두면 여전히 그렇게 됩니다. 즉 null 역할은 키가 가질 수 있는 가장 넓은 상태이지 가장 좁은 상태가 아닙니다. 역할을 삭제할 때 그 키들을 어디로 보낼지 지정하게 하는 이유이기도 합니다. 고아로 두면 그 전부를 조용히 승격시키게 됩니다.

교집합은 발급 시점에 키에 찍히지 않고 요청마다 해석됩니다. 그래서 역할을 좁히는 것은 키를 교체하지 않아도 호출자의 다음 호출부터 효력이 생기는 실시간 회수이고, 넓히는 것도 정확히 같은 방식으로 실시간인데, 그 절반을 기억해 둘 만합니다.

GET /pingGET /keys/self는 특히 한 가지 실패 때문에 scopesgrantedScopes, roleId와 나란히 보고합니다. scopes는 유효 목록이며 무언가를 인가하는 유일한 목록입니다. grantedScopes는 키가 발급될 때 받은 것입니다. 두 번째에 있고 첫 번째에 없는 것은 역할이 가져간 것이며, 그 차이가 "내 키에 emails:send가 있는데 insufficient_scope가 납니다"에 대한 답 전부입니다. 해법은 새 키가 아니라 역할 변경입니다.

역할이 좁힌 키
curl "$OE/ping" -H "$AUTH" {  "ok": true,  "keyId": "4c1b257a66287fd113bd89d0",  "mode": "live",  "scopes": ["emails:read", "threads:read"],  "roleId": "role_c40a95f21cc65d31c2a89e07",  "grantedScopes": ["emails:send", "emails:read", "threads:read"],  "workspaceId": "10417196-e324-4283-af98-66ec62167c47"}

다섯 개의 권한은 키에 절대 닿을 수 없습니다. api-keys:read, api-keys:write, billing:read, billing:write, workspace:manage입니다. 권한이지만 스코프는 아니므로, 아무리 너그러운 역할도 토큰에 그것들을 얹을 수 없습니다. 다른 키를 발급하거나, 다른 키가 할 수 있는 일을 바꾸거나, 플랜을 옮기는 것은 로그인한 사람만 하는 일입니다. 키가 자신에게 할 수 있는 유일한 일은 keys:write 스코프 뒤에서 자기 시크릿을 교체하는 것입니다. GET /roles/permissions는 그 다섯 개를 scope: false로 표시하며, 덕분에 하나의 컴포넌트가 역할 매트릭스와 키 생성 체크박스 목록을 모두 그릴 수 있습니다.

roles:write는 사실상 어휘 전체이며, 아닌 척하는 문서가 더 위험할 것입니다. 이를 가진 키는 자신을 제한하는 바로 그 역할을 PATCH해 나머지 전부를 스스로에게 줄 수 있고, 상한이 요청마다 해석되므로 넓어진 쪽이 바로 다음 호출에 적용됩니다. 역할을 편집할 수 없는 역할 편집기는 역할 편집기가 아니므로, 이는 막아야 할 구멍이 아닙니다. 멤버 목록만 읽으면 되는 키에 roles:write를 얹지 말아야 할 이유입니다.

발송 범위

스코프와 별개로, 키는 무엇으로 보낼 수 있는지도 좁힐 수 있습니다. 키는 두 개의 목록을 갖습니다. domainAllowlist는 도메인 전체를 담으며, 도메인을 가진 키는 그 도메인의 어떤 주소로도 보낼 수 있고 키가 만들어진 뒤에 생긴 주소도 포함합니다. addressAllowlist는 개별 주소를 담습니다. 둘 다 비워 두면 키는 워크스페이스만큼 넓어지며, 그보다 넓어지지는 않습니다. GET /keys/self가 두 목록을 보여주고, GET /addresses가 해당 키가 실제로 쓸 수 있는 것을 보고하는데, 이것이 설명되지 않는 from_address_forbidden에 대한 답입니다.

같은 집합이 키가 읽는 범위도 좁힙니다. 보낸 메일, 트래킹, 캘린더는 키가 보낼 수 있는 주소에 대해서만 답하므로, 한 도메인으로 좁혀진 키는 다른 도메인을 대신해 보내지도 읽지도 않습니다. 도메인 전체가 있으면 그 도메인의 트래킹 호스트도 설정할 수 있는데, 개별 주소로만 제한된 키는 그럴 수 없습니다.

그래서 좁히기는 세 가지이며, 서로 덮어쓰지 않고 합쳐집니다. 키의 스코프, 그 위 역할의 권한, 그리고 From 헤더에 넣을 수 있는 도메인과 주소입니다. 발송에는 셋 다 필요하고, 거부는 가장 먼저 걸린 하나만 알려줍니다.