KrakenKey is live with free and paid plans. Issue your first TLS certificate in minutes.

Case study: TLS for internal services without DNS credentials on the proxy

engineeringacmednscertificate-transparencyproduct

A common way to give internal services real certificates is to put them behind one reverse proxy that terminates TLS and obtains certificates through the ACME DNS-01 challenge. The services stay off the internet, and every device trusts them without a private CA.

We run this pattern in our own lab environment. One Caddy gateway serves 13 internal-only hostnames: a hypervisor console, the internal DNS resolvers, out-of-band server management, home automation, and several internal web applications. This post covers moving that gateway to KrakenKey:

The configuration itself is published as the Caddy integration guide.

Starting point: DNS-01 from the proxy

The gateway used Caddy’s Cloudflare plugin to get one Let’s Encrypt certificate per hostname over DNS-01. It held a zone-scoped, IP-restricted API token for that. This is a standard configuration, and it carried three ongoing costs.

Target design: delegated validation and one certificate

KrakenKey answers DNS-01 challenges in its own zone. The domain owner creates one CNAME from _acme-challenge to a KrakenKey-managed record, once. From then on:

The KrakenKey CLI generates the private key on the gateway. Only the certificate signing request leaves the host.

Reconsidering wildcards

Per-host certificates are often preferred because a compromised proxy then exposes only the names it serves. That argument assumes the proxy can’t obtain other certificates. A proxy with DNS write access can obtain a certificate for any name. Once the DNS credentials are gone, a wildcard certificate is a smaller exposure than per-host certificates issued with them.

The credential that remains is the KrakenKey API key, and it got the same scrutiny. Until this migration, a user API key carried the same permissions as a dashboard session, including creating more keys. A leaked key could therefore create a replacement and survive its own revocation. We closed that gap before publishing: API keys can no longer manage keys, the account, organizations or billing, and revoking a key in the dashboard ends its access.

A key still covers every domain on its account. We contained that by giving the gateway a dedicated account that holds only its domain. Keys restricted to specific domains, certificates or source addresses are in development.

Rollout

The rule for the cutover was to have the new certificate on the host and verified before anything served it.

  1. Prerequisites.
    • The challenge CNAME, created next to the existing ownership TXT record.
    • The domain verified in KrakenKey.
    • An API key in a root-only file on the gateway.
    • A check that the gateway can reach the API.
  2. Issue alongside the existing setup. We added a renewal script and a daily systemd timer, delivered through the gateway’s existing configuration sync. A read-only mount gave the Caddy container the certificate directory. Caddy’s configuration was unchanged at this stage. The first certificate was installed about two minutes after the timer fired and four minutes after the change merged, with no manual steps on the host. We then checked it in place: issuer, both names, and the chain with openssl verify.
  3. Switch every site in one change. Caddy can’t move a single site to a wildcard (see below), so the check on the host in step 2 served as the canary.
    • Rollback: Caddy keeps the previous Let’s Encrypt certificates in its storage, so reverting one commit undoes the switch.
    • Verification: after the switch, we ran openssl s_client -verify_return_error from a client on the network against all 13 hostnames. Every one returned the new certificate with a trusted chain.

Caddy behaviors to plan for

Changes to KrakenKey

Running the integration the way a customer would surfaced several gaps.

Shipped:

In progress, with the current workaround for each:

Results

Before After
Certificates One per hostname (13) One, covering the apex and wildcard
DNS credentials on the gateway Zone-edit API token None in use
Local DNS during issuance Required, and broken by resolver policy Not involved
New hostnames in CT logs One per new service None
Merged change to installed certificate n/a About 4 minutes

Applying this to your environment

The Caddy integration guide has the Caddyfile snippet, the renewal script and the systemd units used here.