Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom key to be used for whitelist and X-Forwarded-User instead of the hardcoded email #1

Merged
merged 28 commits into from
Nov 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1b7d054
init commit
maxisme Aug 1, 2020
bcadb3a
add github workflow
maxisme Aug 1, 2020
b290b21
fix naming
maxisme Aug 1, 2020
f041759
fix missing param
maxisme Aug 1, 2020
0e2bb23
upgrade Go version to 1.14
maxisme Aug 1, 2020
b0f7b17
tmp remove of tests
maxisme Aug 1, 2020
c792263
add more specific error message
maxisme Aug 1, 2020
189e4a1
put back tests
maxisme Aug 1, 2020
399f3da
rename User ID Key to User ID Path
maxisme Aug 1, 2020
40bd110
upgrade dependencies
maxisme Aug 1, 2020
2a2d542
Revert "upgrade dependencies"
maxisme Aug 1, 2020
22aa772
Revert "upgrade dependencies"
maxisme Aug 1, 2020
d9c2ec2
mention the user that is not authorized
maxisme Aug 1, 2020
cb02259
mention the user that is not authorized
maxisme Aug 1, 2020
c77e649
tidy error message
maxisme Aug 3, 2020
4b554e7
tidy error message
maxisme Aug 3, 2020
c7f5f0a
remove actions
maxisme Aug 22, 2020
fb70085
rename UserIDPath to UserID
maxisme Aug 22, 2020
42b3750
rename GetUsedID function to GetUser
maxisme Aug 22, 2020
a33a869
revert docker golang version to 1.13
Jan 12, 2021
9ea7d98
change whitelist comment to indicate userIDs instead of explicitly em…
Jan 12, 2021
49439c7
revert go version
Jan 12, 2021
4906a18
Merge branch 'master' of https://github.com/thomseddon/traefik-forwar…
Jan 12, 2021
4091bb1
fix conflicts
Jan 12, 2021
58d555c
add tests
Jan 12, 2021
dc20081
push to docker for testing
Jan 13, 2021
a98e568
Merge pull request #1 from maxisme/fix-conflicts
maxisme Jan 15, 2021
740389b
Merge branch 'master' into maxisme
jordemort Nov 5, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Traefik Forward Auth
on: [push]
jobs:
test:
name: Test with Go version -
runs-on: ubuntu-latest

strategy:
matrix:
go: ['1.12', '1.13', '1.14']

steps:
- uses: actions/checkout@v1

- name: Setup Go
uses: actions/setup-go@v1
with:
go-version: ${{ matrix.go }}

- name: Run Tests
run: go test ./...

