Skip to content
Configuration

Configuration

DRL uses KDL configuration files. Configuration is applied in three layers (highest wins):

  1. Environment variables (DRL_* prefix)
  2. KDL configuration file (path passed via --config)
  3. Built-in defaults (embedded in the binary)

Full example

// DRL configuration file

listen {
    grpc    ":8081"
    metrics ":9091"
}

membership {
    service-name    "drl"
    port            7946
    bind-addr       "0.0.0.0"
    startup-delay   "3s"
    gossip-interval "50ms"
    gossip-nodes    5
    // Optional AES encryption (16, 24, or 32 byte keys)
    secret-keys "primary-key-16b" "old-key-16-bytes"
}

logging {
    level  "info"    // debug | info | warn | error
    format "json"    // json | text
}

internal-api {
    enabled true
    address ":8082"
}

cache {
    blocklist-size-mb             64
    accounting-size-mb           128
    sync-timeout-seconds          30
    blocklist-default-ttl-seconds 300
}

accounting {
    settings {
        algorithm        "sliding-window"
        retry-after-type "delay-seconds"
        flush-interval   "200ms"
        max-batch-size   1000

        // Optional: extract the real client IP from the X-Forwarded-For header.
        // use-x-forwarded-for           true
        // use-x-forwarded-for-direction "right"  // left | right
        // use-x-forwarded-for-index     1
    }

    rules {
        payments-api {
            path-prefix "/api/v1/payments"
            headers     "X-API-Key" "X-Tenant-ID"
            redactions {
                "X-API-Key"   "^(.{0,3}).*$"
                "X-Tenant-ID" "^(.{0,4}).*$"
            }
            limit        500
            per          "minute"
        }
        users-api {
            path-prefix "/api/v1/users"
            limit        2000
            per          "minute"
        }
    }
}

Reference

listen

Controls the addresses DRL binds its public servers to.

KDL keyEnv varDefaultDescription
grpcDRL_LISTEN_GRPC:8081gRPC server address (Envoy rate-limit service)
metricsDRL_LISTEN_METRICS:9091Prometheus metrics HTTP endpoint

membership

Controls cluster formation and gossip.

KDL keyEnv varDefaultDescription
service-nameDRL_MEMBERSHIP_SERVICE_NAMEdrlDNS name resolved to discover peers
portDRL_MEMBERSHIP_PORT7946Memberlist gossip UDP/TCP port
bind-addrDRL_MEMBERSHIP_BIND_ADDR0.0.0.0Address to bind the Memberlist listener
startup-delayDRL_MEMBERSHIP_STARTUP_DELAY3sDelay before joining the cluster (allows DNS to propagate)
gossip-intervalDRL_MEMBERSHIP_GOSSIP_INTERVAL50msInterval between gossip rounds
gossip-nodesDRL_MEMBERSHIP_GOSSIP_NODES5Number of peers contacted per gossip round
secret-keysSee note belowAES encryption keys (16, 24, or 32 bytes)

Encryption key environment variables (special handling — override the full secret-keys list):

Env varDescription
DRL_MEMBERSHIP_PRIMARY_KEYPrimary encryption key (replaces all KDL-configured keys)
DRL_MEMBERSHIP_SECONDARY_KEYSComma-separated secondary keys accepted for decryption only

All keys must be the same length and a valid AES size (16, 24, or 32 bytes). The first key is used for encryption; additional keys are decryption-only (key rotation support).

Validation rules:

  • service-name must not be empty
  • port must be 1–65535
  • bind-addr must not be empty

logging

KDL keyEnv varDefaultDescription
levelDRL_LOGGING_LEVELinfoLog level: debug, info, warn, error
formatDRL_LOGGING_FORMATjsonLog format: json, text

internal-api

KDL keyEnv varDefaultDescription
enabledDRL_INTERNAL_API_ENABLEDtrueEnable the internal HTTP management API
addressDRL_INTERNAL_API_ADDRESS:8082Bind address for the internal API

The internal API requires an API key set via the DRL_PRIVATE_API_KEY environment variable (minimum 16 characters). This variable has no KDL equivalent — it is always sourced from the environment.

Env varDescription
DRL_PRIVATE_API_KEYAPI authentication key (required, min 16 chars)
DRL_NODE_NAMEOverride the node name (defaults to the system hostname)

