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.
- An API key of the organization (
api_key). - An application key (
credential) created for a dedicated service account or user and scoped touser_access_read,teams_readand 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.
- 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: |
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.
| 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 |
| 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 |
- The permission: one of the user's roles must list it. On the
orgresource that is the answer. - The asset is read (
GET /api/v1/monitor/{id},/api/v1/dashboard/{id},/api/v1/slo/{id},/api/v1/notebooks/{id}); a 404 isresource_not_visiblewhatever the permission. - 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) asuser:<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) ororg:<any>. A policy with noviewerbinding restricts editing only: reads fall back to the permission. - Without a policy, the legacy fields: a monitor's or dashboard's
restricted_rolesmust 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 byrestricted_roles.
| 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.
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.
- Self-elevation: a user with
user_access_managecan 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>onorgfor 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.
Marked # UNVERIFIED: in the code:
- Whether a monitor's creator keeps edit rights under
restricted_rolesthe way a dashboard's author does. Datadog documents roles only for monitors, so the creator is not exempted. - That a restriction policy without a
viewerbinding leaves viewing to the permission. The UI writes an explicitviewerbinding 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).
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.