Authorize with X509 client cert and include cert info in Header of non-TLS service

Dear all,

I am scratching my head if this is possible.

I am trying to configure traefik that is

  • Listens to a SSL enabled port
  • asks for a X509 client cert
  • forward the information of this client certificate to the backend.

I tried with v1 and now with v2 but couldn't get it to run. My current traefik.toms looks like this:

[global]
  checkNewVersion = false
  sendAnonymousUsage = false
  
[log]
level="DEBUG"

[accessLog]


[entryPoints]
  [entryPoints.https]
  address = ":443"
  [entryPoints.achtzwei]
  address = ":8082"
    [entryPoints.achtzwei.forwardedHeaders]
    insecure = true

[providers.file]

[tls]
  [tls.options]
  [tls.options.default]
    [tls.options.default.clientCA]
      # in PEM format. each file can contain multiple CAs.
      #files = ["tests/clientca1.crt", "tests/clientca2.crt"]
      optional = true

[http]
  [http.routers]
    [http.routers.router0]
      entryPoints="achtzwei"
      middlewares="test-passtlsclientcert"
      rule="Host(`XXX`)"
      service="service1"
      [http.routers.router0.tls]
        options="default"

[http.services]
  [http.services.service1.loadBalancer]
    [[http.services.service1.loadBalancer.servers]]
        url = "http://localhost:8080"

[http.middlewares]
[http.middlewares.test-passtlsclientcert]
  [http.middlewares.test-passtlsclientcert.passTLSClientCert]
    [http.middlewares.test-passtlsclientcert.passTLSClientCert.info]
      notAfter = true
      notBefore = true
      sans = true
      [http.middlewares.test-passtlsclientcert.passTLSClientCert.info.subject]
        country = true
        province = true
        locality = true
        organization = true
        commonName = true
        serialNumber = true
        domainComponent = true
      [http.middlewares.test-passtlsclientcert.passTLSClientCert.info.issuer]
        country = true
        province = true
        locality = true
        organization = true
        commonName = true
        serialNumber = true
        domainComponent = true
	

[acme]
  email = "XXX"
  onHostRule = false
  storage = "/tmp/acme.json"
  entryPoint = "https"
  [acme.tlsChallenge]
  [[acme.domains]]
    main = "XXX"

(hostname and mail address "XXX"ed).

But connecting to the created port 8082, I only get a 404 page not found and the request is not forwarded to the backend at all. Presenting the letsecrypt certificate, however, works ok.

I am somehow lost and hope someone has an idea.

Thanks a lot!

Hi harenber,

I'm not sure to really understand your use case.
Do you want to pass the certificate given by let's encrypt to the backends?
Could you please provide us more details about your need?

Hi jbd,

no.. I want the user to authorize (log in) using a client certificate and want the DN of this certificate to be passed to the backend. I thought passTLSClientCert is exacly this feature.

Hope that is clearer??

Thanks!!!

Ok, let's try this simple v2 configuration :

# static configuration
[global]
  checkNewVersion = false
  sendAnonymousUsage = false

[log]
    level="DEBUG"

[api]

[entryPoints]
  [entryPoints.https]
  address = ":443"

[providers.file]

# dynamic configuration
[tls.options.default.clientCA]
    files = ["/app/certs/ca/MyRootCA.pem"]
    optional = true

[http.routers.router0]
    middlewares=["test-passtlsclientcert"]
    rule="Host(`XXX`)"
    service="service1"
    [http.routers.router0.tls]

[[http.services.service1.loadBalancer.servers]]
    url = "http://10.0.1.12:80"

[http.middlewares.test-passtlsclientcert.passTLSClientCert.info]
    notAfter = true
    notBefore = true
    sans = true
    [http.middlewares.test-passtlsclientcert.passTLSClientCert.info.subject]
        country = true
        province = true
        locality = true
        organization = true
        commonName = true
        serialNumber = true
        domainComponent = true
    [http.middlewares.test-passtlsclientcert.passTLSClientCert.info.issuer]
        country = true
        province = true
        locality = true
        organization = true
        commonName = true
        serialNumber = true
        domainComponent = true

