diff --git a/README.md b/README.md index 50ca7bd..1c0a113 100644 --- a/README.md +++ b/README.md @@ -218,8 +218,11 @@ Pup supports two authentication methods. **OAuth2 is preferred** and will be use OAuth2 provides secure, browser-based authentication with automatic token refresh. ```bash -# Set your Datadog site (optional) -export DD_SITE="datadoghq.com" # Defaults to datadoghq.com +# Set your Datadog site (optional, defaults to datadoghq.com). +# Common values: datadoghq.com, datadoghq.eu, us3.datadoghq.com, +# us5.datadoghq.com, ap1.datadoghq.com, ap2.datadoghq.com, ddog-gov.com. +# Other Datadog sites are also accepted. +export DD_SITE="datadoghq.com" # Login via browser pup auth login @@ -234,7 +237,57 @@ pup auth status pup auth logout ``` -**Token Storage**: Tokens are stored securely in your system's keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service). Set `DD_TOKEN_STORAGE=file` to use file-based storage instead. +#### Multiple sites and orgs + +Pup persists each login as a separate session, so you can authenticate against multiple Datadog sites and orgs and switch between them with `--org ` (or `DD_ORG=`) on any subcommand. + +```bash +# Login to a non-default site. --site is only accepted by `pup auth login` +# and `pup auth status`. For other commands, select the site via DD_SITE +# (or use a named session and pass --org on every subsequent command; see +# the Named session examples below). +pup auth login --site datadoghq.eu +DD_SITE=datadoghq.eu pup monitors list + +# Named session for a parent/child sub-org on the same site. +pup auth login --org staging-child +pup monitors list --org staging-child # site recalled from the session, no DD_SITE needed + +# Named session on another site. DD_SITE / --site is only needed at login. +pup auth login --site ap2.datadoghq.com --org ap2-prod +pup monitors list --org ap2-prod # site recalled + +# SAML/SSO org. --subdomain narrows the consent page to one org for tenants +# with subdomain-routed SSO. It is only used during the browser flow and is +# not persisted. +pup auth login --org acme-prod --subdomain acme + +# Pre-target a specific org by UUID (sent as dd_oid). Skips the org switcher +# when the browser session already matches and pre-routes SAML/SSO. The UUID +# is persisted and re-emitted on subsequent `pup auth login` invocations for +# the same named session. +pup auth login --org acme-prod --org-uuid 11111111-2222-3333-4444-555555555555 + +# List all stored sessions (site, org, org_uuid, scopes, expiry, status). +pup auth list + +# Refresh or log out a specific named session. +pup auth refresh --org staging-child +pup auth logout --org staging-child # clears only that named session +``` + +Note: `pup auth logout` (default session) also deletes the shared DCR client credentials for that site. Named-org sessions on the same site keep their access tokens but will fail to refresh until the shared credentials are re-registered, which happens automatically on the next `pup auth login` on that site (any org, named or default). Logging out a named session (`--org `) does not touch the shared client credentials. + +**Site selection rules** (when pup resolves a site for a non-auth command): +1. `DD_SITE` env var (or `site:` in `~/.config/pup/config.yaml`), if set. +2. The site recorded in `~/.config/pup/sessions.json` for the named `--org` / `DD_ORG`, when the lookup is unambiguous. +3. Default: `datadoghq.com`. + +`pup auth login` and `pup auth status` additionally accept `--site`, which wins over the above for those two commands. + +If multiple sessions share the same org name on different sites, step 2 is skipped (ambiguous) and pup warns to stderr; pass `DD_SITE` to disambiguate. An unnamed (default) session can't be selected by `--org` at all -- if you have multiple unnamed sessions on different sites, set `DD_SITE` to pick one. + +**Token Storage**: By default, OAuth tokens and DCR client credentials are stored in your platform's secure store: macOS Keychain (via Apple's Security framework, with Touch ID prompts), Linux Secret Service (via the `keyring` crate), or Windows Credential Manager (via the `keyring` crate; sharded across multiple WinCred entries to stay within WinCred's per-record size limit). When no secure store is available, pup falls back to JSON files under `~/.config/pup/` with `0600` permissions; in file mode tokens and client credentials are kept in separate files (`tokens_.json`, `client_.json`). Set `DD_TOKEN_STORAGE=file` to force file storage. In either mode, all tokens for a given site share one tokens entry, keyed internally by org name. **Note**: OAuth2 requires Dynamic Client Registration (DCR) to be enabled on your Datadog site. If DCR is not available yet, use API key authentication. diff --git a/docs/OAUTH2.md b/docs/OAUTH2.md index f658b38..5ab88b7 100644 --- a/docs/OAUTH2.md +++ b/docs/OAUTH2.md @@ -27,7 +27,9 @@ Pup supports OAuth2 authentication with PKCE (Proof Key for Code Exchange) for s ### 1. Login ```bash -pup auth login +pup auth login # default site (datadoghq.com), default org +pup auth login --site datadoghq.eu # a different Datadog site +pup auth login --org staging-child # a named session for a second org ``` This will: @@ -39,6 +41,9 @@ This will: 6. Exchange the authorization code for access/refresh tokens 7. Store tokens securely (OS keychain, or JSON file under `~/.config/pup/` with `0600` permissions when no keychain is available) +See [Multi-Site Support](#multi-site-support) and +[Multi-Org Support](#multi-org-support) for managing multiple sessions. + ### 2. Check Status ```bash @@ -61,10 +66,26 @@ Manually refresh your access token using the refresh token. This happens automat ### 4. Logout ```bash -pup auth logout +pup auth logout # default session for the current site +DD_SITE=datadoghq.eu pup auth logout # default session for a non-default site +pup auth logout --org staging-child # one named session, leaves others intact ``` -Clears all stored tokens and client credentials for the current site. +`pup auth logout` itself doesn't accept a `--site` flag; use `DD_SITE` to +pick which default session to clear. + +**Side effect on sibling sessions:** logging out the default (unnamed) +session for a site also deletes that site's shared DCR client +credentials. Any named-org sessions on the same site will still hold +valid access tokens, but their next automatic refresh will fail (no +client credentials). The shared credentials are re-registered +automatically by the next `pup auth login` on that site (any org, +named or default), and the sibling sessions can refresh again from +that point on. Logging out a named session (`--org `) does not +touch the shared client credentials. + +See [Multi-Org Support](#multi-org-support) for managing multiple named +sessions side-by-side. ## OAuth2 Flow Details @@ -146,11 +167,23 @@ Proof Key for Code Exchange prevents authorization code interception: #### Token Storage -Tokens are stored in the OS keychain by default (macOS Keychain, Windows -Credential Manager, Linux Secret Service via the `keyring` crate). When a -keychain is unavailable, pup falls back to a JSON file at -`~/.config/pup/tokens_.json` with `0600` permissions. Set -`DD_TOKEN_STORAGE=file` to force file storage. +By default, OAuth tokens and DCR client credentials are stored in your +platform's secure store: macOS Keychain (via Apple's Security framework, +with Touch ID prompts), Linux Secret Service (via the `keyring` crate), +or Windows Credential Manager (via the `keyring` crate). When the +secure store is unavailable, pup falls back to JSON files under +`~/.config/pup/` with `0600` permissions. Set `DD_TOKEN_STORAGE=file` +to force file storage. + +In secure-store mode each site has one per-site entry holding both +tokens and client credentials (on Windows, sharded across multiple +WinCred records to stay within WinCred's per-record size limit). In +file mode tokens and client credentials are kept in separate files +(`tokens_.json` and `client_.json`). In either mode, when +a site has multiple named-org sessions (see +[Multi-Org Support](#multi-org-support)) all of their tokens live +inside the per-site tokens entry, keyed internally by org name; there +is no separate `tokens__.json` file. The token payload is: @@ -262,29 +295,128 @@ Pup supports all Datadog sites with separate credentials per site: ```bash # US1 (default) -export DD_SITE="datadoghq.com" -pup auth login +pup auth login --site datadoghq.com # EU1 -export DD_SITE="datadoghq.eu" -pup auth login +pup auth login --site datadoghq.eu # US3 -export DD_SITE="us3.datadoghq.com" -pup auth login +pup auth login --site us3.datadoghq.com # US5 -export DD_SITE="us5.datadoghq.com" -pup auth login +pup auth login --site us5.datadoghq.com # AP1 -export DD_SITE="ap1.datadoghq.com" -pup auth login +pup auth login --site ap1.datadoghq.com + +# AP2 +pup auth login --site ap2.datadoghq.com + +# Gov +pup auth login --site ddog-gov.com + +# DD_SITE env var also works on any of the above. +DD_SITE=datadoghq.eu pup auth login +``` + +Each site maintains separate state: +- Client credentials, shared across orgs on the same site. +- Access/refresh tokens, in a single per-site entry keyed internally by org. + +See [Token Storage](#token-storage) for the secure-store-vs-file layout. + +## Multi-Org Support + +Pup supports multiple Datadog orgs side-by-side via *named sessions*. Each +named session is a `(site, org)` pair, and `--org ` (or +`DD_ORG=`) selects which session a command runs against. The flag is +global and works on every subcommand, not just `auth`. + +**Recommended pattern if you work with more than one org:** give every +session an explicit `--org ` rather than mixing the default +(unnamed) session with named ones. This way `--org ` always +appears in your commands and there's no ambiguity about which org a +query targeted. Sharing one default slot across multiple orgs is easy +to get wrong (you re-log into a different org without realizing it), +and as noted in [Logout](#4-logout), logging out the default also +removes the shared DCR client credentials for that site. + +### Logging into multiple orgs + +```bash +# Two child orgs on the default site (US1). +pup auth login --org prod-child +pup auth login --org staging-child + +# A child org on a different site. --site is only needed at login; +# subsequent commands recall it from the session registry. +pup auth login --site ap2.datadoghq.com --org ap2-prod + +# A SAML/SSO org. --subdomain narrows the consent page to one org for +# tenants with subdomain-routed SSO. It is only used during the browser +# flow and is not persisted with the session. +pup auth login --org acme-prod --subdomain acme + +# Pre-target a specific org by UUID (sent as `dd_oid`). Skips the org +# switcher when the existing browser session matches, and pre-routes +# SAML/SSO routing for first-time logins. The UUID is persisted with the +# session and re-emitted on subsequent `pup auth login` invocations for +# the same named session. +pup auth login --org acme-prod --org-uuid 11111111-2222-3333-4444-555555555555 +``` + +### Using a named session + +```bash +# Site is recalled from sessions.json; no DD_SITE / --site needed. +pup monitors list --org prod-child +pup logs query --org ap2-prod --query "service:web-store" --limit 10 + +# DD_ORG env var is equivalent to --org. +DD_ORG=prod-child pup metrics query --query "avg:system.cpu.user{*}" +``` + +### Inspecting and managing sessions + +```bash +# List every stored session (site, org, org_uuid, scopes, expiry, status). +pup auth list + +# Refresh a specific named session. +pup auth refresh --org prod-child + +# Log out of a single named session (other sessions are untouched). +pup auth logout --org staging-child + +# Log out of the default (unnamed) session for the current site. +pup auth logout ``` -Each site maintains separate: -- Client credentials (`client_.json`) -- Access/refresh tokens (`tokens_.json`) +### Site selection rules + +When pup resolves a site for a non-auth command: + +1. `DD_SITE` env var (or `site:` in `~/.config/pup/config.yaml`), if set. +2. The site recorded in `~/.config/pup/sessions.json` for the named + `--org` / `DD_ORG`, when the lookup is unambiguous. +3. Default: `datadoghq.com`. + +`pup auth login` and `pup auth status` additionally accept `--site`, +which wins over the above for those two commands. No other subcommand +accepts `--site`. + +If multiple sessions share the same org name on different sites, step 2 +is skipped (ambiguous) and pup warns to stderr; pass `DD_SITE` to +disambiguate. An unnamed (default) session can't be selected by `--org` +at all (it has no name to look up), so if you have multiple unnamed +sessions on different sites, set `DD_SITE` to pick one. + +### Session registry + +Named-session metadata lives in `~/.config/pup/sessions.json`. The file +records the `site`, `org`, and (when supplied at login) `org_uuid` for +each session. No tokens or secrets are stored here. The registry is what +enables `--org ` to recall the right site on a non-auth command. ## Troubleshooting @@ -396,10 +528,19 @@ This indicates a potential security issue. Run `pup auth login` again to start a ``` ~/.config/pup/ -├── client_datadoghq_com.json # DCR client credentials -└── tokens_datadoghq_com.json # OAuth2 tokens +├── client_.json # DCR client credentials, one per site (shared across orgs) +├── tokens_.json # OAuth2 tokens, one per site (keyed internally by org) +└── sessions.json # Named-session registry (site, org, org_uuid; no secrets) ``` +On platforms using the secure-store backend (macOS, plus Linux/Windows +when a keychain is available), both `client_.json` and +`tokens_.json` are absent: their contents live together in a +per-site secure-store entry. On Windows, this entry is sharded into +multiple WinCred records (one count record plus one or more chunk +records) to stay within WinCred's per-record size limit. +`sessions.json` is always file-based regardless of backend. + ### Code Structure ```