Skip to content

Commit

Permalink
(feat) add rules feature for more granular auth control
Browse files Browse the repository at this point in the history
  • Loading branch information
leonjza committed Jan 28, 2024
1 parent 409dbff commit e345774
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@

# other
build/
vendor/
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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
Expand All @@ -50,13 +50,22 @@ 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

By default, trauth will generate a new, random value for `cookiekey` if one is not explicitly set, or is set to a value that is not 32 asci characters long. In many cases, this is ok, however, special consideration should be given to cases where this plugin is used in multiple places. By setting a static `cookiekey`, you garuantee that cookies across instances of the plugin can read the values within. If each instance of the plugin (where multiple instances will spawn if there are multiple unique definititions of the middleware) generated their own key, none will accept the cookie value set by another.

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:
Expand All @@ -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:
Expand All @@ -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
```
Expand Down
21 changes: 19 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package trauth

import (
"fmt"
"regexp"
"strings"

"github.com/gorilla/securecookie"
Expand All @@ -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"`
Expand Down Expand Up @@ -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 != "" {
Expand All @@ -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)
}
Expand Down
5 changes: 5 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
version: "3"

# example docker-compose to demonstrate how to use trauth.
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
version: "3"

# example docker-compose to demonstrate how to use trauth.
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
36 changes: 36 additions & 0 deletions rules.go
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 8 additions & 0 deletions trauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package trauth

const version = `1.4.3`
const version = `1.5.0`

0 comments on commit e345774

Please sign in to comment.