Add config file support
This commit is contained in:
parent
899749a08d
commit
d4fe9a4f57
@ -2,6 +2,8 @@ language: go
|
|||||||
install:
|
install:
|
||||||
- go get github.com/bmizerany/assert
|
- go get github.com/bmizerany/assert
|
||||||
- go get github.com/bitly/go-simplejson
|
- go get github.com/bitly/go-simplejson
|
||||||
|
- go get github.com/mreiferson/go-options
|
||||||
|
- go get github.com/BurntSushi/toml
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
|
|
||||||
|
33
README.md
33
README.md
@ -42,28 +42,40 @@ intend to run `google_auth_proxy` on.
|
|||||||
5. Take note of the **Client ID** and **Client Secret**
|
5. Take note of the **Client ID** and **Client Secret**
|
||||||
|
|
||||||
|
|
||||||
## Command Line Options
|
## Configuration
|
||||||
|
|
||||||
|
`google_auth_proxy` can be configured via [config file](#config-file), [command line options](#command-line-options) or [environment variables](#environment-variables).
|
||||||
|
|
||||||
|
### Config File
|
||||||
|
|
||||||
|
An example [google_auth_proxy.cfg](contrib/google_auth_proxy.cfg.example) config file is in the contrib directory. It can be used by specifying `-config=/etc/google_auth_proxy.cfg`
|
||||||
|
|
||||||
|
### Command Line Options
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage of ./google_auth_proxy:
|
Usage of google_auth_proxy:
|
||||||
-authenticated-emails-file="": authenticate against emails via file (one per line)
|
-authenticated-emails-file="": authenticate against emails via file (one per line)
|
||||||
-client-id="": the Google OAuth Client ID: ie: "123456.apps.googleusercontent.com"
|
-client-id="": the Google OAuth Client ID: ie: "123456.apps.googleusercontent.com"
|
||||||
-client-secret="": the OAuth Client Secret
|
-client-secret="": the OAuth Client Secret
|
||||||
-cookie-domain="": an optional cookie domain to force cookies to
|
-config="": path to config file
|
||||||
-cookie-expire=168h: expire timeframe for cookie
|
-cookie-domain="": an optional cookie domain to force cookies to (ie: .yourcompany.com)
|
||||||
|
-cookie-expire=168h0m0s: expire timeframe for cookie
|
||||||
-cookie-https-only=false: set HTTPS only cookie
|
-cookie-https-only=false: set HTTPS only cookie
|
||||||
-cookie-secret="": the seed string for secure cookies
|
-cookie-secret="": the seed string for secure cookies
|
||||||
-google-apps-domain=[]: authenticate against the given google apps domain (may be given multiple times)
|
-google-apps-domain=: authenticate against the given Google apps domain (may be given multiple times)
|
||||||
-htpasswd-file="": additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
|
-htpasswd-file="": additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
|
||||||
-http-address="127.0.0.1:4180": <addr>:<port> to listen on for HTTP clients
|
-http-address="127.0.0.1:4180": <addr>:<port> to listen on for HTTP clients
|
||||||
-pass-basic-auth=true: pass HTTP Basic Auth information to upstream
|
-pass-basic-auth=true: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
|
||||||
-redirect-url="": the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
|
-redirect-url="": the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
|
||||||
-upstream=[]: the http url(s) of the upstream endpoint. If multiple, routing is based on path
|
-upstream=: the http url(s) of the upstream endpoint. If multiple, routing is based on path
|
||||||
-version=false: print version string
|
-version=false: print version string
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Environment variables
|
||||||
|
|
||||||
## Example Configuration
|
The environment variables `google_auth_client_id`, `google_auth_secret` and `google_auth_cookie_secret` can be used in place of the corresponding command-line arguments.
|
||||||
|
|
||||||
|
### Example Nginx Configuration
|
||||||
|
|
||||||
This example has a [Nginx](http://nginx.org/) SSL endpoint proxying to `google_auth_proxy` on port `4180`.
|
This example has a [Nginx](http://nginx.org/) SSL endpoint proxying to `google_auth_proxy` on port `4180`.
|
||||||
`google_auth_proxy` then authenticates requests for an upstream application running on port `8080`. The external
|
`google_auth_proxy` then authenticates requests for an upstream application running on port `8080`. The external
|
||||||
@ -105,13 +117,10 @@ The command line to run `google_auth_proxy` would look like this:
|
|||||||
--client-secret=...
|
--client-secret=...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment variables
|
|
||||||
|
|
||||||
The environment variables `google_auth_client_id`, `google_auth_secret` and `google_auth_cookie_secret` can be used in place of the corresponding command-line arguments.
|
|
||||||
|
|
||||||
## Endpoint Documentation
|
## Endpoint Documentation
|
||||||
|
|
||||||
Google Auth Proxy responds directly to the following endpoints. All other endpoints will be authenticated.
|
Google Auth Proxy responds directly to the following endpoints. All other endpoints will be proxied upstream when authenticated.
|
||||||
|
|
||||||
* /ping - returns an 200 OK response
|
* /ping - returns an 200 OK response
|
||||||
* /oauth2/sign_in - the login page, which also doubles as a sign out page (it clears cookies)
|
* /oauth2/sign_in - the login page, which also doubles as a sign out page (it clears cookies)
|
||||||
|
44
contrib/google_auth_proxy.cfg.example
Normal file
44
contrib/google_auth_proxy.cfg.example
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
## Google Auth Proxy Config File
|
||||||
|
## https://github.com/bitly/google_auth_proxy
|
||||||
|
|
||||||
|
## <addr>:<port> to listen on for HTTP clients
|
||||||
|
# http_address = "127.0.0.1:4180"
|
||||||
|
|
||||||
|
## the OAuth Redirect URL.
|
||||||
|
# redirect_url = "https://internalapp.yourcompany.com/oauth2/callback"
|
||||||
|
|
||||||
|
## the http url(s) of the upstream endpoint. If multiple, routing is based on path
|
||||||
|
# upstreams = [
|
||||||
|
# "http://127.0.0.1:8080/"
|
||||||
|
# ]
|
||||||
|
|
||||||
|
## pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
|
||||||
|
# pass_basic_auth = true
|
||||||
|
|
||||||
|
## Google Apps Domains to allow authentication for
|
||||||
|
# google_apps_domains = [
|
||||||
|
# "yourcompany.com"
|
||||||
|
# ]
|
||||||
|
|
||||||
|
|
||||||
|
## The Google OAuth Client ID, Secret
|
||||||
|
# client_id = "123456.apps.googleusercontent.com"
|
||||||
|
# client_secret = ""
|
||||||
|
|
||||||
|
## Authenticated Email Addresses File (one email per line)
|
||||||
|
# authenticated_emails_file = ""
|
||||||
|
|
||||||
|
## Htpasswd File (optional)
|
||||||
|
## Additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
|
||||||
|
## enabling exposes a username/login signin form
|
||||||
|
# htpasswd_file = ""
|
||||||
|
|
||||||
|
|
||||||
|
## Cookie Settings
|
||||||
|
## Secret - the seed string for secure cookies
|
||||||
|
## Domain - optional cookie domain to force cookies to (ie: .yourcompany.com)
|
||||||
|
## Expire - expire timeframe for cookie
|
||||||
|
# cookie_secret = ""
|
||||||
|
# cookie_domain = ""
|
||||||
|
# cookie_expire = "168h"
|
||||||
|
# cookie_https_only = false
|
136
main.go
136
main.go
@ -6,49 +6,63 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
"github.com/mreiferson/go-options"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "0.1.0"
|
|
||||||
|
|
||||||
var (
|
|
||||||
showVersion = flag.Bool("version", false, "print version string")
|
|
||||||
httpAddr = flag.String("http-address", "127.0.0.1:4180", "<addr>:<port> to listen on for HTTP clients")
|
|
||||||
redirectUrl = flag.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
|
|
||||||
clientID = flag.String("client-id", "", "the Google OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
|
|
||||||
clientSecret = flag.String("client-secret", "", "the OAuth Client Secret")
|
|
||||||
passBasicAuth = flag.Bool("pass-basic-auth", true, "pass HTTP Basic Auth information to upstream")
|
|
||||||
htpasswdFile = flag.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption")
|
|
||||||
cookieSecret = flag.String("cookie-secret", "", "the seed string for secure cookies")
|
|
||||||
cookieDomain = flag.String("cookie-domain", "", "an optional cookie domain to force cookies to")
|
|
||||||
cookieExpire = flag.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
|
|
||||||
cookieHttpsOnly = flag.Bool("cookie-https-only", false, "set HTTPS only cookie")
|
|
||||||
authenticatedEmailsFile = flag.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)")
|
|
||||||
googleAppsDomains = StringArray{}
|
|
||||||
upstreams = StringArray{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.Var(&googleAppsDomains, "google-apps-domain", "authenticate against the given google apps domain (may be given multiple times)")
|
|
||||||
flag.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint. If multiple, routing is based on path")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
flagSet := flag.NewFlagSet("google_auth_proxy", flag.ExitOnError)
|
||||||
|
|
||||||
flag.Parse()
|
googleAppsDomains := StringArray{}
|
||||||
|
upstreams := StringArray{}
|
||||||
|
|
||||||
|
config := flagSet.String("config", "", "path to config file")
|
||||||
|
showVersion := flagSet.Bool("version", false, "print version string")
|
||||||
|
|
||||||
|
flagSet.String("http-address", "127.0.0.1:4180", "<addr>:<port> to listen on for HTTP clients")
|
||||||
|
flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
|
||||||
|
flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint. If multiple, routing is based on path")
|
||||||
|
flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
|
||||||
|
|
||||||
|
flagSet.Var(&googleAppsDomains, "google-apps-domain", "authenticate against the given Google apps domain (may be given multiple times)")
|
||||||
|
flagSet.String("client-id", "", "the Google OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
|
||||||
|
flagSet.String("client-secret", "", "the OAuth Client Secret")
|
||||||
|
flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)")
|
||||||
|
flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption")
|
||||||
|
|
||||||
|
flagSet.String("cookie-secret", "", "the seed string for secure cookies")
|
||||||
|
flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)")
|
||||||
|
flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
|
||||||
|
flagSet.Bool("cookie-https-only", false, "set HTTPS only cookie")
|
||||||
|
|
||||||
|
flagSet.Parse(os.Args[1:])
|
||||||
|
|
||||||
|
opts := NewOptions()
|
||||||
|
|
||||||
|
var cfg map[string]interface{}
|
||||||
|
if *config != "" {
|
||||||
|
_, err := toml.DecodeFile(*config, &cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("ERROR: failed to load config file %s - %s", *config, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options.Resolve(opts, flagSet, cfg)
|
||||||
|
|
||||||
// Try to use env for secrets if no flag is set
|
// Try to use env for secrets if no flag is set
|
||||||
if *clientID == "" {
|
// TODO: better parsing of these values
|
||||||
*clientID = os.Getenv("google_auth_client_id")
|
if opts.ClientID == "" {
|
||||||
|
opts.ClientID = os.Getenv("google_auth_client_id")
|
||||||
}
|
}
|
||||||
if *clientSecret == "" {
|
if opts.ClientSecret == "" {
|
||||||
*clientSecret = os.Getenv("google_auth_secret")
|
opts.ClientSecret = os.Getenv("google_auth_secret")
|
||||||
}
|
}
|
||||||
if *cookieSecret == "" {
|
if opts.CookieSecret == "" {
|
||||||
*cookieSecret = os.Getenv("google_auth_cookie_secret")
|
opts.CookieSecret = os.Getenv("google_auth_cookie_secret")
|
||||||
}
|
}
|
||||||
|
|
||||||
if *showVersion {
|
if *showVersion {
|
||||||
@ -56,59 +70,41 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(upstreams) < 1 {
|
err := opts.Validate()
|
||||||
log.Fatal("missing --upstream")
|
|
||||||
}
|
|
||||||
if *cookieSecret == "" {
|
|
||||||
log.Fatal("missing --cookie-secret")
|
|
||||||
}
|
|
||||||
if *clientID == "" {
|
|
||||||
log.Fatal("missing --client-id")
|
|
||||||
}
|
|
||||||
if *clientSecret == "" {
|
|
||||||
log.Fatal("missing --client-secret")
|
|
||||||
}
|
|
||||||
|
|
||||||
var upstreamUrls []*url.URL
|
|
||||||
for _, u := range upstreams {
|
|
||||||
upstreamUrl, err := url.Parse(u)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error parsing --upstream %s", err.Error())
|
|
||||||
}
|
|
||||||
upstreamUrls = append(upstreamUrls, upstreamUrl)
|
|
||||||
}
|
|
||||||
redirectUrl, err := url.Parse(*redirectUrl)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("error parsing --redirect-url %s", err.Error())
|
log.Printf("%s", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
validator := NewValidator(googleAppsDomains, *authenticatedEmailsFile)
|
validator := NewValidator(opts.GoogleAppsDomains, opts.AuthenticatedEmailsFile)
|
||||||
oauthproxy := NewOauthProxy(upstreamUrls, *clientID, *clientSecret, validator)
|
oauthproxy := NewOauthProxy(opts, validator)
|
||||||
oauthproxy.SetRedirectUrl(redirectUrl)
|
|
||||||
if len(googleAppsDomains) != 0 && *authenticatedEmailsFile == "" {
|
if len(opts.GoogleAppsDomains) != 0 && opts.AuthenticatedEmailsFile == "" {
|
||||||
if len(googleAppsDomains) > 1 {
|
if len(opts.GoogleAppsDomains) > 1 {
|
||||||
oauthproxy.SignInMessage = fmt.Sprintf("Authenticate using one of the following domains: %v", strings.Join(googleAppsDomains, ", "))
|
oauthproxy.SignInMessage = fmt.Sprintf("Authenticate using one of the following domains: %v", strings.Join(opts.GoogleAppsDomains, ", "))
|
||||||
} else {
|
} else {
|
||||||
oauthproxy.SignInMessage = fmt.Sprintf("Authenticate using %v", googleAppsDomains[0])
|
oauthproxy.SignInMessage = fmt.Sprintf("Authenticate using %v", opts.GoogleAppsDomains[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if *htpasswdFile != "" {
|
|
||||||
oauthproxy.HtpasswdFile, err = NewHtpasswdFromFile(*htpasswdFile)
|
if opts.HtpasswdFile != "" {
|
||||||
|
oauthproxy.HtpasswdFile, err = NewHtpasswdFromFile(opts.HtpasswdFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("FATAL: unable to open %s %s", *htpasswdFile, err.Error())
|
log.Fatalf("FATAL: unable to open %s %s", opts.HtpasswdFile, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listener, err := net.Listen("tcp", *httpAddr)
|
|
||||||
|
listener, err := net.Listen("tcp", opts.HttpAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("FATAL: listen (%s) failed - %s", *httpAddr, err.Error())
|
log.Fatalf("FATAL: listen (%s) failed - %s", opts.HttpAddress, err)
|
||||||
}
|
}
|
||||||
log.Printf("listening on %s", *httpAddr)
|
log.Printf("listening on %s", opts.HttpAddress)
|
||||||
|
|
||||||
server := &http.Server{Handler: oauthproxy}
|
server := &http.Server{Handler: oauthproxy}
|
||||||
err = server.Serve(listener)
|
err = server.Serve(listener)
|
||||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
log.Printf("ERROR: http.Serve() - %s", err.Error())
|
log.Printf("ERROR: http.Serve() - %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("HTTP: closing %s", listener.Addr().String())
|
log.Printf("HTTP: closing %s", listener.Addr())
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,12 @@ const oauthStartPath = "/oauth2/start"
|
|||||||
const oauthCallbackPath = "/oauth2/callback"
|
const oauthCallbackPath = "/oauth2/callback"
|
||||||
|
|
||||||
type OauthProxy struct {
|
type OauthProxy struct {
|
||||||
CookieSeed string
|
CookieSeed string
|
||||||
CookieKey string
|
CookieKey string
|
||||||
Validator func(string) bool
|
CookieDomain string
|
||||||
|
CookieHttpsOnly bool
|
||||||
|
CookieExpire time.Duration
|
||||||
|
Validator func(string) bool
|
||||||
|
|
||||||
redirectUrl *url.URL // the url to receive requests at
|
redirectUrl *url.URL // the url to receive requests at
|
||||||
oauthRedemptionUrl *url.URL // endpoint to redeem the code
|
oauthRedemptionUrl *url.URL // endpoint to redeem the code
|
||||||
@ -35,40 +38,41 @@ type OauthProxy struct {
|
|||||||
SignInMessage string
|
SignInMessage string
|
||||||
HtpasswdFile *HtpasswdFile
|
HtpasswdFile *HtpasswdFile
|
||||||
serveMux *http.ServeMux
|
serveMux *http.ServeMux
|
||||||
|
PassBasicAuth bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOauthProxy(proxyUrls []*url.URL, clientID string, clientSecret string, validator func(string) bool) *OauthProxy {
|
func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
|
||||||
login, _ := url.Parse("https://accounts.google.com/o/oauth2/auth")
|
login, _ := url.Parse("https://accounts.google.com/o/oauth2/auth")
|
||||||
redeem, _ := url.Parse("https://accounts.google.com/o/oauth2/token")
|
redeem, _ := url.Parse("https://accounts.google.com/o/oauth2/token")
|
||||||
serveMux := http.NewServeMux()
|
serveMux := http.NewServeMux()
|
||||||
for _, u := range proxyUrls {
|
for _, u := range opts.proxyUrls {
|
||||||
path := u.Path
|
path := u.Path
|
||||||
if len(path) == 0 {
|
|
||||||
path = "/"
|
|
||||||
}
|
|
||||||
u.Path = ""
|
u.Path = ""
|
||||||
log.Printf("mapping %s => %s", path, u)
|
log.Printf("mapping path %q => upstream %q", path, u)
|
||||||
serveMux.Handle(path, httputil.NewSingleHostReverseProxy(u))
|
serveMux.Handle(path, httputil.NewSingleHostReverseProxy(u))
|
||||||
}
|
}
|
||||||
return &OauthProxy{
|
redirectUrl := opts.redirectUrl
|
||||||
CookieKey: "_oauthproxy",
|
redirectUrl.Path = oauthCallbackPath
|
||||||
CookieSeed: *cookieSecret,
|
|
||||||
Validator: validator,
|
|
||||||
|
|
||||||
clientID: clientID,
|
return &OauthProxy{
|
||||||
clientSecret: clientSecret,
|
CookieKey: "_oauthproxy",
|
||||||
|
CookieSeed: opts.CookieSecret,
|
||||||
|
CookieDomain: opts.CookieDomain,
|
||||||
|
CookieHttpsOnly: opts.CookieHttpsOnly,
|
||||||
|
CookieExpire: opts.CookieExpire,
|
||||||
|
Validator: validator,
|
||||||
|
|
||||||
|
clientID: opts.ClientID,
|
||||||
|
clientSecret: opts.ClientSecret,
|
||||||
oauthScope: "profile email",
|
oauthScope: "profile email",
|
||||||
oauthRedemptionUrl: redeem,
|
oauthRedemptionUrl: redeem,
|
||||||
oauthLoginUrl: login,
|
oauthLoginUrl: login,
|
||||||
serveMux: serveMux,
|
serveMux: serveMux,
|
||||||
|
redirectUrl: redirectUrl,
|
||||||
|
PassBasicAuth: opts.PassBasicAuth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OauthProxy) SetRedirectUrl(redirectUrl *url.URL) {
|
|
||||||
redirectUrl.Path = oauthCallbackPath
|
|
||||||
p.redirectUrl = redirectUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *OauthProxy) GetLoginURL(redirectUrl string) string {
|
func (p *OauthProxy) GetLoginURL(redirectUrl string) string {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Add("redirect_uri", p.redirectUrl.String())
|
params.Add("redirect_uri", p.redirectUrl.String())
|
||||||
@ -164,8 +168,8 @@ func jwtDecodeSegment(seg string) ([]byte, error) {
|
|||||||
|
|
||||||
func (p *OauthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) {
|
func (p *OauthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) {
|
||||||
domain := strings.Split(req.Host, ":")[0]
|
domain := strings.Split(req.Host, ":")[0]
|
||||||
if *cookieDomain != "" && strings.HasSuffix(domain, *cookieDomain) {
|
if p.CookieDomain != "" && strings.HasSuffix(domain, p.CookieDomain) {
|
||||||
domain = *cookieDomain
|
domain = p.CookieDomain
|
||||||
}
|
}
|
||||||
cookie := &http.Cookie{
|
cookie := &http.Cookie{
|
||||||
Name: p.CookieKey,
|
Name: p.CookieKey,
|
||||||
@ -181,8 +185,8 @@ func (p *OauthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) {
|
|||||||
func (p *OauthProxy) SetCookie(rw http.ResponseWriter, req *http.Request, val string) {
|
func (p *OauthProxy) SetCookie(rw http.ResponseWriter, req *http.Request, val string) {
|
||||||
|
|
||||||
domain := strings.Split(req.Host, ":")[0] // strip the port (if any)
|
domain := strings.Split(req.Host, ":")[0] // strip the port (if any)
|
||||||
if *cookieDomain != "" && strings.HasSuffix(domain, *cookieDomain) {
|
if p.CookieDomain != "" && strings.HasSuffix(domain, p.CookieDomain) {
|
||||||
domain = *cookieDomain
|
domain = p.CookieDomain
|
||||||
}
|
}
|
||||||
cookie := &http.Cookie{
|
cookie := &http.Cookie{
|
||||||
Name: p.CookieKey,
|
Name: p.CookieKey,
|
||||||
@ -190,8 +194,8 @@ func (p *OauthProxy) SetCookie(rw http.ResponseWriter, req *http.Request, val st
|
|||||||
Path: "/",
|
Path: "/",
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Secure: *cookieHttpsOnly,
|
Secure: p.CookieHttpsOnly,
|
||||||
Expires: time.Now().Add(*cookieExpire),
|
Expires: time.Now().Add(p.CookieExpire),
|
||||||
}
|
}
|
||||||
http.SetCookie(rw, cookie)
|
http.SetCookie(rw, cookie)
|
||||||
}
|
}
|
||||||
@ -267,11 +271,11 @@ func (p *OauthProxy) GetRedirect(req *http.Request) (string, error) {
|
|||||||
|
|
||||||
func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
// check if this is a redirect back at the end of oauth
|
// check if this is a redirect back at the end of oauth
|
||||||
remoteIP := req.Header.Get("X-Real-IP")
|
remoteAddr := req.RemoteAddr
|
||||||
if remoteIP == "" {
|
if req.Header.Get("X-Real-IP") != "" {
|
||||||
remoteIP = req.RemoteAddr
|
remoteAddr += fmt.Sprintf(" (%q)", req.Header.Get("X-Real-IP"))
|
||||||
}
|
}
|
||||||
log.Printf("%s %s %s", remoteIP, req.Method, req.URL.Path)
|
log.Printf("%s %s %s", remoteAddr, req.Method, req.URL.RequestURI())
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
var user string
|
var user string
|
||||||
@ -322,7 +326,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
_, email, err := p.redeemCode(req.Form.Get("code"))
|
_, email, err := p.redeemCode(req.Form.Get("code"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error redeeming code %s", err)
|
log.Printf("%s error redeeming code %s", remoteAddr, err)
|
||||||
p.ErrorPage(rw, 500, "Internal Error", err.Error())
|
p.ErrorPage(rw, 500, "Internal Error", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -334,7 +338,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
// set cookie, or deny
|
// set cookie, or deny
|
||||||
if p.Validator(email) {
|
if p.Validator(email) {
|
||||||
log.Printf("authenticating %s completed", email)
|
log.Printf("%s authenticating %s completed", remoteAddr, email)
|
||||||
p.SetCookie(rw, req, email)
|
p.SetCookie(rw, req, email)
|
||||||
http.Redirect(rw, req, redirect, 302)
|
http.Redirect(rw, req, redirect, 302)
|
||||||
return
|
return
|
||||||
@ -362,13 +366,13 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("invalid cookie")
|
log.Printf("%s - invalid cookie session", remoteAddr)
|
||||||
p.SignInPage(rw, req, 403)
|
p.SignInPage(rw, req, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, the user is authenticated. proxy normally
|
// At this point, the user is authenticated. proxy normally
|
||||||
if *passBasicAuth {
|
if p.PassBasicAuth {
|
||||||
req.SetBasicAuth(user, "")
|
req.SetBasicAuth(user, "")
|
||||||
req.Header["X-Forwarded-User"] = []string{user}
|
req.Header["X-Forwarded-User"] = []string{user}
|
||||||
req.Header["X-Forwarded-Email"] = []string{email}
|
req.Header["X-Forwarded-Email"] = []string{email}
|
||||||
|
67
options.go
Normal file
67
options.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configuration Options that can be set by Command Line Flag, or Config File
|
||||||
|
type Options struct {
|
||||||
|
HttpAddress string `flag:"http-address" cfg:"http_address"`
|
||||||
|
RedirectUrl string `flag:"redirect-url" cfg:"redirect_url"`
|
||||||
|
ClientID string `flag:"client-id" cfg:"client_id"`
|
||||||
|
ClientSecret string `flag:"client-secret" cfg:"client_secret"`
|
||||||
|
PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
|
||||||
|
HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file"`
|
||||||
|
CookieSecret string `flag:"cookie-secret" cfg:"cookie_secret"`
|
||||||
|
CookieDomain string `flag:"cookie-domain" cfg:"cookie_domain"`
|
||||||
|
CookieExpire time.Duration `flag:"cookie-expire" cfg:"cookie_expire"`
|
||||||
|
CookieHttpsOnly bool `flag:"cookie-https-only" cfg:"cookie_https_only"`
|
||||||
|
AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
|
||||||
|
GoogleAppsDomains []string `flag:"google-apps-domain" cfg:"google_apps_domains"`
|
||||||
|
Upstreams []string `flag:"upstream" cfg:"upstreams"`
|
||||||
|
|
||||||
|
// internal values that are set after config validation
|
||||||
|
redirectUrl *url.URL
|
||||||
|
proxyUrls []*url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOptions() *Options {
|
||||||
|
return &Options{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Options) Validate() error {
|
||||||
|
if len(o.Upstreams) < 1 {
|
||||||
|
return errors.New("missing -upstream")
|
||||||
|
}
|
||||||
|
if o.CookieSecret == "" {
|
||||||
|
errors.New("missing -cookie-secret")
|
||||||
|
}
|
||||||
|
if o.ClientID == "" {
|
||||||
|
return errors.New("missing -client-id")
|
||||||
|
}
|
||||||
|
if o.ClientSecret == "" {
|
||||||
|
return errors.New("missing -client-secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectUrl, err := url.Parse(o.RedirectUrl)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing -redirect-url=%q %s", o.RedirectUrl, err)
|
||||||
|
}
|
||||||
|
o.redirectUrl = redirectUrl
|
||||||
|
|
||||||
|
for _, u := range o.Upstreams {
|
||||||
|
upstreamUrl, err := url.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing -upstream=%q %s", upstreamUrl, err)
|
||||||
|
}
|
||||||
|
if upstreamUrl.Path == "" {
|
||||||
|
upstreamUrl.Path = "/"
|
||||||
|
}
|
||||||
|
o.proxyUrls = append(o.proxyUrls, upstreamUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StringArray []string
|
type StringArray []string
|
||||||
@ -12,5 +12,5 @@ func (a *StringArray) Set(s string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *StringArray) String() string {
|
func (a *StringArray) String() string {
|
||||||
return fmt.Sprint(*a)
|
return strings.Join(*a, ",")
|
||||||
}
|
}
|
||||||
|
3
version.go
Normal file
3
version.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
const VERSION = "0.1.0"
|
Loading…
Reference in New Issue
Block a user