Recipes

Legion 머신의 secrets broker를 위한 Google ID 로그인

README 링크에서 발견 · today

Security, Science & HardwareSoftware & DevOps

머신은 Google ID 토큰을 DPoP 바인딩된 시크릿과 교환하고, 사람은 서명한다

What they did

저자는 Legion의 시크릿 브로커가 머신과 사람을 인증하는 방식을 재작업했습니다. 이전에는 머신이 디바이스 그랜트 — 사람이 입력하는 코드 — 를 통해서만 브로커에 접근할 수 있었고, 그 로그인은 마지막 갱신 후 30일이 지나면 만료되어 재구축된 머신에는 다시 사람이 필요했습니다. 또한 머신의 세션은 자체 Dispatch 토큰을 얻을 방법이 없어서, 사람들이 개인 토큰을 머신에 복사해 넣었습니다(INFRA-958). 이번 변경으로 머신은 자신의 Google 아이덴티티 토큰을, 해당 플릿의 서비스를 명시하고 한 시간 이하로 유지되며 리프레시 토큰을 포함하지 않는 DPoP 바인딩 액세스 토큰과 교환할 수 있게 되었습니다. 같은 교환으로 세션이 등록되는 런처 자격 증명도 발급되므로, 기존의 VM 토큰 로그인과 `vm` 등록 종류는 사라졌습니다. 이제 사람은 박스에서 `agent-secrets login`을 한 번 실행합니다. 그 로그인은 Dispatch의 자격 증명 페이지에 나열되고 철회할 수 있으며, 철회하면 해당 박스의 토큰은 즉시 종료되지만 그 사람의 다른 머신은 계속 작동합니다. 시크릿 권한은 액세스 토큰을 따릅니다. 머신은 자신의 서비스 시크릿과 공유 에이전트 계층 시크릿을 읽고 어떤 사람의 시크릿도 읽지 않으며, 사람은 해당 박스에서 자신의 로그인을 통해서만 자신의 시크릿에 접근합니다. 서비스를 명시하고 사람을 명시하지 않은 토큰은 Dispatch를 읽을 수 있지만 모든 쓰기는 `SIGN_IN_REQUIRED`로 거부되며, 그 메시지는 로그인 명령을 명시합니다. Envoy 리스너도 그러한 토큰은 받아들이지만 다른 세션을 명시한 토큰은 거부합니다. Go와 TypeScript로 된 하나의 토큰 클라이언트가 `agent-secrets`, Legion 컨트롤러, Oh My Pi, Claude Code 및 `dispatch` 명령을 위해 토큰을 획득하고 갱신하며, 둘 다 하나의 와이어 코퍼스에 맞춰집니다. `BROKER_MACHINE_LOGIN_URL`은 선택 사항이 되었습니다. 설정하지 않으면 브로커는 시작하되 디바이스 그랜트를 제공하지 않고, 이를 RFC 8414 메타데이터에 명시하며, Dispatch는 머신 코드 페이지를 제공하지 않습니다. 저자는 개발 스택 — 실제 Postgres 위의 실제 브로커, Dispatch, 리스너, NATS 및 OIDC 발급자 — 을 추가하고 두 가지 증명을 수행했습니다. 머신의 교환이 플릿 토큰을 읽는 것(사람 소유 시크릿에 대해 거부되는 부정 통제 포함)과, 사람이 입력한 코드로 `agent-secrets login`을 수행한 뒤 자신의 시크릿을 읽는 것입니다.

How it went

변경 사항은 head f767a511에 있으며 43개의 PR 스레드가 답변되고 해결되었고, b80a24af에서 `ce-simplify-code` 패스가 일곱 가지 발견 사항을 적용하고 세 가지 실제 결함(놓친 Dispatch 클라이언트 구성, 정규화되지 않은 로그인 목록 쿼리, 조용히 잘못된 자격 증명 만료)과 두 가지 요청별 비용을 잡아냈습니다. dev-stack E2E는 머신 거래와 개인 로그인 모두를 각각 네거티브 컨트롤과 함께 입증했습니다. CodeQL은 14개의 경고를 보고하며, 그중 하나는 새 코드의 오탐입니다.

Worth knowing

TypeScript 세션은 설계상 세션을 고정하는 fleet 또는 pod 항목 아래에서 거래할 수 없다. 오늘날 broker route로 그러한 항목을 설정하는 배포는 없다. 복사된 `identity-key.pem`은 키가 제거되고 helper가 재시작될 때까지 machine-login revoke 이후에도 launcher proof 서명을 계속한다. 그리고 복사된 sign-in record는 Google grant가 revoke될 때까지 Dispatch revoke 이후에도 살아남는다. 새로운 거부들은 오늘날 아무것도 닫지 못한다. 아직 `AGENT_SECRETS_MACHINE_IDENTITY`나 broker exchange 설정을 지정하는 배포가 없기 때문이다. `cmd/agent-secrets`, `cmd/agent-secrets-helper`, `internal/broker/helper`에 대한 socket 테스트는 gVisor pod에서 실행할 수 없으므로 CI가 유일한 실행 환경이다. Migration 0024에는 down migration이 없다. 박스는 복원되는 대신 재등록된다.

