Giter Site home page Giter Site logo

creoox / cx-traefik-forward-auth Goto Github PK

View Code? Open in Web Editor NEW
7.0 2.0 1.0 2.48 MB

Forward authentication service that provides OIDC authentication and/or opaque token validation for the traefik reverse proxy.

Home Page: https://hub.docker.com/r/creoox/cx-traefik-forward-auth

License: GNU Affero General Public License v3.0

Dockerfile 0.98% Makefile 5.72% TypeScript 82.04% JavaScript 0.94% EJS 10.32%
middleware auth authentication oauth2 oidc oidc-client oidc-proxy sso treafik

cx-traefik-forward-auth's Introduction

Creoox Traefik Forward-Auth

Inspired by traefik-forward-auth by thomseddon (MIT License).

cx-traefik-forward-auth is a standalone authorization middleware for Traefik that provides OIDC authentication and/or opaque token validation (introspection) for the traefik reverse proxy. It's main goal is to work as authentication feature in API Gateway solution that Traefik provides. At the current state of implementation, the authentication is based on reading the Authorization header from the request and verifying it.

"Authorization": "Bearer <access-token>"

Basic Authentication

There are two types of verification possible:

In both cases the introspection endpoint or provider signature keys that are needed to verify the token are read from its discovery endpoint from OIDC_ISSUER_URL variable. Please mind two aspects:

  1. introspection is only possible if the client secret was provided.
  2. Currently decoding of symetrical (e.g. HS256) or eliptical (e.g. ES256 - TODO) keys is not supported.

In addition to that, it is possible to use this service to obtain the token (so it acts as OIDC Client). In order to that the LOGIN_WHEN_NO_TOKEN should be set to true.

WARNING! This feature is NOT meant to be used on production.

Login Authentication

At the current state of implementation, two authentication flows are possible (both OIDC-conformant):

Currently tested providers:

Provider Version Result Comment
Keycloak 17.1
SAP Commerce ?
Google ? Planned
GitHub

Project usage

Prerequisites

  1. Prepared traefik-based infrastructure
  2. [OPTIONAL] ModHeader to include authorization header in browser request

Variables


Environmental variables:
Variable Name Type Obligatory Comment
APP_NAME string No Displayed service (app) name
APP_VERSION string No Displayed service (app) version
APP_PORT int No Service running port
HOST_URI string Yes URI of the host the service is running on
ENVIRONMENT string Yes 'development' or 'production'
OIDC_ISSUER_URL string Yes Main Issuer's URL - all data are retrieved from there
OIDC_CLIENT_ID string Yes OIDC client id
OIDC_CLIENT_SECRET string No OIDC client secret (if set)
OIDC_VERIFICATION_TYPE string Yes 'jwt' - decoding or 'introspection' - asking AS
JWT_STRICT_AUDIENCE boolean Yes true if token should be used for strict audinence only
JWT_TOKEN_TYPE string No Used token, either 'access_token' (default) or 'id_token'
AUTH_ENDPOINT string No Service redirection endpoint, '/_oauth' by default
AUTH_ALLOW_UNSEC_OPTIONS boolean No Allow unsecured OPTIONS request, false by default
LOGIN_WHEN_NO_TOKEN boolean Yes true if login functionality should be on (dev only!)
LOGIN_AUTH_FLOW string No 'code' (default) or 'id_token token' (implicit flow)
LOGIN_SCOPE string No Requested scope(s), defaults to "openid email profile"
LOGIN_COOKIE_NAME string No Name of the browser cookie, only if LOGIN_WHEN_NO_TOKEN=true
LOGIN_SESSION_SECRET string No Randomized secret for cookie-session
AUTH_ROLES_STRUCT string No Structure of roles (list) in token payload (dot notation)
AUTH_ROLE_NAME string No Role name to check in token roles

Please mind that if AUTH_ALLOW_UNSEC_OPTIONS is set to true, then the endpoint that should accept OPTIONS request, should provide separate rule for that and pass X-Forwarded-Method: OPTIONS header to cx-traefik-forward-auth there, for instance (docker).

    ...
    labels:
      - "traefik.enable=true"
      - "traefik.http.middlewares.add-options-header.headers.customrequestheaders.X-Forwarded-Method=OPTIONS"
      - "traefik.http.routers.your-endpoint-options.rule=Host(`your-endpoint.com`) && Method(`OPTIONS`)"
      - "traefik.http.routers.your-endpoint-options.middlewares=add-options-header,cx-traefik-forward-auth"
      ...

Additionally, both AUTH_ROLES_STRUCT and AUTH_ROLE_NAME have to be either set or empty. Object dot notation is presented below:

resource_access.dummy-client.roles

and is equall to JSON notation:

...
"resource_access": {
  "dummy-client": {
    "roles": [...],
  },
},

Examples

