docs/integrations/datadog.md
Rama main · réplica de lectura del repositorio original.
# datadog
One connection is one Datadog organization. toolpass authenticates with an API key and a scoped
application key, finds the user by email, reads the permissions carried by the user's roles, and for
a monitor, dashboard, SLO or notebook reads the asset's restriction policy and its legacy
`restricted_roles` and author fields. A user may change such an asset only when a role carries the
write permission **and** the asset's restrictions, if any, name the user, one of the user's roles or
teams, or the whole org. Nothing is written.
## Credential
1. An **API key** of the organization (`api_key`).
2. An **application key** (`credential`) created for a dedicated service account or user and
**scoped** to `user_access_read`, `teams_read` and the read scope of each asset type asked about:
`monitors_read`, `dashboards_read`, `slos_read`, `notebooks_read`. Reading a restriction policy
needs no named scope. An unscoped application key carries every permission of its creator, which
is far more than toolpass needs.
`toolpass probe` validates the API key, lists one user (which proves `user_access_read`) and reminds
you of the other scopes.
## Connection
```yaml
- id: datadog-acme
integration: datadog
api_key: env:DATADOG_API_KEY
credential: env:DATADOG_APP_KEY
# url: https://api.datadoghq.eu # default https://api.datadoghq.com; US3 api.us3.datadoghq.com, US5 api.us5.datadoghq.com, AP1 api.ap1.datadoghq.com
```
| Key | Meaning |
|---|---|
| `url` | the site's API URL |
| `api_key` | sent as `DD-API-KEY`, `env:` or `file:` |
| `credential` | the application key, sent as `DD-APPLICATION-KEY`, `env:` or `file:` |
### Identity
`GET /api/v2/users?filter=<email>&filter[status]=Active,Pending,Disabled`, every page. The filter is
a substring match on name, handle and email, so only the record whose `email` equals the address
(ignoring case) is the user; none is `user_not_found`, two are `user_ambiguous`. A `disabled` user, or one
whose `status` is still `Pending`, is denied every action. The identity carries the user's handle and the ids of the user's roles; each
role's permissions come from `GET /api/v2/roles/{id}/permissions`, cached for five minutes. Groups
sent by the caller are ignored.
## Resources
| Resource | Meaning |
|---|---|
| `monitor:<id>` | a monitor |
| `dashboard:<id>` | a dashboard (`abc-def-ghi`) |
| `slo:<id>` | a service level objective |
| `notebook:<id>` | a notebook |
| `org` | the organization, for permissions that are not about one asset |
## Actions
| Action | Permission | Restriction relation | Resource |
|---|---|---|---|
| `monitor.edit` | `monitors_write` | editor | monitor |
| `monitor.mute` | `monitors_downtime` | editor (muting counts as editing a restricted monitor) | monitor |
| `monitor.read` | `monitors_read` | viewer | monitor |
| `dashboard.edit` | `dashboards_write` | editor | dashboard |
| `dashboard.read` | `dashboards_read` | viewer | dashboard |
| `slo.edit` | `slos_write` | editor | slo |
| `notebook.edit` | `notebooks_write` | editor | notebook |
| `logs.read` | `logs_read_data` | | org |
| `users.manage` | `user_access_manage` | | org |
| `apikeys.manage` | `api_keys_write` | | org |
| `raw:<permission>` | that permission | on an asset: editor when it is one of the type's write permissions (`monitors_write`, `monitors_downtime`, `dashboards_write`, `slos_write`, `notebooks_write`), viewer otherwise | any |
### Evaluation
1. The permission: one of the user's roles must list it. On the `org` resource that is the answer.
2. The asset is read (`GET /api/v1/monitor/{id}`, `/api/v1/dashboard/{id}`, `/api/v1/slo/{id}`,
`/api/v1/notebooks/{id}`); a 404 is `resource_not_visible` whatever the permission.
3. The restriction policy: `GET /api/v2/restriction_policy/{type}:{id}`. When it has bindings, the
user must appear in a binding whose relation is the one needed or higher (`viewer` < `editor`;
type-specific relations above editor count as editor) as `user:<id>`, `role:<one of the user's
roles>`, `team:<a team the user is a member of>` (`GET /api/v2/team/{id}/memberships`, filtered
by the user's email and compared by id) or `org:<any>`. A policy with no `viewer` binding
restricts editing only: reads fall back to the permission.
4. Without a policy, the legacy fields: a monitor's or dashboard's `restricted_roles` must include
one of the user's roles for an edit, except that a dashboard's author (`author_handle`) may always
edit. Reads are not restricted by `restricted_roles`.
## Decisions
| Situation | toolpass answers |
|---|---|
| a role carries the permission and the asset has no restriction | allow |
| a role carries the permission and a binding at the relation names the user, a role, a team the user is on, or the org | allow, naming the principal kind |
| a role carries the permission and `restricted_roles` includes a role of the user, or the user authored the dashboard | allow |
| no role carries the permission | deny |
| the asset's restriction policy or `restricted_roles` name none of the user's principals | deny |
| disabled user, or a user whose invitation is still pending | deny |
| no user with the email | deny (`user_not_found`) |
| several users | unknown (`user_ambiguous`) |
| user record without `disabled` | unknown (`unsupported`) |
| the asset, a role of the user, or a team named by the policy answers 404 | unknown (`resource_not_visible`) |
| 401, 403 (Datadog answers 403 for an invalid key as well as for a missing scope) | unknown (`credential_rejected`) |
| 429, 5xx, timeout | unknown (`upstream_rate_limited` / `upstream_error` / `upstream_timeout`) |
Error bodies are never copied into a decision text.
## Routes
The secure-tools toolkit maps a tool's requests to the API (`api.<site>`, the site's web host and
its logs intake host, from `url`) to these actions; `toolpass routes datadog` prints the table.
| Requests | Action and resource |
|---|---|
| a monitor, dashboard, SLO or notebook by id (`/api/v1/monitor/{id}`, ...) | its read or edit action on the asset (`monitor.edit` on `monitor:123`, `raw:slos_read` on `slo:<id>`), so restriction policies apply |
| creating or listing them | the permission on `org` (`raw:monitors_write`, `raw:dashboards_read`) |
| a restriction policy of one of those assets | the asset's edit action (changes) or read action |
| a downtime naming a monitor (v1 `monitor_id`, v2 `monitor_identifier.monitor_id`) | `monitor.mute` on the monitor |
| a downtime by scope or monitor tags, or changing or cancelling one by id; the downtimes matching a monitor (`GET /api/v2/monitor/{id}/downtime_matches`) | `raw:monitors_downtime` on `org` |
| searching or aggregating logs; an archive search's results | `logs.read` |
| reading API keys or any user's application keys, which returns the keys | `raw:api_keys_read` or `raw:org_app_keys_read` on `org` |
| searching spans; reading a trace (`/api/v2/trace/{id}`, `/api/v2/pruned_trace/{id}`) | `raw:apm_read` on `org` |
| users, roles, API and application keys, service accounts, logs and metrics configuration, metric and event queries | the one permission the API description names for the operation, as `raw:<permission>` on `org` (`users.manage` and `apikeys.manage` where they are that permission) |
Refused: a body that names a second asset (an `id` or `data.id` other than the path's, more than one
dashboard or SLO in a bulk request); creating a user or service account with roles (assign them
afterwards with `POST /api/v2/roles/{id}/users`); writes to assets whose restriction policies the
integration does not read (synthetic tests, security rules, workflows, on-call, logs pipelines, ...);
writes the API description gives no permission for, or several; sharing dashboards outside the org;
the agent's own application keys and profile (`/api/v2/current_user`); data intake (metrics, events,
logs), which needs the API key alone; and every other product's writes until a route maps them.
Bodies are read as `application/json` only; a duplicated member or a compressed body is refused.
Reads with no route are allowed and marked untrusted.
## What it cannot see
- Self-elevation: a user with `user_access_manage` can add themselves to any restriction policy.
toolpass answers for the policy as it stands, so such a user is denied until they do.
- Assets other than monitors, dashboards, SLOs and notebooks (synthetics tests, security rules,
workflows, ...), which have restriction policies of their own but no action here yet; use
`raw:<permission>` on `org` for their permissions alone.
- Log restriction queries, index-level log permissions, and RBAC on APM services.
- Team membership through team-links or provisioning that the memberships endpoint does not list.
## Unverified
Marked `# UNVERIFIED:` in the code:
- Whether a monitor's creator keeps edit rights under `restricted_roles` the way a dashboard's
author does. Datadog documents roles only for monitors, so the creator is not exempted.
- That a restriction policy without a `viewer` binding leaves viewing to the permission. The UI
writes an explicit `viewer` binding for the org whenever it restricts an asset, which is why
toolpass reads a policy without one as restricting editing only.
Assumed from Datadog's documentation rather than tested live: that the write permission is still
required when a restriction policy grants `editor` ("the limitations are applied both in the UI and
API"), and that `GET /api/v2/restriction_policy/{id}` answers `200` with empty bindings, not 404,
for an asset without a policy (a 404 is treated the same).
## Test
Unit tests run against a fake that serves users (paged), role permissions, monitors, dashboards,
SLOs, notebooks, restriction policies and team memberships (paged), validating every request against
Datadog's v1 and v2 OpenAPI descriptions (`datadog-v1` and `datadog-v2` in `test/specs/fetch.sh`).
There is no live test; after configuring, run `toolpass probe` and one check for a user you know is
allowed.