external-auth-server icon indicating copy to clipboard operation
external-auth-server copied to clipboard

How to use EAS with Traefik IngressRoute CRD?

Open thomafred opened this issue 4 years ago • 21 comments
trafficstars

Hi there!

We are looking to use EAS with an IngressRoute CRD.

This achievable, and if so, how?

thomafred avatar Oct 29 '21 09:10 thomafred

You need a Middleware for this:

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: my-middleware-name
  namespace: mynamespace
spec:
  forwardAuth:
    address: "https://myurl-to-eas.com/verify?config_token_store_id=default&config_token_id=whatever"
    trustForwardHeader: true
    authResponseHeaders:
      - X-Forwarded-User
      - X-Id-Token
      - X-Userinfo
      - X-Access-Token
      - Authorization

and use this in your IngressRoute:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: my-route
  namespace: mynamespace
spec:
  entryPoints:
    - https
  tls: {} # just use the wildcard certificate matching per domain
  routes:
  - match: Host(`my.host.com`) # Hostname to match
    kind: Rule
    middlewares:
      - name: my-middleware-name
        namespace: mynamespace
    services:
    - name: myservice
      port: 80

kettenbach-it avatar Oct 29 '21 13:10 kettenbach-it

Been meaning to add a traefik 2 example. We’ll get one added to the docs.

travisghansen avatar Oct 29 '21 14:10 travisghansen

Thank you for your response. We have tested this, but are seeing some issues with how the redirect is handled.

From the logs, we are seeing the following (hostname redacted):

{"service":"external-auth-server","level":"verbose","message":"parent request info: {\"uri\":\"https://app.hostname.comundefined\",\"parsedUri\":{\"scheme\":\"https\",\"host\":\"eas.hostname.comundefined\",\"path\":\"\",\"reference\":\"absolute\"},\"parsedQuery\":{}}"}

Furthermore, the URL in the webbrowser also has the unhandled-part:

https://eas.hostname.comundefined/?__eas_oauth_handler__=authorization_callback&code=<AUTHORIZATION_CALLBACK>

thomafred avatar Nov 01 '21 08:11 thomafred

Ref. eas.hostname.comundefined, we found the undefined postfix to the domain comes from this line: https://github.com/travisghansen/external-auth-server/blob/master/src/utils.js#L191

It happens because req.headers["x-forwarded-uri"] was not defined nor checked for existence before use. We simply add the following to fix it:

originalRequestURI += req.headers["x-forwarded-uri"] || '';

sseppola avatar Nov 01 '21 08:11 sseppola

Ref. eas.hostname.comundefined, we found the undefined postfix to the domain comes from this line: https://github.com/travisghansen/external-auth-server/blob/master/src/utils.js#L191

It happens because req.headers["x-forwarded-uri"] was not defined nor checked for existence before use. We simply add the following to fix it:

originalRequestURI += req.headers["x-forwarded-uri"] || '';

@travisghansen

I had this problem a while ago, too - before I set x-forwarded-uri - then it disappeared. I think this needs to be fixed in the code!

kettenbach-it avatar Nov 01 '21 10:11 kettenbach-it

Agreed. Although in the pre reqs it is mentioned this is a must. Without setting it (to a proper value) there could be adverse effects.

Is traefik not sending it at all? Or simply not setting it when the path is the root path?

travisghansen avatar Nov 01 '21 14:11 travisghansen

From the logs we can't see any x-forwarded-uri header, so it was missing.

We tried to follow the HOWTO.md document as close as possible, but had to modify it to use the IngressRoute CRD

sseppola avatar Nov 01 '21 14:11 sseppola

Interesting. Let me fire this up locally and do a little testing. What exact version of traefik are you running?

travisghansen avatar Nov 01 '21 14:11 travisghansen

Traefik 2.4.13

sseppola avatar Nov 01 '21 15:11 sseppola

I think I may know what's going on here, but need more info to be sure.

In your config you have this: address: "https://myurl-to-eas.com/verify?config_token_store_id=default&config_token_id=whatever"

Is that going directly to eas using a k8s service or is that getting proxied through something (possibly even the same traefik instance as the original request)?

travisghansen avatar Nov 01 '21 16:11 travisghansen

Here is our values.yml that we use with the Helm-chart:

configTokenSignSecret: "${config_token_sign_secret}"
configTokenEncryptSecret: "${config_token_encrypt_secret}"
issuerSignSecret: "${issuer_sign_secret}"
issuerEncryptSecret: "${issuer_encrypt_secret}"
cookieSignSecret: "${cookie_sign_secret}"
cookieEncryptSecret: "${cookie_encrypt_secret}"
sessionEncryptSecret: "${session_encrypt_secret}"

logLevel: "silly"

redis-ha:
  enabled: false

 image:
   repository: hostname.azurecr.io/external-auth-server
   tag: "test-3"
   pullPolicy: IfNotPresent

ingress:
  enabled: false

We are using a Traefik IngressRoute for the ingress instead of the ingress included with the Helm-chart. As a result, the EAS-request is indeed getting proxied through the same Traefik instance as you suggest, however the IngressRoute has no middleware.

thomafred avatar Nov 01 '21 18:11 thomafred

OK, is there any way you can point the middleware directly to the k8s internal service instead of through an Ingress/IngressRoute?

To give some context:

client -> traefik (actual service) -> traefik (eas)

I believe what's happening is the traefik in front of eas is actually stripping the headers (that get added by the traefik from the actual service).

You can bypass traefik fronting eas entirely (in the context of the /verify endpoint) OR you can probably add a proper forwardedHeaders value on the entryPoint (the entry point fronting eas).

  • https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers

