Limen.Config (limen v0.2.1)

Copy Markdown View Source

Instance configuration.

Every Limen instance is configured on its own. Options come from your application's environment when started with {Limen, otp_app: app} (see from_app/1), or are given directly with {Limen, name: name, config: opts}. They are validated once, when the instance starts, and published with it, so the request path never reads the application environment.

config :my_app, Limen,
  mode: :enforce,
  ipv6_prefix: 56

Options

  • :secret_key - at least 32 random bytes, used to sign challenge tokens and pass cookies. Every node serving the same site needs the same key. Without one, a random key is generated at startup and a warning is logged. Set it from config/runtime.exs, never in compiled config.

  • :previous_secret_keys - earlier secret keys, still accepted when verifying tokens, for rotating the secret.

  • :mode - :dry_run (default) or :enforce. In dry-run mode every decision is computed, recorded and emitted exactly as in enforce mode, but the request always continues. Routes and policies can override it.

  • :trusted_proxies - CIDR ranges of the reverse proxies and TLS terminators in front of the application. Forwarding headers and the JA4 header are only read from these peers. Defaults to [].

  • :client_ip_header - how trusted proxies report the client address: "x-forwarded-for", "x-real-ip", "forwarded" (RFC 7239) or nil (default) to always use the peer address. Forwarding chains are walked from the right, skipping trusted proxies, so clients cannot spoof their address by sending the header themselves.

  • :ja4_header - request header the TLS terminator stores the JA4 fingerprint in. Defaults to "x-ja4".

  • :ipv4_prefix - prefix length IPv4 clients are aggregated to. Defaults to 32.

  • :ipv6_prefix - prefix length IPv6 clients are aggregated to. One of 48, 56 or 64 (default).

  • :decision_log - sampled structured decision log, see Limen.DecisionLog:

    • :sample_rate - fraction of :allow decisions logged. Defaults to 0.0.
    • :non_allow_sample_rate - fraction of other decisions logged. Defaults to 1.0.
    • :size - ring buffer capacity. Defaults to 1024.
    • :flush_interval - milliseconds between flushes to the sink. Defaults to 1000.
    • :sink - where flushed decisions go: a Limen.DecisionLog.Sink module, or {module, opts}. Defaults to Limen.DecisionLog.Logger, which logs them at its :level (:info by default).
    • :delivery - :batched (default): the flusher hands sampled decisions to the sink in batches, away from the request path. :inline, for tests only: each sampled decision goes to the sink at once, in the process that made it, so a sink writing through an Ecto sandbox sees the test's connection and a test can assert on what it wrote right after the request. A sink that raises raises in that process, failing the test. It puts the sink on the request path, so never use it in production. See "Decision log sinks" in the testing guide.
  • :asn - IP to ASN data, see Limen.Signal.Asn:

    • :file - an iptoasn.com ip2asn-combined.tsv file (optionally gzipped) loaded at startup. With :url, where downloads are kept. Defaults to nil.
    • :url - where to download the data from and check it for changes, such as "https://iptoasn.com/data/ip2asn-combined.tsv.gz". Needs a :file. Checks ask for data newer than the file, so when changing the :url to another source, remove the file or change :file too. Defaults to nil: no downloads.
    • :refresh - when to check the :url, see Limen.Signal.Asn.Schedule:
      • :every - milliseconds between checks, at least a minute. Defaults to a day.
      • :jitter - up to this share of :every is added to each wait at random, so nodes do not check together. Defaults to 0.1.
      • :window - a {from, to} pair of Times (UTC), or a list of them, outside which scheduled checks do not run; to before from runs over midnight. Defaults to nil: any time.
      • :max_utilization - postpone a scheduled check while the BEAM's schedulers are busier than this share, between 0 and 1. Defaults to 0.9; nil does not check.
      • :max_memory - postpone a scheduled check while the BEAM uses more than this many bytes. Defaults to nil: no limit.
      • :retry - milliseconds before trying a postponed check again, and a failed one, doubling with each failure up to :every. Defaults to ten minutes.
      • :timeout - milliseconds a download may take. Defaults to five minutes.
    • :hosting - additional ASNs classified as hosting providers, on top of the built-in list.
    • :source - the Limen.Signal.Asn.Source addresses are looked up in. Defaults to Limen.Signal.Asn, the iptoasn.com data of :file and :url, which must not be set with another source.
  • :fcrdns - crawler verification, see Limen.Signal.Fcrdns:

    • :crawlers - map of crawler names (as classified by Limen.Signal.UserAgent) to the DNS suffixes their hosts must have, merged over the built-in map.
    • :dns - module implementing Limen.Signal.Fcrdns.DNS. Defaults to Limen.Signal.Fcrdns.InetRes.
    • :verified_ttl, :failed_ttl, :error_ttl - seconds results are cached. Default to 86_400, 3_600 and 60.
    • :max_pending - addresses waiting for verification. Defaults to 1_000; beyond that, claims stay unverified until the queue drains.
    • :max_cache - cached results. Defaults to 100_000.
    • :interval - milliseconds between resolver runs. Defaults to 50.
    • :concurrency and :timeout - lookups in flight and milliseconds each may take. Default to 16 and 2_000.
  • :user_agents - tokens Limen.Signal.UserAgent classifies, on top of its built-in lists, by family: :crawler, :ai_crawler, :link_preview, :headless or :tool, each a list of tokens (named after themselves) or {token, name} pairs, and :ignore, a list of built-in tokens to leave out. Listing a built-in token under a family moves it there. Tokens are matched as they appear in user agents, case included. Defaults to [].

  • :shape - HTTP shape analysis, see Limen.Signal.HttpShape:

    • :ignore_headers - headers left out of the header-order shape, such as those added by your proxies. Forwarding headers and the JA4 header are always left out.
  • :challenge - the proof-of-work challenge, see Limen.Challenge:

    • :path - where Limen serves its challenge endpoints. Defaults to "/__limen".
    • :ttl - seconds a challenge stays valid. Defaults to 300.
    • :pass_ttl - seconds a pass cookie stays valid. Defaults to 3_600.
    • :bind - what pass cookies and socket tokens are bound to: any of :prefix, :ja4 and :user_agent. Defaults to all three. Leaving :prefix out keeps passes valid when a client changes networks, such as a phone moving from Wi-Fi to mobile data, at the cost of letting a pass be used from anywhere with the same JA4 and user agent. Changing it invalidates the passes already issued.
    • :cookie - the pass cookie name. Defaults to "_limen_pass".
    • :status - HTTP status of the challenge page. Defaults to 403.
    • :page - the module writing the challenge page, with texts in the visitor's language or markup of your own, see Limen.Challenge.Page. Defaults to Limen.Challenge.Page, in English.
    • :no_js - what clients without JavaScript get: {:meta_refresh, seconds} (default {:meta_refresh, 5}) lets them through after waiting, :deny shows a message asking to enable JavaScript.
    • :secure_cookie - true, false or :auto (default, secure over HTTPS).
    • :replay_capacity - solved challenges remembered per :ttl to reject replays. Defaults to 100_000.
  • :cluster - ban propagation across nodes, see Limen.Cluster:

    • :enabled - false (default) or true. Read at startup.
    • :scope - the :pg scope the instance starts and uses, which must be unique among the instances of a node. Defaults to one named after the instance, so instances of the same name on different nodes share their bans.
    • :interval - milliseconds between broadcasts. Defaults to 100.
    • :max_outbox - bans waiting to be broadcast. Defaults to 10_000.
  • :trap - honeypots, see Limen.Trap:

    • :paths - trap path prefixes, such as ["/archive/directory"]. A request under one of them is a confession: its prefix is flagged and sent to the maze. Defaults to [] (no link traps).
    • :ban - seconds a prefix that fell into a trap stays flagged. Defaults to 86_400.
    • :form_field - name of the decoy field of Limen.Trap.form_fields/2. Defaults to "website".
    • :min_fill_time - milliseconds a person needs at least to fill in a form; faster submissions are confessions. Defaults to 3_000.
  • :maze - the slow, endless pages trapped clients get, see Limen.Maze:

    • :corpus - text files the maze's language model also learns from, so its pages read like your site. Defaults to [].
    • :bundled_corpus - whether to learn from Limen's own neutral text too. Defaults to true; at least one corpus is needed.
    • :drift - nil (default), :daily or :weekly: how often every page changes. Without drift a page never changes.
    • :max_concurrent - requests held in the maze at once; beyond that, they get an immediate 429. Defaults to 200.
    • :admit - {module, function, args}, called with args before a client is held; unless it returns true, the client gets an immediate 429, as when the maze is full. For refusing the maze while the application is under load; it runs on the request path, so it must be cheap and must not call a process. Defaults to nil: every client is admitted up to :max_concurrent.
    • :max_duration - milliseconds a maze response may take. Defaults to 60_000.
    • :delay - {min, max} milliseconds between chunks. Defaults to {1_000, 5_000}.
    • :chunk - {min, max} bytes per chunk. Defaults to {64, 512}.
    • :paragraphs - {min, max} paragraphs per page. Defaults to {4, 10}.
    • :links - {min, max} links in a page's navigation and related lists. Defaults to {4, 10}.
  • :tarpit - limits on tarpitted requests, see Limen.Tarpit:

    • :max_concurrent - requests held at once. Defaults to 1_000.
    • :max_delay - longest delay in milliseconds. Defaults to 30_000.
  • :params - values of policy parameters, overriding the defaults policies declare, as a keyword list; see "Parameters" in Limen.Policy. Defaults to [].

  • :lists - named lists loaded at startup, see Limen.Lists: a list of values, {:cidr, ranges} or {:substrings, strings} per name.

  • :state - sizing of the shared state, see Limen.State:

    • :max_keys - exact keys per time-window epoch slot. Each window keeps up to three slots. Defaults to 50_000.
    • :sketch_width and :sketch_depth - dimensions of the Count-Min Sketch counting keys beyond :max_keys, one per slot. Defaults to 8_192 and 4 (256 KiB per slot).
    • :gcra_max_keys - keys tracked by hard limits. Defaults to 100_000.
    • :max_bans - concurrent bans. Defaults to 100_000.
    • :sweep_interval - milliseconds between sweeps of expired bans and idle limits. Defaults to 5_000.
    • :path_filter_capacity - (prefix, path) pairs remembered per minute to count distinct paths per client. Defaults to 1_000_000 (about 1 MiB per generation).
    • :hll_precision - precision of the HyperLogLog counting distinct client prefixes. Defaults to 12 (4 KiB, 1.6% error).

    Sketch dimensions are read once at startup.

