Duo Setup for dfe-fetcher
What a Cisco Duo administrator needs to configure so dfe-fetcher can read authentication logs from a Duo tenant via the Admin API.
Overview
dfe-fetcher reads Duo authentication events from the Duo Admin API
endpoint /admin/v2/logs/authentication. Duo does NOT use a bearer token:
every request is signed with Duo's own scheme. The fetcher computes a signature
over a canonical request string and sends it in a Basic auth header, with a
fresh Date header per request (no token is cached).
The default is signature version 5, the scheme Duo documents and the only one some newer Admin API endpoints accept:
canonical = HTTP_DATE \n METHOD \n LOWER(HOST) \n PATH \n SORTED_QUERY
\n hex(SHA-512(body)) \n hex(SHA-512(signed x-duo- headers))
sig = hex(HMAC-SHA512(skey, canonical))
Auth = Basic base64(ikey:sig)
A GET carries no body and the fetcher sends no x-duo- headers, so both hash
lines are the SHA-512 of the empty string.
Signature version 2 is Duo's legacy scheme: the first five lines alone,
HMAC-SHA1. A tenant whose endpoints still verify it selects it with
signature_version: v2, on the type or on one connection, as below. Leave it
unset otherwise.
SHA-1 is kept out of a security purpose everywhere else in the platform, so version 2 is a deliberate exemption rather than a setting like any other. A connection that binds on it logs a warning at startup naming the connection; that warning is the signal to move the tenant to version 5 once its endpoints verify it, and it is expected to stay until then.
The admin provisions an Admin API application in the Duo Admin Panel, grants it log-read permission, and hands dfe-fetcher three values: the integration key (ikey), secret key (skey), and API hostname.
The v2 endpoint takes mintime/maxtime as millisecond Unix timestamps,
returns up to limit records (default 100, max 1000), and paginates with
metadata.next_offset, the two-element list the API documents, sent back
comma-joined until it is null. Duo enforces a deliberate two-minute
delay: authentications less than two minutes old are not yet returned. The
profile's window.delay: 2m ends every window two minutes before now, so the
cursor never passes events Duo has not yet published.
The source is the shipped duo profile (crates/fetcher/profiles/duo.yaml)
on the signature auth mode; the sources.duo block below maps onto an
instance of it at load. A 429 or 5xx is retried with backoff (honouring
Retry-After), a 401 or 403 ends the tick, a 2xx carrying stat: FAIL fails
the tick with Duo's message, and a tick that fails does not advance the
fetch window. A missing host or key is refused at load, naming sources.duo.
Prerequisites
- A Duo subscription whose edition includes Admin API access.
- A Duo administrator with the Owner (or equivalent) role - required to create applications and grant Admin API permissions.
- Access to the Duo Admin Panel for your tenant
(
https://admin.duosecurity.com).
Required Permissions
| Service | Object/Endpoint | Permission or Scope | Notes |
|---|---|---|---|
| Authentication logs | GET /admin/v2/logs/authentication | Grant read log | Read-only. The only permission dfe-fetcher needs. |
| Health probe | GET /admin/v1/check | (covered by the same application keys) | Cheap auth check; no extra permission. |
Grant only "Grant read log". Leave all other Admin API permission toggles unchecked (least privilege).
Source-Side Setup
-
Sign in to the Duo Admin Panel at
https://admin.duosecurity.comas an administrator with rights to create applications. -
Start a new Admin API application. Either path works depending on your panel version:
- Newer panel: Applications > Application Catalog, find Admin API, click + Add.
- Older panel: Applications > Protect an Application, search for
Admin API, then click Protect next to it.
-
Grant the log-read permission. On the new application's page, in the Permissions section, tick Grant read log. Untick everything else. Click Save Changes.
-
Copy the credentials. In the application's Details section, copy:
- Integration key (the
ikey) - Secret key (the
skey) - treat as a password; shown here so capture it now - API hostname (the
api_host, formatapi-XXXXXXXX.duosecurity.com)
- Integration key (the
-
(Optional) Name the application something recognisable, e.g.
dfe-fetcher-authlogs, and Save Changes again.
dfe-fetcher Configuration
The Duo source config fields are: enabled, api_host, integration_key
(ikey), secret_key (skey), credential_secret, signature_version,
api_url_override (test mock only), services, topic. The only service is
authentication_logs.
Config File
sources:
duo:
enabled: true
api_host: "api-XXXXXXXX.duosecurity.com" # API hostname from Admin Panel
integration_key: "your-ikey"
secret_key: "your-skey"
services:
- name: authentication_logs
# config:
# limit: 100 # per-page size, max 1000
topic: "duo"
# filter: 'result == "FRAUD"' # hot-reloaded CEL expression
Environment Variables
DFE_FETCHER_SOURCES__DUO__ENABLED="true"
DFE_FETCHER_SOURCES__DUO__API_HOST="api-XXXXXXXX.duosecurity.com"
DFE_FETCHER_SOURCES__DUO__INTEGRATION_KEY="your-ikey"
DFE_FETCHER_SOURCES__DUO__SECRET_KEY="your-skey"
Secrets Manager (Production)
Keep the secret key out of the config file with credential_secret:
sources:
duo:
enabled: true
api_host: "api-XXXXXXXX.duosecurity.com"
integration_key: "your-ikey"
credential_secret: "vault:kv/data/dfe/duo:skey"
services:
- name: authentication_logs
topic: "duo"
credential_secret resolves to the secret key (skey). When set, it takes
precedence over an inline secret_key. The ikey and api_host are not secret
and can stay inline.
An Older Tenant
Leave signature_version unset and the fetcher signs the version Duo
documents. A tenant whose endpoints refuse it verifies the legacy scheme
instead, which is selected per type or per connection:
sources:
duo:
enabled: true
api_host: "api-XXXXXXXX.duosecurity.com"
integration_key: "your-ikey"
credential_secret: "vault:kv/data/dfe/duo:skey"
signature_version: v2
services:
- name: authentication_logs
topic: "duo"
A signature the tenant does not verify comes back as a 401 whose message
names the credential, and the tick fails without advancing the fetch window.
A connection on v2 warns at startup, because SHA-1 signing is an exemption
from the platform's crypto baseline taken for this tenant alone.
Verification
Health check. The profile's probe signs and calls GET /admin/v1/check,
which returns {"response": "valid", "stat": "OK"} for working credentials;
a stat other than OK is a health error. A healthy result confirms the
ikey, skey, and api_host are correct and the application is active.
Live smoke tests. crates/fetcher/tests/e2e/smoke_remote.rs has
env-gated, #[ignore]d tests that hit a real tenant. They read these
variables (canonical .env-cloud, fallback .env):
DUO_API_HOSTDUO_INTEGRATION_KEYDUO_SECRET_KEY
Run them:
cargo test -p dfe-fetcher --test e2e duo_ -- --ignored
Results are tagged duo.authentication_logs. Zero records is normal on a
quiet tenant, a short lookback, or if all events fall inside the two-minute
delay window.
Common failures.
40103 Invalid signature/stat != OK- the skey is wrong, clock skew between the host and Duo broke the signedDateheader, or the tenant verifies the other signature version. Verify the skey, check NTP is in sync, and trysignature_version: v2on an older tenant.40101 Missing request credentials- the ikey or api_host is wrong.40301 Access denied- the Admin API application lacks Grant read log; re-check the Permissions section and save.- Health passes but zero records every cycle - widen the window or confirm authentications exist; remember events under two minutes old are withheld.
Cost
dfe-fetcher only reads. The Admin API and authentication-log reads add no charge of their own, but access may require a Duo plan/edition that includes Admin API access. Confirm any cost implications against your own Duo plan.
References
- Cisco Duo - Admin API (application creation, signing, permissions): https://duo.com/docs/adminapi
- Cisco Duo - Admin API v2 authentication logs
(
/admin/v2/logs/authentication, mintime/maxtime in ms, next_offset): https://duo.com/docs/adminapi#authentication-logs - Cisco Duo - Protecting applications (Application Catalog / Protect an Application): https://duo.com/docs/protecting-applications