Set the trustedIPs to the possible IPs of traefik itself...or set insecure to true. Obviously taking into consideration the security implications of your deployment.

In short X-Forwarded-Uri is a header specifically added to the forward auth endpoints and not generally added by traefik across the board. So an incoming request to an entryPoint with that value is likely getting stripped without further configuration to build 'trust' in the client.

travisghansen avatar Nov 01 '21 19:11 travisghansen

Set the trustedIPs to the possible IPs of traefik itself...or set insecure to true. Obviously taking into consideration the security implications of your deployment.

What I forgot to mention: I had to set trustedIPs, to make it work, since my eas indeed is behind traefik.

kettenbach-it avatar Nov 02 '21 06:11 kettenbach-it

For setting it as an forward auth url I would recommend using the internal service endpoint to avoid the overhead and issues.

However, I would also expose it externally using ingress/crd so that sso can be used by setting cookie domain and/or static callback url endpoint.

travisghansen avatar Nov 02 '21 06:11 travisghansen

Good evening,

Sorry about the late response - modifying the traefik config caused our SSL certificates to be renewed excessively, which in turn lead to us getting rate-limited by letsencrypt. Oops..

DEV-cluster only, so no real harm done :)

After solving the SSL-issue (also implementing persistance), we have added the changes you suggested. We added the AKS pod and service CIDR to trustedIPs.

Not sure why, but the Traefik middleware appear to want to redirect to port 8443. This is the default port of the websecure entrypoint in the Traefik Helm-chart.

thomafred avatar Nov 02 '21 19:11 thomafred

Bad logic in eas or something else? Can you send over relevant logs?

travisghansen avatar Nov 02 '21 19:11 travisghansen

Seems I was tricked by the Firefox cache. Sorry about the confusion.

We were finally able to get the middleware to direct to the correct service (https://eas.myhost.com), and we are being prompted with the authentication flow, as expected.

Will let keep you updated as we progress

thomafred avatar Nov 02 '21 19:11 thomafred

The changes @travisghansen and @kettenbach-it suggested does in indeed work.

Quick summary:

First of all, we have deployed Traefik (version 2.4.13) in our case using the Helm-chart (version 10.1.2). Following is our values.yaml-file:

additionalArguments:
  - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
  - "--certificatesresolvers.letsencrypt.acme.email=${letsencrypt_email}"
  - "--certificatesresolvers.letsencrypt.acme.storage=/data/acme.json"
  - "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-v02.api.letsencrypt.org/directory"
  - "--api.insecure=true"
  - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
  - "--entryPoints.websecure.forwardedHeaders.trustedIPs=127.0.0.1,10.244.0.0/16,10.0.0.0/16"
  - "--serverstransport.insecureskipverify=true"

deployment:
  initContainers:
    # The "volume-permissions" init container is required if you run into permission issues.
    # Related issue: https://github.com/containous/traefik/issues/6972
    - name: volume-permissions
      image: busybox:1.31.1
      command: ["sh", "-c", "chmod -Rv 600 /data"]
      volumeMounts:
        - name: data
          mountPath: /data
logs:
  general:
    level: INFO
  access:
    enabled: true

persistence:
  enabled: true
  accessMode: ReadWriteMany
  storageClass: ssl-certificates

Do note that we have set --entryPoints.websecure.forwardedHeaders.trustedIPs=127.0.0.1,10.244.0.0/16,10.0.0.0/16, where 10.244.0.0/16 and 10.0.0.0/16 are the default pod and service CIDRs respectively for AKS.

We have also used Helm to deploy EAS. Following is our values.yaml file:

configTokenSignSecret: "${config_token_sign_secret}"
configTokenEncryptSecret: "${config_token_encrypt_secret}"
issuerSignSecret: "${issuer_sign_secret}"
issuerEncryptSecret: "${issuer_encrypt_secret}"
cookieSignSecret: "${cookie_sign_secret}"
cookieEncryptSecret: "${cookie_encrypt_secret}"
sessionEncryptSecret: "${session_encrypt_secret}"

redis-ha:
  enabled: false

ingress:
  enabled: false

Note that we have used a Traefik IngressRoute CRD to define the ingress instead of the ingress provided with the Helm-chart.

The middleware is basically the same as the one provided by @kettenbach-it:

**apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: my-middleware-name
  namespace: mynamespace
spec:
  forwardAuth:
    address: "https://eas.myhost.com/verify?config_token_store_id=default&config_token_id=whatever"
    trustForwardHeader: true
    authResponseHeaders:
      - X-Forwarded-User
      - X-Forwarded-Uri
      - X-Id-Token
      - X-Userinfo
      - X-Access-Token
      - Authorization

The same is also the case for the IngressRoute for the desired service:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: my-route
  namespace: mynamespace
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`app.myhost.com`) # Hostname to match
    kind: Rule
    middlewares:
      - name: my-middleware-name
        namespace: mynamespace
    services:
      - name: myservice
        port: 80
  tls:
    certResolver: letsencrypt
    domains:
      - app.myhost.com

thomafred avatar Nov 03 '21 08:11 thomafred

Looks great!

Back to the original ask, I’ll probably add some code that throws an error instead of behaving badly as it does now.

travisghansen avatar Nov 03 '21 13:11 travisghansen

awesome, thank you!

@sseppola is also working on a PR for you

thomafred avatar Nov 03 '21 14:11 thomafred

I have added more strict handling of this issue: https://github.com/travisghansen/external-auth-server/commit/b26a1a2ec62e0163d3a3cc24cbfd5bf4bdd0151b

If the necessary headers are not present it now throws an error.

travisghansen avatar Jan 19 '23 14:01 travisghansen