Search code examples
dockercertificatelets-encrypttraefikhsts

Traefik uses a certificate whithout chain after completing tls-alpn-01 challenge


I decided to switch from jwilder/nginx-proxy to traefik since I want to route to containers based on the URI-path which is not possible with jwilder/nginx-proxy but should be with traefik.

To do so I first wanted to transform my current setup (on single domain routed to one single container) that already used lets-encrypt.

My question is: How do I get traefik to give me the proper certificate signed by lets-encrypt instead of a self-signed thing?

So I have traefik in a docker container set up with acme. I first tried to use HTTP-01-challenge without success but then decided to go with TLS-ALPN-01 anyhow.

After the startup of the container (and some waiting) the log finally reads (sensitive information replaced with Xs):

time="2019-05-30T20:01:25Z" level=info msg="legolog: [INFO] acme: Registering account for XXXXXXXX@XXXXXXXX.XXXXXXXX"
time="2019-05-30T20:01:25Z" level=info msg="legolog: [INFO] [XXXXXXXX.ddns.net] acme: Obtaining bundled SAN certificate"
time="2019-05-30T20:01:26Z" level=info msg="legolog: [INFO] [XXXXXXXX.ddns.net] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/XXXXXXXX"
time="2019-05-30T20:01:26Z" level=info msg="legolog: [INFO] [XXXXXXXX.ddns.net] acme: use tls-alpn-01 solver"
time="2019-05-30T20:01:26Z" level=info msg="legolog: [INFO] [XXXXXXXX.ddns.net] acme: Trying to solve TLS-ALPN-01"
time="2019-05-30T20:01:33Z" level=info msg="legolog: [INFO] [XXXXXXXX.ddns.net] The server validated our request"
time="2019-05-30T20:01:33Z" level=info msg="legolog: [INFO] [XXXXXXXX.ddns.net] acme: Validations succeeded; requesting certificates"
time="2019-05-30T20:02:17Z" level=info msg="legolog: [INFO] [XXXXXXXX.ddns.net] Server responded with a certificate."

When navigating to the "AuthURL it reads (sensitive information again replaced with Xs):

{
"identifier": {
"type": "dns",
"value": "XXXXXXXX.ddns.net"
},
"status": "valid",
"expires": "2019-06-29T20:01:29Z",
"challenges": [
{
"type": "dns-01",
"status": "pending",
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/challenge/XXXXXXXX/XXXXXXXX",
"token": "XXXXXXXX"
},
{
"type": "http-01",
"status": "pending",
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/challenge/XXXXXXXX/XXXXXXXX",
"token": "XXXXXXXX"
},
{
"type": "tls-alpn-01",
"status": "valid",
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/challenge/XXXXXXXX/XXXXXXXX",
"token": "XXXXXXXX",
"validationRecord": [
{
  "hostname": "XXXXXXXX.ddns.net",
  "port": "443",
  "addressesResolved": [
  "XXXXXXXX.XXXXXXXX.XXXXXXXX.XXXXXXXX"
  ],
  "addressUsed": "XXXXXXXX.XXXXXXXX.XXXXXXXX.XXXXXXXX"
}
]
}
]
}

So everything had to be be fine I wrongly assumed.

When navigating to the traefik-frontend the certificate was issued for firefox show a certificate without a chain (like self-signed I suppose) which is naturally rejected (HTTP Strict Transport Security (HSTS), SEC_ERROR_UNKNOWN_ISSUER).

The domain of the certificate shows the correct domain I gave traefik for the frontend but the issuer reads "Fake LE Intermediate X1" which does not look like some lets-encrypt thing. The saved acme.json (yes traefik configured with file store) has a certificate-entry (base64) which when decoded reveals two certificates.

Using https://www.sslshopper.com/certificate-decoder.html I found out that the first is the same certificate my browsers reject and the second has also "Fake LE Intermediate X1". Both do NOT look like the "TRAEFIK DEFAULT CERT" that you get when you try to get to the server via IP (not URL/domain) but that seems irrelevant.

I still have the certificates/keys from my previous setup where the issuer reads "Let's Encrypt Authority X3, Let's Encrypt Write review of Let's Encrypt". I could of course paste those in the acme.json wihtout knowing if that would work at all. But before to long the certificate expires anyway and I think I would face the same problem as I do now.

My traefik.toml looks like following:

logLevel = "INFO"
defaultEntryPoints = ["http", "https"]


################################################################
# API and dashboard configuration
################################################################
[api]
################################################################
# Docker configuration backend
dashboard = true
################################################################

#[web]
#address = ":8080"
#  [web.auth.basic]
#    users = ["admin:traefikW0rd"]

[docker]
domain = "XXXXXXXX.ddns.net"
watch = true
endpoint = "unix:///var/run/docker.sock"
exposedByDefault = false

[entryPoints]
  [entryPoints.http]
    address = ":80"
    [entryPoints.http.redirect]
      entryPoint = "https"
  [entryPoints.https]
    address = ":443"
      [entryPoints.https.tls]

[acme]
email = "XXXXXXXX@XXXXXXXX.XXXXXXXX"
storage = "/etc/traefik/ACME/acme.json"
keyType = "RSA4096"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
acmeLogging = true
entryPoint = "https"
#OnHostRule = true
  [acme.tlsChallenge]
  entryPoint = "https"

[[acme.domains]]
  main = "XXXXXXXX.ddns.net"

Solution

  • The samples I got for my traefik.toml pointed to a staging directory https://acme-staging-v02.api.letsencrypt.org/directory

    This is also called "dry-run" in other applications.

    I found this by googling for "Fake LE Intermediate X1".

    After changing the variable to https://acme-v02.api.letsencrypt.org/directory and wiping acme.json I got usable certificates. \O/