cache

KDL keyEnv varDefaultDescription
blocklist-size-mbDRL_CACHE_BLOCKLIST_SIZE_MB64Maximum RAM (MB) for the blocklist cache
accounting-size-mbDRL_CACHE_ACCOUNTING_SIZE_MB128Maximum RAM (MB) for the accounting cache
sync-timeout-secondsDRL_CACHE_SYNC_TIMEOUT_SECONDS30Max wait (s) for initial Memberlist state sync
blocklist-default-ttl-secondsDRL_CACHE_BLOCKLIST_DEFAULT_TTL_SECONDS300Default TTL (s) for manual admin-API blocks

Validation rules:

  • blocklist-size-mb must be ≥ 1
  • accounting-size-mb must be ≥ 1
  • sync-timeout-seconds must be ≥ 1
  • blocklist-default-ttl-seconds must be ≥ 1

accounting.settings

Global settings for the accounting engine. Each field can be overridden via its corresponding DRL_ACCOUNTING_SETTINGS_* environment variable.

KDL keyEnv varDefaultDescription
algorithmDRL_ACCOUNTING_SETTINGS_ALGORITHMsliding-windowRate-limiting algorithm: sliding-window or token-bucket
retry-after-typeDRL_ACCOUNTING_SETTINGS_RETRY_AFTER_TYPEdelay-secondsFormat of the Retry-After response header: delay-seconds or http-date
flush-intervalDRL_ACCOUNTING_SETTINGS_FLUSH_INTERVAL200msHow often the Flusher drains per-owner buffers
max-batch-sizeDRL_ACCOUNTING_SETTINGS_MAX_BATCH_SIZE1000Maximum entries per batch; triggers an immediate flush when reached
capacityDRL_ACCOUNTING_SETTINGS_CAPACITYToken-bucket burst size (required when algorithm is token-bucket)
refill-rateDRL_ACCOUNTING_SETTINGS_REFILL_RATEToken-bucket refill rate in tokens per second (required when algorithm is token-bucket)
use-x-forwarded-forDRL_ACCOUNTING_SETTINGS_USE_X_FORWARDED_FORfalseExtract the client IP from the X-Forwarded-For header instead of the socket remote address. See X-Forwarded-For below.
use-x-forwarded-for-directionDRL_ACCOUNTING_SETTINGS_USE_X_FORWARDED_FOR_DIRECTIONleftWhich end of the XFF chain to read from: left (client end) or right (proxy end). Only evaluated when use-x-forwarded-for is true.
use-x-forwarded-for-indexDRL_ACCOUNTING_SETTINGS_USE_X_FORWARDED_FOR_INDEX0Zero-based offset from the chosen direction. Only evaluated when use-x-forwarded-for is true.

Validation rules (when use-x-forwarded-for is true):

  • use-x-forwarded-for-direction must be left or right (case-insensitive); defaults to left if omitted
  • use-x-forwarded-for-index must be ≥ 0

X-Forwarded-For (XFF) IP extraction

When DRL runs behind one or more reverse proxies, the Envoy socket remote address is the proxy IP rather than the originating client. Enabling use-x-forwarded-for tells the accounting engine to parse the X-Forwarded-For header and use a specific entry as the effective source IP for rate-limit accounting and entity key construction.

How the header is structured

Each proxy in the chain appends the address it received the connection from, left to right:

X-Forwarded-For: <client>, <proxy1>, <proxy2>

Index 0 from the left is the original client IP (cheapest to obtain, but trivially spoofable by the client). Index 0 from the right is the last proxy.

Recipe 1 — Single trusted proxy, simple setup

Use the leftmost entry when there is exactly one proxy and you accept that a malicious client could forge the header value:

accounting {
    settings {
        use-x-forwarded-for           true
        use-x-forwarded-for-direction "left"
        use-x-forwarded-for-index     0
    }
}

Recipe 2 — Single trusted proxy, anti-spoofing

Use direction "right" with index 1 to read the IP that your outermost controlled proxy recorded as the upstream address. This entry is appended by your proxy — the client cannot forge it:

X-Forwarded-For: <client-or-spoofed>, <trusted-proxy-appended-this>
                                       └── right index 1 reads here
