traefik-helm-chart icon indicating copy to clipboard operation
traefik-helm-chart copied to clipboard

Provide a "default" Certificate for TLS

Open SantoDE opened this issue 5 years ago • 12 comments

If a user provides us his certificate data, we could create a TLSStore and attach the cert as a default certificate.

Partially fixes #185

SantoDE avatar May 27 '20 10:05 SantoDE

I was searching a lot how I could swap Traefik auto generated certificates out and provide the default ones. Since there is a lot of confusion around this, I'll make an informational post here.

Specifically I use Kubernetes and Ingresses with Cloudflare as DNS and Firewall provider. Cloudflare provides their own Edge certificates (for connection between CF and host server) that I need to upload and serve for all the hosts.

I created secret of type tls. This is necessary. kubectl create secret tls tls-secret --cert tls.crt --key tls.key

A) And tried using TLSStore (src: https://github.com/traefik/traefik/issues/6057#issuecomment-615903064)

apiVersion: traefik.containo.us/v1alpha1
kind: TLSStore
metadata:
  name: cloudflare-tls
  namespace: traefik
spec:
  defaultCertificate:
    secretName: tls-secret

but seems to be ignored by Kubernetes Ingress resources. This may work if you are using IngressRoute (Kubernetes CRD provided by Traefik)

B) Then I went the dynamic config way: Created ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: traefik-config
  labels:
    name: traefik-config
  namespace: traefik
data:
  dyn.yaml: |
    # https://doc.traefik.io/traefik/https/tls/
    tls:
      stores:
        default:
          defaultCertificate:
            certFile: '/certs/tls.crt'
            keyFile: '/certs/tls.key'

and used these values when deploying Traefik chart

additionalArguments:
  - '--providers.file.filename=/config/dyn.yaml'
volumes:
  - name: tls-secret
    mountPath: '/certs'
    type: secret
  - name: traefik-config
    mountPath: '/config'
    type: configMap

and it works!

It can be challenging to understand Traefik, especially when Kubernetes are involved, hope this will shed some light.

Relevant: https://github.com/traefik/traefik/issues/5468#issuecomment-590067174

flexchar avatar Sep 29 '20 19:09 flexchar

I would like to see the ConfigMap and therefore my custom configuration to be managed within this HelmChart as well. So in values.yaml

additionalConfig: |
   # https://doc.traefik.io/traefik/https/tls/
    tls:
      stores:
        default:
          defaultCertificate:
            certFile: '/certs/tls.crt'
            keyFile: '/certs/tls.key'

and a ConfigMap like