Docker Standalone:
traefik:
    image: traefik:latest
    container_name: cx-example-traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - cx-example-net
    ports:
      - 80:80
      - 443:443
    volumes:
      - /etc/localtime:/etc/localtime:ro
      # - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/traefik.toml:/etc/traefik/traefik.toml:ro
      - ./traefik/services.toml:/etc/traefik/services.toml:ro
      - ./traefik/acme.json:/etc/traefik/acme.json
      - ./logs/traefik-access.log:/traefik-access.log
      - ./logs/traefik-service.log:/traefik-service.log
    labels:
      - "traefik.enable=true"
      - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"

      - "traefik.http.routers.traefik.entrypoints=web"
      - "traefik.http.routers.traefik.rule=Host(`localhost`)"
      - "traefik.http.routers.traefik.middlewares=traefik-https-redirect"

      - "traefik.http.routers.traefik-secure.entrypoints=websecure"
      - "traefik.http.routers.traefik-secure.rule=Host(`localhost`)"
      - "traefik.http.routers.traefik-secure.tls=true"
      - "traefik.http.routers.traefik-secure.tls.certresolver=hypercpq"
      - "traefik.http.routers.traefik-secure.service=api@internal"
      - "traefik.http.routers.traefik-secure.middlewares=traefik-forward-auth"

  # https://doc.traefik.io/traefik/providers/docker/#docker-api-access
  socket-proxy:
      image: tecnativa/docker-socket-proxy
      container_name: cx-example-socket-proxy
      restart: unless-stopped
      volumes:
        - /var/run/docker.sock:/var/run/docker.sock:ro
      environment:
        CONTAINERS: 1
      networks:
        - cx-example-net

  traefik-forward-auth:
    image: creoox/cx-traefik-forward-auth:1.1.5
    container_name: cx-example-traefik-forward-auth
    env_file:
      - ./cx-traefik-forward-auth.env
    networks:
      - cx-example-net
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=cx-example-net"
      - "traefik.http.middlewares.traefik-forward-auth.forwardauth.address=http://traefik-forward-auth:4181"
      - "traefik.http.middlewares.traefik-forward-auth.forwardauth.authResponseHeaders=X-Forwarded-User"
      - "traefik.http.services.traefik-forward-auth.loadbalancer.server.port=4181"

Docker Swarm:

Not tested -> TODO


Kubernetes:

Not implemented -> TODO


Project setup (containerized)

Prerequisites

  1. docker
  2. docker-compose
  3. [Optional & HIGHLY Recommended] GNU make (see below)

GNU make - Make use of Makefile

It is recommended to make use of make commands and in order to do so install GNU make

  • Unix/Linux -> ready-to-go more info
  • Windows (Powershell) -> install chocolatey and then run choco install make in Powershell
  • MacOS -> for most recent versions you should be ready-to-go, if not try installing it with homebrew

Environments setup

Mind that all below commands can be run natively using docker-compose (not recommended, see Makefile for details)


Development Environment:

Prepare development environment

make build-dev-env

Run development environment

make run-dev-env

Run unit tests (in separate container)

make run-unit-tests

Run unit tests with coverage HTML-report (in separate container)

make run-ut-coverage-html

Run lint check (in separate container)

make run-lint-check

Shut down and clean development environment

make down-dev-env

Production Environment:

Prepare production environment

make pull-prod-env

You may use make build-prod-env for environment build, mind that it's meant for developers only!

Run production environment

make run-prod-env

Shut down and clean production environment

make down-prod-env

cx-traefik-forward-auth's People

Contributors

and-ratajski avatar creooxtech avatar damianbobrowski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

guidorugo

cx-traefik-forward-auth's Issues

[OPTIONS] Restrict preflight for defined origins (clients) only

Allow only selected origins (clients) to pass their OPTIONS requests through cx-treafik-forward-auth. For the time being, if the AUTH_ALLOW_UNSEC_OPTIONS is set to true all OPTIONS requests are accepted.

Ref:

* TODO: Restrict preflight for defined origins (clients) only.
*/
if (AUTH_ALLOW_UNSEC_OPTIONS) {
app.use(
async (req: Request, res: Response, next: NextFunction): Promise<void> => {
if (req.headers["x-forwarded-method"] === "OPTIONS") {
logger.debug(
`Detected OPTIONS request from ${req.url} - passing through!`
);
res.sendStatus(200);
return;
} else {
next();
}
}
);
}

Works on Okta

This is not a bug. There is no option to create a "discussion", so I'm posting it here.
Just want to say that using Okta as provider works. I have it deployed in EKS using terraform. Here is an example

variable "cx_traefik_forward_authimage_source" {
  type = string
  description = "Container source"
  default = "creoox/cx_traefik_forward_auth:1.1.5"
}

