From e34577449a01a7c3ef6593c7f4c3b81fd9259214 Mon Sep 17 00:00:00 2001 From: Leon Jacobs Date: Sun, 28 Jan 2024 13:12:47 +0200 Subject: [PATCH] (feat) add rules feature for more granular auth control --- .gitignore | 1 + README.md | 22 ++++++++++++++++++++-- config.go | 21 +++++++++++++++++++-- docker-compose.dev.yml | 5 +++++ docker-compose.yml | 7 ++++++- go.sum | 2 -- rules.go | 36 ++++++++++++++++++++++++++++++++++++ trauth.go | 8 ++++++++ version.go | 2 +- 9 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 rules.go diff --git a/.gitignore b/.gitignore index e0f3a4a..38debd8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ # other build/ +vendor/ diff --git a/README.md b/README.md index 5921253..8f01664 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ experimental: plugins: trauth: moduleName: github.com/leonjza/trauth - version: v1.4.2 # or whatever is the latest version + version: v1.5.0 # or whatever the latest version is ``` Or, add it as labels to your `docker-compose.yml` (or similar). @@ -30,7 +30,7 @@ services: image: traefik command: - --experimental.plugins.trauth.moduleName=github.com/leonjza/trauth - - --experimental.plugins.trauth.version=v1.4.2 + - --experimental.plugins.trauth.version=v1.5.0 ``` ### configuration @@ -50,6 +50,7 @@ The available configuration options are as follows: | `cookiekhttponly` | False | `false` | Use the `httponly` flag when setting the authentication cookie. | | `users` | False if `usersfile` is set | | A htpasswd formatted list of users to accept authentication for. If `usersfile` is not set, then this value must be set. | | `usersfile` | False if `users` is set | | A path to a htpasswd formatted file with a list of users to accept authentication for. If `users` is not set, then this value must be set. | +| `rules` | False | | A rules object that defines hostnames and paths where authentication requirements are skipped | #### cookiekey @@ -57,6 +58,14 @@ By default, trauth will generate a new, random value for `cookiekey` if one is n For an example, have a look a the [docker-compose.yml](docker-compose.yml) file in this repository. +#### rules + +Rules are optional and useful if you have web services that should be authenticated by default, but some parts of the service should be available without authentication (or at least, not blocked by this middleware as it may have its own authentication you want to use instead). For example, say a web application exposes an API, and you want to have that API (or parts of it), available externally. + +Rules have two configuration options. A domain, and a regular expression defining the resource (or URL path) to exclude. + +For an example, have a look a the [docker-compose.yml](docker-compose.yml) file in this repository. + #### configuration examples As dynamic configuration: @@ -75,6 +84,11 @@ http: domain: mydomain.local cookiname: sso-cookie users: admin:$$2y$$05$$fVvJElbTaB/Cw9FevNc2keGo6sMRhY2e55..U.6zOsca3rQuuAU1e + rules: + - domain: service.mydomain.local + excludes: + - ^/api/v1/.*$ + - ^/api/v2/.*$ ``` As labels: @@ -83,6 +97,10 @@ As labels: traefik.http.routers.myservice.middlewares: sso traefik.http.middlewares.sso.plugin.trauth.domain: mydomain.local traefik.http.middlewares.sso.plugin.trauth.cookiename: sso-cookie +# optional rules +traefik.http.middlewares.sso.plugin.trauth.rules[0].domain: service.mydomain.local +traefik.http.middlewares.sso.plugin.trauth.rules[0].excludes[0].exclude: ^/api/v1/.*$ +traefik.http.middlewares.sso.plugin.trauth.rules[0].excludes[1].exclude: ^/api/v2/.*$ # *note* the double $$ here to escape a single $ traefik.http.middlewares.sso.plugin.trauth.users: admin:$$2y$$05$$fVvJElbTaB/Cw9FevNc2keGo6sMRhY2e55..U.6zOsca3rQuuAU1e ``` diff --git a/config.go b/config.go index 2ea4d12..470ac72 100644 --- a/config.go +++ b/config.go @@ -2,6 +2,7 @@ package trauth import ( "fmt" + "regexp" "strings" "github.com/gorilla/securecookie" @@ -16,6 +17,9 @@ type Config struct { Users string `yaml:"users"` UsersFile string `yaml:"usersfile"` + // The rules engine, used to bypass auth + Rules []Rule `yaml:"rules"` + // Values with internal defaults CookieName string `yaml:"cookiename"` CookiePath string `yaml:"cookiepath"` @@ -71,9 +75,22 @@ func (c *Config) Validate() error { Secure: c.CookieSecure, } + // process rules by compiling the provided regular expressions + for ridx, rule := range c.Rules { + for sidx, res := range rule.Excludes { + rex, err := regexp.Compile(res.Exclude) + if err != nil { + return fmt.Errorf("failed to compile rule regex '%s' for domain %s", res.Exclude, rule.Domain) + } + + // assign the compiled regex to the struct + c.Rules[ridx].Excludes[sidx].Regex = rex + } + } + // htpasswd setup if c.Users != "" && c.UsersFile != "" { - return fmt.Errorf("both users and usersfile is set for '%s'", c.Domain) + return fmt.Errorf("both users and usersfile are set for '%s'", c.Domain) } if c.Users != "" { @@ -88,7 +105,7 @@ func (c *Config) Validate() error { return nil } - // finally, process a usersfile + // process a usersfile if c.UsersFile == "" { return fmt.Errorf("'%s' does not have a users / usersfile configuration", c.Domain) } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 3def592..2358561 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,3 +1,4 @@ +--- version: "3" # example docker-compose to demonstrate how to use trauth. @@ -46,6 +47,10 @@ services: traefik.http.middlewares.local-sso.plugin.trauth.cookiename: sso-cookie # *note* the double $$ here to escape a single $ traefik.http.middlewares.local-sso.plugin.trauth.users: admin:$$2y$$05$$fVvJElbTaB/Cw9FevNc2keGo6sMRhY2e55..U.6zOsca3rQuuAU1e + # skip authentication for this service matching the domain and the /test/* or /another-test/* paths + traefik.http.middlewares.local-sso.plugin.trauth.rules[0].domain: whoami-1.dev.local + traefik.http.middlewares.local-sso.plugin.trauth.rules[0].excludes[0].exclude: ^/test/.*$ + traefik.http.middlewares.local-sso.plugin.trauth.rules[0].excludes[1].exclude: ^/another-test/.*$ whoami-2: image: traefik/whoami diff --git a/docker-compose.yml b/docker-compose.yml index 0fb5aff..3b7387e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,4 @@ +--- version: "3" # example docker-compose to demonstrate how to use trauth. @@ -14,7 +15,7 @@ services: - --providers.docker.exposedByDefault=false - --entryPoints.web.address=:80 - --experimental.plugins.trauth.modulename=github.com/leonjza/trauth - - --experimental.plugins.trauth.version=v1.4.2 # or whatever is the latest + - --experimental.plugins.trauth.version=v1.5.0 # or whatever the latest version is labels: traefik.enable: true traefik.http.routers.dashboard.entrypoints: web @@ -46,6 +47,10 @@ services: traefik.http.middlewares.local-sso.plugin.trauth.cookiename: sso-cookie # *note* the double $$ here to escape a single $ traefik.http.middlewares.local-sso.plugin.trauth.users: admin:$$2y$$05$$fVvJElbTaB/Cw9FevNc2keGo6sMRhY2e55..U.6zOsca3rQuuAU1e + # skip authentication for this service matching the domain and the /test/* or /another-test/* paths + "traefik.http.middlewares.local-sso.plugin.trauth.rules[0].domain": whoami-1.dev.local + "traefik.http.middlewares.local-sso.plugin.trauth.rules[0].excludes[0].exclude": ^/test/.*$ + "traefik.http.middlewares.local-sso.plugin.trauth.rules[0].excludes[1].exclude": ^/another-test/.*$ whoami-2: image: traefik/whoami diff --git a/go.sum b/go.sum index bdba391..1cc3d0e 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/tg123/go-htpasswd v1.2.2 h1:tmNccDsQ+wYsoRfiONzIhDm5OkVHQzN3w4FOBAlN6BY= github.com/tg123/go-htpasswd v1.2.2/go.mod h1:FcIrK0J+6zptgVwK1JDlqyajW/1B4PtuJ/FLWl7nx8A= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/rules.go b/rules.go new file mode 100644 index 0000000..8a12a87 --- /dev/null +++ b/rules.go @@ -0,0 +1,36 @@ +package trauth + +import ( + "net/http" + "regexp" +) + +type Exclude struct { + Exclude string `yaml:"exclude"` + Regex *regexp.Regexp +} + +// Rule defines a trauth rule to exclude authentication +type Rule struct { + Domain string `yaml:"domain"` + Excludes []Exclude `yaml:"excludes"` +} + +func skipViaRule(rules []Rule, req *http.Request) bool { + for _, rule := range rules { + + // skip processing rules for domains that dont match + if req.Host != rule.Domain { + continue + } + + // check paths in rules to see if a regexp matches the url path + for _, res := range rule.Excludes { + if res.Regex.MatchString(req.URL.Path) { + return true + } + } + } + + return false +} diff --git a/trauth.go b/trauth.go index f56e700..c945801 100644 --- a/trauth.go +++ b/trauth.go @@ -42,6 +42,14 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h func (t *Trauth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if skipViaRule(t.config.Rules, req) { + t.logger.Printf("skipping auth due to exclusion rule matching the domain and path") + t.next.ServeHTTP(rw, req) + + return + } + + // continue with authentication checks user := getUser(t.config, req) if auth := user.Authenticated; !auth { diff --git a/version.go b/version.go index dea8e16..56f901e 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package trauth -const version = `1.4.3` +const version = `1.5.0`