Discover SSL certificates and keys from environment variables and produce
configuration suitable for :ssl, Cowboy, Ranch, Bandit, Thousand Island,
or Phoenix.
How it works
Snippy runs in three lazy phases backed by a single shared GenServer:
Scan (cheap). The Store walks the environment once and records every variable whose name ends in a recognized suffix (
_CRT,_KEY,_PWD, ...). No PEM is decoded, no files are read. The scan is shared across every helper call.Filter by prefix (per call). Each helper takes the broad scan results and peels off entries whose names start with the requested prefix.
Materialize (lazy, per group). Only when a helper actually asks about a
(prefix, key)group does Snippy decode PEM, decrypt keys, validate the cert/key match, check expiry, and build the final:sslpayload. Successes and errors are memoized in ETS, so repeated lookups are constant-time and broken groups don't spam the log.
This shape gives Snippy a small DoS surface: env vars that no helper ever asks about never get decoded, even if an attacker can set arbitrary environment variables.
Helper option groups
All helpers accept the same option categories:
Required
:prefix- string, atom, or list of either.
Discovery passthrough (forwarded to the shared scan)
:case_sensitive- defaulttrue.:env- env map override (mainly for testing).:reload_interval_ms- if set, the Store schedules background re-scans at this cadence.
Per-lookup options
:default_hostname- SNI fallback host.:expiry_grace_seconds- tolerate certs that expired up to this many seconds ago (default 0).:public_ca_validation-:auto | :always | :never(default:auto).:only- list of hostname patterns; only matching groups are exposed.:keys- list of group key strings (or atoms); only matching groups are exposed.
Escape hatch
:discovered_certs- a%Snippy.Discovery{}from a prior call todiscover_certificates/1. When provided, the helper uses that discovery's groups directly and skips the shared Store entirely. Useful when you want to control exactly when (and against what env) materialization happens, e.g. pre-warming at boot.
Quick example
Snippy.cowboy_opts(prefix: "MYAPP")
# equivalent to:
{:ok, disc} = Snippy.discover_certificates(prefix: "MYAPP")
Snippy.cowboy_opts(prefix: "MYAPP", discovered_certs: disc)
Summary
Functions
Build options suitable for Bandit.start_link/1.
Build options suitable for Plug.Cowboy.https/3 / :cowboy.start_tls/3.
Run discovery against the env and return a %Snippy.Discovery{} handle.
Build the keyword list to assign to the :https key of a Phoenix
endpoint config.
Build options suitable for Ranch's :ranch.start_listener/5.
Re-scan the env (and re-read all _FILE sources). Clears the
materialization cache so subsequent helper calls re-decode.
Build an SNI fun (suitable for the :sni_fun :ssl option).
Build a keyword list of :ssl.listen/2 options.
Build options suitable for ThousandIsland.start_link/1.
Types
Functions
Build options suitable for Bandit.start_link/1.
Build options suitable for Plug.Cowboy.https/3 / :cowboy.start_tls/3.
Run discovery against the env and return a %Snippy.Discovery{} handle.
Eagerly materializes every group that matches :prefix, so this is a
good pre-warm step at boot. The returned handle's :groups field
contains the successful groups (without their internal :ssl payloads —
those live in the Store's ETS); :errors contains any per-group
materialization failures as {prefix, key, reason} tuples.
Options: see the moduledoc.
Build the keyword list to assign to the :https key of a Phoenix
endpoint config.
Accepts both Phoenix transport opts (e.g. :port, :cipher_suite,
:otp_app) and Snippy scoping opts (:only, :keys). Snippy's SSL
options (:sni_fun, :certs_keys) are merged in last so they win on
collision; everything else is passed through unchanged.
Discovery passthrough opts (:prefix, :case_sensitive, ...) are
consumed for discovery and stripped from the result.
Adapter
Pass :adapter to control how SSL options are nested in the result:
:cowboy(default) — SSL options are merged flat, suitable forPhoenix.Endpoint.Cowboy2Adapter.:bandit— SSL options are nested underthousand_island_options: [transport_options: [...]], as required byBandit.PhoenixAdapter.
Examples
# Cowboy (default)
config :my_app, MyAppWeb.Endpoint,
https:
Snippy.phx_endpoint_config(
prefix: "MYAPP",
port: 4443,
cipher_suite: :strong
)
# Bandit
config :my_app, MyAppWeb.Endpoint,
https:
Snippy.phx_endpoint_config(
prefix: "MYAPP",
adapter: :bandit,
port: 4443
)
Build options suitable for Ranch's :ranch.start_listener/5.
Re-scan the env (and re-read all _FILE sources). Clears the
materialization cache so subsequent helper calls re-decode.
Returns a refreshed %Snippy.Discovery{} for the same prefix(es) the
handle was created with.
Build an SNI fun (suitable for the :sni_fun :ssl option).
Build a keyword list of :ssl.listen/2 options.
Build options suitable for ThousandIsland.start_link/1.