Description
The Umbrella DNS Security integration fails every sync attempt with a 401 Unauthorized from Cisco's
own API — confirmed NOT a credentials issue: tested against four different Umbrella key types
(Umbrella API key, Legacy Network Devices, Umbrella Reporting, Umbrella Management), all fail
identically, including a direct curl reproduction outside of Breeze entirely using confirmed-active
credentials. Root cause: UmbrellaProvider sends Basic Auth directly to the reports data endpoint,
but Cisco's current Reporting API v2 requires a two-step OAuth2 client-credentials flow instead.
Steps to Reproduce
- Create a DNS Security integration with provider: umbrella, any valid apiKey/apiSecret pair
generated from Umbrella's console, and a correct config.organizationId.
- Trigger a sync: POST /dns-security/integrations/:id/sync
- Sync fails. API logs show:
[DnsSyncJob] sync failed (upstream HTTP error) {"status":401,"statusText":"Unauthorized",
"responseBody":"{"meta":{},"data":{"error":"unauthorized"}}"}
- Reproduced independently outside Breeze entirely, confirming this is not a Breeze-side bug in
how credentials are stored/decrypted:
curl -u ":"
"https://reports.api.umbrella.com/v2/organizations//security-activity?from=...&to=...&limit=10"
-> also returns 401 directly from Cisco, with the exact same credentials.
- Tried regenerating credentials from all 4 available Umbrella key categories in Admin > API Keys
(Umbrella API Keys, KeyAdmin Keys, Static Keys, Legacy Keys > Umbrella Reporting / Umbrella
Management / Umbrella Network Devices / Legacy Network Devices) — every single one produces the
identical 401, both through Breeze and via direct curl.
Expected Behavior
The Umbrella integration should authenticate successfully using valid, active Umbrella credentials
and begin syncing DNS security events.
Actual Behavior
Every sync attempt fails with 401, regardless of Umbrella key type, confirmed via direct API testing
outside Breeze with the identical credentials. Root cause identified from Cisco's own current
OpenAPI spec for this endpoint (developer.cisco.com/docs/cloud-security/umbrella-api-api-reference-
reports-reporting-api-activity-get-activities-all/):
"security": [{"oauthFlow": ["reports.granularEvents:read"]}],
"securitySchemes": {"oauthFlow": {"type": "oauth2", "description": "client credential flow",
"flows": {"clientCredentials": {
"tokenUrl": "https://api.umbrella.com/auth/v2/token",
"scopes": {"reports.granularEvents:read": "Read reports granular events"}
}}}}
Cisco's current Reporting API v2 requires:
- POST https://api.umbrella.com/auth/v2/token with Basic Auth (apiKey:apiSecret) to obtain a
short-lived Bearer access token
- Use that Bearer token (Authorization: Bearer ) on the actual data endpoint
Confirmed from apps/api/src/services/dnsProviders/umbrella.ts that UmbrellaProvider does neither
step — it sends Basic Auth directly to the data endpoint:
basicAuthHeader() {
const token = Buffer.from(${this.apiKey}:${this.apiSecret}).toString("base64");
return Basic ${token};
}
...
const url = new URL(https://reports.api.umbrella.com/v2/organizations/${orgId}/security-activity);
...
const payload = await requestJson(url, { headers: { Authorization: this.basicAuthHeader() } });
Also worth noting: the base URL/path used (reports.api.umbrella.com/v2/organizations/{orgId}/
security-activity) differs from what Cisco's current spec documents (api.umbrella.com/reports/v2/
activity, org scoping handled via the token's own claims rather than a path segment) — this
integration appears to have been written against an older generation of Cisco's Reporting API that
has since been superseded.
Component
API Server
Version
Web/API 0.103.0, self-hosted via Docker Compose
Relevant Logs
[DnsSyncJob] sync failed (upstream HTTP error) {"integrationId":"...","status":401,
"statusText":"Unauthorized","responseBody":"{\"meta\":{},\"data\":{\"error\":\"unauthorized\"}}"}
[DnsSyncJob] Job sync-integration-... failed: DnsProviderHttpError: HTTP 401 Unauthorized
at async UmbrellaProvider.syncEvents (/app/apps/api/dist/index.cjs:223002:27)
Additional Context
Suggested fix: implement the OAuth2 client-credentials token exchange (POST to
https://api.umbrella.com/auth/v2/token with Basic Auth to obtain a Bearer token, cache it for its
stated expiry, then use Authorization: Bearer on subsequent report requests) rather than
sending Basic Auth directly to the data endpoint. May also need to update the base URL/path to
match Cisco's current api.umbrella.com/reports/v2 structure if the legacy reports.api.umbrella.com
host is deprecated alongside the auth change (untested — worth confirming whether that host still
resolves/functions at all once OAuth2 is implemented, or whether both the host and auth need
updating together).
This affects any self-hosted Breeze instance attempting to integrate Cisco Umbrella for DNS
Security — not something fixable via configuration on the operator's end, confirmed by testing
against 4 separate Umbrella key categories, all failing identically both through Breeze and via
direct API testing with the same credentials.
Description
The Umbrella DNS Security integration fails every sync attempt with a 401 Unauthorized from Cisco's
own API — confirmed NOT a credentials issue: tested against four different Umbrella key types
(Umbrella API key, Legacy Network Devices, Umbrella Reporting, Umbrella Management), all fail
identically, including a direct curl reproduction outside of Breeze entirely using confirmed-active
credentials. Root cause: UmbrellaProvider sends Basic Auth directly to the reports data endpoint,
but Cisco's current Reporting API v2 requires a two-step OAuth2 client-credentials flow instead.
Steps to Reproduce
generated from Umbrella's console, and a correct config.organizationId.
[DnsSyncJob] sync failed (upstream HTTP error) {"status":401,"statusText":"Unauthorized",
"responseBody":"{"meta":{},"data":{"error":"unauthorized"}}"}
how credentials are stored/decrypted:
curl -u ":"
"https://reports.api.umbrella.com/v2/organizations//security-activity?from=...&to=...&limit=10"
-> also returns 401 directly from Cisco, with the exact same credentials.
(Umbrella API Keys, KeyAdmin Keys, Static Keys, Legacy Keys > Umbrella Reporting / Umbrella
Management / Umbrella Network Devices / Legacy Network Devices) — every single one produces the
identical 401, both through Breeze and via direct curl.
Expected Behavior
The Umbrella integration should authenticate successfully using valid, active Umbrella credentials
and begin syncing DNS security events.
Actual Behavior
Every sync attempt fails with 401, regardless of Umbrella key type, confirmed via direct API testing
outside Breeze with the identical credentials. Root cause identified from Cisco's own current
OpenAPI spec for this endpoint (developer.cisco.com/docs/cloud-security/umbrella-api-api-reference-
reports-reporting-api-activity-get-activities-all/):
"security": [{"oauthFlow": ["reports.granularEvents:read"]}],
"securitySchemes": {"oauthFlow": {"type": "oauth2", "description": "client credential flow",
"flows": {"clientCredentials": {
"tokenUrl": "https://api.umbrella.com/auth/v2/token",
"scopes": {"reports.granularEvents:read": "Read reports granular events"}
}}}}
Cisco's current Reporting API v2 requires:
short-lived Bearer access token
Confirmed from apps/api/src/services/dnsProviders/umbrella.ts that UmbrellaProvider does neither
step — it sends Basic Auth directly to the data endpoint:
basicAuthHeader() {
const token = Buffer.from(
${this.apiKey}:${this.apiSecret}).toString("base64");return
Basic ${token};}
...
const url = new URL(
https://reports.api.umbrella.com/v2/organizations/${orgId}/security-activity);...
const payload = await requestJson(url, { headers: { Authorization: this.basicAuthHeader() } });
Also worth noting: the base URL/path used (reports.api.umbrella.com/v2/organizations/{orgId}/
security-activity) differs from what Cisco's current spec documents (api.umbrella.com/reports/v2/
activity, org scoping handled via the token's own claims rather than a path segment) — this
integration appears to have been written against an older generation of Cisco's Reporting API that
has since been superseded.
Component
API Server
Version
Web/API 0.103.0, self-hosted via Docker Compose
Relevant Logs
[DnsSyncJob] sync failed (upstream HTTP error) {"integrationId":"...","status":401, "statusText":"Unauthorized","responseBody":"{\"meta\":{},\"data\":{\"error\":\"unauthorized\"}}"} [DnsSyncJob] Job sync-integration-... failed: DnsProviderHttpError: HTTP 401 Unauthorized at async UmbrellaProvider.syncEvents (/app/apps/api/dist/index.cjs:223002:27)Additional Context
Suggested fix: implement the OAuth2 client-credentials token exchange (POST to
https://api.umbrella.com/auth/v2/token with Basic Auth to obtain a Bearer token, cache it for its
stated expiry, then use Authorization: Bearer on subsequent report requests) rather than
sending Basic Auth directly to the data endpoint. May also need to update the base URL/path to
match Cisco's current api.umbrella.com/reports/v2 structure if the legacy reports.api.umbrella.com
host is deprecated alongside the auth change (untested — worth confirming whether that host still
resolves/functions at all once OAuth2 is implemented, or whether both the host and auth need
updating together).
This affects any self-hosted Breeze instance attempting to integrate Cisco Umbrella for DNS
Security — not something fixable via configuration on the operator's end, confirmed by testing
against 4 separate Umbrella key categories, all failing identically both through Breeze and via
direct API testing with the same credentials.