Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 56 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <name>` (or `DD_ORG=<name>`) 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 <name>`) 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_<site>.json`, `client_<site>.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.

Expand Down
187 changes: 164 additions & 23 deletions docs/OAUTH2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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 <name>`) 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

Expand Down Expand Up @@ -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_<site>.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_<site>.json` and `client_<site>.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_<site>_<org>.json` file.

The token payload is:

Expand Down Expand Up @@ -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 <name>` (or
`DD_ORG=<name>`) 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 <name>` rather than mixing the default
(unnamed) session with named ones. This way `--org <name>` 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_<site>.json`)
- Access/refresh tokens (`tokens_<site>.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 <name>` to recall the right site on a non-auth command.

## Troubleshooting

Expand Down Expand Up @@ -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_<site>.json # DCR client credentials, one per site (shared across orgs)
├── tokens_<site>.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_<site>.json` and
`tokens_<site>.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

```
Expand Down
Loading