publish:
name: Publish Docker image
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
with:
fetch-depth: '0'
- name: Publish to Docker Registry
uses: docker/build-push-action@v1
with:
repository: ${{ github.repository }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
tag_with_ref: true
tag_with_sha: true

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,12 @@ Application Options:
--csrf-cookie-name= CSRF Cookie Name (default: _forward_auth_csrf) [$CSRF_COOKIE_NAME]
--default-action=[auth|allow] Default action (default: auth) [$DEFAULT_ACTION]
--default-provider=[google|oidc|generic-oauth] Default provider (default: google) [$DEFAULT_PROVIDER]
--domain= Only allow given email domains, can be set multiple times [$DOMAIN]
--domain= Only allow given email domains, comma separated, can be set multiple times [$DOMAIN]
--lifetime= Lifetime in seconds (default: 43200) [$LIFETIME]
--logout-redirect= URL to redirect to following logout [$LOGOUT_REDIRECT]
--url-path= Callback URL Path (default: /_oauth) [$URL_PATH]
--secret= Secret used for signing (required) [$SECRET]
--whitelist= Only allow given email addresses, can be set multiple times [$WHITELIST]
--whitelist= Only allow given user ID, comma separated, can be set multiple times [$WHITELIST]
--port= Port to listen on (default: 4181) [$PORT]
--rule.<name>.<param>= Rule definitions, param can be: "action", "rule" or "provider"

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/thomseddon/traefik-forward-auth
go 1.13

require (
github.com/Jeffail/gabs/v2 v2.5.1
github.com/containous/traefik/v2 v2.1.2
github.com/coreos/go-oidc v2.1.0+incompatible
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/ExpediaDotCom/haystack-client-go v0.0.0-20190315171017-e7edbdf53a61/go.mod h1:62qWSDaEI0BLykU+zQza5CAKgW0lOy9oBSz3/DvYz4w=
github.com/Jeffail/gabs/v2 v2.5.1 h1:ANfZYjpMlfTTKebycu4X1AgkVWumFVDYQl7JwOr4mDk=
github.com/Jeffail/gabs/v2 v2.5.1/go.mod h1:xCn81vdHKxFUuWWAaD5jCTQDNPBMh5pPs9IJ+NcziBI=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.20.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
Expand Down
26 changes: 13 additions & 13 deletions internal/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
// Request Validation

// ValidateCookie verifies that a cookie matches the expected format of:
// Cookie = hash(secret, cookie domain, email, expires)|expires|email
// Cookie = hash(secret, cookie domain, user, expires)|expires|user
func ValidateCookie(r *http.Request, c *http.Cookie) (string, error) {
parts := strings.Split(c.Value, "|")

Expand Down Expand Up @@ -56,10 +56,10 @@ func ValidateCookie(r *http.Request, c *http.Cookie) (string, error) {
return parts[2], nil
}

// ValidateEmail checks if the given email address matches either a whitelisted
// email address, as defined by the "whitelist" config parameter. Or is part of
// ValidateUser checks if the given user matches either a whitelisted
// user, as defined by the "whitelist" config parameter. Or is part of
// a permitted domain, as defined by the "domains" config parameter
func ValidateEmail(email, ruleName string) bool {
func ValidateUser(user, ruleName string) bool {
// Use global config by default
whitelist := config.Whitelist
domains := config.Domains
Expand All @@ -79,7 +79,7 @@ func ValidateEmail(email, ruleName string) bool {

// Email whitelist validation
if len(whitelist) > 0 {
if ValidateWhitelist(email, whitelist) {
if ValidateWhitelist(user, whitelist) {
return true
}

Expand All @@ -90,26 +90,26 @@ func ValidateEmail(email, ruleName string) bool {
}

// Domain validation
if len(domains) > 0 && ValidateDomains(email, domains) {
if len(domains) > 0 && ValidateDomains(user, domains) {
return true
}

return false
}

// ValidateWhitelist checks if the email is in whitelist
func ValidateWhitelist(email string, whitelist CommaSeparatedList) bool {
func ValidateWhitelist(user string, whitelist CommaSeparatedList) bool {
for _, whitelist := range whitelist {
if email == whitelist {
if user == whitelist {
return true
}
}
return false
}

// ValidateDomains checks if the email matches a whitelisted domain
func ValidateDomains(email string, domains CommaSeparatedList) bool {
parts := strings.Split(email, "@")
func ValidateDomains(user string, domains CommaSeparatedList) bool {
parts := strings.Split(user, "@")
if len(parts) < 2 {
return false
}
Expand Down Expand Up @@ -162,10 +162,10 @@ func useAuthDomain(r *http.Request) (bool, string) {
// Cookie methods

// MakeCookie creates an auth cookie
func MakeCookie(r *http.Request, email string) *http.Cookie {
func MakeCookie(r *http.Request, user string) *http.Cookie {
expires := cookieExpiry()
mac := cookieSignature(r, email, fmt.Sprintf("%d", expires.Unix()))
value := fmt.Sprintf("%s|%d|%s", mac, expires.Unix(), email)
mac := cookieSignature(r, user, fmt.Sprintf("%d", expires.Unix()))
value := fmt.Sprintf("%s|%d|%s", mac, expires.Unix(), user)

return &http.Cookie{
Name: config.CookieName,
Expand Down
80 changes: 48 additions & 32 deletions internal/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,54 +62,66 @@ func TestAuthValidateCookie(t *testing.T) {
assert.Equal("[email protected]", email, "valid request should return user email")
}

func TestAuthValidateEmail(t *testing.T) {
func TestAuthValidateUser(t *testing.T) {
assert := assert.New(t)
config, _ = NewConfig([]string{})

// Should allow any with no whitelist/domain is specified
v := ValidateEmail("[email protected]", "default")
v := ValidateUser("[email protected]", "default")
assert.True(v, "should allow any domain if email domain is not defined")
v = ValidateEmail("[email protected]", "default")
v = ValidateUser("[email protected]", "default")
assert.True(v, "should allow any domain if email domain is not defined")

// Should allow matching domain
config.Domains = []string{"test.com"}
v = ValidateEmail("[email protected]", "default")
v = ValidateUser("[email protected]", "default")
assert.False(v, "should not allow user from another domain")
v = ValidateEmail("[email protected]", "default")
v = ValidateUser("[email protected]", "default")
assert.True(v, "should allow user from allowed domain")

// Should block non whitelisted email address
config.Domains = []string{}
config.Whitelist = []string{"[email protected]"}
v = ValidateUser("[email protected]", "default")
assert.False(v, "should not allow user not in whitelist")

// Should allow matching whitelisted email address
config.Domains = []string{}
config.Whitelist = []string{"[email protected]"}
v = ValidateEmail("[email protected]", "default")
v = ValidateUser("[email protected]", "default")
assert.False(v, "should not allow user not in whitelist")
v = ValidateEmail("[email protected]", "default")
v = ValidateUser("[email protected]", "default")
assert.True(v, "should allow user in whitelist")

// Should allow only matching email address when
// MatchWhitelistOrDomain is disabled
config.Domains = []string{"example.com"}
config.Whitelist = []string{"[email protected]"}
config.MatchWhitelistOrDomain = false
v = ValidateEmail("[email protected]", "default")
v = ValidateUser("[email protected]", "default")
assert.True(v, "should allow user in whitelist")
v = ValidateUser("[email protected]", "default")
assert.False(v, "should not allow user from valid domain")
v = ValidateUser("[email protected]", "default")
assert.False(v, "should not allow user not in either")
v = ValidateEmail("[email protected]", "default")
v = ValidateUser("[email protected]", "default")
assert.False(v, "should not allow user from allowed domain")
v = ValidateEmail("[email protected]", "default")
v = ValidateUser("[email protected]", "default")
assert.True(v, "should allow user in whitelist")

// Should allow either matching domain or email address when
// MatchWhitelistOrDomain is enabled
config.Domains = []string{"example.com"}
config.Whitelist = []string{"[email protected]"}
config.MatchWhitelistOrDomain = true
v = ValidateEmail("[email protected]", "default")
v = ValidateUser("[email protected]", "default")
assert.False(v, "should not allow user not in either")
v = ValidateEmail("[email protected]", "default")
v = ValidateUser("[email protected]", "default")
assert.True(v, "should allow user from allowed domain")
v = ValidateEmail("[email protected]", "default")
v = ValidateUser("[email protected]", "default")
assert.True(v, "should allow user in whitelist")
v = ValidateUser("[email protected]", "default")
assert.True(v, "should allow user from valid domain")

// Rule testing

Expand All @@ -118,11 +130,11 @@ func TestAuthValidateEmail(t *testing.T) {
config.Whitelist = []string{"[email protected]"}
config.Rules = map[string]*Rule{"test": NewRule()}
config.MatchWhitelistOrDomain = true
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.False(v, "should not allow user not in either")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.True(v, "should allow user from allowed global domain")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.True(v, "should allow user in global whitelist")

// Should allow matching domain in rule
Expand All @@ -132,25 +144,29 @@ func TestAuthValidateEmail(t *testing.T) {
config.Rules = map[string]*Rule{"test": rule}
rule.Domains = []string{"testrule.com"}
config.MatchWhitelistOrDomain = false
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.False(v, "should not allow user from another domain")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.False(v, "should not allow user from global domain")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.True(v, "should allow user from allowed domain")

// Should allow comma separated email
config.Whitelist = []string{"[email protected]", "[email protected]"}
v = ValidateUser("[email protected]", "default")

// Should allow matching whitelist in rule
config.Domains = []string{}
config.Whitelist = []string{"[email protected]"}
rule = NewRule()
config.Rules = map[string]*Rule{"test": rule}
rule.Whitelist = []string{"[email protected]"}
config.MatchWhitelistOrDomain = false
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.False(v, "should not allow user from another domain")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.False(v, "should not allow user from global domain")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.True(v, "should allow user from allowed domain")

// Should allow only matching email address when
Expand All @@ -162,15 +178,15 @@ func TestAuthValidateEmail(t *testing.T) {
rule.Domains = []string{"examplerule.com"}
rule.Whitelist = []string{"[email protected]"}
config.MatchWhitelistOrDomain = false
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.False(v, "should not allow user not in either")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.False(v, "should not allow user in global whitelist")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.False(v, "should not allow user from global domain")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.False(v, "should not allow user from allowed domain")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.True(v, "should allow user in whitelist")

// Should allow either matching domain or email address when
Expand All @@ -182,15 +198,15 @@ func TestAuthValidateEmail(t *testing.T) {
rule.Domains = []string{"examplerule.com"}
rule.Whitelist = []string{"[email protected]"}
config.MatchWhitelistOrDomain = true
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.False(v, "should not allow user not in either")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.False(v, "should not allow user in global whitelist")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.False(v, "should not allow user from global domain")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.True(v, "should allow user from allowed domain")
v = ValidateEmail("[email protected]", "test")
v = ValidateUser("[email protected]", "test")
assert.True(v, "should allow user in whitelist")
}

Expand Down
8 changes: 4 additions & 4 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ type Config struct {
CSRFCookieName string `long:"csrf-cookie-name" env:"CSRF_COOKIE_NAME" default:"_forward_auth_csrf" description:"CSRF Cookie Name"`
DefaultAction string `long:"default-action" env:"DEFAULT_ACTION" default:"auth" choice:"auth" choice:"allow" description:"Default action"`
DefaultProvider string `long:"default-provider" env:"DEFAULT_PROVIDER" default:"google" choice:"google" choice:"oidc" choice:"generic-oauth" description:"Default provider"`
Domains CommaSeparatedList `long:"domain" env:"DOMAIN" env-delim:"," description:"Only allow given email domains, can be set multiple times"`
Domains CommaSeparatedList `long:"domain" env:"DOMAIN" env-delim:"," description:"Only allow given email domains, comma separated, can be set multiple times"`
LifetimeString int `long:"lifetime" env:"LIFETIME" default:"43200" description:"Lifetime in seconds"`
LogoutRedirect string `long:"logout-redirect" env:"LOGOUT_REDIRECT" description:"URL to redirect to following logout"`
MatchWhitelistOrDomain bool `long:"match-whitelist-or-domain" env:"MATCH_WHITELIST_OR_DOMAIN" description:"Allow users that match *either* whitelist or domain (enabled by default in v3)"`
Path string `long:"url-path" env:"URL_PATH" default:"/_oauth" description:"Callback URL Path"`
SecretString string `long:"secret" env:"SECRET" description:"Secret used for signing (required)" json:"-"`
Whitelist CommaSeparatedList `long:"whitelist" env:"WHITELIST" env-delim:"," description:"Only allow given email addresses, can be set multiple times"`
UserPath string `long:"user-id-path" env:"USER_ID_PATH" default:"email" description:"Dot notation path of a UserID for use with whitelist and X-Forwarded-User"`
Whitelist CommaSeparatedList `long:"whitelist" env:"WHITELIST" env-delim:"," description:"Only allow given UserID, comma separated, can be set multiple times"`
Port int `long:"port" env:"PORT" default:"4181" description:"Port to listen on"`

Providers provider.Providers `group:"providers" namespace:"providers" env-namespace:"PROVIDERS"`
Expand Down Expand Up @@ -326,8 +327,7 @@ func (c *Config) setupProvider(name string) error {
}

// Setup
err = p.Setup()
if err != nil {
if err := p.Setup(); err != nil {
return err
}

Expand Down
11 changes: 11 additions & 0 deletions internal/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,17 @@ func TestConfigParseRuleError(t *testing.T) {
assert.Equal(map[string]*Rule{}, c.Rules)
}

func TestConfigCommaSeperated(t *testing.T) {
assert := assert.New(t)
c, err := NewConfig([]string{
"[email protected],[email protected]",
})
require.Nil(t, err)

expected1 := CommaSeparatedList{"[email protected]", "[email protected]"}
assert.Equal(expected1, c.Whitelist, "should read legacy comma separated list whitelist")
}

func TestConfigFlagBackwardsCompatability(t *testing.T) {
assert := assert.New(t)
c, err := NewConfig([]string{
Expand Down
Loading