accounting {
    settings {
        use-x-forwarded-for           true
        use-x-forwarded-for-direction "right"
        use-x-forwarded-for-index     1
    }
}

Fallback behaviour

When use-x-forwarded-for is true but the header is absent, empty, or the computed index is out of range, DRL falls back silently to the socket remote address without logging a warning.


accounting.rules

Rate-limiting rules are defined as named children under the rules node. Each rule matches a URI path prefix and optionally a set of headers.

accounting {
    rules {
        <rule-name> {
            path-prefix "/api/v1/..."
            headers     "Header-Name-1" "Header-Name-2"
            redactions {
                "Header-Name-1" "^(.{0,3}).*$"
            }
            limit        1000
            per          "minute"
        }
    }
}
FieldRequiredDescription
path-prefixYesURI path prefix to match. Matched using longest-prefix (radix tree).
headersNoOne or more header names whose values are included in the entity key
redactionsNoMap of header name → regex pattern. Applied when the internal API lists blocked entities. See Header Redactions below.
limitYesRequest count threshold before the entity is blocked
perYesWindow unit: second or minute

DRL evaluates rules in definition order and applies the first matching rule to an entity. Entities that match no rule are passed through without accounting.


Header Redactions

The redactions block controls how sensitive header values are displayed when listing blocked entities via the internal API (GET /v1/blocked-entity). It does not affect rate-limit accounting — headers are still used in full for entity key construction.

Each entry is a "Header-Name" "regex" pair. The regex must contain exactly one capturing group (...). When a blocked entity is listed:

  • The portion matched by the first capturing group is kept verbatim.
  • Every other character in the value is replaced with *.
  • If the regex does not match, the original value is returned unchanged.

The capture group (...) marks exactly the portion of the value to keep visible. Everything before and after it is replaced with *. The table below shows the four supported patterns:

PatternExample inputOutputNotes
^(.{0,3}).*$sk_live_abc123sk_************Keep first N chars
^(Bearer .{0,3}).*$Bearer tok_abc123Bearer to**********Keep scheme + first N token chars
^glpat(.{0,7}).*$glpat_abcdefgh1234*****_abcdef******Keep middle segment (group after a fixed prefix)
^(\\w+).*$tenant.example.comtenant**********Keep up to the first non-word character

Examples

Keep only the first 3 characters of an API key:

redactions {
    "X-Api-Key" "^(.{0,3}).*$"
}

sk_live_abc123xyzsk_**************


Preserve the Bearer scheme word and the first 3 token characters:

redactions {
    "Authorization" "^(Bearer .{0,3}).*$"
}

Bearer tok_abc123Bearer to**********


Expose only the middle segment of a structured token (group after a known prefix):

The capture group sits in the middle; characters before it are also masked.

redactions {
    "Private-Token" "^glpat(.{0,7}).*$"
}

glpat_abcdefgh1234*****_abcdef******


Keep only the first hostname label from a multi-part Host header:

redactions {
    "Host" "^(\\w+).*$"
}

tenant.example.comtenant************


Full rule example with redactions:

accounting {
    rules {
        payments-api {
            path-prefix "/api/v1/payments"
            headers     "X-Api-Key" "X-Tenant-ID"
            redactions {
                "X-Api-Key"   "^(.{0,3}).*$"
                "X-Tenant-ID" "^(.{0,4}).*$"
            }
            limit 500
            per   "minute"
        }
    }
}

Validation rules:

  • Each regex value must be a valid Go regular expression (checked at startup).
  • At least one capturing group is required for masking to take effect; a pattern with no group leaves the value unmodified.

Docker Compose example

services:
  drl:
    image: drl:latest
    environment:
      - DRL_PRIVATE_API_KEY=your-secure-api-key-minimum-16-chars
      - DRL_MEMBERSHIP_SERVICE_NAME=drl
      - DRL_LISTEN_GRPC=:8081
      - DRL_LISTEN_METRICS=:9091
      - DRL_LOGGING_LEVEL=info
      - DRL_LOGGING_FORMAT=json
    volumes:
      - ./config.kdl:/etc/drl/config.kdl:ro
    command: ["./drl", "--config", "/etc/drl/config.kdl"]