Duo Admin API Integration

Configure HTTP Pull to retrieve audit logs from all Duo Security Admin API endpoints including authentication, administrator, telephony, activity, and offline enrollment logs.

Overview

Duo Security’s Admin API provides access to comprehensive audit logs across five different log types. This integration allows you to retrieve authentication events, administrator actions, telephony logs, user activity, and offline enrollment data for security monitoring and compliance.

The Duo Admin API uses HMAC-SHA1 signature authentication and supports both v1 and v2 API versions depending on the log type.

Endpoints

Endpoint Description API Version
Authentication Logs User authentication attempts and results v2
Administrator Logs Administrative actions and config changes v1
Telephony Logs Phone-based authentication events v2
Activity Logs User activity and application usage v2
Offline Enrollment Offline device enrollment events v1

Authentication Method

Duo uses HMAC-SHA1 signature authentication with the following components:

  • Integration Key: Your Duo application integration key
  • Secret Key: Your Duo application secret key
  • API Hostname: Your Duo API hostname (e.g., api-xxxxxxxx.duosecurity.com)

The HMAC signature is calculated using the HTTP method, hostname, path, parameters, and current timestamp.

Environment Variables

Set these environment variables for secure credential management:

# Duo Integration Key (from Admin Panel > Applications)
export DUO_INTEGRATION_KEY="DIXXXXXXXXXXXXXXXXXXXX"

# Duo Secret Key (from Admin Panel > Applications)
export DUO_SECRET_KEY="your_secret_key_here"

# Duo API Hostname (from Admin Panel > Applications)
export DUO_API_HOSTNAME="api-xxxxxxxx.duosecurity.com"

Configuration Examples

This section provides configurations for all five Duo Admin API log types with optimized pull intervals:

Authentication Logs (v2)

Monitor user authentication attempts and outcomes:

nodes:
- name: duo_authentication_logs
  type: http_pull_input
  endpoint: https://YOUR_API_HOSTNAME/admin/v2/logs/authentication
  method: GET
  pull_interval: 5m

  header_expressions:
    - header: "Date"
      value_expression: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
    - header: "Accept"
      value_expression: "application/json"
    - header: "Authorization"
      value_expression: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_INTEGRATION_KEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOSTNAME", ""), "/admin/v2/logs/authentication", Concat(["limit=1000&maxtime=", String(Int(UnixSeconds(Now())) * 1000), "&mintime=", String(Int(UnixSeconds(Now()) - 360) * 1000), "&sort=ts%3Adesc"], "")]), EDXEnv("DUO_SECRET_KEY", ""), "sha1", "hex")], ""), "base64", false)], "")

  parameter_expressions:
    - name: "limit"
      value_expression: "1000"
    - name: "sort"
      value_expression: "ts:desc"
    - name: "mintime"
      value_expression: String(Int(UnixSeconds(Now()) - 360) * 1000)
    - name: "maxtime"
      value_expression: String(Int(UnixSeconds(Now())) * 1000)

Administrator Logs (v1)

Track administrative actions and configuration changes:

nodes:
- name: duo_administrator_logs
  type: http_pull_input
  endpoint: https://YOUR_API_HOSTNAME/admin/v1/logs/administrator
  method: GET
  pull_interval: 6m

  header_expressions:
    - header: "Date"
      value_expression: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
    - header: "Accept"
      value_expression: "application/json"
    - header: "Authorization"
      value_expression: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_INTEGRATION_KEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOSTNAME", ""), "/admin/v1/logs/administrator", Concat(["mintime=", String(Int(UnixSeconds(Now()) - 420))], "")]), EDXEnv("DUO_SECRET_KEY", ""), "sha1", "hex")], ""), "base64", false)], "")

  parameter_expressions:
    - name: "mintime"
      value_expression: String(Int(UnixSeconds(Now()) - 420))

Telephony Logs (v2)

Monitor phone-based authentication events:

nodes:
- name: duo_telephony_logs
  type: http_pull_input
  endpoint: https://YOUR_API_HOSTNAME/admin/v2/logs/telephony
  method: GET
  pull_interval: 7m

  header_expressions:
    - header: "Date"
      value_expression: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
    - header: "Accept"
      value_expression: "application/json"
    - header: "Authorization"
      value_expression: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_INTEGRATION_KEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOSTNAME", ""), "/admin/v2/logs/telephony", Concat(["limit=1000&maxtime=", String(Int(UnixSeconds(Now())) * 1000), "&mintime=", String(Int(UnixSeconds(Now()) - 480) * 1000), "&sort=ts%3Adesc"], "")]), EDXEnv("DUO_SECRET_KEY", ""), "sha1", "hex")], ""), "base64", false)], "")

  parameter_expressions:
    - name: "limit"
      value_expression: "1000"
    - name: "sort"
      value_expression: "ts:desc"
    - name: "mintime"
      value_expression: String(Int(UnixSeconds(Now()) - 480) * 1000)
    - name: "maxtime"
      value_expression: String(Int(UnixSeconds(Now())) * 1000)

Activity Logs (v2)

Track user activity and application usage:

nodes:
- name: duo_activity_logs
  type: http_pull_input
  endpoint: https://YOUR_API_HOSTNAME/admin/v2/logs/activity
  method: GET
  pull_interval: 8m

  header_expressions:
    - header: "Date"
      value_expression: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
    - header: "Accept"
      value_expression: "application/json"
    - header: "Authorization"
      value_expression: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_INTEGRATION_KEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOSTNAME", ""), "/admin/v2/logs/activity", Concat(["limit=1000&maxtime=", String(Int(UnixSeconds(Now())) * 1000), "&mintime=", String(Int(UnixSeconds(Now()) - 540) * 1000), "&sort=ts%3Adesc"], "")]), EDXEnv("DUO_SECRET_KEY", ""), "sha1", "hex")], ""), "base64", false)], "")

  parameter_expressions:
    - name: "limit"
      value_expression: "1000"
    - name: "sort"
      value_expression: "ts:desc"
    - name: "mintime"
      value_expression: String(Int(UnixSeconds(Now()) - 540) * 1000)
    - name: "maxtime"
      value_expression: String(Int(UnixSeconds(Now())) * 1000)

Offline Enrollment Logs (v1)

Monitor offline device enrollment activities:

nodes:
- name: duo_offline_enrollment_logs
  type: http_pull_input
  endpoint: https://YOUR_API_HOSTNAME/admin/v1/logs/offline_enrollment
  method: GET
  pull_interval: 10m

  header_expressions:
    - header: "Date"
      value_expression: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
    - header: "Accept"
      value_expression: "application/json"
    - header: "Authorization"
      value_expression: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_INTEGRATION_KEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOSTNAME", ""), "/admin/v1/logs/offline_enrollment", Concat(["mintime=", String(Int(UnixSeconds(Now()) - 720))], "")]), EDXEnv("DUO_SECRET_KEY", ""), "sha1", "hex")], ""), "base64", false)], "")

  parameter_expressions:
    - name: "mintime"
      value_expression: String(Int(UnixSeconds(Now()) - 720))

API Endpoints Reference

Log Type API Version Endpoint Description
Authentication v2 /admin/v2/logs/authentication User authentication attempts and results
Administrator v1 /admin/v1/logs/administrator Administrative actions and config changes
Telephony v2 /admin/v2/logs/telephony Phone-based authentication events
Activity v2 /admin/v2/logs/activity User activity and application usage
Offline Enrollment v1 /admin/v1/logs/offline_enrollment Offline device enrollment events

Time Windows and Pull Intervals

Each endpoint uses optimized time windows and pull intervals to prevent data gaps and API rate limiting:

Endpoint Time Window Pull Interval Rationale
Authentication 6 minutes 5 minutes High-frequency events, needs overlap
Administrator 7 minutes 6 minutes Moderate frequency admin actions
Telephony 8 minutes 7 minutes Phone auth events, less frequent
Activity 9 minutes 8 minutes User activity tracking
Offline Enrollment 12 minutes 10 minutes Infrequent enrollment events

HMAC-SHA1 Signature Details

The Duo Admin API requires HMAC-SHA1 signatures calculated from:

  1. Canonical Request: DATE\nMETHOD\nHOST\nPATH\nPARAMETERS
  2. HMAC Calculation: HMAC-SHA1 of canonical request using secret key
  3. Authorization Header: Basic base64(integration_key:hmac_signature)

The signature is automatically calculated by the Edge Delta OTTL expressions using:

  • FormatTime() for RFC 2822 date format
  • Format() for canonical request string assembly
  • EDXHmac() for HMAC-SHA1 calculation
  • EDXEncode() for Base64 encoding

Advanced OTTL Expression Breakdown

Here’s the authentication endpoint OTTL expression broken down for understanding:

