Ankusa.Admin.Redact (ankusa v0.7.0)

Copy Markdown View Source

Turn a %Ankusa.Config{} into a JSON-encodable map safe to hand to anyone who can reach the admin API.

Adapter options are an open vocabulary (every adapter, and every third-party adapter, names its own credentials), so redaction is an allowlist, not a blocklist. There are two modes.

Open covers %Ankusa.Config{}'s own fields and a source entry's own fields (tenant, name, source_id, ingest_path, on_verify_failure, trust_url_tenant): none of them holds a secret, so values are converted as they are. Maps and keyword lists become string-keyed maps, atoms become strings (module atoms via inspect/1), numbers, booleans, nil and binaries stay, functions become "#Function", and any other term goes through inspect/1. A value shaped like a {module, opts} pair (an atom head, an Erlang module included, with list or map opts), or a list of them, is never converted as it is: its opts switch to closed mode.

Closed covers everything inside a {module, opts} pair (sinks, verifiers, source store, blob store, codec, retry policy, route resolver, routes store, lifecycle sinks) and a source's stored JSON spec. For each key/value, the first matching rule wins:

  1. under headers, names stay and values become "[REDACTED]";
  2. a {module, opts} pair (or a list of them) becomes %{"module" => "Ankusa.Sink.Log", "opts" => %{}}, its opts closed;
  3. a {module, fun, args} callback becomes "Module.fun/arity", never its args (a static GCS token is a bare arg);
  4. numbers, booleans, nil and atoms stay; functions become "#Function";
  5. structs, maps and non-empty keyword lists become closed maps;
  6. a list made only of such maps, keyword lists and pairs is converted element by element;
  7. under url, endpoint or resource a binary is shown with the password of user:pass@ userinfo hidden, a lone token@ userinfo hidden, and every query value hidden (names stay);
  8. a value under a key on the shown-key allowlist (ids, names, regions, topics and other non-secret identifiers) is shown;
  9. anything else, strings and charlists included, is "[REDACTED]".

A new adapter option is therefore hidden until its key is added to @shown_keys. Path and host of a URL are shown, so a capability URL whose secret sits in the path is not protected.

Summary

Functions

A redacted, JSON-encodable view of the whole config.

reduce_hidden/3 without an accumulator: replace every hidden string by fun.(string).

Walk a stored source spec (string keys, plain JSON values) and hand every string the closed-mode rules would hide to fun, threading an accumulator. Everything else comes back as it was.

A redacted, JSON-encodable view of one tenant-scoped source, built from the Ankusa.SourceStore.stored() map: the shape the admin API's source routes return and the data of a source lifecycle event.

Functions

config(config)

@spec config(Ankusa.Config.t()) :: map()

A redacted, JSON-encodable view of the whole config.

map_hidden(spec, fun)

@spec map_hidden(map(), (String.t() -> String.t())) :: map()

reduce_hidden/3 without an accumulator: replace every hidden string by fun.(string).

reduce_hidden(spec, acc, fun)

@spec reduce_hidden(map(), acc, (String.t(), acc -> {String.t(), acc})) ::
  {map(), acc}
when acc: term()

Walk a stored source spec (string keys, plain JSON values) and hand every string the closed-mode rules would hide to fun, threading an accumulator. Everything else comes back as it was.

This is the inverse of the allowlist above, and it is what decides which fields of a spec are encrypted at rest (Ankusa.Crypto.Envelope.seal/2): header values, every string under a key that is not on the shown-key allowlist (secret included, in a list too), and a url/endpoint/ resource that carries credentials. A new adapter option is hidden, and so encrypted, until its key is added to @shown_keys.

source_entry(stored)

@spec source_entry(Ankusa.SourceStore.stored()) :: map()

A redacted, JSON-encodable view of one tenant-scoped source, built from the Ankusa.SourceStore.stored() map: the shape the admin API's source routes return and the data of a source lifecycle event.

The stored spec is plain JSON (string keys, plus strings, numbers, maps, and lists), so its verify and sinks take the closed-mode rules of the module doc.