git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add comments to exported methods for root package
commitee913fb788545db407d2f53ff0e2a47b15244ccc authorJoel Speed <Joel.speed@hotmail.co.uk> dateThu 20 Dec 2018, 09:30 (8 years ago) parent8ee802d
10 files changed+88−1
env_options.go+6 htpasswd.go+4 http.go+5−1 logging_handler.go+8 oauthproxy.go+48 options.go+4 string_array.go+3 validator.go+6 version.go+1 watcher.go+3
env_options.go+6
@@ -6,8 +6,14 @@ import (
66 "strings"
77)
88
9+// EnvOptions holds program options loaded from the process environment
910type EnvOptions map[string]interface{}
1011
12+// LoadEnvForStruct loads environment variables for each field in an options
13+// struct passed into it.
14+//
15+// Fields in the options struct must have an `env` and `cfg` tag to be read
16+// from the environment
1117func (cfg EnvOptions) LoadEnvForStruct(options interface{}) {
1218 val := reflect.ValueOf(options).Elem()
1319 typ := val.Type()
htpasswd.go+4
@@ -14,10 +14,12 @@ import (
1414// Lookup passwords in a htpasswd file
1515// Passwords must be generated with -B for bcrypt or -s for SHA1.
1616
17+// HtpasswdFile represents the structure of an htpasswd file
1718type HtpasswdFile struct {
1819 Users map[string]string
1920}
2021
22+// NewHtpasswdFromFile constructs an HtpasswdFile from the file at the path given
2123func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
2224 r, err := os.Open(path)
2325 if err != nil {
@@ -27,6 +29,7 @@ func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
2729 return NewHtpasswd(r)
2830}
2931
32+// NewHtpasswd consctructs an HtpasswdFile from an io.Reader (opened file)
3033func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) {
3134 csvReader := csv.NewReader(file)
3235 csvReader.Comma = ':'
@@ -44,6 +47,7 @@ func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) {
4447 return h, nil
4548}
4649
50+// Validate checks a users password against the HtpasswdFile entries
4751func (h *HtpasswdFile) Validate(user string, password string) bool {
4852 realPassword, exists := h.Users[user]
4953 if !exists {
http.go+5−1
@@ -9,11 +9,13 @@ import (
99 "time"
1010)
1111
12+// Server represents an HTTP server
1213type Server struct {
1314 Handler http.Handler
1415 Opts *Options
1516}
1617
18+// ListenAndServe will serve traffic on HTTP or HTTPS depending on TLS options
1719func (s *Server) ListenAndServe() {
1820 if s.Opts.TLSKeyFile != "" || s.Opts.TLSCertFile != "" {
1921 s.ServeHTTPS()
@@ -22,9 +24,10 @@ func (s *Server) ListenAndServe() {
2224 }
2325}
2426
27+// ServeHTTP constructs a net.Listener and starts handling HTTP requests
2528func (s *Server) ServeHTTP() {
2629 HTTPAddress := s.Opts.HTTPAddress
27− scheme := ""
30+ var scheme string
2831
2932 i := strings.Index(HTTPAddress, "://")
3033 if i > -1 {
@@ -57,6 +60,7 @@ func (s *Server) ServeHTTP() {
5760 log.Printf("HTTP: closing %s", listener.Addr())
5861}
5962
63+// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
6064func (s *Server) ServeHTTPS() {
6165 addr := s.Opts.HTTPSAddress
6266 config := &tls.Config{
logging_handler.go+8
@@ -27,10 +27,13 @@ type responseLogger struct {
2727 authInfo string
2828}
2929
30+// Header returns the ResponseWriter's Header
3031func (l *responseLogger) Header() http.Header {
3132 return l.w.Header()
3233}
3334
35+// ExtractGAPMetadata extracts and removes GAP headers from the ResponseWriter's
36+// Header
3437func (l *responseLogger) ExtractGAPMetadata() {
3538 upstream := l.w.Header().Get("GAP-Upstream-Address")
3639 if upstream != "" {
@@ -44,6 +47,7 @@ func (l *responseLogger) ExtractGAPMetadata() {
4447 }
4548}
4649
50+// Write writes the response using the ResponseWriter
4751func (l *responseLogger) Write(b []byte) (int, error) {
4852 if l.status == 0 {
4953 // The status will be StatusOK if WriteHeader has not been called yet
@@ -55,16 +59,19 @@ func (l *responseLogger) Write(b []byte) (int, error) {
5559 return size, err
5660}
5761
62+// WriteHeader writes the status code for the Response
5863func (l *responseLogger) WriteHeader(s int) {
5964 l.ExtractGAPMetadata()
6065 l.w.WriteHeader(s)
6166 l.status = s
6267}
6368
69+// Status returns the response status code
6470func (l *responseLogger) Status() int {
6571 return l.status
6672}
6773
74+// Size returns teh response size
6875func (l *responseLogger) Size() int {
6976 return l.size
7077}
@@ -94,6 +101,7 @@ type loggingHandler struct {
94101 logTemplate *template.Template
95102}
96103
104+// LoggingHandler provides an http.Handler which logs requests to the HTTP server
97105func LoggingHandler(out io.Writer, h http.Handler, v bool, requestLoggingTpl string) http.Handler {
98106 return loggingHandler{
99107 writer: out,
oauthproxy.go+48
@@ -20,12 +20,16 @@ import (
2020)
2121
2222const (
23+ // SignatureHeader is the name of the request header containing the GAP Signature
24+ // Part of hmacauth
2325 SignatureHeader = "GAP-Signature"
2426
2527 httpScheme = "http"
2628 httpsScheme = "https"
2729)
2830
31+// SignatureHeaders contains the headers to be signed by the hmac algorithm
32+// Part of hmacauth
2933var SignatureHeaders = []string{
3034 "Content-Length",
3135 "Content-Md5",
@@ -39,6 +43,7 @@ var SignatureHeaders = []string{
3943 "Gap-Auth",
4044}
4145
46+// OAuthProxy is the main authentication proxy
4247type OAuthProxy struct {
4348 CookieSeed string
4449 CookieName string
@@ -79,12 +84,15 @@ type OAuthProxy struct {
7984 Footer string
8085}
8186
87+// UpstreamProxy represents an upstream server to proxy to
8288type UpstreamProxy struct {
8389 upstream string
8490 handler http.Handler
8591 auth hmacauth.HmacAuth
8692}
8793
94+// ServeHTTP proxies requests to the upstream provider while signing the
95+// request headers
8896func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
8997 w.Header().Set("GAP-Upstream-Address", u.upstream)
9098 if u.auth != nil {
@@ -94,9 +102,12 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
94102 u.handler.ServeHTTP(w, r)
95103}
96104
105+// NewReverseProxy creates a new reverse proxy for proxying requests to upstream
106+// servers
97107func NewReverseProxy(target *url.URL) (proxy *httputil.ReverseProxy) {
98108 return httputil.NewSingleHostReverseProxy(target)
99109}
110+
100111func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
101112 director := proxy.Director
102113 proxy.Director = func(req *http.Request) {
@@ -107,6 +118,7 @@ func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
107118 req.URL.RawQuery = ""
108119 }
109120}
121+
110122func setProxyDirector(proxy *httputil.ReverseProxy) {
111123 director := proxy.Director
112124 proxy.Director = func(req *http.Request) {
@@ -116,10 +128,13 @@ func setProxyDirector(proxy *httputil.ReverseProxy) {
116128 req.URL.RawQuery = ""
117129 }
118130}
131+
132+// NewFileServer creates a http.Handler to serve files from the filesystem
119133func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
120134 return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
121135}
122136
137+// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
123138func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
124139 serveMux := http.NewServeMux()
125140 var auth hmacauth.HmacAuth
@@ -214,6 +229,8 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
214229 }
215230}
216231
232+// GetRedirectURI returns the redirectURL that the upstream OAuth Provider will
233+// redirect clients to once authenticated
217234func (p *OAuthProxy) GetRedirectURI(host string) string {
218235 // default to the request Host if not set
219236 if p.redirectURL.Host != "" {
@@ -259,6 +276,8 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, e
259276 return
260277}
261278
279+// MakeSessionCookie creates an http.Cookie containing the authenticated user's
280+// authentication details
262281func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
263282 if value != "" {
264283 value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
@@ -270,6 +289,7 @@ func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expirati
270289 return p.makeCookie(req, p.CookieName, value, expiration, now)
271290}
272291
292+// MakeCSRFCookie creates a cookie for CSRF
273293func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
274294 return p.makeCookie(req, p.CSRFCookieName, value, expiration, now)
275295}
@@ -296,14 +316,19 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
296316 }
297317}
298318
319+// ClearCSRFCookie creates a cookie to unset the CSRF cookie stored in the user's
320+// session
299321func (p *OAuthProxy) ClearCSRFCookie(rw http.ResponseWriter, req *http.Request) {
300322 http.SetCookie(rw, p.MakeCSRFCookie(req, "", time.Hour*-1, time.Now()))
301323}
302324
325+// SetCSRFCookie adds a CSRF cookie to the response
303326func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, val string) {
304327 http.SetCookie(rw, p.MakeCSRFCookie(req, val, p.CookieExpire, time.Now()))
305328}
306329
330+// ClearSessionCookie creates a cookie to unset the user's authentication cookie
331+// stored in the user's session
307332func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
308333 clr := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now())
309334 http.SetCookie(rw, clr)
@@ -316,10 +341,12 @@ func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Reques
316341 }
317342}
318343
344+// SetSessionCookie adds the user's session cookie to the response
319345func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
320346 http.SetCookie(rw, p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()))
321347}
322348
349+// LoadCookiedSession reads the user's authentication details from the request
323350func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) {
324351 var age time.Duration
325352 c, err := req.Cookie(p.CookieName)
@@ -341,6 +368,7 @@ func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionSt
341368 return session, age, nil
342369}
343370
371+// SaveSession creates a new session cookie value and sets this on the response
344372func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *providers.SessionState) error {
345373 value, err := p.provider.CookieForSession(s, p.CookieCipher)
346374 if err != nil {
@@ -350,16 +378,19 @@ func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *p
350378 return nil
351379}
352380
381+// RobotsTxt disallows scraping pages from the OAuthProxy
353382func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) {
354383 rw.WriteHeader(http.StatusOK)
355384 fmt.Fprintf(rw, "User-agent: *\nDisallow: /")
356385}
357386
387+// PingPage responds 200 OK to requests
358388func (p *OAuthProxy) PingPage(rw http.ResponseWriter) {
359389 rw.WriteHeader(http.StatusOK)
360390 fmt.Fprintf(rw, "OK")
361391}
362392
393+// ErrorPage writes an error response
363394func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
364395 log.Printf("ErrorPage %d %s %s", code, title, message)
365396 rw.WriteHeader(code)
@@ -375,6 +406,7 @@ func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, m
375406 p.templates.ExecuteTemplate(rw, "error.html", t)
376407}
377408
409+// SignInPage writes the sing in template to the response
378410func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code int) {
379411 p.ClearSessionCookie(rw, req)
380412 rw.WriteHeader(code)
@@ -407,6 +439,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
407439 p.templates.ExecuteTemplate(rw, "sign_in.html", t)
408440}
409441
442+// ManualSignIn handles basic auth logins to the proxy
410443func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (string, bool) {
411444 if req.Method != "POST" || p.HtpasswdFile == nil {
412445 return "", false
@@ -424,6 +457,8 @@ func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (st
424457 return "", false
425458}
426459
460+// GetRedirect reads the query parameter to get the URL to redirect clients to
461+// once authenticated with the OAuthProxy
427462func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error) {
428463 err = req.ParseForm()
429464 if err != nil {
@@ -438,11 +473,13 @@ func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error)
438473 return
439474}
440475
476+// IsWhitelistedRequest is used to check if auth should be skipped for this request
441477func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok bool) {
442478 isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
443479 return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path)
444480}
445481
482+// IsWhitelistedPath is used to check if the request path is allowed without auth
446483func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) {
447484 for _, u := range p.compiledRegex {
448485 ok = u.MatchString(path)
@@ -484,6 +521,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
484521 }
485522}
486523
524+// SignIn serves a page prompting users to sign in
487525func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
488526 redirect, err := p.GetRedirect(req)
489527 if err != nil {
@@ -505,11 +543,13 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
505543 }
506544}
507545
546+// SignOut sends a response to clear the authentication cookie
508547func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
509548 p.ClearSessionCookie(rw, req)
510549 http.Redirect(rw, req, "/", 302)
511550}
512551
552+// OAuthStart starts the OAuth2 authentication flow
513553func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
514554 nonce, err := cookie.Nonce()
515555 if err != nil {
@@ -526,6 +566,8 @@ func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
526566 http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), 302)
527567}
528568
569+// OAuthCallback is the OAuth2 authentication flow callback that finishes the
570+// OAuth2 authentication flow
529571func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
530572 remoteAddr := getRemoteAddr(req)
531573
@@ -587,6 +629,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
587629 }
588630}
589631
632+// AuthenticateOnly checks whether the user is currently logged in
590633func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) {
591634 status := p.Authenticate(rw, req)
592635 if status == http.StatusAccepted {
@@ -596,6 +639,8 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request)
596639 }
597640}
598641
642+// Proxy proxies the user request if the user is authenticated else it prompts
643+// them to authenticate
599644func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
600645 status := p.Authenticate(rw, req)
601646 if status == http.StatusInternalServerError {
@@ -612,6 +657,7 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
612657 }
613658}
614659
660+// Authenticate checks whether a user is authenticated
615661func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int {
616662 var saveSession, clearSession, revalidated bool
617663 remoteAddr := getRemoteAddr(req)
@@ -711,6 +757,8 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
711757 return http.StatusAccepted
712758}
713759
760+// CheckBasicAuth checks the requests Authorization header for basic auth
761+// credentials and authenticates these against the proxies HtpasswdFile
714762func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState, error) {
715763 if p.HtpasswdFile == nil {
716764 return nil, nil
options.go+4
@@ -89,11 +89,13 @@ type Options struct {
8989 oidcVerifier *oidc.IDTokenVerifier
9090}
9191
92+// SignatureData holds hmacauth signature hash and key
9293type SignatureData struct {
9394 hash crypto.Hash
9495 key string
9596}
9697
98+// NewOptions constructs a new Options with defaulted values
9799func NewOptions() *Options {
98100 return &Options{
99101 ProxyPrefix: "/oauth2",
@@ -126,6 +128,8 @@ func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string
126128 return parsed, msgs
127129}
128130
131+// Validate checks that required options are set and validates those that they
132+// are of the correct format
129133func (o *Options) Validate() error {
130134 if o.SSLInsecureSkipVerify {
131135 // TODO: Accept a certificate bundle.
string_array.go+3
@@ -4,13 +4,16 @@ import (
44 "strings"
55)
66
7+// StringArray is a type alias for a slice of strings
78type StringArray []string
89
10+// Set appends a string to the StringArray
911func (a *StringArray) Set(s string) error {
1012 *a = append(*a, s)
1113 return nil
1214}
1315
16+// String joins elements of the StringArray into a single comma separated string
1417func (a *StringArray) String() string {
1518 return strings.Join(*a, ",")
1619}
validator.go+6
@@ -10,11 +10,13 @@ import (
1010 "unsafe"
1111)
1212
13+// UserMap holds information from the authenticated emails file
1314type UserMap struct {
1415 usersFile string
1516 m unsafe.Pointer
1617}
1718
19+// NewUserMap parses the authenticated emails file into a new UserMap
1820func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
1921 um := &UserMap{usersFile: usersFile}
2022 m := make(map[string]bool)
@@ -30,12 +32,15 @@ func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
3032 return um
3133}
3234
35+// IsValid checks if an email is allowed
3336func (um *UserMap) IsValid(email string) (result bool) {
3437 m := *(*map[string]bool)(atomic.LoadPointer(&um.m))
3538 _, result = m[email]
3639 return
3740}
3841
42+// LoadAuthenticatedEmailsFile loads the authenticated emails file from disk
43+// and parses the contents as CSV
3944func (um *UserMap) LoadAuthenticatedEmailsFile() {
4045 r, err := os.Open(um.usersFile)
4146 if err != nil {
@@ -91,6 +96,7 @@ func newValidatorImpl(domains []string, usersFile string,
9196 return validator
9297}
9398
99+// NewValidator constructs a function to validate email addresses
94100func NewValidator(domains []string, usersFile string) func(string) bool {
95101 return newValidatorImpl(domains, usersFile, nil, func() {})
96102}
version.go+1
@@ -1,3 +1,4 @@
11package main
22
3+// VERSION contains version information
34const VERSION = "2.2.1-alpha"
watcher.go+3
@@ -11,6 +11,8 @@ import (
1111 fsnotify "gopkg.in/fsnotify/fsnotify.v1"
1212)
1313
14+// WaitForReplacement waits for a file to exist on disk and then starts a watch
15+// for the file
1416func WaitForReplacement(filename string, op fsnotify.Op,
1517 watcher *fsnotify.Watcher) {
1618 const sleepInterval = 50 * time.Millisecond
@@ -30,6 +32,7 @@ func WaitForReplacement(filename string, op fsnotify.Op,
3032 }
3133}
3234
35+// WatchForUpdates performs an action every time a file on disk is updated
3336func WatchForUpdates(filename string, done <-chan bool, action func()) {
3437 filename = filepath.Clean(filename)
3538 watcher, err := fsnotify.NewWatcher()