Zakresy
Co wolno kluczowi.
Słownictwo
Zbiór zamknięty, resource:action. Dość mały, by pokazać go człowiekowi na liście checkboxów, i dość stabilny, by zapisane uprawnienie znaczyło to samo rok później. Tym samym słownictwem napisana jest ROLA w przestrzeni roboczej i tym samym bramkowane są narzędzia MCP, więc klient tylko do odczytu nie zobaczy nawet narzędzia do wysyłki. Jeden alfabet, trzy powierzchnie.
| Zakres | Przyznaje |
|---|---|
| emails:send | Wysyłanie poczty |
| emails:read | Odczyt wysłanych wiadomości i ich statusu doręczenia |
| drafts:read | Odczyt wersji roboczych |
| drafts:write | Tworzenie i edycja wersji roboczych |
| threads:read | Odczyt wątków i wiadomości |
| threads:write | Etykietowanie, oznaczanie jako przeczytane i archiwizowanie wątków |
| labels:read | Odczyt etykiet |
| labels:write | Tworzenie i edycja etykiet |
| contacts:read | Odczyt kontaktów |
| contacts:write | Dodawanie, edycja i usuwanie kontaktów |
| audiences:read | Odczyt list odbiorców i tego, kto się w nich znajduje |
| audiences:write | Tworzenie i edycja list odbiorców oraz zmiana ich składu |
| calendar:read | Odczyt wydarzeń i zaproszeń w kalendarzu |
| calendar:write | Tworzenie i zmiana wydarzeń w kalendarzu oraz odpowiadanie na nie |
| templates:read | Odczyt i podgląd szablonów e-mail |
| templates:write | Tworzenie i edycja szablonów e-mail oraz wysyłka z nich |
| domains:read | Odczyt domen i ich statusu DNS |
| domains:write | Weryfikacja i konfiguracja domen |
| webhooks:read | Odczyt endpointów webhooków i doręczeń |
| webhooks:write | Tworzenie, edycja i testowanie webhooków |
| rules:read | Odczyt reguł poczty i ich testowanie |
| rules:write | Tworzenie, edycja i zmiana kolejności reguł poczty |
| connections:read | Odczyt tego, które skrzynki są podłączone |
| members:read | Podgląd, kto jest w przestrzeni roboczej i co ma |
| members:write | Dodawanie i usuwanie osób oraz zmiana tego, do czego mają dostęp |
| roles:read | Odczyt ról zdefiniowanych w tej przestrzeni roboczej |
| roles:write | Tworzenie, edycja i usuwanie ról |
| settings:read | Odczyt ustawień skrzynki, w tym podpisu |
| settings:write | Zmiana ustawień skrzynki i podpisu |
| keys:write | Wymiana własnego sekretu bez otwierania konsoli przez kogokolwiek |
Klucz utworzony bez przemyślanej listy zakresów dostaje emails:send i nic więcej. Bezpieczna wartość domyślna dla poświadczenia to najwęższa, jaka czyni je użytecznym.
Klucz jest ograniczony rolą, która za nim stoi
Klucz można wydać pod ROLĄ, a rola jest pułapem, a nie drugim nadaniem uprawnień. To, co klucz faktycznie może, to jego własne zakresy PRZECIĘTE z uprawnieniami tej roli (key.scopes ∩ role.permissions), liczone raz na granicy, przy każdym żądaniu, zanim dojdzie do jakiegokolwiek endpointu. Nic dalej nie wie, że role istnieją: zakresu, którego rola nie ma, po prostu nie ma na liście, którą czytają kontrole zakresów.
Obie listy czyta się więc razem i żadna nie wygrywa sama z siebie. Klucz z emails:send pod rolą, która go nie ma, nie wyśle; rola z emails:send nie daje nic kluczowi, który o to nie poprosił. Zaznaczenie zakresu to prośba o uprawnienie, a rola decyduje, ile z tego, o co poprosiłeś, dostaniesz.
Klucz BEZ roli nie ma pułapu i jest przez to tak szeroki jak przestrzeń robocza, pod którą go wydano. To właśnie niesie każdy klucz utworzony przed powstaniem ról i to dostaje właściciel, zostawiając to pole w spokoju, więc null w roli to NAJSZERSZY stan, w jakim klucz może być, a nie najwęższy. To także powód, dla którego usunięcie roli wymaga wskazania, dokąd mają trafić jej klucze: osierocenie ich po cichu awansowałoby każdy z nich.
Przecięcie jest rozstrzygane per żądanie, a nie odciskane na kluczu w chwili wydania. Dzięki temu zawężenie roli jest odwołaniem uprawnień na żywo, obowiązującym przy następnym wywołaniu klienta i bez rotowania klucza, a rozszerzenie działa dokładnie tak samo, i to ta druga połowa warta zapamiętania.
GET /ping i GET /keys/self zwracają scopes obok grantedScopes i roleId z powodu jednej konkretnej usterki. scopes to lista efektywna i jedyna, która cokolwiek autoryzuje; grantedScopes to to, z czym klucz wydano. Wszystko, co jest w drugiej, a czego brak w pierwszej, zabrała rola, i ta różnica to cała odpowiedź na „mój klucz ma emails:send, a dostaję insufficient_scope”. Naprawą jest zmiana roli, a nie kolejny klucz.
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"}Pięć uprawnień nigdy nie trafi na klucz: api-keys:read, api-keys:write, billing:read, billing:write i workspace:manage. To uprawnienia, ale nie zakresy, więc żadna, choćby najhojniejsza rola nie umieści ich w tokenie: wydanie kolejnego klucza, zmiana tego, co wolno innemu kluczowi, czy zmiana planu to rzeczy, które robi wyłącznie zalogowany człowiek. Jedyne, co klucz może zrobić sam sobie, to wymienić własny sekret, za zakresem keys:write. GET /roles/permissions oznacza tę piątkę jako scope: false, i to pozwala jednemu komponentowi renderować zarówno macierz ról, jak i listę checkboxów przy tworzeniu klucza.
roles:write to w praktyce całe słownictwo, a udawanie, że jest inaczej, byłoby groźniejszą dokumentacją. Klucz, który je ma, może zrobić PATCH na tej właśnie roli, która go ogranicza, i przyznać sobie całą resztę, a ponieważ pułap jest rozstrzygany per żądanie, szerszy obowiązuje już przy następnym wywołaniu. To nie jest dziura do załatania, bo edytor ról, który nie może edytować ról, nie jest edytorem ról. To powód, by nie dawać roles:write kluczowi, który miał tylko czytać listę członków.
Zakres wysyłania
Niezależnie od zakresów klucz można zawęzić w tym, jako kto może wysyłać. Niesie dwie listy. domainAllowlist zawiera całe domeny, a klucz mający domenę może wysyłać jako dowolny adres na niej, również adres utworzony po kluczu. addressAllowlist zawiera pojedyncze adresy. Zostaw obie puste, a klucz będzie tak szeroki jak przestrzeń robocza, nigdy szerszy. GET /keys/self pokazuje obie listy, a GET /addresses zwraca to, czego dany klucz faktycznie może użyć, co jest odpowiedzią na niewyjaśnione from_address_forbidden.
Ten sam zbiór zawęża to, co klucz czyta. Poczta wysłana, śledzenie i kalendarz odpowiadają tylko dla adresów, jako które klucz może wysyłać, więc klucz ograniczony do jednej domeny ani nie wysyła, ani nie czyta w imieniu innej. Cała domena pozwala kluczowi dodatkowo ustawić host śledzenia tej domeny, czego klucz ograniczony do pojedynczych adresów nie może.
Są więc trzy zawężenia i składają się ze sobą, zamiast się nadpisywać: zakresy na kluczu, uprawnienia roli nad nim oraz domeny i adresy, które może wpisać w nagłówek From. Wysyłka potrzebuje wszystkich trzech, a odmowa nazywa tylko to pierwsze, które napotkała.