Changing options at runtime

Limen.update_config/3 changes an option of a running instance. Options given as keyword lists are merged into their current values, as if they had been given together at startup, so changing one leaves the others as they are:

Limen.update_config(:my_app, :trap, min_fill_time: 1_000)

Some options are only read when the instance starts, and changing them at runtime raises: :lists (use Limen.Lists instead), :secret_key and :previous_secret_keys, and in their groups :state :max_keys, :sketch_width, :sketch_depth, :path_filter_capacity and :hll_precision; :cluster :enabled and :scope; :decision_log :size; :challenge :replay_capacity; :maze :corpus and :bundled_corpus; :asn :file, :url and :refresh. Restart the instance to change them.

Summary

Functions

Builds a validated configuration from options.

The instances configured for app, with their options.

Validates and changes one option of a built configuration.

Types

t()

@type t() :: %{required(atom()) => term()}

Functions

build(opts)

@spec build(keyword()) :: t()

Builds a validated configuration from options.

Raises ArgumentError on an unknown or invalid option.

from_app(app)

@spec from_app(atom()) :: [{atom(), keyword()}]

The instances configured for app, with their options.

With no :instances option there is a single instance named after the application. Otherwise each entry of :instances is an instance, whose options are merged over the top-level ones.

config :my_app, Limen,
  mode: :dry_run,
  instances: [public: [], admin: [mode: :enforce]]

update(config, key, value)

@spec update(t(), atom(), term()) :: t()

Validates and changes one option of a built configuration.

A keyword list is merged into the option's current value, recursively, so update(config, :trap, min_fill_time: 0) keeps the trap paths. Any other value replaces the current one. Raises ArgumentError on an invalid value, and on a change to an option only read at startup (see "Changing options at runtime" above).