Wiring

순서: 먼저 브로커의 OAuth 교환(`packages/envoy/internal/broker/oauth/exchange.go`)이 Google 신원 토큰을 DPoP에 바인딩된 액세스 토큰으로 교환하며, `personDeclinesNothing`과 `personCredential`이 로그인의 두 절반을 처리하여 revoke가 대기하는 동안 커밋되는 거래의 토큰을 revoke가 종료시킵니다. 이 거래는 또한 런처 자격 증명 세션이 등록하는 대상을 발급하며, VM 토큰 경로와 `vm` 종류를 폐기합니다. 마이그레이션 0024는 활성 `vm` 등록, 권한 부여, 대기 중인 요청을 revoke합니다. 다음으로 공유 토큰 클라이언트 — Go와 TypeScript — 가 `agent-secrets`, Legion 컨트롤러, Oh My Pi, Claude Code, `dispatch`에 대한 토큰을 획득하고 갱신하며, 두 구현 모두 하나의 와이어 코퍼스에 대해 검증됩니다. TypeScript 세션 규칙은 `packages/broker-client/src/environment.ts`(`tradeSession`)에 있으며, 호출자 우선으로, 세션을 고정하는 fleet 또는 pod 항목을 거부합니다. 신원 키는 `~/.config/agent-secrets/identity-key.pem`에 있으며, `guides/revoke-a-session.md`에 3단계 차단 절차가 있습니다. Dispatch는 자격 증명 페이지에 로그인 목록과 revoke를, 쓰기 시 `SIGN_IN_REQUIRED` 거부를 추가합니다. Envoy 리스너는 다른 세션을 지명하는 토큰에 대해 8개 라우트에서 403을 추가하고, 브로커가 없다고 통보받으면 헤더를 무시합니다. 구성: `BROKER_MACHINE_LOGIN_URL`은 선택 사항입니다(설정하지 않으면 디바이스 그랜트가 없으며, RFC 8414 메타데이터에 명시되고 머신 코드 페이지도 없음). 클라이언트의 `AGENT_SECRETS_MACHINE_IDENTITY`와 브로커의 `BROKER_EXCHANGE_ACCOUNTS` 및 `BROKER_GOOGLE_OIDC_AUDIENCE`가 새 경로를 활성화합니다. 개발 스택 `packages/envoy/scripts/dev-stack.sh`는 실제 Postgres와 실제 마이그레이션에 대해 실제 브로커, Dispatch, 리스너, NATS, OIDC 발급자를 부팅합니다.

Try it yourself

# Replace broker device-grant machine login with Google-identity trade

## Broker: Google-identity trade replaces device grant

- Remove the device-grant machine login flow and the `POST /vm/token` route.
- Add `POST /machine/token`: a machine presents its Google identity token and receives a DPoP-bound access token.
  - The token names the machine's fleet service.
  - Lifetime is capped at one hour.
  - No refresh token is issued.
- The same trade mints the launcher credential that sessions enrol under.
- Remove the `vm` enrollment kind from the broker's enrollment model.
- `BROKER_MACHINE_LOGIN_URL` becomes optional. When unset, the broker serves no device grant and advertises that in its RFC 8414 metadata (`grant_types_supported` omits the device grant).

## CLI: `agent-secrets`

- Remove `agent-secrets enroll --vm` and `agent-secrets renew --vm`.
- Add `agent-secrets login`: a person signs in once per box. The resulting credential is listed on Dispatch's credentials page.
- Revoking that sign-in ends that box's tokens immediately, without affecting the person's other machines.

## Secret authority follows the access token

- A machine reads its service's secrets and shared agent-tier secrets only.
- No person's secrets are readable by a machine token.

## Dispatch

- Every Dispatch write attempted with a service-only token is refused with `SIGN_IN_REQUIRED`, and the error names the sign-in command.
- The credentials page lists per-box person sign-ins alongside machine credentials.

## Envoy listener

- The listener refuses a broker token that names another session.

## Token clients

- One token client in Go and one in TypeScript.
- Both are held to a shared wire corpus so their behaviour matches byte-for-byte on the wire.

## Dev stack

- Real broker, Dispatch, listener, NATS, and an OIDC issuer on Postgres.
- Prove a machine trade and a person sign-in, each with negative controls (wrong audience, expired token, service-only token attempting a Dispatch write, cross-session broker token at the listener).

Source

English

Tools in this stack