cli/auth: add `token --jurisdiction` for jurisdictional identity tokens · Entire

cli/auth: add token --jurisdiction for jurisdictional identity tokens

2b08ff1·

georg·2w ago·8 files·+457 added/-69 removed

entire auth token printed only the control-plane bearer, which the per-jurisdiction entire-api cells (e.g. https://aws-us-east-2.api.entire.io/api/v1) reject. Add --jurisdiction <slug> to mint a jurisdictional identity token (RFC 8693 exchange, scope=openid, aud=) for that jurisdiction's cells, reusing the existing exchange pipeline in cell_data_api.go. It exchanges ENTIRE_TOKEN when set (deriving the environment from the env token's aud so it works with no ENTIRE_API_BASE_URL, else the active stored login.

Also surface the caller's home jurisdiction in Logged in to https://us.auth.entire.io
User: Georg Friedrich @georg
Identity: github/1940
Context: us.auth.entire.io
Token: stored in OS keychain

Active sessions (32): NAME CREATED LAST USED EXPIRES OIDC login 2026-06-30 2026-06-30 2026-07-03 2026-08-02 OIDC login 2026-06-30 2026-06-30 2026-07-03 2026-08-02 OIDC login 2026-07-01 2026-07-01 2026-07-03 2026-08-02 Console login 2026-07-02 2026-07-02 2026-07-02 2026-07-09 Console login 2026-07-01 2026-07-01 2026-07-01 2026-07-08 OIDC login 2026-06-29 2026-06-29 2026-06-30 2026-07-30 Console login 2026-06-30 2026-06-30 2026-06-30 2026-07-07 OIDC login 2026-06-25 2026-06-25 2026-06-29 2026-07-29 Console login 2026-06-29 2026-06-29 2026-06-29 2026-07-06 OIDC login 2026-06-22 2026-06-22 2026-06-29 2026-07-29 OIDC login 2026-06-18 2026-06-18 2026-06-25 2026-07-25 OIDC login 2026-06-24 2026-06-24 2026-06-24 2026-07-24 OIDC login 2026-06-16 2026-06-16 2026-06-21 2026-07-21 OIDC login 2026-06-19 2026-06-19 2026-06-19 2026-07-19 OIDC login 2026-06-14 2026-06-14 2026-06-18 2026-07-18 OIDC login 2026-06-13 2026-06-13 2026-06-13 2026-07-13 OIDC login 2026-06-11 2026-06-11 2026-06-12 2026-07-12 OIDC login 2026-06-11 2026-06-11 2026-06-11 2026-07-11 OIDC login 2026-06-09 2026-06-09 2026-06-09 2026-07-09 OIDC login 2026-06-08 2026-06-08 2026-06-08 2026-07-08 OIDC login 2026-06-08 2026-06-08 2026-06-08 2026-07-08 OIDC login 2026-06-08 2026-06-08 2026-06-08 2026-07-08 OIDC login 2026-06-07 2026-06-07 2026-06-07 2026-07-07 OIDC login 2026-06-06 2026-06-06 2026-06-06 2026-07-06 OIDC login 2026-06-06 2026-06-06 2026-06-06 2026-07-06 OIDC login 2026-06-06 2026-06-06 2026-06-06 2026-07-06 OIDC login 2026-06-05 2026-06-05 2026-06-05 2026-07-05 OIDC login 2026-06-05 2026-06-05 2026-06-05 2026-07-05 OIDC login 2026-06-05 2026-06-05 2026-06-05 2026-07-05 OIDC login 2026-06-05 2026-06-05 2026-06-05 2026-07-05 OIDC login 2026-06-05 2026-06-05 2026-06-05 2026-07-05 OIDC login 2026-06-04 2026-06-04 2026-06-04 2026-07-04

Run 'entire logout' to end this session, or 'entire logout --everywhere' to end all of them.

2 login contexts saved; run 'entire auth contexts' to list or 'entire auth use ' to switch.

Update available! 0.7.9-nightly.202607020716.b58b5fe8e -> 0.7.9-nightly.202607030712.7b6b53b52
Release notes: https://github.com/entireio/cli/releases/tag/v0.7.9-nightly.202607030712.7b6b53b52 To update, run: brew upgrade entire@nightly so the slug is discoverable non-interactively.

--- token ------------------------------------------------------------------

// newAuthTokenCmd prints the active control-plane bearer to stdout for // scripting. The user-facing Long and Example carry the detail and the // "treat the output as a secret" caveat; the token resolves the same way the // API client's does (ENTIRE_TOKEN verbatim when set, otherwise the active // context's login JWT, refreshed if it's near expiry), and only the token is // printed — errors and the not-logged-in hint go to stderr so command // substitution stays clean. func newAuthTokenCmd() *cobra.Command { var insecureHTTPAuth bool var jurisdiction string cmd := &cobra.Command{ Use: "token", Short: "Print the active control-plane bearer token — a live credential, treat as a secret", Long: "Print the active control-plane bearer token to stdout so scripts and\n" + "ad-hoc curl can authenticate against the control-plane API.\n\n" + "The output is a live credential — treat it as a secret. It is the same\n" + "bearer the API client uses: ENTIRE_TOKEN verbatim when set, otherwise the\n" + "active context's login JWT (refreshed if it's near expiry). Only the token\n" + "is printed to stdout; errors and the not-logged-in hint go to stderr so\n" + "command substitution stays clean.", Example: " curl -H "Authorization: Bearer $(entire auth token)" "https://us.console.entire.io/api/v1/clusters\"", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { // Refresh may exchange/refresh over the network; honor the\n // plain-HTTP opt-in before resolving so local dev cores work. insecure := applyInsecureHTTPAuth(insecureHTTPAuth)

// --jurisdiction mints a data-plane cell identity token instead of the // control-plane bearer. JurisdictionToken performs its own TLS/exchange // guards and returns context-rich errors. if strings.TrimSpace(jurisdiction) != "" { token, err := auth.JurisdictionToken(cmd.Context(), insecure, jurisdiction) if err != nil { cmd.SilenceUsage = true if errors.Is(err, auth.ErrNotLoggedIn) { fmt.Fprintln(cmd.ErrOrStderr(), "Not logged in. Run 'entire login' to authenticate.") return NewSilentError(err) } return err //nolint:wrapcheck // JurisdictionToken already returns contextual auth errors } fmt.Fprintln(cmd.OutOrStdout(), token) return nil }

target, err := resolveAuthStatusTarget(cmd.Context(), auth.Contexts, auth.RefreshedLoginToken) if err != nil { return err }

    },
}
addInsecureHTTPAuthFlag(cmd, &insecureHTTPAuth)
cmd.Flags().StringVar(&jurisdiction, "jurisdiction", "", "mint a jurisdictional identity token for this jurisdiction slug (e.g. us, eu) for use against that jurisdiction's entire-api cells")
return cmd

}....