{{ if .Values.additionalConfig }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: traefik-config
  labels:
    name: traefik-config
data:
  dyn.yaml: |
 {{ .Values.additionalConfig  }}
{{ end }}

Maybe it could be possible to do that with the defaultCert as well, at least for the standard Ingress type.

What do you think @SantoDE ?

tillepille avatar Dec 03 '20 07:12 tillepille

Please add the additional configuration section to the helm chart !

i have been spinning for days until i found this issue ..

halradaideh avatar Feb 22 '21 13:02 halradaideh

+1 wishing for an easier / more flexible way to configure this, as it feels like a very common use case when using wildcard certificates with LetsEncrypt: we want to be able to reuse this same certificate as the default at the cluster-level without needing to sync, copy, or reissue the TLS secret to each namespace where it is needed.

@flexchar mentions a workaround, but I believe that this assumes that you are in the same namespace in which the secret was created by cert-manager, since we can't mount a secret from a different namespace. This secret would typically be in the same namespace as the Ingress rules that it secures, but not necessarily where Traefik itself is running.

For example:

  • CertManager Helm chart running in namespace cert-manager
  • Traefik Helm chart running in namespace traefik
  • Our application running in namespace example, where our Ingress rules are created

What happens:

  • CertManager sees a new Ingress rule requiring TLS in example, so it creates a new secret in the example namespace containing the needed tls.key / tls.crt files
  • We cannot mount in the tls.key / tls.crt files that are needed by Traefik to set the default certificates for the cluster, because they are not in the traefik namespace
  • To properly support this case, we would need to make sure that the correct TLS secret exists in both namespaces, which seems odd

Syncing/copying to every needed namespace adds complexity to automating the renewal of these certs, and it can be difficult to debug or even know if the sync/copy ever fails. Reissuing the same cert for every namespace can lead to being rate-limited by LetsEncrypt if your application scales out too far or too quickly.

For our use case, we may need to switch back to NGINX, where this is a simple command-line flag that we can add to extraArgs in the Helm chart: --set controller.extraArgs.default-ssl-certificate=<namespace>/<secretname>. In this case, they do not assume that the secret will actually live in the same namespace, but are somehow able to read it anyways.

bodom0015 avatar Mar 24 '21 19:03 bodom0015

I'm going to quickly clarify that I have manually uploaded a certificate files (we are using Cloudflare ones) as a secret to Traefik namespace. Works great.

Each of the actual applications incl. the Ingress manifest are in their own respective namespace zone - that works fine.

flexchar avatar Mar 24 '21 22:03 flexchar

I was able to achieve this by just creating a TLSStore

apiVersion: traefik.containo.us/v1alpha1
kind: TLSStore
metadata:
  name: default
spec:
  defaultCertificate:
    secretName: custom-default-cert

Note that i did need to enable the feature for the IngressRoute custom resources, even though i am not using them. The default cert defined in the TLSStore works for Ingress objects. I also named the TLSStore "default". not sure if that is required, but the docs do mention that the only valid store is "default"

gmorse81 avatar Apr 08 '21 20:04 gmorse81

@gmorse81 's solution works. It would be great if we could configure it beforehand at the helm chart installation.

iTaybb avatar Apr 27 '21 13:04 iTaybb

I can also confirm that the @gmorse81's solution works, but with the caveat that the TLSStore needs to exist when Traefik starts, otherwise traefik generates and uses a self signed certificate until someone restarts the pod.

This seems strange, because the [tls.stores.default.defaultCertificate] must be declared inside a "dynamic configuration" instead of a "static configuration", so I would normally assume that traefik reloads this certificate "on the fly" when the config changes.

So, to use a default tls certificate using the custom resource TLSStore you currently have to deploy traefik in this order:

  1. Deploy the custom resource definitions (CRDs)
  2. Deploy your TLSStore custom resource as shown by @gmorse81
  3. Deploy the actual pods using the helm chart.

Edit: Actually, this is false. The default TLSStore can be deployed at runtime. It just took a minute or so for traefik to pick up my changes.

ChristianCiach avatar Apr 30 '21 16:04 ChristianCiach

@bodom0015 I have exactly the same issue. I've got

  • cert-manager running in namespace cert-manager and
  • Traefik (deployed using k3s that internally uses this helm chart) in namespace kube-system.

Therefore using the solution proposed by @gmorse81 with a TLSStore does not work as this would require referencing a secret across namespaces. I also tried using a namespace prefix (cert-manager/name-of-the-secret) but this does not work, too. So I guess I need to revert to an nginx ingress.

pfisterer avatar Nov 10 '21 15:11 pfisterer

@pfisterer, the answer provided by @gmorse81 does work, but you need to use a ClusterIssuer for cert-manager instead of an Issuer.

My setup:

  • k3s cluster with 3 nodes (--disable=traefik)
  • cert-manager helm chart deployed to the namespace cert-manager
  • Traefik helm chart deployed to the namespace traefik

First I create my ClusterIssuer which doesn't belong to a namespace.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - dns01:
          cloudflare:
            email: [email protected]
            apiTokenSecretRef:
              name: super-secret-secret-name
              key: api-token

Then I create a Certificate and assign it as default in the TLSStore. Note, the namespace for both the Certificate and TLSStore should match where you deployed Traefik. Generally kube-system or traefik, either will work.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-example-com
  namespace: `YOUR_TRAEFIK_NAMESPACE`
spec:
  secretName: wildcard-example-com-tls
  dnsNames:
    - "example.com"
    - "*.example.com"
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
---
apiVersion: traefik.containo.us/v1alpha1
kind: TLSStore
metadata:
  name: default
  namespace: `YOUR_TRAEFIK_NAMESPACE`
spec:
  defaultCertificate:
    secretName: wildcard-example-com-tls

Once the certificate creation process has completed, Traefik will reload using the new default certificate.

kraihn avatar Jul 21 '22 17:07 kraihn

@kraihn Great, thank you!

pfisterer avatar Jul 21 '22 17:07 pfisterer

@kraihn, I'm trying to do the same thing, and I can't seem to get it to work.

I have:

  • A TLSStore in the kube-system namespace with a default secret
  • That secret in the kube-system namespace
  • the traefik entrypoint configured as:
websecure:
   address: :8443
   http:
      tls: {}
  • I have tried configuring the IngressRoute with
    • no tls: entry
    • tls: {}
    • and
tls:
  store:
  name: default

All with no success. Traefik debug level logging just keeps claiming there is "No certificate found for domain:..."

Could you share your entrypoint and ingress tls configuration?

bretmckee avatar Aug 01 '22 18:08 bretmckee

Hello,

Now that PR #601 has been merged, as @gmorse81 said, you can use the TLSStore to achieve this.

mloiseleur avatar Sep 29 '22 09:09 mloiseleur

For anyone reading this and trying to get @kraihn's solution to work, I had to add the certificates array to the TLSStore for it to work:

apiVersion: traefik.containo.us/v1alpha1
kind: TLSStore
metadata:
  name: default
  namespace: `YOUR_TRAEFIK_NAMESPACE`
spec:
  certificates:
    - secretName: wildcard-example-com-tls
  defaultCertificate:
    secretName: wildcard-example-com-tls

jpjonte avatar Dec 05 '22 20:12 jpjonte

@jpjonte I checked and I was able to make it work without this with current version of Traefik and this Helm Chart.

I provided a complete example in PR #754, does this work for you ?

mloiseleur avatar Dec 06 '22 13:12 mloiseleur