# This is for educational purposes - remember to write as single line in actual config
header_expressions:
  - header: "Authorization"
    value_expression: >
      Concat([
        "Basic ",
        EDXEncode(
          Concat([
            EDXEnv("DUO_INTEGRATION_KEY", ""),
            ":",
            EDXHmac(
              Format("%s\n%s\n%s\n%s\n%s", [
                FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"),  # 1. Current date in RFC 2822 format
                "GET",                                                   # 2. HTTP method
                EDXEnv("DUO_API_HOSTNAME", ""),                        # 3. API hostname
                "/admin/v2/logs/authentication",                       # 4. API endpoint path
                Concat([                                               # 5. Query parameters string
                  "limit=1000&maxtime=",
                  String(Int(UnixSeconds(Now())) * 1000),              # Current time in milliseconds
                  "&mintime=",
                  String(Int(UnixSeconds(Now()) - 360) * 1000),        # 6 minutes ago in milliseconds
                  "&sort=ts%3Adesc"                                    # URL-encoded sort parameter
                ], "")
              ]),
              EDXEnv("DUO_SECRET_KEY", ""),                           # Secret key for HMAC
              "sha1",                                                  # HMAC algorithm
              "hex"                                                    # Output format
            )
          ], ""),
          "base64",                                                   # Base64 encode the integration_key:signature
          false                                                       # Don't URL encode the result
        )
      ], "")      

Step-by-Step Process:

  1. Date Generation: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700") creates RFC 2822 timestamp
  2. Parameter Assembly: Query parameters are built with current time windows (6-minute lookback)
  3. Canonical Request: All components are formatted into Duo’s required string format:
    DATE
    METHOD
    HOSTNAME
    PATH
    PARAMETERS
    
  4. HMAC Calculation: EDXHmac() computes SHA1 hash using the secret key
  5. Credential Assembly: Integration key and HMAC signature are combined with :
  6. Base64 Encoding: Final credentials are base64 encoded for HTTP Basic Auth
  7. Header Formation: Result is prefixed with “Basic " for Authorization header

Time Window Logic:

  • UnixSeconds(Now()) * 1000: Current time in milliseconds (maxtime)
  • UnixSeconds(Now()) - 360) * 1000: 6 minutes ago in milliseconds (mintime)
  • This creates overlapping windows to ensure no log entries are missed between pulls

API Documentation

For detailed API documentation, visit:

Rate Limiting

Duo Admin API implements rate limiting:

  • Authentication: 120 requests per minute
  • Administrator: 60 requests per minute
  • Telephony: 60 requests per minute
  • Activity: 60 requests per minute
  • Offline Enrollment: 60 requests per minute

The configured pull intervals respect these limits while ensuring continuous data collection.

Troubleshooting

401 Unauthorized:

  • Verify DUO_INTEGRATION_KEY and DUO_SECRET_KEY environment variables
  • Check that the Admin API application has “Grant read information” permission
  • Ensure API hostname matches your Duo account

400 Bad Request:

  • Check time parameters are valid Unix timestamps
  • Verify endpoint URLs match your API version requirements
  • Ensure signature calculation includes all required components

403 Forbidden:

  • Verify the Admin API application has appropriate permissions
  • Check if IP restrictions are configured in Duo Admin Panel

Rate Limiting (429):

  • The configured intervals should prevent rate limiting
  • If needed, increase pull_interval values
  • Monitor Duo Admin Panel for API usage statistics

Security Best Practices

  1. Credential Management: Store integration and secret keys securely as environment variables
  2. Network Security: Use HTTPS-only connections (enforced by Duo)
  3. Access Control: Limit Admin API application permissions to required log types only
  4. Monitoring: Set up alerts for authentication failures and admin actions
  5. Key Rotation: Regularly rotate integration and secret keys per security policy

Sample Log Data

Authentication Log Sample

{
  "access_device": {
    "browser": "Chrome",
    "os": "Windows"
  },
  "application": {
    "key": "DIXXXXXXXXXXXXXXXXXXXX",
    "name": "Company VPN"
  },
  "auth_device": {
    "name": "iPhone"
  },
  "result": "SUCCESS",
  "timestamp": 1640995200,
  "username": "john.doe@company.com"
}

Administrator Log Sample

{
  "action": "user_create",
  "description": "Created user jane.smith@company.com",
  "timestamp": 1640995200,
  "username": "admin@company.com",
  "object": "User"
}