// JurisdictionToken mints and returns a jurisdictional identity token // (scope=openid, aud=jurisdiction host) for jurisdiction, for authenticating // against that jurisdiction's entire-api cells (e.g. // https://aws-us-east-2.api.entire.io/api/v1). Unlike NewEntireAPICellClient it // returns the raw token string (it skips the cell-base-URL resolution, which is // only needed to build a client) and it honours ENTIRE_TOKEN.

// Subject credential precedence: // - ENTIRE_TOKEN set: the env token is the exchange subject_token, and its own // aud core drives the environment family (so this works with only // ENTIRE_TOKEN set, no ENTIRE_API_BASE_URL, in prod/staging/loopback). // Presence is exclusive and fail-closed — a malformed/blank value errors // rather than falling back to a stored login. The env token must be a login // JWT (subject-capable); a rejected exchange surfaces the server error. // - otherwise: the active stored context's refreshed login JWT.

// An empty jurisdiction falls back to the subject token's home_jurisdiction // claim. func JurisdictionToken(ctx context.Context, insecureHTTP bool, jurisdiction string) (string, error) { subject, err := resolveCellSubject(ctx, insecureHTTP) if err != nil { return "", err }

j, err := resolveJurisdiction(jurisdiction, subject.loginJWT) if err != nil { return "", err }

coreURL := jurisdictionCoreURL(j, subject.dataOrigin, subject.discoveredCore) if err := requireSafeExchangeURL("entire-core", coreURL); err != nil { return "", err }

audience := jurisdictionAudience(j, subject.dataOrigin, subject.discoveredCore) token, err := exchangeJurisdictionToken(ctx, coreURL, subject.loginJWT, audience, subject.httpClient) if err != nil { return "", fmt.Errorf("exchange jurisdictional identity token: %w", err) } return token, nil }