Compare commits
12 Commits
v2.0.0-rc1
...
v2.0.1
Author | SHA1 | Date | |
---|---|---|---|
3e92400202 | |||
72fc88a82b | |||
2c148d3a23 | |||
d33ecc0654 | |||
41a3f2a5a9 | |||
5a17187855 | |||
e7b567bc92 | |||
a4a34dcd76 | |||
d1b12e4ffb | |||
6f3ac5efe5 | |||
b0e4b6333d | |||
dd13f42ddf |
34
README.md
34
README.md
@ -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.
|
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?
|
## Why?
|
||||||
|
|
||||||
- Seamlessly overlays any http service with a single endpoint (see: `url-path` in [Configuration](#configuration))
|
- Seamlessly overlays any http service with a single endpoint (see: `url-path` in [Configuration](#configuration))
|
||||||
@ -16,7 +15,8 @@ A minimal forward authentication service that provides Google oauth based login
|
|||||||
|
|
||||||
# Contents
|
# Contents
|
||||||
|
|
||||||
- [Usage](#installation)
|
- [Releases](#releases)
|
||||||
|
- [Usage](#usage)
|
||||||
- [Simple](#simple)
|
- [Simple](#simple)
|
||||||
- [Advanced](#advanced)
|
- [Advanced](#advanced)
|
||||||
- [OAuth Configuration](#oauth-configuration)
|
- [OAuth Configuration](#oauth-configuration)
|
||||||
@ -29,6 +29,18 @@ A minimal forward authentication service that provides Google oauth based login
|
|||||||
- [Operation Modes](#operation-modes)
|
- [Operation Modes](#operation-modes)
|
||||||
- [Overlay Mode](#overlay-mode)
|
- [Overlay Mode](#overlay-mode)
|
||||||
- [Auth Host Mode](#auth-host-mode)
|
- [Auth Host Mode](#auth-host-mode)
|
||||||
|
- [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
|
## Usage
|
||||||
|
|
||||||
@ -43,7 +55,7 @@ version: '3'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:1
|
image: traefik:1.7
|
||||||
ports:
|
ports:
|
||||||
- "8085:80"
|
- "8085:80"
|
||||||
volumes:
|
volumes:
|
||||||
@ -94,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)
|
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
|
## Configuration
|
||||||
|
|
||||||
### Overview
|
### Overview
|
||||||
@ -126,9 +134,9 @@ Application Options:
|
|||||||
--rules.<name>.<param>= Rule definitions, param can be: "action" or "rule"
|
--rules.<name>.<param>= Rule definitions, param can be: "action" or "rule"
|
||||||
|
|
||||||
Google Provider:
|
Google Provider:
|
||||||
--providers.google.client-id= Client ID [$CLIENT_ID]
|
--providers.google.client-id= Client ID [$PROVIDERS_GOOGLE_CLIENT_ID]
|
||||||
--providers.google.client-secret= Client Secret [$CLIENT_SECRET]
|
--providers.google.client-secret= Client Secret [$PROVIDERS_GOOGLE_CLIENT_SECRET]
|
||||||
--providers.google.prompt= Space separated list of OpenID prompt options [$PROMPT]
|
--providers.google.prompt= Space separated list of OpenID prompt options [$PROVIDERS_GOOGLE_PROMPT]
|
||||||
|
|
||||||
Help Options:
|
Help Options:
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
@ -283,7 +291,7 @@ The authenticated user is set in the `X-Forwarded-User` header, to pass this on
|
|||||||
|
|
||||||
### Operation Modes
|
### Operation Modes
|
||||||
|
|
||||||
#### Overlay
|
#### Overlay Mode
|
||||||
|
|
||||||
Overlay is the default operation mode, in this mode the authorisation endpoint is overlayed onto any domain. By default the `/_oauth` path is used, this can be customised using the `url-path` option.
|
Overlay is the default operation mode, in this mode the authorisation endpoint is overlayed onto any domain. By default the `/_oauth` path is used, this can be customised using the `url-path` option.
|
||||||
|
|
||||||
@ -298,7 +306,7 @@ The user flow will be:
|
|||||||
|
|
||||||
As the hostname in the `redirect_uri` is dynamically generated based on the original request, every hostname must be permitted in the Google OAuth console (e.g. `www.myappp.com` would need to be added in the above example)
|
As the hostname in the `redirect_uri` is dynamically generated based on the original request, every hostname must be permitted in the Google OAuth console (e.g. `www.myappp.com` would need to be added in the above example)
|
||||||
|
|
||||||
#### Auth Host
|
#### Auth Host Mode
|
||||||
|
|
||||||
This is an optional mode of operation that is useful when dealing with a large number of subdomains, it is activated by using the `auth-host` config option (see [this example docker-compose.yml](https://github.com/thomseddon/traefik-forward-auth/blob/master/examples/docker-compose-auth-host.yml)).
|
This is an optional mode of operation that is useful when dealing with a large number of subdomains, it is activated by using the `auth-host` config option (see [this example docker-compose.yml](https://github.com/thomseddon/traefik-forward-auth/blob/master/examples/docker-compose-auth-host.yml)).
|
||||||
|
|
||||||
@ -321,6 +329,8 @@ Two criteria must be met for an `auth-host` to be used:
|
|||||||
1. Request matches given `cookie-domain`
|
1. Request matches given `cookie-domain`
|
||||||
2. `auth-host` is also subdomain of same `cookie-domain`
|
2. `auth-host` is also subdomain of same `cookie-domain`
|
||||||
|
|
||||||
|
Please note: For Auth Host mode to work, you must ensure that requests to your auth-host are routed to the traefik-forward-auth container, as demonstrated with the service labels in the [docker-compose-auth.yml](https://github.com/thomseddon/traefik-forward-auth/blob/master/examples/docker-compose-auth-host.yml) example.
|
||||||
|
|
||||||
## Copyright
|
## Copyright
|
||||||
|
|
||||||
2018 Thom Seddon
|
2018 Thom Seddon
|
||||||
|
@ -33,7 +33,7 @@ services:
|
|||||||
- AUTH_HOST=auth.yourdomain.com
|
- AUTH_HOST=auth.yourdomain.com
|
||||||
networks:
|
networks:
|
||||||
- traefik
|
- traefik
|
||||||
# When using an auth host, adding it here prompts traefik to generate certs
|
# When using an auth host, the below must be added
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- traefik.enable=true
|
||||||
- traefik.port=4181
|
- traefik.port=4181
|
||||||
|
@ -23,13 +23,15 @@ services:
|
|||||||
- "traefik.frontend.rule=Host:whoami.localhost.com"
|
- "traefik.frontend.rule=Host:whoami.localhost.com"
|
||||||
|
|
||||||
traefik-forward-auth:
|
traefik-forward-auth:
|
||||||
image: thomseddon/traefik-forward-auth
|
build: ../
|
||||||
|
command: ./traefik-forward-auth --rule.1.action=allow --rule.1.rule="Path(`/`)"
|
||||||
environment:
|
environment:
|
||||||
- PROVIDERS_GOOGLE_CLIENT_ID=your-client-id
|
- PROVIDERS_GOOGLE_CLIENT_ID=your-client-id
|
||||||
- PROVIDERS_GOOGLE_CLIENT_SECRET=your-client-secret
|
- PROVIDERS_GOOGLE_CLIENT_SECRET=your-client-secret
|
||||||
- SECRET=something-random
|
- SECRET=something-random
|
||||||
- INSECURE_COOKIE=true
|
- INSECURE_COOKIE=true
|
||||||
- DOMAIN=yourcompany.com
|
- DOMAIN=yourcompany.com
|
||||||
|
- LOG_LEVEL=debug
|
||||||
networks:
|
networks:
|
||||||
- traefik
|
- traefik
|
||||||
|
|
||||||
|
3
go.mod
3
go.mod
@ -13,7 +13,7 @@ require (
|
|||||||
github.com/go-kit/kit v0.8.0 // indirect
|
github.com/go-kit/kit v0.8.0 // indirect
|
||||||
github.com/gorilla/context v1.1.1 // indirect
|
github.com/gorilla/context v1.1.1 // indirect
|
||||||
github.com/gravitational/trace v0.0.0-20190409171327-f30095ced5ff // indirect
|
github.com/gravitational/trace v0.0.0-20190409171327-f30095ced5ff // indirect
|
||||||
github.com/jessevdk/go-flags v1.4.0
|
github.com/jessevdk/go-flags v1.4.1-0.20181221193153-c0795c8afcf4
|
||||||
github.com/jonboulle/clockwork v0.1.0 // indirect
|
github.com/jonboulle/clockwork v0.1.0 // indirect
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
@ -25,6 +25,7 @@ require (
|
|||||||
github.com/sirupsen/logrus v1.4.1
|
github.com/sirupsen/logrus v1.4.1
|
||||||
github.com/stretchr/objx v0.2.0 // indirect
|
github.com/stretchr/objx v0.2.0 // indirect
|
||||||
github.com/stretchr/testify v1.3.0
|
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
|
github.com/vulcand/predicate v1.1.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd // indirect
|
golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd // indirect
|
||||||
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6 // indirect
|
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6 // indirect
|
||||||
|
8
go.sum
8
go.sum
@ -24,6 +24,8 @@ github.com/gravitational/trace v0.0.0-20190409171327-f30095ced5ff h1:xL/fJdlTJL6
|
|||||||
github.com/gravitational/trace v0.0.0-20190409171327-f30095ced5ff/go.mod h1:RvdOUHE4SHqR3oXlFFKnGzms8a5dugHygGw1bqDstYI=
|
github.com/gravitational/trace v0.0.0-20190409171327-f30095ced5ff/go.mod h1:RvdOUHE4SHqR3oXlFFKnGzms8a5dugHygGw1bqDstYI=
|
||||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jessevdk/go-flags v1.4.1-0.20181221193153-c0795c8afcf4 h1:xKkUL6QBojwguhKKetf1SocCAKqc6W7S/mGm9xEGllo=
|
||||||
|
github.com/jessevdk/go-flags v1.4.1-0.20181221193153-c0795c8afcf4/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
@ -53,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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
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 h1:Gq/uWopa4rx/tnZu2opOSBqHK63Yqlou/SzrbwdJiNg=
|
||||||
github.com/vulcand/predicate v1.1.0/go.mod h1:mlccC5IRBoc2cIFmCB8ZM62I3VDb6p2GXESMHa3CnZg=
|
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=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jessevdk/go-flags"
|
"github.com/thomseddon/go-flags"
|
||||||
"github.com/thomseddon/traefik-forward-auth/internal/provider"
|
"github.com/thomseddon/traefik-forward-auth/internal/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,32 +25,31 @@ type Config struct {
|
|||||||
LogFormat string `long:"log-format" env:"LOG_FORMAT" default:"text" choice:"text" choice:"json" choice:"pretty" description:"Log format"`
|
LogFormat string `long:"log-format" env:"LOG_FORMAT" default:"text" choice:"text" choice:"json" choice:"pretty" description:"Log format"`
|
||||||
|
|
||||||
AuthHost string `long:"auth-host" env:"AUTH_HOST" description:"Single host to use when returning from 3rd party auth"`
|
AuthHost string `long:"auth-host" env:"AUTH_HOST" description:"Single host to use when returning from 3rd party auth"`
|
||||||
Config func(s string) error `long:"config" env:"CONFIG" description:"Path to config file"`
|
Config func(s string) error `long:"config" env:"CONFIG" description:"Path to config file" json:"-"`
|
||||||
CookieDomains []CookieDomain `long:"cookie-domain" env:"COOKIE_DOMAIN" description:"Domain to set auth cookie on, can be set multiple times"`
|
CookieDomains []CookieDomain `long:"cookie-domain" env:"COOKIE_DOMAIN" description:"Domain to set auth cookie on, can be set multiple times"`
|
||||||
InsecureCookie bool `long:"insecure-cookie" env:"INSECURE_COOKIE" description:"Use insecure cookies"`
|
InsecureCookie bool `long:"insecure-cookie" env:"INSECURE_COOKIE" description:"Use insecure cookies"`
|
||||||
CookieName string `long:"cookie-name" env:"COOKIE_NAME" default:"_forward_auth" description:"Cookie Name"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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)"`
|
SecretString string `long:"secret" env:"SECRET" description:"Secret used for signing (required)" json:"-"`
|
||||||
Whitelist CommaSeparatedList `long:"whitelist" env:"WHITELIST" description:"Only allow given email addresses, can be set multiple times"`
|
Whitelist CommaSeparatedList `long:"whitelist" env:"WHITELIST" description:"Only allow given email addresses, can be set multiple times"`
|
||||||
|
|
||||||
Providers provider.Providers `group:"providers" namespace:"providers" env-namespace:"PROVIDERS"`
|
Providers provider.Providers `group:"providers" namespace:"providers" env-namespace:"PROVIDERS"`
|
||||||
Rules map[string]*Rule `long:"rules.<name>.<param>" description:"Rule definitions, param can be: \"action\" or \"rule\""`
|
Rules map[string]*Rule `long:"rules.<name>.<param>" description:"Rule definitions, param can be: \"action\" or \"rule\""`
|
||||||
|
|
||||||
// Filled during transformations
|
// Filled during transformations
|
||||||
Secret []byte
|
Secret []byte `json:"-"`
|
||||||
Lifetime time.Duration
|
Lifetime time.Duration
|
||||||
|
|
||||||
// Legacy
|
// Legacy
|
||||||
CookieDomainsLegacy CookieDomains `long:"cookie-domains" env:"COOKIE_DOMAINS" description:"DEPRECATED - Use \"cookie-domain\""`
|
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\""`
|
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\""`
|
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\""`
|
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\""`
|
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\""`
|
PromptLegacy string `long:"prompt" env:"PROMPT" description:"DEPRECATED - Use \"providers.google.prompt\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +99,7 @@ func NewConfig(args []string) (Config, error) {
|
|||||||
|
|
||||||
// Backwards compatability
|
// Backwards compatability
|
||||||
if c.CookieSecretLegacy != "" && c.SecretString == "" {
|
if c.CookieSecretLegacy != "" && c.SecretString == "" {
|
||||||
|
fmt.Println("cookie-secret config option is deprecated, please use secret")
|
||||||
c.SecretString = c.CookieSecretLegacy
|
c.SecretString = c.CookieSecretLegacy
|
||||||
}
|
}
|
||||||
if c.ClientIdLegacy != "" {
|
if c.ClientIdLegacy != "" {
|
||||||
@ -109,9 +109,11 @@ func NewConfig(args []string) (Config, error) {
|
|||||||
c.Providers.Google.ClientSecret = c.ClientSecretLegacy
|
c.Providers.Google.ClientSecret = c.ClientSecretLegacy
|
||||||
}
|
}
|
||||||
if c.PromptLegacy != "" {
|
if c.PromptLegacy != "" {
|
||||||
|
fmt.Println("prompt config option is deprecated, please use providers.google.prompt")
|
||||||
c.Providers.Google.Prompt = c.PromptLegacy
|
c.Providers.Google.Prompt = c.PromptLegacy
|
||||||
}
|
}
|
||||||
if c.CookieSecureLegacy != "" {
|
if c.CookieSecureLegacy != "" {
|
||||||
|
fmt.Println("cookie-secure config option is deprecated, please use insecure-cookie")
|
||||||
secure, err := strconv.ParseBool(c.CookieSecureLegacy)
|
secure, err := strconv.ParseBool(c.CookieSecureLegacy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c, err
|
return c, err
|
||||||
@ -119,11 +121,9 @@ func NewConfig(args []string) (Config, error) {
|
|||||||
c.InsecureCookie = !secure
|
c.InsecureCookie = !secure
|
||||||
}
|
}
|
||||||
if len(c.CookieDomainsLegacy) > 0 {
|
if len(c.CookieDomainsLegacy) > 0 {
|
||||||
|
fmt.Println("cookie-domains config option is deprecated, please use cookie-domain")
|
||||||
c.CookieDomains = append(c.CookieDomains, c.CookieDomainsLegacy...)
|
c.CookieDomains = append(c.CookieDomains, c.CookieDomainsLegacy...)
|
||||||
}
|
}
|
||||||
if len(c.DomainsLegacy) > 0 {
|
|
||||||
c.Domains = append(c.Domains, c.DomainsLegacy...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transformations
|
// Transformations
|
||||||
if len(c.Path) > 0 && c.Path[0] != '/' {
|
if len(c.Path) > 0 && c.Path[0] != '/' {
|
||||||
@ -136,7 +136,7 @@ func NewConfig(args []string) (Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) parseFlags(args []string) 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
|
p.UnknownOptionHandler = c.parseUnknownFlag
|
||||||
|
|
||||||
i := flags.NewIniParser(p)
|
i := flags.NewIniParser(p)
|
||||||
@ -152,6 +152,7 @@ func (c *Config) parseFlags(args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("config format deprecated, please use ini format")
|
||||||
return i.Parse(converted)
|
return i.Parse(converted)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,16 +171,15 @@ func (c *Config) parseUnknownFlag(option string, arg flags.SplitArgument, args [
|
|||||||
// Parse rules in the format "rule.<name>.<param>"
|
// Parse rules in the format "rule.<name>.<param>"
|
||||||
parts := strings.Split(option, ".")
|
parts := strings.Split(option, ".")
|
||||||
if len(parts) == 3 && parts[0] == "rule" {
|
if len(parts) == 3 && parts[0] == "rule" {
|
||||||
// Get or create rule
|
// Ensure there is a name
|
||||||
rule, ok := c.Rules[parts[1]]
|
name := parts[1]
|
||||||
if !ok {
|
if len(name) == 0 {
|
||||||
rule = NewRule()
|
return args, errors.New("route name is required")
|
||||||
c.Rules[parts[1]] = rule
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get value, or pop the next arg
|
// Get value, or pop the next arg
|
||||||
val, ok := arg.Value()
|
val, ok := arg.Value()
|
||||||
if !ok {
|
if !ok && len(args) > 1 {
|
||||||
val = args[0]
|
val = args[0]
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
}
|
}
|
||||||
@ -198,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
|
// Add param value to rule
|
||||||
switch parts[2] {
|
switch parts[2] {
|
||||||
case "action":
|
case "action":
|
||||||
@ -244,7 +251,7 @@ func (c *Config) Validate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.Providers.Google.ClientId == "" || c.Providers.Google.ClientSecret == "" {
|
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
|
// Check rules
|
||||||
@ -271,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() {
|
func (r *Rule) Validate() {
|
||||||
if r.Action != "auth" && r.Action != "allow" {
|
if r.Action != "auth" && r.Action != "allow" {
|
||||||
log.Fatal("invalid rule action, must be \"auth\" or \"allow\"")
|
log.Fatal("invalid rule action, must be \"auth\" or \"allow\"")
|
||||||
|
@ -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) {
|
func TestConfigFlagBackwardsCompatability(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
c, err := NewConfig([]string{
|
c, err := NewConfig([]string{
|
||||||
@ -109,7 +131,7 @@ func TestConfigFlagBackwardsCompatability(t *testing.T) {
|
|||||||
"--cookie-secure=false",
|
"--cookie-secure=false",
|
||||||
"--cookie-domains=test1.com,example.org",
|
"--cookie-domains=test1.com,example.org",
|
||||||
"--cookie-domain=another1.net",
|
"--cookie-domain=another1.net",
|
||||||
"--domains=test2.com,example.org",
|
"--domain=test2.com,example.org",
|
||||||
"--domain=another2.net",
|
"--domain=another2.net",
|
||||||
"--whitelist=test3.com,example.org",
|
"--whitelist=test3.com,example.org",
|
||||||
"--whitelist=another3.net",
|
"--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")
|
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")
|
assert.Equal(expected2, c.Domains, "should read legacy comma separated list domains")
|
||||||
|
|
||||||
expected3 := CommaSeparatedList{"test3.com", "example.org", "another3.net"}
|
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("inicookiename", c.CookieName, "should be read from ini file")
|
||||||
assert.Equal("csrfcookiename", c.CSRFCookieName, "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("/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) {
|
func TestConfigFileBackwardsCompatability(t *testing.T) {
|
||||||
@ -181,10 +215,76 @@ func TestConfigFileBackwardsCompatability(t *testing.T) {
|
|||||||
func TestConfigParseEnvironment(t *testing.T) {
|
func TestConfigParseEnvironment(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
os.Setenv("COOKIE_NAME", "env_cookie_name")
|
os.Setenv("COOKIE_NAME", "env_cookie_name")
|
||||||
|
os.Setenv("PROVIDERS_GOOGLE_CLIENT_ID", "env_client_id")
|
||||||
c, err := NewConfig([]string{})
|
c, err := NewConfig([]string{})
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
|
|
||||||
assert.Equal("env_cookie_name", c.CookieName, "variable should be read from environment")
|
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) {
|
func TestConfigTransformation(t *testing.T) {
|
||||||
|
@ -26,11 +26,11 @@ func (s *Server) buildRoutes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Let's build a router
|
// Let's build a router
|
||||||
for _, rule := range config.Rules {
|
for name, rule := range config.Rules {
|
||||||
if rule.Action == "allow" {
|
if rule.Action == "allow" {
|
||||||
s.router.AddRoute(rule.Rule, 1, s.AllowHandler())
|
s.router.AddRoute(rule.formattedRule(), 1, s.AllowHandler(name))
|
||||||
} else {
|
} else {
|
||||||
s.router.AddRoute(rule.Rule, 1, s.AuthHandler())
|
s.router.AddRoute(rule.formattedRule(), 1, s.AuthHandler(name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,14 +39,16 @@ func (s *Server) buildRoutes() {
|
|||||||
|
|
||||||
// Add a default handler
|
// Add a default handler
|
||||||
if config.DefaultAction == "allow" {
|
if config.DefaultAction == "allow" {
|
||||||
s.router.NewRoute().Handler(s.AllowHandler())
|
s.router.NewRoute().Handler(s.AllowHandler("default"))
|
||||||
} else {
|
} else {
|
||||||
s.router.NewRoute().Handler(s.AuthHandler())
|
s.router.NewRoute().Handler(s.AuthHandler("default"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RootHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) RootHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// Modify 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"))
|
r.URL, _ = url.Parse(r.Header.Get("X-Forwarded-Uri"))
|
||||||
|
|
||||||
// Pass to mux
|
// Pass to mux
|
||||||
@ -54,18 +56,18 @@ func (s *Server) RootHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handler that allows requests
|
// Handler that allows requests
|
||||||
func (s *Server) AllowHandler() http.HandlerFunc {
|
func (s *Server) AllowHandler(rule string) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
s.logger(r, "Allowing request")
|
s.logger(r, rule, "Allowing request")
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate requests
|
// Authenticate requests
|
||||||
func (s *Server) AuthHandler() http.HandlerFunc {
|
func (s *Server) AuthHandler(rule string) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Logging setup
|
// Logging setup
|
||||||
logger := s.logger(r, "Authenticating request")
|
logger := s.logger(r, rule, "Authenticating request")
|
||||||
|
|
||||||
// Get auth cookie
|
// Get auth cookie
|
||||||
c, err := r.Cookie(config.CookieName)
|
c, err := r.Cookie(config.CookieName)
|
||||||
@ -118,7 +120,7 @@ func (s *Server) AuthHandler() http.HandlerFunc {
|
|||||||
func (s *Server) AuthCallbackHandler() http.HandlerFunc {
|
func (s *Server) AuthCallbackHandler() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Logging setup
|
// Logging setup
|
||||||
logger := s.logger(r, "Handling callback")
|
logger := s.logger(r, "default", "Handling callback")
|
||||||
|
|
||||||
// Check for CSRF cookie
|
// Check for CSRF cookie
|
||||||
c, err := r.Cookie(config.CSRFCookieName)
|
c, err := r.Cookie(config.CSRFCookieName)
|
||||||
@ -165,16 +167,17 @@ func (s *Server) AuthCallbackHandler() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) logger(r *http.Request, msg string) *logrus.Entry {
|
func (s *Server) logger(r *http.Request, rule, msg string) *logrus.Entry {
|
||||||
// Create logger
|
// Create logger
|
||||||
logger := log.WithFields(logrus.Fields{
|
logger := log.WithFields(logrus.Fields{
|
||||||
"SourceIP": r.Header.Get("X-Forwarded-For"),
|
"source_ip": r.Header.Get("X-Forwarded-For"),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Log request
|
// Log request
|
||||||
logger.WithFields(logrus.Fields{
|
logger.WithFields(logrus.Fields{
|
||||||
"Headers": r.Header,
|
"rule": rule,
|
||||||
}).Debugf(msg)
|
"headers": r.Header,
|
||||||
|
}).Debug(msg)
|
||||||
|
|
||||||
return logger
|
return logger
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func TestServerAuthHandler(t *testing.T) {
|
|||||||
config, _ = NewConfig([]string{})
|
config, _ = NewConfig([]string{})
|
||||||
|
|
||||||
// Should redirect vanilla request to login url
|
// Should redirect vanilla request to login url
|
||||||
req := newHttpRequest("/foo")
|
req := newDefaultHttpRequest("/foo")
|
||||||
res, _ := doHttpRequest(req, nil)
|
res, _ := doHttpRequest(req, nil)
|
||||||
assert.Equal(307, res.StatusCode, "vanilla request should be redirected")
|
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")
|
assert.Equal("/o/oauth2/auth", fwd.Path, "vanilla request should be redirected to google")
|
||||||
|
|
||||||
// Should catch invalid cookie
|
// Should catch invalid cookie
|
||||||
req = newHttpRequest("/foo")
|
req = newDefaultHttpRequest("/foo")
|
||||||
c := MakeCookie(req, "test@example.com")
|
c := MakeCookie(req, "test@example.com")
|
||||||
parts := strings.Split(c.Value, "|")
|
parts := strings.Split(c.Value, "|")
|
||||||
c.Value = fmt.Sprintf("bad|%s|%s", parts[1], parts[2])
|
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")
|
assert.Equal(401, res.StatusCode, "invalid cookie should not be authorised")
|
||||||
|
|
||||||
// Should validate email
|
// Should validate email
|
||||||
req = newHttpRequest("/foo")
|
req = newDefaultHttpRequest("/foo")
|
||||||
c = MakeCookie(req, "test@example.com")
|
c = MakeCookie(req, "test@example.com")
|
||||||
config.Domains = []string{"test.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")
|
assert.Equal(401, res.StatusCode, "invalid email should not be authorised")
|
||||||
|
|
||||||
// Should allow valid request email
|
// Should allow valid request email
|
||||||
req = newHttpRequest("/foo")
|
req = newDefaultHttpRequest("/foo")
|
||||||
c = MakeCookie(req, "test@example.com")
|
c = MakeCookie(req, "test@example.com")
|
||||||
config.Domains = []string{}
|
config.Domains = []string{}
|
||||||
|
|
||||||
@ -91,18 +91,18 @@ func TestServerAuthCallback(t *testing.T) {
|
|||||||
config.Providers.Google.UserURL = userUrl
|
config.Providers.Google.UserURL = userUrl
|
||||||
|
|
||||||
// Should pass auth response request to callback
|
// Should pass auth response request to callback
|
||||||
req := newHttpRequest("/_oauth")
|
req := newDefaultHttpRequest("/_oauth")
|
||||||
res, _ := doHttpRequest(req, nil)
|
res, _ := doHttpRequest(req, nil)
|
||||||
assert.Equal(401, res.StatusCode, "auth callback without cookie shouldn't be authorised")
|
assert.Equal(401, res.StatusCode, "auth callback without cookie shouldn't be authorised")
|
||||||
|
|
||||||
// Should catch invalid csrf cookie
|
// Should catch invalid csrf cookie
|
||||||
req = newHttpRequest("/_oauth?state=12345678901234567890123456789012:http://redirect")
|
req = newDefaultHttpRequest("/_oauth?state=12345678901234567890123456789012:http://redirect")
|
||||||
c := MakeCSRFCookie(req, "nononononononononononononononono")
|
c := MakeCSRFCookie(req, "nononononononononononononononono")
|
||||||
res, _ = doHttpRequest(req, c)
|
res, _ = doHttpRequest(req, c)
|
||||||
assert.Equal(401, res.StatusCode, "auth callback with invalid cookie shouldn't be authorised")
|
assert.Equal(401, res.StatusCode, "auth callback with invalid cookie shouldn't be authorised")
|
||||||
|
|
||||||
// Should redirect valid request
|
// Should redirect valid request
|
||||||
req = newHttpRequest("/_oauth?state=12345678901234567890123456789012:http://redirect")
|
req = newDefaultHttpRequest("/_oauth?state=12345678901234567890123456789012:http://redirect")
|
||||||
c = MakeCSRFCookie(req, "12345678901234567890123456789012")
|
c = MakeCSRFCookie(req, "12345678901234567890123456789012")
|
||||||
res, _ = doHttpRequest(req, c)
|
res, _ = doHttpRequest(req, c)
|
||||||
assert.Equal(307, res.StatusCode, "valid auth callback should be allowed")
|
assert.Equal(307, res.StatusCode, "valid auth callback should be allowed")
|
||||||
@ -117,33 +117,151 @@ func TestServerDefaultAction(t *testing.T) {
|
|||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
config, _ = NewConfig([]string{})
|
config, _ = NewConfig([]string{})
|
||||||
|
|
||||||
req := newHttpRequest("/random")
|
req := newDefaultHttpRequest("/random")
|
||||||
res, _ := doHttpRequest(req, nil)
|
res, _ := doHttpRequest(req, nil)
|
||||||
assert.Equal(307, res.StatusCode, "request should require auth with auth default handler")
|
assert.Equal(307, res.StatusCode, "request should require auth with auth default handler")
|
||||||
|
|
||||||
config.DefaultAction = "allow"
|
config.DefaultAction = "allow"
|
||||||
req = newHttpRequest("/random")
|
req = newDefaultHttpRequest("/random")
|
||||||
res, _ = doHttpRequest(req, nil)
|
res, _ = doHttpRequest(req, nil)
|
||||||
assert.Equal(200, res.StatusCode, "request should be allowed with default handler")
|
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)
|
assert := assert.New(t)
|
||||||
config, _ = NewConfig([]string{})
|
config, _ = NewConfig([]string{})
|
||||||
config.Rules = map[string]*Rule{
|
config.Rules = map[string]*Rule{
|
||||||
"web1": {
|
"1": {
|
||||||
Action: "allow",
|
Action: "allow",
|
||||||
Rule: "PathPrefix(`/api`)",
|
Rule: "Headers(`X-Test`, `test123`)",
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
Action: "allow",
|
||||||
|
Rule: "HeadersRegexp(`X-Test`, `test(456|789)`)",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should block any request
|
// 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)
|
res, _ := doHttpRequest(req, nil)
|
||||||
assert.Equal(307, res.StatusCode, "request not matching any rule should require auth")
|
assert.Equal(307, res.StatusCode, "request not matching any rule should require auth")
|
||||||
|
|
||||||
// Should allow /api request
|
// 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)
|
res, _ = doHttpRequest(req, nil)
|
||||||
assert.Equal(200, res.StatusCode, "request matching allow rule should be allowed")
|
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)
|
return res, string(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHttpRequest(uri string) *http.Request {
|
func newDefaultHttpRequest(uri string) *http.Request {
|
||||||
r := httptest.NewRequest("", "http://example.com/", nil)
|
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)
|
r.Header.Add("X-Forwarded-Uri", uri)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
cookie-name=inicookiename
|
cookie-name=inicookiename
|
||||||
csrf-cookie-name=inicsrfcookiename
|
csrf-cookie-name=inicsrfcookiename
|
||||||
url-path=one
|
url-path=one
|
||||||
|
rule.1.action=allow
|
||||||
|
rule.1.rule=PathPrefix(`/one`)
|
||||||
|
@ -1 +1,3 @@
|
|||||||
url-path=two
|
url-path=two
|
||||||
|
rule.two.action=auth
|
||||||
|
rule.two.rule=Host(`two.com`) && Path(`/two`)
|
||||||
|
Reference in New Issue
Block a user