10 Commits

Author SHA1 Message Date
3e92400202 Fix backwards compat on "domain" config + remove "domains" config
Fixes #48
2019-06-11 13:14:29 +01:00
72fc88a82b Add extra tests for env var backwards compat 2019-06-11 10:08:47 +01:00
2c148d3a23 Add releases info to README 2019-06-10 12:19:53 +01:00
d33ecc0654 Make rule parsing more robust
- check args length before popping
- ensure rule has name
2019-06-10 11:38:50 +01:00
41a3f2a5a9 Fix missing client id/secret log message 2019-06-10 11:24:14 +01:00
5a17187855 Fix go-flags dep + formatting 2019-05-13 11:56:43 +01:00
e7b567bc92 Fix typos. Inspired by #43 2019-05-13 11:27:31 +01:00
a4a34dcd76 Handle unknown ini options 2019-05-07 19:17:42 +01:00
d1b12e4ffb Fix host/method rule matching + tests 2019-05-07 14:16:38 +01:00
6f3ac5efe5 pre-release logging + docs improvements and fixes 2019-05-07 12:05:47 +01:00
9 changed files with 302 additions and 52 deletions

View File

@ -4,7 +4,6 @@
A minimal forward authentication service that provides Google oauth based login and authentication for the [traefik](https://github.com/containous/traefik) reverse proxy/load balancer.
## Why?
- Seamlessly overlays any http service with a single endpoint (see: `url-path` in [Configuration](#configuration))
@ -16,6 +15,7 @@ A minimal forward authentication service that provides Google oauth based login
# Contents
- [Releases](#releases)
- [Usage](#usage)
- [Simple](#simple)
- [Advanced](#advanced)
@ -32,6 +32,16 @@ A minimal forward authentication service that provides Google oauth based login
- [Copyright](#copyright)
- [License](#license)
## Releases
We recommend using the `2` tag on docker hub.
You can also use the latest incremental releases found on [docker hub](https://hub.docker.com/r/thomseddon/traefik-forward-auth/tags) and [github](https://github.com/thomseddon/traefik-forward-auth/releases).
#### Upgrade Guide
v2 was released in June 2019, whilst this is fully backwards compatible, a number of configuration options were modified, please see the [upgrade guide](https://github.com/thomseddon/traefik-forward-auth/wiki/v2-Upgrade-Guide) to prevent warnings on startup and ensure you are using the current configuration.
## Usage
#### Simple:
@ -96,10 +106,6 @@ Create a new project then search for and select "Credentials" in the search bar.
Click "Create Credentials" > "OAuth client ID". Select "Web Application", fill in the name of your app, skip "Authorized JavaScript origins" and fill "Authorized redirect URIs" with all the domains you will allow authentication from, appended with the `url-path` (e.g. https://app.test.com/_oauth)
#### Upgrade Guide
v2 was released in April 2019, whilst this is fully backwards compatibile, a number of configuration options were modified, please see the [upgrade guide](https://github.com/thomseddon/traefik-forward-auth/wiki/v2-Upgrade-Guide) to prevent warnings on startup and ensure you are using the current configuration.
## Configuration
### Overview

1
go.mod
View File

@ -25,6 +25,7 @@ require (
github.com/sirupsen/logrus v1.4.1
github.com/stretchr/objx v0.2.0 // indirect
github.com/stretchr/testify v1.3.0
github.com/thomseddon/go-flags v1.4.1-0.20190507184247-a3629c504486
github.com/vulcand/predicate v1.1.0 // indirect
golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd // indirect
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6 // indirect

6
go.sum
View File

@ -55,6 +55,12 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/thomseddon/go-flags v1.4.0 h1:cHj56pbnQxlGo2lx2P8f0Dph4TRYKBJzoPuF2lqNvW4=
github.com/thomseddon/go-flags v1.4.0/go.mod h1:NK9eZpNBmSKVxvyB/MExg6jW0Bo9hQyAuCP+b8MJFow=
github.com/thomseddon/go-flags v1.4.1-0.20190507181358-ce437f05b7fb h1:L311/fJ7WXmFDDtuhf22PkVJqZpqLbEsmGSTEGv7ZQY=
github.com/thomseddon/go-flags v1.4.1-0.20190507181358-ce437f05b7fb/go.mod h1:NK9eZpNBmSKVxvyB/MExg6jW0Bo9hQyAuCP+b8MJFow=
github.com/thomseddon/go-flags v1.4.1-0.20190507184247-a3629c504486 h1:hk17f4niAl4e6viTj2uf/fpfACa6QPmrtMDAo+1tifE=
github.com/thomseddon/go-flags v1.4.1-0.20190507184247-a3629c504486/go.mod h1:NK9eZpNBmSKVxvyB/MExg6jW0Bo9hQyAuCP+b8MJFow=
github.com/vulcand/predicate v1.1.0 h1:Gq/uWopa4rx/tnZu2opOSBqHK63Yqlou/SzrbwdJiNg=
github.com/vulcand/predicate v1.1.0/go.mod h1:mlccC5IRBoc2cIFmCB8ZM62I3VDb6p2GXESMHa3CnZg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View File

@ -14,7 +14,7 @@ import (
"strings"
"time"
"github.com/jessevdk/go-flags"
"github.com/thomseddon/go-flags"
"github.com/thomseddon/traefik-forward-auth/internal/provider"
)
@ -31,7 +31,7 @@ type Config struct {
CookieName string `long:"cookie-name" env:"COOKIE_NAME" default:"_forward_auth" description:"Cookie Name"`
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"`
Domains []string `long:"domain" env:"DOMAIN" description:"Only allow given email domains, can be set multiple times"`
Domains CommaSeparatedList `long:"domain" env:"DOMAIN" description:"Only allow given email domains, can be set multiple times"`
LifetimeString int `long:"lifetime" env:"LIFETIME" default:"43200" description:"Lifetime in seconds"`
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:"-"`
@ -41,17 +41,16 @@ type Config struct {
Rules map[string]*Rule `long:"rules.<name>.<param>" description:"Rule definitions, param can be: \"action\" or \"rule\""`
// Filled during transformations
Secret []byte `json:"-"`
Secret []byte `json:"-"`
Lifetime time.Duration
// Legacy
CookieDomainsLegacy CookieDomains `long:"cookie-domains" env:"COOKIE_DOMAINS" description:"DEPRECATED - Use \"cookie-domain\""`
CookieSecretLegacy string `long:"cookie-secret" env:"COOKIE_SECRET" description:"DEPRECATED - Use \"secret\"" json:"-"`
CookieSecureLegacy string `long:"cookie-secure" env:"COOKIE_SECURE" description:"DEPRECATED - Use \"insecure-cookie\""`
DomainsLegacy CommaSeparatedList `long:"domains" env:"DOMAINS" description:"DEPRECATED - Use \"domain\""`
ClientIdLegacy string `long:"client-id" env:"CLIENT_ID" group:"DEPs" description:"DEPRECATED - Use \"providers.google.client-id\""`
ClientSecretLegacy string `long:"client-secret" env:"CLIENT_SECRET" description:"DEPRECATED - Use \"providers.google.client-id\"" json:"-"`
PromptLegacy string `long:"prompt" env:"PROMPT" description:"DEPRECATED - Use \"providers.google.prompt\""`
CookieDomainsLegacy CookieDomains `long:"cookie-domains" env:"COOKIE_DOMAINS" description:"DEPRECATED - Use \"cookie-domain\""`
CookieSecretLegacy string `long:"cookie-secret" env:"COOKIE_SECRET" description:"DEPRECATED - Use \"secret\"" json:"-"`
CookieSecureLegacy string `long:"cookie-secure" env:"COOKIE_SECURE" description:"DEPRECATED - Use \"insecure-cookie\""`
ClientIdLegacy string `long:"client-id" env:"CLIENT_ID" group:"DEPs" description:"DEPRECATED - Use \"providers.google.client-id\""`
ClientSecretLegacy string `long:"client-secret" env:"CLIENT_SECRET" description:"DEPRECATED - Use \"providers.google.client-id\"" json:"-"`
PromptLegacy string `long:"prompt" env:"PROMPT" description:"DEPRECATED - Use \"providers.google.prompt\""`
}
func NewGlobalConfig() Config {
@ -100,7 +99,7 @@ func NewConfig(args []string) (Config, error) {
// Backwards compatability
if c.CookieSecretLegacy != "" && c.SecretString == "" {
log.Warn("cookie-secret config option is deprecated, please use secret")
fmt.Println("cookie-secret config option is deprecated, please use secret")
c.SecretString = c.CookieSecretLegacy
}
if c.ClientIdLegacy != "" {
@ -110,11 +109,11 @@ func NewConfig(args []string) (Config, error) {
c.Providers.Google.ClientSecret = c.ClientSecretLegacy
}
if c.PromptLegacy != "" {
log.Warn("prompt config option is deprecated, please use providers.google.prompt")
fmt.Println("prompt config option is deprecated, please use providers.google.prompt")
c.Providers.Google.Prompt = c.PromptLegacy
}
if c.CookieSecureLegacy != "" {
log.Warn("cookie-secure config option is deprecated, please use insecure-cookie")
fmt.Println("cookie-secure config option is deprecated, please use insecure-cookie")
secure, err := strconv.ParseBool(c.CookieSecureLegacy)
if err != nil {
return c, err
@ -122,13 +121,9 @@ func NewConfig(args []string) (Config, error) {
c.InsecureCookie = !secure
}
if len(c.CookieDomainsLegacy) > 0 {
log.Warn("cookie-domains config option is deprecated, please use cookie-domain")
fmt.Println("cookie-domains config option is deprecated, please use cookie-domain")
c.CookieDomains = append(c.CookieDomains, c.CookieDomainsLegacy...)
}
if len(c.DomainsLegacy) > 0 {
log.Warn("domains config option is deprecated, please use domain")
c.Domains = append(c.Domains, c.DomainsLegacy...)
}
// Transformations
if len(c.Path) > 0 && c.Path[0] != '/' {
@ -141,7 +136,7 @@ func NewConfig(args []string) (Config, error) {
}
func (c *Config) parseFlags(args []string) error {
p := flags.NewParser(c, flags.Default)
p := flags.NewParser(c, flags.Default|flags.IniUnknownOptionHandler)
p.UnknownOptionHandler = c.parseUnknownFlag
i := flags.NewIniParser(p)
@ -157,6 +152,7 @@ func (c *Config) parseFlags(args []string) error {
return err
}
fmt.Println("config format deprecated, please use ini format")
return i.Parse(converted)
}
@ -175,16 +171,15 @@ func (c *Config) parseUnknownFlag(option string, arg flags.SplitArgument, args [
// Parse rules in the format "rule.<name>.<param>"
parts := strings.Split(option, ".")
if len(parts) == 3 && parts[0] == "rule" {
// Get or create rule
rule, ok := c.Rules[parts[1]]
if !ok {
rule = NewRule()
c.Rules[parts[1]] = rule
// Ensure there is a name
name := parts[1]
if len(name) == 0 {
return args, errors.New("route name is required")
}
// Get value, or pop the next arg
val, ok := arg.Value()
if !ok {
if !ok && len(args) > 1 {
val = args[0]
args = args[1:]
}
@ -203,6 +198,13 @@ func (c *Config) parseUnknownFlag(option string, arg flags.SplitArgument, args [
}
}
// Get or create rule
rule, ok := c.Rules[name]
if !ok {
rule = NewRule()
c.Rules[name] = rule
}
// Add param value to rule
switch parts[2] {
case "action":
@ -249,7 +251,7 @@ func (c *Config) Validate() {
}
if c.Providers.Google.ClientId == "" || c.Providers.Google.ClientSecret == "" {
log.Fatal("google.providers.client-id, google.providers.client-secret must be set")
log.Fatal("providers.google.client-id, providers.google.client-secret must be set")
}
// Check rules
@ -276,6 +278,12 @@ func NewRule() *Rule {
}
}
func (r *Rule) formattedRule() string {
// Traefik implements their own "Host" matcher and then offers "HostRegexp"
// to invoke the mux "Host" matcher. This ensures the mux version is used
return strings.ReplaceAll(r.Rule, "Host(", "HostRegexp(")
}
func (r *Rule) Validate() {
if r.Action != "auth" && r.Action != "allow" {
log.Fatal("invalid rule action, must be \"auth\" or \"allow\"")

View File

@ -98,6 +98,28 @@ func TestConfigParseUnknownFlags(t *testing.T) {
}
}
func TestConfigParseRuleError(t *testing.T) {
assert := assert.New(t)
// Rule without name
_, err := NewConfig([]string{
"--rule..action=auth",
})
if assert.Error(err) {
assert.Equal("route name is required", err.Error())
}
// Rule without value
c, err := NewConfig([]string{
"--rule.one.action=",
})
if assert.Error(err) {
assert.Equal("route param value is required", err.Error())
}
// Check rules
assert.Equal(map[string]*Rule{}, c.Rules)
}
func TestConfigFlagBackwardsCompatability(t *testing.T) {
assert := assert.New(t)
c, err := NewConfig([]string{
@ -109,7 +131,7 @@ func TestConfigFlagBackwardsCompatability(t *testing.T) {
"--cookie-secure=false",
"--cookie-domains=test1.com,example.org",
"--cookie-domain=another1.net",
"--domains=test2.com,example.org",
"--domain=test2.com,example.org",
"--domain=another2.net",
"--whitelist=test3.com,example.org",
"--whitelist=another3.net",
@ -124,7 +146,7 @@ func TestConfigFlagBackwardsCompatability(t *testing.T) {
}
assert.Equal(expected1, c.CookieDomains, "should read legacy comma separated list cookie-domains")
expected2 := []string{"another2.net", "test2.com", "example.org"}
expected2 := CommaSeparatedList{"test2.com", "example.org", "another2.net"}
assert.Equal(expected2, c.Domains, "should read legacy comma separated list domains")
expected3 := CommaSeparatedList{"test3.com", "example.org", "another3.net"}
@ -165,6 +187,18 @@ func TestConfigParseIni(t *testing.T) {
assert.Equal("inicookiename", c.CookieName, "should be read from ini file")
assert.Equal("csrfcookiename", c.CSRFCookieName, "should be read from ini file")
assert.Equal("/two", c.Path, "variable in second ini file should override first ini file")
assert.Equal(map[string]*Rule{
"1": {
Action: "allow",
Rule: "PathPrefix(`/one`)",
Provider: "google",
},
"two": {
Action: "auth",
Rule: "Host(`two.com`) && Path(`/two`)",
Provider: "google",
},
}, c.Rules)
}
func TestConfigFileBackwardsCompatability(t *testing.T) {
@ -187,6 +221,70 @@ func TestConfigParseEnvironment(t *testing.T) {
assert.Equal("env_cookie_name", c.CookieName, "variable should be read from environment")
assert.Equal("env_client_id", c.Providers.Google.ClientId, "namespace variable should be read from environment")
os.Unsetenv("COOKIE_NAME")
os.Unsetenv("PROVIDERS_GOOGLE_CLIENT_ID")
}
func TestConfigParseEnvironmentBackwardsCompatability(t *testing.T) {
assert := assert.New(t)
vars := map[string]string{
"CLIENT_ID": "clientid",
"CLIENT_SECRET": "verysecret",
"PROMPT": "prompt",
"COOKIE_SECRET": "veryverysecret",
"LIFETIME": "200",
"COOKIE_SECURE": "false",
"COOKIE_DOMAINS": "test1.com,example.org",
"COOKIE_DOMAIN": "another1.net",
"DOMAIN": "test2.com,example.org",
"WHITELIST": "test3.com,example.org",
}
for k, v := range vars {
os.Setenv(k, v)
}
c, err := NewConfig([]string{})
require.Nil(t, err)
// The following used to be passed as comma separated list
expected1 := []CookieDomain{
*NewCookieDomain("another1.net"),
*NewCookieDomain("test1.com"),
*NewCookieDomain("example.org"),
}
assert.Equal(expected1, c.CookieDomains, "should read legacy comma separated list cookie-domains")
expected2 := CommaSeparatedList{"test2.com", "example.org"}
assert.Equal(expected2, c.Domains, "should read legacy comma separated list domains")
expected3 := CommaSeparatedList{"test3.com", "example.org"}
assert.Equal(expected3, c.Whitelist, "should read legacy comma separated list whitelist")
// Name changed
assert.Equal([]byte("veryverysecret"), c.Secret)
// Google provider params used to be top level
assert.Equal("clientid", c.ClientIdLegacy)
assert.Equal("clientid", c.Providers.Google.ClientId, "--client-id should set providers.google.client-id")
assert.Equal("verysecret", c.ClientSecretLegacy)
assert.Equal("verysecret", c.Providers.Google.ClientSecret, "--client-secret should set providers.google.client-secret")
assert.Equal("prompt", c.PromptLegacy)
assert.Equal("prompt", c.Providers.Google.Prompt, "--prompt should set providers.google.promot")
// "cookie-secure" used to be a standard go bool flag that could take
// true, TRUE, 1, false, FALSE, 0 etc. values.
// Here we're checking that format is still suppoted
assert.Equal("false", c.CookieSecureLegacy)
assert.True(c.InsecureCookie, "--cookie-secure=false should set insecure-cookie true")
c, err = NewConfig([]string{"--cookie-secure=TRUE"})
assert.Nil(err)
assert.Equal("TRUE", c.CookieSecureLegacy)
assert.False(c.InsecureCookie, "--cookie-secure=TRUE should set insecure-cookie false")
for k := range vars {
os.Unsetenv(k)
}
}
func TestConfigTransformation(t *testing.T) {

View File

@ -28,9 +28,9 @@ func (s *Server) buildRoutes() {
// Let's build a router
for name, rule := range config.Rules {
if rule.Action == "allow" {
s.router.AddRoute(rule.Rule, 1, s.AllowHandler(name))
s.router.AddRoute(rule.formattedRule(), 1, s.AllowHandler(name))
} else {
s.router.AddRoute(rule.Rule, 1, s.AuthHandler(name))
s.router.AddRoute(rule.formattedRule(), 1, s.AuthHandler(name))
}
}
@ -47,6 +47,8 @@ func (s *Server) buildRoutes() {
func (s *Server) RootHandler(w http.ResponseWriter, r *http.Request) {
// Modify request
r.Method = r.Header.Get("X-Forwarded-Method")
r.Host = r.Header.Get("X-Forwarded-Host")
r.URL, _ = url.Parse(r.Header.Get("X-Forwarded-Uri"))
// Pass to mux
@ -173,7 +175,7 @@ func (s *Server) logger(r *http.Request, rule, msg string) *logrus.Entry {
// Log request
logger.WithFields(logrus.Fields{
"rule": rule,
"rule": rule,
"headers": r.Header,
}).Debug(msg)

View File

@ -32,7 +32,7 @@ func TestServerAuthHandler(t *testing.T) {
config, _ = NewConfig([]string{})
// Should redirect vanilla request to login url
req := newHttpRequest("/foo")
req := newDefaultHttpRequest("/foo")
res, _ := doHttpRequest(req, nil)
assert.Equal(307, res.StatusCode, "vanilla request should be redirected")
@ -42,7 +42,7 @@ func TestServerAuthHandler(t *testing.T) {
assert.Equal("/o/oauth2/auth", fwd.Path, "vanilla request should be redirected to google")
// Should catch invalid cookie
req = newHttpRequest("/foo")
req = newDefaultHttpRequest("/foo")
c := MakeCookie(req, "test@example.com")
parts := strings.Split(c.Value, "|")
c.Value = fmt.Sprintf("bad|%s|%s", parts[1], parts[2])
@ -51,7 +51,7 @@ func TestServerAuthHandler(t *testing.T) {
assert.Equal(401, res.StatusCode, "invalid cookie should not be authorised")
// Should validate email
req = newHttpRequest("/foo")
req = newDefaultHttpRequest("/foo")
c = MakeCookie(req, "test@example.com")
config.Domains = []string{"test.com"}
@ -59,7 +59,7 @@ func TestServerAuthHandler(t *testing.T) {
assert.Equal(401, res.StatusCode, "invalid email should not be authorised")
// Should allow valid request email
req = newHttpRequest("/foo")
req = newDefaultHttpRequest("/foo")
c = MakeCookie(req, "test@example.com")
config.Domains = []string{}
@ -91,18 +91,18 @@ func TestServerAuthCallback(t *testing.T) {
config.Providers.Google.UserURL = userUrl
// Should pass auth response request to callback
req := newHttpRequest("/_oauth")
req := newDefaultHttpRequest("/_oauth")
res, _ := doHttpRequest(req, nil)
assert.Equal(401, res.StatusCode, "auth callback without cookie shouldn't be authorised")
// Should catch invalid csrf cookie
req = newHttpRequest("/_oauth?state=12345678901234567890123456789012:http://redirect")
req = newDefaultHttpRequest("/_oauth?state=12345678901234567890123456789012:http://redirect")
c := MakeCSRFCookie(req, "nononononononononononononononono")
res, _ = doHttpRequest(req, c)
assert.Equal(401, res.StatusCode, "auth callback with invalid cookie shouldn't be authorised")
// Should redirect valid request
req = newHttpRequest("/_oauth?state=12345678901234567890123456789012:http://redirect")
req = newDefaultHttpRequest("/_oauth?state=12345678901234567890123456789012:http://redirect")
c = MakeCSRFCookie(req, "12345678901234567890123456789012")
res, _ = doHttpRequest(req, c)
assert.Equal(307, res.StatusCode, "valid auth callback should be allowed")
@ -117,33 +117,151 @@ func TestServerDefaultAction(t *testing.T) {
assert := assert.New(t)
config, _ = NewConfig([]string{})
req := newHttpRequest("/random")
req := newDefaultHttpRequest("/random")
res, _ := doHttpRequest(req, nil)
assert.Equal(307, res.StatusCode, "request should require auth with auth default handler")
config.DefaultAction = "allow"
req = newHttpRequest("/random")
req = newDefaultHttpRequest("/random")
res, _ = doHttpRequest(req, nil)
assert.Equal(200, res.StatusCode, "request should be allowed with default handler")
}
func TestServerRoutePathPrefix(t *testing.T) {
func TestServerRouteHeaders(t *testing.T) {
assert := assert.New(t)
config, _ = NewConfig([]string{})
config.Rules = map[string]*Rule{
"web1": {
"1": {
Action: "allow",
Rule: "PathPrefix(`/api`)",
Rule: "Headers(`X-Test`, `test123`)",
},
"2": {
Action: "allow",
Rule: "HeadersRegexp(`X-Test`, `test(456|789)`)",
},
}
// Should block any request
req := newHttpRequest("/random")
req := newDefaultHttpRequest("/random")
req.Header.Add("X-Random", "hello")
res, _ := doHttpRequest(req, nil)
assert.Equal(307, res.StatusCode, "request not matching any rule should require auth")
// Should allow matching
req = newDefaultHttpRequest("/api")
req.Header.Add("X-Test", "test123")
res, _ = doHttpRequest(req, nil)
assert.Equal(200, res.StatusCode, "request matching allow rule should be allowed")
// Should allow matching
req = newDefaultHttpRequest("/api")
req.Header.Add("X-Test", "test789")
res, _ = doHttpRequest(req, nil)
assert.Equal(200, res.StatusCode, "request matching allow rule should be allowed")
}
func TestServerRouteHost(t *testing.T) {
assert := assert.New(t)
config, _ = NewConfig([]string{})
config.Rules = map[string]*Rule{
"1": {
Action: "allow",
Rule: "Host(`api.example.com`)",
},
"2": {
Action: "allow",
Rule: "HostRegexp(`sub{num:[0-9]}.example.com`)",
},
}
// Should block any request
req := newHttpRequest("GET", "https://example.com/", "/")
res, _ := doHttpRequest(req, nil)
assert.Equal(307, res.StatusCode, "request not matching any rule should require auth")
// Should allow matching request
req = newHttpRequest("GET", "https://api.example.com/", "/")
res, _ = doHttpRequest(req, nil)
assert.Equal(200, res.StatusCode, "request matching allow rule should be allowed")
// Should allow matching request
req = newHttpRequest("GET", "https://sub8.example.com/", "/")
res, _ = doHttpRequest(req, nil)
assert.Equal(200, res.StatusCode, "request matching allow rule should be allowed")
}
func TestServerRouteMethod(t *testing.T) {
assert := assert.New(t)
config, _ = NewConfig([]string{})
config.Rules = map[string]*Rule{
"1": {
Action: "allow",
Rule: "Method(`PUT`)",
},
}
// Should block any request
req := newHttpRequest("GET", "https://example.com/", "/")
res, _ := doHttpRequest(req, nil)
assert.Equal(307, res.StatusCode, "request not matching any rule should require auth")
// Should allow matching request
req = newHttpRequest("PUT", "https://example.com/", "/")
res, _ = doHttpRequest(req, nil)
assert.Equal(200, res.StatusCode, "request matching allow rule should be allowed")
}
func TestServerRoutePath(t *testing.T) {
assert := assert.New(t)
config, _ = NewConfig([]string{})
config.Rules = map[string]*Rule{
"1": {
Action: "allow",
Rule: "Path(`/api`)",
},
"2": {
Action: "allow",
Rule: "PathPrefix(`/private`)",
},
}
// Should block any request
req := newDefaultHttpRequest("/random")
res, _ := doHttpRequest(req, nil)
assert.Equal(307, res.StatusCode, "request not matching any rule should require auth")
// Should allow /api request
req = newHttpRequest("/api")
req = newDefaultHttpRequest("/api")
res, _ = doHttpRequest(req, nil)
assert.Equal(200, res.StatusCode, "request matching allow rule should be allowed")
// Should allow /private request
req = newDefaultHttpRequest("/private")
res, _ = doHttpRequest(req, nil)
assert.Equal(200, res.StatusCode, "request matching allow rule should be allowed")
req = newDefaultHttpRequest("/private/path")
res, _ = doHttpRequest(req, nil)
assert.Equal(200, res.StatusCode, "request matching allow rule should be allowed")
}
func TestServerRouteQuery(t *testing.T) {
assert := assert.New(t)
config, _ = NewConfig([]string{})
config.Rules = map[string]*Rule{
"1": {
Action: "allow",
Rule: "Query(`q=test123`)",
},
}
// Should block any request
req := newHttpRequest("GET", "https://example.com/", "/?q=no")
res, _ := doHttpRequest(req, nil)
assert.Equal(307, res.StatusCode, "request not matching any rule should require auth")
// Should allow matching request
req = newHttpRequest("GET", "https://api.example.com/", "/?q=test123")
res, _ = doHttpRequest(req, nil)
assert.Equal(200, res.StatusCode, "request matching allow rule should be allowed")
}
@ -194,8 +312,15 @@ func doHttpRequest(r *http.Request, c *http.Cookie) (*http.Response, string) {
return res, string(body)
}
func newHttpRequest(uri string) *http.Request {
r := httptest.NewRequest("", "http://example.com/", nil)
func newDefaultHttpRequest(uri string) *http.Request {
return newHttpRequest("", "http://example.com/", uri)
}
func newHttpRequest(method, dest, uri string) *http.Request {
r := httptest.NewRequest("", "http://should-use-x-forwarded.com", nil)
p, _ := url.Parse(dest)
r.Header.Add("X-Forwarded-Method", method)
r.Header.Add("X-Forwarded-Host", p.Host)
r.Header.Add("X-Forwarded-Uri", uri)
return r
}

View File

@ -1,3 +1,5 @@
cookie-name=inicookiename
csrf-cookie-name=inicsrfcookiename
url-path=one
rule.1.action=allow
rule.1.rule=PathPrefix(`/one`)

View File

@ -1 +1,3 @@
url-path=two
rule.two.action=auth
rule.two.rule=Host(`two.com`) && Path(`/two`)