resource "kubernetes_deployment" "cx_traefik_forward_auth" {
  wait_for_rollout = true
  metadata {
    annotations      = {}
    labels           = {
        "app" = var.cx_traefik_forward_auth_name
    }
    name             = var.cx_traefik_forward_auth_name
    namespace        = kubernetes_namespace.traefik.metadata[0].name
  }
  spec {
    min_ready_seconds         = 0
    paused                    = false
    progress_deadline_seconds = 300
    replicas                  = "1"
    selector {
      match_labels = {
        "app" = var.cx_traefik_forward_auth_name
      }
    }
    strategy {
      type = "Recreate"
    }
    template {
      metadata {
        annotations = {}
        labels      = {
          "app" = var.cx_traefik_forward_auth_name
        }
      }
      spec {
        automount_service_account_token  = false
        enable_service_links             = false
        termination_grace_period_seconds = 60
        restart_policy                   = "Always"
        container {
          image                      = var.cx_traefik_forward_auth_image_source
          image_pull_policy          = "IfNotPresent"
          name                       = var.cx_traefik_forward_auth_name
          env {
            name  = "OIDC_ISSUER_URL"
            value_from {
              secret_key_ref {
                key      = "issuer-url"
                name     = var.cx_traefik_forward_auth_name
                optional = false
              }
            }
          }
          env {
            name = "OIDC_CLIENT_ID"
            value_from {
              secret_key_ref {
                key      = "client-id"
                name     = var.cx_traefik_forward_auth_name
                optional = false
              }
            }
          }
          env {
            name = "OIDC_CLIENT_SECRET"
            value_from {
              secret_key_ref {
                key      = "client-secret"
                name     = var.cx_traefik_forward_auth_name
                optional = false
              }
            }
          }
          env {
            name  = "OIDC_VERIFICATION_TYPE"
            value = "jwt"
          }
          env {
            name  = "JWT_STRICT_AUDIENCE"
            value = false
          }
          env {
            name  = "ENVIRONMENT"
            value = "development"
          }
          env {
            name  = "HOST_URI"
            value = "https://<URI>"
          }
          env {
            name  = "LOGIN_WHEN_NO_TOKEN"
            value = false
          }
          env {
            name  = "LOGIN_SCOPE"
            value_from {
              secret_key_ref {
                key      = "scope"
                name     = var.cx_traefik_forward_auth_name
                optional = false
              }
            }          
          }
          port {
            container_port = 4181
            protocol       = "TCP"
          }
          resources {
            limits   = {}
            requests = {}
          }
        }
        volume {
          name = var.cx_traefik_forward_auth_name
          secret {
            default_mode = "0644"
            optional     = false
            secret_name  = kubernetes_secret.cx_traefik_forward_authsecrets.metadata[0].name
          }
        }
      }
    }
  }
  depends_on = [ kubernetes_secret.cx_traefik_forward_auth_secrets, kubernetes_namespace.traefik ]
}

resource "kubernetes_service" "cx_traefik_forward_auth" {
  metadata {
    name      = var.cx_traefik_forward_auth_name
    namespace = kubernetes_namespace.traefik.metadata[0].name
    labels           = {
        "app" = var.cx_traefik_forward_auth_name
    }
  }
  spec {
    selector = {
      app = var.cx_traefik_forward_auth_name
    }
    session_affinity = "None"
    port {
      port        = 4181
      target_port = 4181
    }
    type = "ClusterIP"
  }
}

resource "kubernetes_manifest" "cx_traefik_forward_auth" {
  manifest = {
    apiVersion = "traefik.io/v1alpha1"
    kind       = "Middleware"
    metadata   = {
      name      = var.cx_traefik_forward_auth_name
      namespace = kubernetes_namespace.traefik.metadata[0].name
    }
    spec = {
      forwardAuth = {
        address = "http://${var.cx_traefik_forward_auth_name}:4181"
        authResponseHeaders = [ "X-Forwarded-User" ]
      }
    }
  }
  depends_on = [ helm_release.traefik, kubernetes_deployment.cx_traefik_forward_auth ]
}

resource "kubernetes_manifest" "api_ingressroute" {
  manifest = {
    "apiVersion"    = "traefik.containo.us/v1alpha1"
    "kind"          = "IngressRoute"
    "metadata"      = {
      "name" = var.cx_traefik_forward_auth_name
      "namespace" = kubernetes_namespace.traefik.metadata[0].name
    }
    "spec"          = {
      "entryPoints" = [
        "websecure"
      ]
      "routes" = [
        {
          "match"     = "Host(`<API endpoint>`)"
          "kind"      = "Rule"
          "services"  = [
            {
              "name" = "<service name>"
              "port" = <service port>
            }
          ]
          "middlewares" = [
            {
              "name"      = var.cx_traefik_forward_auth_name
              "namespace" = kubernetes_namespace.traefik.metadata[0].name
            }
          ]
          "tls" = {
          }
        }
      ]
    }
  }
  depends_on = [ helm_release.traefik, kubernetes_deployment.cx_traefik_forward_auth ]
}

resource "kubernetes_secret" "cx_traefik_forward_auth_secrets" {
  metadata {
    name      = var.cx_traefik_forward_auth_name
    namespace = kubernetes_namespace.traefik.metadata[0].name
  }

  data = {
    client-id = "<client id>"
    client-secret = "<client secret>"
    issuer-url = "<full URL with issuer - https://okta/oauth2/*REALM* >"
    scope = "<scope>"
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.