I used the containous/whoami as backend and the following request:

curl -kvvv https://whoami --cert certs/client.pem --key certs/client.key

The request worked and I had a X-Forwarded-Tls-Client-Cert-Info header with all the specified fields.

Note that the clientCA must be able to validate the certificate passed by the client.

I hope this will help you :slight_smile:

For more details see the official documentation.

Wow.. Thanks @jbd . Works like a charm :slight_smile: That was exactly what I was looking for.

1 Like

unfortunately for me - this isn't working.

i.e. - I can't get the certificate information to show up in "x-forwarded-tls-client-cert-info"

I am using the kubernetes-CRD and there are no examples on how to integrate the static file configuration or where to put the CA-certificate to validate against...

currently I have the following setup - where the file-config (supplied as configMap - as I had working with traefik 1.7) is not commented out:

kind: Deployment
apiVersion: apps/v1
metadata:
  namespace: kube-system
  name: traefik
  labels:
    app: traefik
spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik-ingress-controller
      volumes:
      - name: ssl
        secret:
          secretName: ssl 
      - name: config
        configMap:
          name: traefik-config
      containers:
        - name: traefik
          image: traefik:v2.0.2
          args:
            - --api.insecure=true
            - --api.dashboard=true
            - --ping=true
            - --log.level=INFO
            - --accesslog
            - --metrics.prometheus=true
            - --metrics.prometheus.entryPoint=metrics
            - --entrypoints.http.Address=:80
            - --entrypoints.https.Address=:443
            - --providers.kubernetescrd
            - --providers.kubernetescrd.certauthfilepath="ssl/ca.crt" 
#            - --providers.file.directory=/config
#            - --providers.file.filename=traefik.toml
#            - --providers.file.watch=true
          ports:
            - name: http
              containerPort: 80
            - name: admin
              containerPort: 8080
            - name: https
              containerPort: 443
          volumeMounts:
            - mountPath: "/ssl" 
              name: ssl
            - mountPath: "/config"
              name: config

My ingress routes contain a middleware for passing the certificate information (into x-forwarded-tls-client-cert-info - not infos(!) as with 1.7):

kind: Middleware
apiVersion: traefik.containo.us/v1alpha1
metadata:
  name: passtlscert
  namespace: default
spec:
  passTLSClientCert: #add to the <<X-Forwarded-Tls-Client-Cert-Info>> header:
#    pem: false
    infos:
      notafter: true
      notbefore: true
      sans: false
      subject:
        country: false
        province: false
        locality: false
        commonname: true
        serialnumber: false
        domaincomponent: false
      issuer:
        country: false
        province: false
        locality: false
        organization: false
        commonname: false
        serialnumber: false
        domaincomponent: false   

the TLSOption is like this:

apiVersion: traefik.containo.us/v1alpha1
kind: TLSOption
metadata:
  name: authtlsoption
  namespace: default
spec:
  minVersion: VersionTLS12
  clientAuth:
    caSecret: sslv2
    secretNames:
      - sslv2
    clientAuthType: RequireAndVerifyClientCert

here there is very little documentation as to how this works in kubernetes... I'm pretty sure caSecret or secretNames should be a either-or...

lastly I put all this into the ingressroute definition like so:

# FRONTEND
kind: IngressRoute
apiVersion: traefik.containo.us/v1alpha1
metadata:
  name: fe-ingress
  namespace: default
spec:
  entryPoints:
    - https  
  routes:
  - match: Host(`test.com`) && PathPrefix(`/`)
    kind: Rule
    services:
    - name: fe
      port: 443
    middlewares:
    - name: passtlscert
  tls:
    options:
      name: authtlsoption
      namespace: default
    secretName: ssl

very simple solution to my own problem:

  • ADD "insecureSkipVerify=true" to deployment of traefik
  • CHANGE "passTLSClientCert" from "infos" as has been the traefik default so far - to "info" (singular!)

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.