PhoenixKit.Modules.Storage.Endpoint (phoenix_kit v2.52.2)

Copy Markdown View Source

The one reading of an S3-compatible endpoint, and the one guard on where the server may connect to.

Three places name an endpoint: a bucket (Providers.S3), an object_storage Integrations connection (Integrations.Validators), and the bucket form. They must all read the same string the same way, or the server would connect to a different host than the one that was validated.

Parsing

parse/1 accepts a bare host (s3.us-west-002.backblazeb2.com), host:port, or an http/https URL with no path (http://minio.local:9000); the scheme defaults to https. Refused rather than read as plain AWS: any other scheme, a path (a gateway prefix would be dropped silently), a query, and an IPv6 zone id (%eth0).

Guarding

The server connects to whatever endpoint it is given, so a user-supplied one is a request-forgery primitive. check/3 classifies the addresses behind an endpoint by policy:

  • :system — set by an admin. Loopback and private ranges are allowed (a MinIO on the same network is a normal setup); what no storage endpoint can be is unspecified, link-local (where cloud metadata lives), multicast or reserved space, so those are refused.
  • :personal — set by an ordinary user. https only, and additionally no loopback, private, carrier-grade-NAT or unique-local address.

A hostname is resolved and every address it yields is checked, so a name with one public and one private record is refused. The check sees DNS as of the moment it runs; it is not a connect-time pin, so a name that changes its answer between the check and the request is not caught. Callers run it at save and test time, and again each time a personal endpoint is used.

Summary

Functions

A URL's audit representation, with credentials, query and fragment withheld. Absolute local paths stay paths unless local_path: false (for CDN URLs).

Whether the server may connect to endpoint (a string or the result of parse/1) under policy.

The class of an address: :public, or what makes it not one (:unspecified, :loopback, :private, :shared, :link_local, :unique_local, :multicast, :reserved). An IPv4 address wrapped in IPv6 (::ffff:a.b.c.d, NAT64 64:ff9b::/96) is classified as the IPv4 address.

An operator-facing sentence for a check/3 or parse/1 error.

An endpoint string, parsed: %{scheme:, host:, port:}; nil when none is set (plain AWS); {:error, :invalid_endpoint} when one is set but cannot be used.

Types

parsed()

@type parsed() :: %{scheme: String.t(), host: String.t(), port: pos_integer()}

policy()

@type policy() :: :system | :personal

reason()

@type reason() ::
  :invalid_endpoint | :insecure_scheme | :blocked_address | :blocked_host

Functions

audit_value(value, opts \\ [])

@spec audit_value(String.t() | nil, keyword()) :: String.t() | nil

A URL's audit representation, with credentials, query and fragment withheld. Absolute local paths stay paths unless local_path: false (for CDN URLs).

check(endpoint, policy, opts \\ [])

@spec check(term(), policy(), keyword()) :: :ok | {:error, reason()}

Whether the server may connect to endpoint (a string or the result of parse/1) under policy.

nil (no endpoint, plain AWS) is always allowed. Options:

  • :resolve — look a hostname up and check every address it yields (default false; a literal IP is always checked). Off on hot paths, on wherever an endpoint is saved, tested, or used for a personal bucket.
  • :resolver — (charlist, :inet | :inet6 -> {:ok, [ip]} | {:error, _}), injectable for tests. Defaults to :inet.getaddrs/2.

classify(arg)

@spec classify(:inet.ip_address()) :: atom()

The class of an address: :public, or what makes it not one (:unspecified, :loopback, :private, :shared, :link_local, :unique_local, :multicast, :reserved). An IPv4 address wrapped in IPv6 (::ffff:a.b.c.d, NAT64 64:ff9b::/96) is classified as the IPv4 address.

error_message(reason)

@spec error_message(reason()) :: String.t()

An operator-facing sentence for a check/3 or parse/1 error.

parse(endpoint)

@spec parse(term()) :: parsed() | nil | {:error, :invalid_endpoint}

An endpoint string, parsed: %{scheme:, host:, port:}; nil when none is set (plain AWS); {:error, :invalid_endpoint} when one is set but cannot be used.