ecluse:ecluse-core
Safe HaskellNone
LanguageGHC2021

Ecluse.Core.Security.Egress

Description

The egress posture for registry traffic: https-only by construction, with TLS certificate validation as the endpoint-authentication boundary.

Every outbound registry URL the proxy dials is an RegistryUrl, built through the https-only mkRegistryUrl. A non-https registry endpoint cannot be represented, so a plain-HTTP target is refused at the configuration boundary (a non-https configured upstream fails closed at boot) and a packument's dist.tarball is normalised through resolveTarballUrl before it is ever dialled. The data-plane Manager is the standard validating tls manager, so the certificate presented by the dialled host is checked against the system trust store for the requested name. An attacker who can steer a name to an internal or rebound address cannot make that address present a CA-trusted certificate for the host, so the credential-exfiltration and resolve-to-internal SSRF class is closed by certificate validation rather than by a resolved-IP pin.

Two complementary controls live alongside this and are not part of this module: the outbound host allowlist (isAllowedUpstreamHost), which is the load-bearing egress-policy control (the proxy dials only configured upstream hosts), and the pure literal internal-range block (isBlockedTarget), kept as cheap defence-in-depth on the dist.tarball host gate. No data-plane request follows an upstream redirect (withToken pins redirectCount = 0), so there is no hop that could downgrade the scheme or escape the allowlist after the URL is built.

A test- and dev-only loopback constructor lives in Ecluse.Core.Security.Egress.DevHttp, compiled only under the dev-http-egress Cabal flag, so the loopback test suites can dial an in-process http://127.0.0.1 server without weakening the production posture; a release build does not compile it.

Synopsis

The https-only egress URL

data RegistryUrl Source #

An outbound registry-egress URL that is https by construction. The only production constructor, mkRegistryUrl, rejects any non-https scheme, so a plain-HTTP registry target cannot be represented in a running system: every configured upstream, mirror, and publication endpoint, and every dist.tarball target, is one of these. Stored normalised (surrounding whitespace trimmed).

mkRegistryUrl :: Text -> Either Text RegistryUrl Source #

Build a RegistryUrl, accepting only an https:// URL (the scheme matched case-insensitively, since URI schemes are). A non-https or empty value is rejected with a reason that names the requirement, so the aggregating configuration layer can fail closed at boot and report the offending value.

>>> mkRegistryUrl "https://registry.npmjs.org"
Right (RegistryUrl "https://registry.npmjs.org")
>>> mkRegistryUrl "http://registry.npmjs.org"
Left "registry URL must use https (got http://registry.npmjs.org)"

registryUrlText :: RegistryUrl -> Text Source #

The underlying URL text.

Packument dist.tarball normalisation

resolveTarballUrl :: Text -> Text -> Either Text RegistryUrl Source #

Resolve a packument's dist.tarball URL against the https-only egress policy, given the bare host the packument itself was served from.

An upstream's dist.tarball is server-chosen data, so its scheme is normalised before the proxy will dial it:

  • an https:// target is kept (validated through mkRegistryUrl);
  • an http:// target on the same host as the packument is upgraded to https://, the legacy case of an older registry that still advertises plaintext artifact URLs on its own host;
  • an http:// target on any other host is refused (a Left carrying a reason), so a foreign plaintext artifact location is dropped rather than dialled;
  • anything that is not an http(s) URL is refused.

The Left reason feeds the per-entry drop record; the Right is the https RegistryUrl the artifact is fetched from. Hosts are compared by the same bare-host extraction the allowlist uses (hostAddress). This composes with, and never replaces, the host allowlist and the same-host tarball policy applied at serve time.