1 Commits

Author SHA1 Message Date
09661579bd Update README + examples for v2 2019-04-24 10:04:24 +01:00
8 changed files with 30 additions and 46 deletions

View File

@ -16,7 +16,7 @@ A minimal forward authentication service that provides Google oauth based login
# Contents # Contents
- [Usage](#usage) - [Usage](#installation)
- [Simple](#simple) - [Simple](#simple)
- [Advanced](#advanced) - [Advanced](#advanced)
- [OAuth Configuration](#oauth-configuration) - [OAuth Configuration](#oauth-configuration)
@ -29,8 +29,6 @@ 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)
## Usage ## Usage
@ -45,7 +43,7 @@ version: '3'
services: services:
traefik: traefik:
image: traefik:1.7 image: traefik:1
ports: ports:
- "8085:80" - "8085:80"
volumes: volumes:
@ -128,12 +126,12 @@ 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 [$PROVIDERS_GOOGLE_CLIENT_ID] --providers.google.client-id= Client ID [$CLIENT_ID]
--providers.google.client-secret= Client Secret [$PROVIDERS_GOOGLE_CLIENT_SECRET] --providers.google.client-secret= Client Secret [$CLIENT_SECRET]
--providers.google.prompt= Space separated list of OpenID prompt options [$PROVIDERS_GOOGLE_PROMPT] --providers.google.prompt= Space separated list of OpenID prompt options [$PROMPT]
Help Options: Help Options:
-h, --help Show this help message -h, --help Show this help message
``` ```
All options can be supplied in any of the following ways, in the following precedence (first is highest precedence): All options can be supplied in any of the following ways, in the following precedence (first is highest precedence):
@ -285,7 +283,7 @@ The authenticated user is set in the `X-Forwarded-User` header, to pass this on
### Operation Modes ### Operation Modes
#### Overlay Mode #### Overlay
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.
@ -300,7 +298,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 Mode #### Auth Host
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)).
@ -323,8 +321,6 @@ 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

View File

@ -33,7 +33,7 @@ services:
- AUTH_HOST=auth.yourdomain.com - AUTH_HOST=auth.yourdomain.com
networks: networks:
- traefik - traefik
# When using an auth host, the below must be added # When using an auth host, adding it here prompts traefik to generate certs
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.port=4181 - traefik.port=4181

View File

@ -23,15 +23,13 @@ services:
- "traefik.frontend.rule=Host:whoami.localhost.com" - "traefik.frontend.rule=Host:whoami.localhost.com"
traefik-forward-auth: traefik-forward-auth:
build: ../ image: thomseddon/traefik-forward-auth
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

2
go.mod
View File

@ -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.1-0.20181221193153-c0795c8afcf4 github.com/jessevdk/go-flags v1.4.0
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

2
go.sum
View File

@ -24,8 +24,6 @@ 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=

View File

@ -25,7 +25,7 @@ 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" json:"-"` Config func(s string) error `long:"config" env:"CONFIG" description:"Path to config file"`
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"`
@ -34,23 +34,23 @@ type Config struct {
Domains []string `long:"domain" env:"DOMAIN" description:"Only allow given email domains, can be set multiple times"` Domains []string `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)" json:"-"` SecretString string `long:"secret" env:"SECRET" description:"Secret used for signing (required)"`
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 `json:"-"` Secret []byte
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\"" json:"-"` CookieSecretLegacy string `long:"cookie-secret" env:"COOKIE_SECRET" description:"DEPRECATED - Use \"secret\""`
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\""` 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\"" json:"-"` ClientSecretLegacy string `long:"client-secret" env:"CLIENT_SECRET" description:"DEPRECATED - Use \"providers.google.client-id\""`
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,7 +100,6 @@ func NewConfig(args []string) (Config, error) {
// Backwards compatability // Backwards compatability
if c.CookieSecretLegacy != "" && c.SecretString == "" { if c.CookieSecretLegacy != "" && c.SecretString == "" {
log.Warn("cookie-secret config option is deprecated, please use secret")
c.SecretString = c.CookieSecretLegacy c.SecretString = c.CookieSecretLegacy
} }
if c.ClientIdLegacy != "" { if c.ClientIdLegacy != "" {
@ -110,11 +109,9 @@ func NewConfig(args []string) (Config, error) {
c.Providers.Google.ClientSecret = c.ClientSecretLegacy c.Providers.Google.ClientSecret = c.ClientSecretLegacy
} }
if c.PromptLegacy != "" { if c.PromptLegacy != "" {
log.Warn("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 != "" {
log.Warn("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
@ -122,11 +119,9 @@ func NewConfig(args []string) (Config, error) {
c.InsecureCookie = !secure c.InsecureCookie = !secure
} }
if len(c.CookieDomainsLegacy) > 0 { if len(c.CookieDomainsLegacy) > 0 {
log.Warn("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 { if len(c.DomainsLegacy) > 0 {
log.Warn("domains config option is deprecated, please use domain")
c.Domains = append(c.Domains, c.DomainsLegacy...) c.Domains = append(c.Domains, c.DomainsLegacy...)
} }

View File

@ -181,12 +181,10 @@ 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")
} }
func TestConfigTransformation(t *testing.T) { func TestConfigTransformation(t *testing.T) {

View File

@ -26,11 +26,11 @@ func (s *Server) buildRoutes() {
} }
// Let's build a router // Let's build a router
for name, rule := range config.Rules { for _, rule := range config.Rules {
if rule.Action == "allow" { if rule.Action == "allow" {
s.router.AddRoute(rule.Rule, 1, s.AllowHandler(name)) s.router.AddRoute(rule.Rule, 1, s.AllowHandler())
} else { } else {
s.router.AddRoute(rule.Rule, 1, s.AuthHandler(name)) s.router.AddRoute(rule.Rule, 1, s.AuthHandler())
} }
} }
@ -39,9 +39,9 @@ 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("default")) s.router.NewRoute().Handler(s.AllowHandler())
} else { } else {
s.router.NewRoute().Handler(s.AuthHandler("default")) s.router.NewRoute().Handler(s.AuthHandler())
} }
} }
@ -54,18 +54,18 @@ func (s *Server) RootHandler(w http.ResponseWriter, r *http.Request) {
} }
// Handler that allows requests // Handler that allows requests
func (s *Server) AllowHandler(rule string) http.HandlerFunc { func (s *Server) AllowHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
s.logger(r, rule, "Allowing request") s.logger(r, "Allowing request")
w.WriteHeader(200) w.WriteHeader(200)
} }
} }
// Authenticate requests // Authenticate requests
func (s *Server) AuthHandler(rule string) http.HandlerFunc { func (s *Server) AuthHandler() 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, rule, "Authenticating request") logger := s.logger(r, "Authenticating request")
// Get auth cookie // Get auth cookie
c, err := r.Cookie(config.CookieName) c, err := r.Cookie(config.CookieName)
@ -118,7 +118,7 @@ func (s *Server) AuthHandler(rule string) 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, "default", "Handling callback") logger := s.logger(r, "Handling callback")
// Check for CSRF cookie // Check for CSRF cookie
c, err := r.Cookie(config.CSRFCookieName) c, err := r.Cookie(config.CSRFCookieName)
@ -165,17 +165,16 @@ func (s *Server) AuthCallbackHandler() http.HandlerFunc {
} }
} }
func (s *Server) logger(r *http.Request, rule, msg string) *logrus.Entry { func (s *Server) logger(r *http.Request, msg string) *logrus.Entry {
// Create logger // Create logger
logger := log.WithFields(logrus.Fields{ logger := log.WithFields(logrus.Fields{
"source_ip": r.Header.Get("X-Forwarded-For"), "SourceIP": r.Header.Get("X-Forwarded-For"),
}) })
// Log request // Log request
logger.WithFields(logrus.Fields{ logger.WithFields(logrus.Fields{
"rule": rule, "Headers": r.Header,
"headers": r.Header, }).Debugf(msg)
}).Debug(msg)
return logger return logger
} }