Skip to content

[Feat]: Serve multiple domains from one zot instance with per-domain TLS certs and independent mTLS trust domains (SNI) #4412

Description

@yinsenyan

Is your feature request related to a problem? Please describe.

When zot is run as a shared registry / pull-through cache that fronts more than one domain, http.tls only supports a single server certificate and a single client-CA. So today, to serve N domains — each needing its own server cert, and (for mTLS) its own client-CA trust domain — you have to either:

  • run one zot instance per domain (N× the footprint, split metadata/storage/state), or
  • terminate TLS/mTLS in a front proxy (e.g. Traefik) and talk plaintext to zot — which moves cert management out of zot and, more importantly, collapses the per-domain mTLS trust domains: zot can no longer distinguish which CA a client authenticated against, so per-domain client identities can't flow into zot's own accessControl.

Concretely: we operate a single zot as an on-demand cache in front of several upstream registries reached under different domains (e.g. hub.a.com, hub.b.com), where each domain has its own server cert and its own client-CA (independent CI identities). One cert + one client-CA can't express that.

Describe the solution you'd like

Add an optional http.tls.additionalCerts list, each entry {domain, cert, key, cacert}, selected per-connection by SNI at the TLS handshake (a GetConfigForClient-style resolver):

"http": {
  "tls": {
    "cert": "/certs/default/tls.crt",       // legacy default, unchanged
    "key":  "/certs/default/tls.key",
    "cacert": "/certs/default/ca.crt",
    "additionalCerts": [
      { "domain": "hub.a.com", "cert": "...", "key": "...", "cacert": "/certs/a/ca.crt" },
      { "domain": "hub.b.com", "cert": "...", "key": "...", "cacert": "/certs/b/ca.crt" }
    ]
  }
}

Behaviour:

  • SNI dispatch selects the per-domain tls.Config: exact match → single left-most-label wildcard (*.example.com) → default fallback.
  • Each entry isolates both the server cert and ClientCAs, so mTLS for each domain is verified against that domain's CA only — independent trust domains, per-domain client identities usable by accessControl.
  • Server certs keep the existing fsnotify hot-reload.
  • Fully backward compatible: when additionalCerts is empty, the legacy single-certificate path is used unchanged (zero behaviour difference).

Describe alternatives you've considered

  • One zot per domain — N× resource cost and operational overhead, and splits storage/metaDB/cache state that we specifically want shared.
  • TLS/mTLS termination in a front proxy — extra hop, cert management leaves zot, and (the blocker) per-domain mTLS trust domains collapse: zot no longer sees which CA the client authenticated against, so per-domain identities can't drive zot's accessControl.
  • A single SAN/multi-SAN server cert — solves server-side hostnames but cannot give each domain an independent client-CA / mTLS trust domain.

Additional context

We have a working implementation on a local branch and are happy to open a PR if there's interest:

  • config: TLSConfig.AdditionalCerts []TLSCertPair{Domain,Cert,Key,CACert}; deep-copy in CopyTLSConfig; IsMTLSAuthEnabled counts per-domain CAs.
  • api: a new SNI resolver selecting a per-connection tls.Config by ServerName (exact → wildcard → default), isolating server cert + ClientCAs per domain; fsnotify reload preserved.
  • controller: resolver wired behind an AdditionalCerts guard; legacy single-cert path untouched when the list is empty.
  • cli: validateTLS requires domain+cert+key per entry and rejects duplicate domains.
  • examples + docs + unit tests (SNI selection incl. wildcard/fallback, per-domain mTLS isolation).

Is this something the maintainers would be open to upstreaming? Happy to align on the config shape and naming before sending the PR.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew feature or requestrm-externalRoadmap item submitted by non-maintainers

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions