Skip to content

Blocklists ​

A security.blocklist plugin fetches lists of addresses to deny from places that publish them: a threat intelligence feed, a reputation service, a ban list shared between servers. A person configures sources; Nginx UI calls the plugin on each source's refresh interval, writes the answer as nginx deny rules into a file of its own and reloads nginx when the list changed.

MethodRequiredPurpose
blocklist.fetchyesFetch the complete current list of one source.

The plugin must request the network permission.

Declaring Source Kinds ​

json
"capabilities": ["security.blocklist"],
"permissions": ["network"],
"blocklist": {
  "sources": [
    {
      "code": "threatfeed",
      "name": "ThreatFeed",
      "refresh_seconds": 900,
      "configuration": {
        "fields": [
          { "key": "api_key", "display_name": "API key", "required": true, "secret": true },
          { "key": "min_score", "type": "number", "display_name": "Minimum score", "help_text": "Only list addresses scored at least this high" }
        ]
      }
    }
  ]
}

code, name and configuration.fields work as for notification channels. refresh_seconds is the default refresh interval of a source of this kind: 0 or absent means one hour, otherwise at least 60, since a list that changes faster cannot be served by reloading nginx.

Fetching ​

json
{ "jsonrpc": "2.0", "id": 48, "method": "blocklist.fetch", "params": { "source": "threatfeed", "config": { "api_key": "key_live_xxx", "min_score": "80" } } }

Answer with the complete current list, not the changes since the last call:

json
{
  "jsonrpc": "2.0", "id": 48,
  "result": {
    "entries": [
      { "cidr": "198.51.100.23", "reason": "SSH brute force" },
      { "cidr": "203.0.113.0/24", "reason": "Botnet command and control" },
      { "cidr": "2001:db8:bad::/48" }
    ],
    "ttl_seconds": 900
  }
}
FieldMeaning
entries[].cidrAn IPv4 or IPv6 address or a CIDR network. No port, zone or host name.
entries[].reasonWhy the source lists it, for display. Optional.
ttl_secondsHow long the list stays fresh. 0 leaves it to the refresh interval.

Send every address once, and never an entry that covers every address (0.0.0.0/0, ::/0).

DANGER

When the list cannot be fetched, answer an error, never an empty list. An empty list tells Nginx UI to deny nothing; an error keeps the current list in place. Use -32003 with data.field for causes in the configuration, including an empty required field, and -32000 otherwise. Never put a config value into a log, an error or a reason.

What Nginx UI Does With the Answer ​

  • It parses every entry as an address or network, drops the rest and entries covering every address, and reports how many it dropped. No other text of the answer, reason included, reaches the configuration. Entries are normalized, deduplicated, sorted and capped at 100000.

  • It writes the list of each source to <nginx conf dir>/blocklists/<source id>.conf:

    nginx
    # generated by nginx-ui
    deny 198.51.100.23;
    deny 203.0.113.0/24;
    deny 2001:db8:bad::/48;
  • It reloads nginx only when the file changed, tests the configuration first and restores the previous file when the test fails. A failed fetch keeps the current file.

  • It never adds the file to a server itself. It shows the person the line to add where the list should apply: include blocklists/<source id>.conf;.

Nginx UI fetches again after the source's interval, earlier when the last answer had a shorter ttl_seconds, but never more than once a minute. A failed fetch is retried after 5 minutes or the interval, whichever is shorter. The outcome of the last fetch is shown next to the source. Nginx UI waits 60 seconds for blocklist.fetch.

Released under the AGPL-3.0 License. (350eea3f)