git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #52 from MisterWil/enhanced_logging

Added auth and standard logging with templating and file rolling

commit93b7d31332e706ad255aad5852c6a97bf346dfdd authorJoel Speed <Joel.speed@hotmail.co.uk> dateWed 1 May 2019, 15:44 (7 years ago) parent46a0d7c9eaa9fd
24 files changed+823−223
CHANGELOG.md+15 Gopkg.lock+9 Gopkg.toml+4 README.md+93−4 api/api.go+5−5 contrib/oauth2_proxy.cfg.example+12−2 htpasswd.go+2−2 http.go+11−10 logger/logger.go+452 logging_handler.go+7−84 logging_handler_test.go+6−2 main.go+24−10 oauthproxy.go+33−25 oauthproxy_test.go+4−4 options.go+94−24 providers/azure.go+3−3 providers/github.go+9−8 providers/gitlab.go+3−3 providers/google.go+8−8 providers/internal_util.go+7−7 templates.go+6−5 validator.go+5−4 watcher.go+9−9 watcher_unsupported.go+2−4
CHANGELOG.md+15
@@ -2,6 +2,21 @@
22
33## Changes since v3.2.0
44
5+- [#52](https://github.com/pusher/oauth2_proxy/pull/52) Logging Improvements (@MisterWil)
6+ - Implement flags to configure file logging
7+ - `-logging-filename` Defines the filename to log to
8+ - `-logging-max-size` Defines the maximum
9+ - `-logging-max-age` Defines the maximum age of backups to retain
10+ - `-logging-max-backups` Defines the maximum number of rollover log files to retain
11+ - `-logging-compress` Defines if rollover log files should be compressed
12+ - `-logging-local-time` Defines if logging date and time should be local or UTC
13+ - Implement two new flags to enable or disable specific logging types
14+ - `-standard-logging` Enables or disables standard (not request or auth) logging
15+ - `-auth-logging` Enables or disables auth logging
16+ - Implement two new flags to customize the logging format
17+ - `-standard-logging-format` Sets the format for standard logging
18+ - `-auth-logging-format` Sets the format for auth logging
19+
520- [#111](https://github.com/pusher/oauth2_proxy/pull/111) Add option for telling where to find a login.gov JWT key file (@timothy-spencer)
621
722# v3.2.0
Gopkg.lock+9
@@ -190,6 +190,14 @@
190190 revision = "836bfd95fecc0f1511dd66bdbf2b5b61ab8b00b6"
191191 version = "v1.2.11"
192192
193+[[projects]]
194+ digest = "1:11c58e19ff7ce22740423bb933f1ddca3bf575def40d5ac3437ec12871b1648b"
195+ name = "gopkg.in/natefinch/lumberjack.v2"
196+ packages = ["."]
197+ pruneopts = ""
198+ revision = "a96e63847dc3c67d17befa69c303767e2f84e54f"
199+ version = "v2.1"
200+
193201[[projects]]
194202 digest = "1:be4ed0a2b15944dd777a663681a39260ed05f9c4e213017ed2e2255622c8820c"
195203 name = "gopkg.in/square/go-jose.v2"
@@ -222,6 +230,7 @@
222230 "google.golang.org/api/admin/directory/v1",
223231 "google.golang.org/api/googleapi",
224232 "gopkg.in/fsnotify/fsnotify.v1",
233+ "gopkg.in/natefinch/lumberjack.v2",
225234 ]
226235 solver-name = "gps-cdcl"
227236 solver-version = 1
Gopkg.toml+4
@@ -38,3 +38,7 @@
3838[[constraint]]
3939 branch = "master"
4040 name = "golang.org/x/crypto"
41+
42+[[constraint]]
43+ name = "gopkg.in/natefinch/lumberjack.v2"
44+ version = "2.1.0"
README.md+93−4
@@ -270,6 +270,8 @@ An example [oauth2_proxy.cfg](contrib/oauth2_proxy.cfg.example) config file is i
270270Usage of oauth2_proxy:
271271 -acr-values string: optional, used by login.gov (default "http://idmanagement.gov/ns/assurance/loa/1")
272272 -approval-prompt string: OAuth approval_prompt (default "force")
273+ -auth-logging: Log authentication attempts (default true)
274+ -auth-logging-format string: Template for authentication log lines (see "Logging Configuration" paragraph below)
273275 -authenticated-emails-file string: authenticate against emails via file (one per line)
274276 -azure-tenant string: go to a tenant-specific or common (tenant-independent) endpoint. (default "common")
275277 -basic-auth-password string: the password to set when passing the HTTP Basic Auth header
@@ -298,6 +300,12 @@ Usage of oauth2_proxy:
298300 -htpasswd-file string: additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
299301 -http-address string: [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients (default "127.0.0.1:4180")
300302 -https-address string: <addr>:<port> to listen on for HTTPS clients (default ":443")
303+ -logging-compress: Should rotated log files be compressed using gzip (default false)
304+ -logging-filename string: File to log requests to, empty for stdout (default to stdout)
305+ -logging-local-time: If the time in log files and backup filenames are local or UTC time (default true)
306+ -logging-max-age int: Maximum number of days to retain old log files (default 7)
307+ -logging-max-backups int: Maximum number of old log files to retain; 0 to disable (default 0)
308+ -logging-max-size int: Maximum size in megabytes of the log file before rotation (default 100)
301309 -jwt-key string: private key in PEM format used to sign JWT, so that you can say something like -jwt-key="${OAUTH2_PROXY_JWT_KEY}": required by login.gov
302310 -jwt-key-file string: path to the private key file in PEM format used to sign the JWT so that you can say something like -jwt-key-file=/etc/ssl/private/jwt_signing_key.pem: required by login.gov
303311 -login-url string: Authentication endpoint
@@ -316,7 +324,7 @@ Usage of oauth2_proxy:
316324 -redeem-url string: Token redemption endpoint
317325 -redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
318326 -request-logging: Log requests to stdout (default true)
319− -request-logging-format: Template for request log lines (see "Logging Format" paragraph below)
327+ -request-logging-format: Template for request log lines (see "Logging Configuration" paragraph below)
320328 -resource string: The resource that is protected (Azure AD only)
321329 -scope string: OAuth scope specification
322330 -set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)
@@ -327,6 +335,8 @@ Usage of oauth2_proxy:
327335 -skip-oidc-discovery: bypass OIDC endpoint discovery. login-url, redeem-url and oidc-jwks-url must be configured in this case
328336 -skip-provider-button: will skip sign-in-page to directly reach the next step: oauth/start
329337 -ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS
338+ -standard-logging: Log standard runtime information (default true)
339+ -standard-logging-format string: Template for standard log lines (see "Logging Configuration" paragraph below)
330340 -tls-cert string: path to certificate file
331341 -tls-key string: path to private key file
332342 -upstream value: the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path
@@ -457,9 +467,52 @@ following:
457467- [rc3.org: Using HMAC to authenticate Web service
458468 requests](http://rc3.org/2011/12/02/using-hmac-to-authenticate-web-service-requests/)
459469
460−## Logging Format
470+## Logging Configuration
461471
462−By default, OAuth2 Proxy logs requests to stdout in a format similar to Apache Combined Log.
472+By default, OAuth2 Proxy logs all output to stdout. Logging can be configured to output to a rotating log file using the `-logging-filename` command.
473+
474+If logging to a file you can also configure the maximum file size (`-logging-max-size`), age (`-logging-max-age`), max backup logs (`-logging-max-backups`), and if backup logs should be compressed (`-logging-compress`).
475+
476+There are three different types of logging: standard, authentication, and HTTP requests. These can each be enabled or disabled with `-standard-logging`, `-auth-logging`, and `-request-logging`.
477+
478+Each type of logging has their own configurable format and variables. By default these formats are similar to the Apache Combined Log.
479+
480+### Auth Log Format
481+Authentication logs are logs which are guaranteed to contain a username or email address of a user attempting to authenticate. These logs are output by default in the below format:
482+
483+```
484+<REMOTE_ADDRESS> - <user@domain.com> [19/Mar/2015:17:20:19 -0400] [<STATUS>] <MESSAGE>
485+```
486+
487+The status block will contain one of the below strings:
488+
489+- `AuthSuccess` If a user has authenticated successfully by any method
490+- `AuthFailure` If the user failed to authenticate explicitly
491+- `AuthError` If there was an unexpected error during authentication
492+
493+If you require a different format than that, you can configure it with the `-auth-logging-format` flag.
494+The default format is configured as follows:
495+
496+```
497+{{.Client}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}
498+```
499+
500+Available variables for auth logging:
501+
502+| Variable | Example | Description |
503+| --- | --- | --- |
504+| Client | 74.125.224.72 | The client/remote IP address. Will use the X-Real-IP header it if exists. |
505+| Host | domain.com | The value of the Host header. |
506+| Protocol | HTTP/1.0 | The request protocol. |
507+| RequestMethod | GET | The request method. |
508+| Timestamp | 19/Mar/2015:17:20:19 -0400 | The date and time of the logging event. |
509+| UserAgent | - | The full user agent as reported by the requesting client. |
510+| Username | username@email.com | The email or username of the auth request. |
511+| Status | AuthSuccess | The status of the auth request. See above for details. |
512+| Message | Authenticated via OAuth2 | The details of the auth attempt. |
513+
514+### Request Log Format
515+HTTP request logs will output by default in the below format:
463516
464517```
465518<REMOTE_ADDRESS> - <user@domain.com> [19/Mar/2015:17:20:19 -0400] <HOST_HEADER> GET <UPSTREAM_HOST> "/path/" HTTP/1.1 "<USER_AGENT>" <RESPONSE_CODE> <RESPONSE_BYTES> <REQUEST_DURATION>
@@ -472,7 +525,43 @@ The default format is configured as follows:
472525{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}
473526```
474527
475−[See `logMessageData` in `logging_handler.go`](./logging_handler.go) for all available variables.
528+Available variables for request logging:
529+
530+| Variable | Example | Description |
531+| --- | --- | --- |
532+| Client | 74.125.224.72 | The client/remote IP address. Will use the X-Real-IP header it if exists. |
533+| Host | domain.com | The value of the Host header. |
534+| Protocol | HTTP/1.0 | The request protocol. |
535+| RequestDuration | 0.001 | The time in seconds that a request took to process. |
536+| RequestMethod | GET | The request method. |
537+| RequestURI | "/oauth2/auth" | The URI path of the request. |
538+| ResponseSize | 12 | The size in bytes of the response. |
539+| StatusCode | 200 | The HTTP status code of the response. |
540+| Timestamp | 19/Mar/2015:17:20:19 -0400 | The date and time of the logging event. |
541+| Upstream | - | The upstream data of the HTTP request. |
542+| UserAgent | - | The full user agent as reported by the requesting client. |
543+| Username | username@email.com | The email or username of the auth request. |
544+
545+### Standard Log Format
546+All other logging that is not covered by the above two types of logging will be output in this standard logging format. This includes configuration information at startup and errors that occur outside of a session. The default format is below:
547+
548+```
549+[19/Mar/2015:17:20:19 -0400] [main.go:40] <MESSAGE>
550+```
551+
552+If you require a different format than that, you can configure it with the `-standard-logging-format` flag. The default format is configured as follows:
553+
554+```
555+[{{.Timestamp}}] [{{.File}}] {{.Message}}
556+```
557+
558+Available variables for standard logging:
559+
560+| Variable | Example | Description |
561+| --- | --- | --- |
562+| Timestamp | 19/Mar/2015:17:20:19 -0400 | The date and time of the logging event. |
563+| File | main.go:40 | The file and line number of the logging statement. |
564+| Message | HTTP: listening on 127.0.0.1:4180 | The details of the log statement. |
476565
477566## Adding a new Provider
478567
api/api.go+5−5
@@ -4,22 +4,22 @@ import (
44 "encoding/json"
55 "fmt"
66 "io/ioutil"
7− "log"
87 "net/http"
98
109 "github.com/bitly/go-simplejson"
10+ "github.com/pusher/oauth2_proxy/logger"
1111)
1212
1313// Request parses the request body into a simplejson.Json object
1414func Request(req *http.Request) (*simplejson.Json, error) {
1515 resp, err := http.DefaultClient.Do(req)
1616 if err != nil {
17− log.Printf("%s %s %s", req.Method, req.URL, err)
17+ logger.Printf("%s %s %s", req.Method, req.URL, err)
1818 return nil, err
1919 }
2020 body, err := ioutil.ReadAll(resp.Body)
2121 resp.Body.Close()
22− log.Printf("%d %s %s %s", resp.StatusCode, req.Method, req.URL, body)
22+ logger.Printf("%d %s %s %s", resp.StatusCode, req.Method, req.URL, body)
2323 if err != nil {
2424 return nil, err
2525 }
@@ -37,12 +37,12 @@ func Request(req *http.Request) (*simplejson.Json, error) {
3737func RequestJSON(req *http.Request, v interface{}) error {
3838 resp, err := http.DefaultClient.Do(req)
3939 if err != nil {
40− log.Printf("%s %s %s", req.Method, req.URL, err)
40+ logger.Printf("%s %s %s", req.Method, req.URL, err)
4141 return err
4242 }
4343 body, err := ioutil.ReadAll(resp.Body)
4444 resp.Body.Close()
45− log.Printf("%d %s %s %s", resp.StatusCode, req.Method, req.URL, body)
45+ logger.Printf("%d %s %s %s", resp.StatusCode, req.Method, req.URL, body)
4646 if err != nil {
4747 return err
4848 }
contrib/oauth2_proxy.cfg.example+12−2
@@ -18,8 +18,18 @@
1818# "http://127.0.0.1:8080/"
1919# ]
2020
21−## Log requests to stdout
22−# request_logging = true
21+## Logging configuration
22+#logging_filename = ""
23+#logging_max_size = 100
24+#logging_max_age = 7
25+#logging_local_time = true
26+#logging_compress = false
27+#standard_logging = true
28+#standard_logging_format = "[{{.Timestamp}}] [{{.File}}] {{.Message}}"
29+#request_logging = true
30+#request_logging_format = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
31+#auth_logging = true
32+#auth_logging_format = "{{.Client}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}"
2333
2434## pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
2535# pass_basic_auth = true
htpasswd.go+2−2
@@ -5,9 +5,9 @@ import (
55 "encoding/base64"
66 "encoding/csv"
77 "io"
8− "log"
98 "os"
109
10+ "github.com/pusher/oauth2_proxy/logger"
1111 "golang.org/x/crypto/bcrypt"
1212)
1313
@@ -67,6 +67,6 @@ func (h *HtpasswdFile) Validate(user string, password string) bool {
6767 return bcrypt.CompareHashAndPassword([]byte(realPassword), []byte(password)) == nil
6868 }
6969
70− log.Printf("Invalid htpasswd entry for %s. Must be a SHA or bcrypt entry.", user)
70+ logger.Printf("Invalid htpasswd entry for %s. Must be a SHA or bcrypt entry.", user)
7171 return false
7272}
http.go+11−10
@@ -2,11 +2,12 @@ package main
22
33import (
44 "crypto/tls"
5− "log"
65 "net"
76 "net/http"
87 "strings"
98 "time"
9+
10+ "github.com/pusher/oauth2_proxy/logger"
1011)
1112
1213// Server represents an HTTP server
@@ -86,17 +87,17 @@ func (s *Server) ServeHTTP() {
8687
8788 listener, err := net.Listen(networkType, listenAddr)
8889 if err != nil {
89− log.Fatalf("FATAL: listen (%s, %s) failed - %s", networkType, listenAddr, err)
90+ logger.Fatalf("FATAL: listen (%s, %s) failed - %s", networkType, listenAddr, err)
9091 }
91− log.Printf("HTTP: listening on %s", listenAddr)
92+ logger.Printf("HTTP: listening on %s", listenAddr)
9293
9394 server := &http.Server{Handler: s.Handler}
9495 err = server.Serve(listener)
9596 if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
96− log.Printf("ERROR: http.Serve() - %s", err)
97+ logger.Printf("ERROR: http.Serve() - %s", err)
9798 }
9899
99− log.Printf("HTTP: closing %s", listener.Addr())
100+ logger.Printf("HTTP: closing %s", listener.Addr())
100101}
101102
102103// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
@@ -114,24 +115,24 @@ func (s *Server) ServeHTTPS() {
114115 config.Certificates = make([]tls.Certificate, 1)
115116 config.Certificates[0], err = tls.LoadX509KeyPair(s.Opts.TLSCertFile, s.Opts.TLSKeyFile)
116117 if err != nil {
117− log.Fatalf("FATAL: loading tls config (%s, %s) failed - %s", s.Opts.TLSCertFile, s.Opts.TLSKeyFile, err)
118+ logger.Fatalf("FATAL: loading tls config (%s, %s) failed - %s", s.Opts.TLSCertFile, s.Opts.TLSKeyFile, err)
118119 }
119120
120121 ln, err := net.Listen("tcp", addr)
121122 if err != nil {
122− log.Fatalf("FATAL: listen (%s) failed - %s", addr, err)
123+ logger.Fatalf("FATAL: listen (%s) failed - %s", addr, err)
123124 }
124− log.Printf("HTTPS: listening on %s", ln.Addr())
125+ logger.Printf("HTTPS: listening on %s", ln.Addr())
125126
126127 tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
127128 srv := &http.Server{Handler: s.Handler}
128129 err = srv.Serve(tlsListener)
129130
130131 if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
131− log.Printf("ERROR: https.Serve() - %s", err)
132+ logger.Printf("ERROR: https.Serve() - %s", err)
132133 }
133134
134− log.Printf("HTTPS: closing %s", tlsListener.Addr())
135+ logger.Printf("HTTPS: closing %s", tlsListener.Addr())
135136}
136137
137138// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
logger/logger.gonew file+452
@@ -0,0 +1,452 @@
1+package logger
2+
3+import (
4+ "fmt"
5+ "io"
6+ "net"
7+ "net/http"
8+ "net/url"
9+ "os"
10+ "runtime"
11+ "sync"
12+ "text/template"
13+ "time"
14+)
15+
16+// AuthStatus defines the different types of auth logging that occur
17+type AuthStatus string
18+
19+const (
20+ // DefaultStandardLoggingFormat defines the default standard log format
21+ DefaultStandardLoggingFormat = "[{{.Timestamp}}] [{{.File}}] {{.Message}}"
22+ // DefaultAuthLoggingFormat defines the default auth log format
23+ DefaultAuthLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}"
24+ // DefaultRequestLoggingFormat defines the default request log format
25+ DefaultRequestLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
26+
27+ // AuthSuccess indicates that an auth attempt has succeeded explicitly
28+ AuthSuccess AuthStatus = "AuthSuccess"
29+ // AuthFailure indicates that an auth attempt has failed explicitly
30+ AuthFailure AuthStatus = "AuthFailure"
31+ // AuthError indicates that an auth attempt has failed due to an error
32+ AuthError AuthStatus = "AuthError"
33+
34+ // Llongfile flag to log full file name and line number: /a/b/c/d.go:23
35+ Llongfile = 1 << iota
36+ // Lshortfile flag to log final file name element and line number: d.go:23. overrides Llongfile
37+ Lshortfile
38+ // LUTC flag to log UTC datetime rather than the local time zone
39+ LUTC
40+ // LstdFlags flag for initial values for the logger
41+ LstdFlags = Lshortfile
42+)
43+
44+// These are the containers for all values that are available as variables in the logging formats.
45+// All values are pre-formatted strings so it is easy to use them in the format string.
46+type stdLogMessageData struct {
47+ Timestamp,
48+ File,
49+ Message string
50+}
51+
52+type authLogMessageData struct {
53+ Client,
54+ Host,
55+ Protocol,
56+ RequestMethod,
57+ Timestamp,
58+ UserAgent,
59+ Username,
60+ Status,
61+ Message string
62+}
63+
64+type reqLogMessageData struct {
65+ Client,
66+ Host,
67+ Protocol,
68+ RequestDuration,
69+ RequestMethod,
70+ RequestURI,
71+ ResponseSize,
72+ StatusCode,
73+ Timestamp,
74+ Upstream,
75+ UserAgent,
76+ Username string
77+}
78+
79+// A Logger represents an active logging object that generates lines of
80+// output to an io.Writer passed through a formatter. Each logging
81+// operation makes a single call to the Writer's Write method. A Logger
82+// can be used simultaneously from multiple goroutines; it guarantees to
83+// serialize access to the Writer.
84+type Logger struct {
85+ mu sync.Mutex
86+ flag int
87+ writer io.Writer
88+ stdEnabled bool
89+ authEnabled bool
90+ reqEnabled bool
91+ stdLogTemplate *template.Template
92+ authTemplate *template.Template
93+ reqTemplate *template.Template
94+}
95+
96+// New creates a new Standarderr Logger.
97+func New(flag int) *Logger {
98+ return &Logger{
99+ writer: os.Stderr,
100+ flag: flag,
101+ stdEnabled: true,
102+ authEnabled: true,
103+ reqEnabled: true,
104+ stdLogTemplate: template.Must(template.New("std-log").Parse(DefaultStandardLoggingFormat)),
105+ authTemplate: template.Must(template.New("auth-log").Parse(DefaultAuthLoggingFormat)),
106+ reqTemplate: template.Must(template.New("req-log").Parse(DefaultRequestLoggingFormat)),
107+ }
108+}
109+
110+var std = New(LstdFlags)
111+
112+// Output a standard log template with a simple message.
113+// Write a final newline at the end of every message.
114+func (l *Logger) Output(calldepth int, message string) {
115+ if !l.stdEnabled {
116+ return
117+ }
118+
119+ now := time.Now()
120+ file := "???:0"
121+
122+ if l.flag&(Lshortfile|Llongfile) != 0 {
123+ file = l.GetFileLineString(calldepth + 1)
124+ }
125+
126+ l.mu.Lock()
127+ defer l.mu.Unlock()
128+
129+ l.stdLogTemplate.Execute(l.writer, stdLogMessageData{
130+ Timestamp: FormatTimestamp(now),
131+ File: file,
132+ Message: message,
133+ })
134+
135+ l.writer.Write([]byte("\n"))
136+}
137+
138+// PrintAuth writes auth info to the logger. Requires an http.Request to
139+// log request details. Remaining arguments are handled in the manner of
140+// fmt.Sprintf. Writes a final newline to the end of every message.
141+func (l *Logger) PrintAuth(username string, req *http.Request, status AuthStatus, format string, a ...interface{}) {
142+ if !l.authEnabled {
143+ return
144+ }
145+
146+ now := time.Now()
147+
148+ if username == "" {
149+ username = "-"
150+ }
151+
152+ client := GetClient(req)
153+
154+ l.mu.Lock()
155+ defer l.mu.Unlock()
156+
157+ l.authTemplate.Execute(l.writer, authLogMessageData{
158+ Client: client,
159+ Host: req.Host,
160+ Protocol: req.Proto,
161+ RequestMethod: req.Method,
162+ Timestamp: FormatTimestamp(now),
163+ UserAgent: fmt.Sprintf("%q", req.UserAgent()),
164+ Username: username,
165+ Status: fmt.Sprintf("%s", status),
166+ Message: fmt.Sprintf(format, a...),
167+ })
168+
169+ l.writer.Write([]byte("\n"))
170+}
171+
172+// PrintReq writes request details to the Logger using the http.Request,
173+// url, and timestamp of the request. Writes a final newline to the end
174+// of every message.
175+func (l *Logger) PrintReq(username, upstream string, req *http.Request, url url.URL, ts time.Time, status int, size int) {
176+ if !l.reqEnabled {
177+ return
178+ }
179+
180+ duration := float64(time.Now().Sub(ts)) / float64(time.Second)
181+
182+ if username == "" {
183+ username = "-"
184+ }
185+
186+ if upstream == "" {
187+ upstream = "-"
188+ }
189+
190+ if url.User != nil && username == "-" {
191+ if name := url.User.Username(); name != "" {
192+ username = name
193+ }
194+ }
195+
196+ client := GetClient(req)
197+
198+ l.mu.Lock()
199+ defer l.mu.Unlock()
200+
201+ l.reqTemplate.Execute(l.writer, reqLogMessageData{
202+ Client: client,
203+ Host: req.Host,
204+ Protocol: req.Proto,
205+ RequestDuration: fmt.Sprintf("%0.3f", duration),
206+ RequestMethod: req.Method,
207+ RequestURI: fmt.Sprintf("%q", url.RequestURI()),
208+ ResponseSize: fmt.Sprintf("%d", size),
209+ StatusCode: fmt.Sprintf("%d", status),
210+ Timestamp: FormatTimestamp(ts),
211+ Upstream: upstream,
212+ UserAgent: fmt.Sprintf("%q", req.UserAgent()),
213+ Username: username,
214+ })
215+
216+ l.writer.Write([]byte("\n"))
217+}
218+
219+// GetFileLineString will find the caller file and line number
220+// taking in to account the calldepth to iterate up the stack
221+// to find the non-logging call location.
222+func (l *Logger) GetFileLineString(calldepth int) string {
223+ var file string
224+ var line int
225+ var ok bool
226+
227+ _, file, line, ok = runtime.Caller(calldepth)
228+ if !ok {
229+ file = "???"
230+ line = 0
231+ }
232+
233+ if l.flag&Lshortfile != 0 {
234+ short := file
235+ for i := len(file) - 1; i > 0; i-- {
236+ if file[i] == '/' {
237+ short = file[i+1:]
238+ break
239+ }
240+ }
241+ file = short
242+ }
243+
244+ return fmt.Sprintf("%s:%d", file, line)
245+}
246+
247+// GetClient parses an HTTP request for the client/remote IP address.
248+func GetClient(req *http.Request) string {
249+ client := req.Header.Get("X-Real-IP")
250+ if client == "" {
251+ client = req.RemoteAddr
252+ }
253+
254+ if c, _, err := net.SplitHostPort(client); err == nil {
255+ client = c
256+ }
257+
258+ return client
259+}
260+
261+// FormatTimestamp returns a formatted timestamp.
262+func (l *Logger) FormatTimestamp(ts time.Time) string {
263+ if l.flag&LUTC != 0 {
264+ ts = ts.UTC()
265+ }
266+
267+ return ts.Format("2006/01/02 15:04:05")
268+}
269+
270+// Flags returns the output flags for the logger.
271+func (l *Logger) Flags() int {
272+ l.mu.Lock()
273+ defer l.mu.Unlock()
274+ return l.flag
275+}
276+
277+// SetFlags sets the output flags for the logger.
278+func (l *Logger) SetFlags(flag int) {
279+ l.mu.Lock()
280+ defer l.mu.Unlock()
281+ l.flag = flag
282+}
283+
284+// SetStandardEnabled enables or disables standard logging.
285+func (l *Logger) SetStandardEnabled(e bool) {
286+ l.mu.Lock()
287+ defer l.mu.Unlock()
288+ l.stdEnabled = e
289+}
290+
291+// SetAuthEnabled enables or disables auth logging.
292+func (l *Logger) SetAuthEnabled(e bool) {
293+ l.mu.Lock()
294+ defer l.mu.Unlock()
295+ l.authEnabled = e
296+}
297+
298+// SetReqEnabled enabled or disables request logging.
299+func (l *Logger) SetReqEnabled(e bool) {
300+ l.mu.Lock()
301+ defer l.mu.Unlock()
302+ l.reqEnabled = e
303+}
304+
305+// SetStandardTemplate sets the template for standard logging.
306+func (l *Logger) SetStandardTemplate(t string) {
307+ l.mu.Lock()
308+ defer l.mu.Unlock()
309+ l.stdLogTemplate = template.Must(template.New("std-log").Parse(t))
310+}
311+
312+// SetAuthTemplate sets the template for auth logging.
313+func (l *Logger) SetAuthTemplate(t string) {
314+ l.mu.Lock()
315+ defer l.mu.Unlock()
316+ l.authTemplate = template.Must(template.New("auth-log").Parse(t))
317+}
318+
319+// SetReqTemplate sets the template for request logging.
320+func (l *Logger) SetReqTemplate(t string) {
321+ l.mu.Lock()
322+ defer l.mu.Unlock()
323+ l.reqTemplate = template.Must(template.New("req-log").Parse(t))
324+}
325+
326+// These functions utilize the standard logger.
327+
328+// FormatTimestamp returns a formatted timestamp for the standard logger.
329+func FormatTimestamp(ts time.Time) string {
330+ return std.FormatTimestamp(ts)
331+}
332+
333+// Flags returns the output flags for the standard logger.
334+func Flags() int {
335+ return std.Flags()
336+}
337+
338+// SetFlags sets the output flags for the standard logger.
339+func SetFlags(flag int) {
340+ std.SetFlags(flag)
341+}
342+
343+// SetOutput sets the output destination for the standard logger.
344+func SetOutput(w io.Writer) {
345+ std.mu.Lock()
346+ defer std.mu.Unlock()
347+ std.writer = w
348+}
349+
350+// SetStandardEnabled enables or disables standard logging for the
351+// standard logger.
352+func SetStandardEnabled(e bool) {
353+ std.SetStandardEnabled(e)
354+}
355+
356+// SetAuthEnabled enables or disables auth logging for the standard
357+// logger.
358+func SetAuthEnabled(e bool) {
359+ std.SetAuthEnabled(e)
360+}
361+
362+// SetReqEnabled enables or disables request logging for the
363+// standard logger.
364+func SetReqEnabled(e bool) {
365+ std.SetReqEnabled(e)
366+}
367+
368+// SetStandardTemplate sets the template for standard logging for
369+// the standard logger.
370+func SetStandardTemplate(t string) {
371+ std.SetStandardTemplate(t)
372+}
373+
374+// SetAuthTemplate sets the template for auth logging for the
375+// standard logger.
376+func SetAuthTemplate(t string) {
377+ std.SetAuthTemplate(t)
378+}
379+
380+// SetReqTemplate sets the template for request logging for the
381+// standard logger.
382+func SetReqTemplate(t string) {
383+ std.SetReqTemplate(t)
384+}
385+
386+// Print calls Output to print to the standard logger.
387+// Arguments are handled in the manner of fmt.Print.
388+func Print(v ...interface{}) {
389+ std.Output(2, fmt.Sprint(v...))
390+}
391+
392+// Printf calls Output to print to the standard logger.
393+// Arguments are handled in the manner of fmt.Printf.
394+func Printf(format string, v ...interface{}) {
395+ std.Output(2, fmt.Sprintf(format, v...))
396+}
397+
398+// Println calls Output to print to the standard logger.
399+// Arguments are handled in the manner of fmt.Println.
400+func Println(v ...interface{}) {
401+ std.Output(2, fmt.Sprintln(v...))
402+}
403+
404+// Fatal is equivalent to Print() followed by a call to os.Exit(1).
405+func Fatal(v ...interface{}) {
406+ std.Output(2, fmt.Sprint(v...))
407+ os.Exit(1)
408+}
409+
410+// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
411+func Fatalf(format string, v ...interface{}) {
412+ std.Output(2, fmt.Sprintf(format, v...))
413+ os.Exit(1)
414+}
415+
416+// Fatalln is equivalent to Println() followed by a call to os.Exit(1).
417+func Fatalln(v ...interface{}) {
418+ std.Output(2, fmt.Sprintln(v...))
419+ os.Exit(1)
420+}
421+
422+// Panic is equivalent to Print() followed by a call to panic().
423+func Panic(v ...interface{}) {
424+ s := fmt.Sprint(v...)
425+ std.Output(2, s)
426+ panic(s)
427+}
428+
429+// Panicf is equivalent to Printf() followed by a call to panic().
430+func Panicf(format string, v ...interface{}) {
431+ s := fmt.Sprintf(format, v...)
432+ std.Output(2, s)
433+ panic(s)
434+}
435+
436+// Panicln is equivalent to Println() followed by a call to panic().
437+func Panicln(v ...interface{}) {
438+ s := fmt.Sprintln(v...)
439+ std.Output(2, s)
440+ panic(s)
441+}
442+
443+// PrintAuthf writes authentication details to the standard logger.
444+// Arguments are handled in the manner of fmt.Printf.
445+func PrintAuthf(username string, req *http.Request, status AuthStatus, format string, a ...interface{}) {
446+ std.PrintAuth(username, req, status, format, a...)
447+}
448+
449+// PrintReq writes request details to the standard logger.
450+func PrintReq(username, upstream string, req *http.Request, url url.URL, ts time.Time, status int, size int) {
451+ std.PrintReq(username, upstream, req, url, ts, status, size)
452+}
logging_handler.go+7−84
@@ -6,17 +6,11 @@ package main
66import (
77 "bufio"
88 "errors"
9− "fmt"
10− "io"
119 "net"
1210 "net/http"
13− "net/url"
14− "text/template"
1511 "time"
16−)
1712
18−const (
19− defaultRequestLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
13+ "github.com/pusher/oauth2_proxy/logger"
2014)
2115
2216// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status
@@ -92,93 +86,22 @@ func (l *responseLogger) Flush() {
9286 }
9387}
9488
95−// logMessageData is the container for all values that are available as variables in the request logging format.
96−// All values are pre-formatted strings so it is easy to use them in the format string.
97−type logMessageData struct {
98− Client,
99− Host,
100− Protocol,
101− RequestDuration,
102− RequestMethod,
103− RequestURI,
104− ResponseSize,
105− StatusCode,
106− Timestamp,
107− Upstream,
108− UserAgent,
109− Username string
110−}
111−
11289// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
11390type loggingHandler struct {
114− writer io.Writer
115− handler http.Handler
116− enabled bool
117− logTemplate *template.Template
91+ handler http.Handler
11892}
11993
12094// LoggingHandler provides an http.Handler which logs requests to the HTTP server
121−func LoggingHandler(out io.Writer, h http.Handler, v bool, requestLoggingTpl string) http.Handler {
95+func LoggingHandler(h http.Handler) http.Handler {
12296 return loggingHandler{
123− writer: out,
124− handler: h,
125− enabled: v,
126− logTemplate: template.Must(template.New("request-log").Parse(requestLoggingTpl)),
97+ handler: h,
12798 }
12899}
129100
130101func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
131102 t := time.Now()
132103 url := *req.URL
133− logger := &responseLogger{w: w}
134− h.handler.ServeHTTP(logger, req)
135− if !h.enabled {
136− return
137− }
138− h.writeLogLine(logger.authInfo, logger.upstream, req, url, t, logger.Status(), logger.Size())
139−}
140−
141−// Log entry for req similar to Apache Common Log Format.
142−// ts is the timestamp with which the entry should be logged.
143−// status, size are used to provide the response HTTP status and size.
144−func (h loggingHandler) writeLogLine(username, upstream string, req *http.Request, url url.URL, ts time.Time, status int, size int) {
145− if username == "" {
146− username = "-"
147− }
148− if upstream == "" {
149− upstream = "-"
150− }
151− if url.User != nil && username == "-" {
152− if name := url.User.Username(); name != "" {
153− username = name
154− }
155− }
156−
157− client := req.Header.Get("X-Real-IP")
158− if client == "" {
159− client = req.RemoteAddr
160− }
161−
162− if c, _, err := net.SplitHostPort(client); err == nil {
163− client = c
164− }
165−
166− duration := float64(time.Now().Sub(ts)) / float64(time.Second)
167−
168− h.logTemplate.Execute(h.writer, logMessageData{
169− Client: client,
170− Host: req.Host,
171− Protocol: req.Proto,
172− RequestDuration: fmt.Sprintf("%0.3f", duration),
173− RequestMethod: req.Method,
174− RequestURI: fmt.Sprintf("%q", url.RequestURI()),
175− ResponseSize: fmt.Sprintf("%d", size),
176− StatusCode: fmt.Sprintf("%d", status),
177− Timestamp: ts.Format("02/Jan/2006:15:04:05 -0700"),
178− Upstream: upstream,
179− UserAgent: fmt.Sprintf("%q", req.UserAgent()),
180− Username: username,
181− })
182−
183− h.writer.Write([]byte("\n"))
104+ responseLogger := &responseLogger{w: w}
105+ h.handler.ServeHTTP(responseLogger, req)
106+ logger.PrintReq(responseLogger.authInfo, responseLogger.upstream, req, url, t, responseLogger.Status(), responseLogger.Size())
184107}
logging_handler_test.go+6−2
@@ -8,6 +8,8 @@ import (
88 "strings"
99 "testing"
1010 "time"
11+
12+ "github.com/pusher/oauth2_proxy/logger"
1113)
1214
1315func TestLoggingHandler_ServeHTTP(t *testing.T) {
@@ -17,7 +19,7 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) {
1719 Format,
1820 ExpectedLogMessage string
1921 }{
20− {defaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0", ts.Format("02/Jan/2006:15:04:05 -0700"))},
22+ {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts))},
2123 {"{{.RequestMethod}}", "GET\n"},
2224 }
2325
@@ -32,7 +34,9 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) {
3234 w.Write([]byte("test"))
3335 }
3436
35− h := LoggingHandler(buf, http.HandlerFunc(handler), true, test.Format)
37+ logger.SetOutput(buf)
38+ logger.SetReqTemplate(test.Format)
39+ h := LoggingHandler(http.HandlerFunc(handler))
3640
3741 r, _ := http.NewRequest("GET", "/foo/bar", nil)
3842 r.RemoteAddr = "127.0.0.1"
main.go+24−10
@@ -3,7 +3,6 @@ package main
33import (
44 "flag"
55 "fmt"
6− "log"
76 "math/rand"
87 "net/http"
98 "os"
@@ -13,10 +12,11 @@ import (
1312
1413 "github.com/BurntSushi/toml"
1514 options "github.com/mreiferson/go-options"
15+ "github.com/pusher/oauth2_proxy/logger"
1616)
1717
1818func main() {
19− log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
19+ logger.SetFlags(logger.Lshortfile)
2020 flagSet := flag.NewFlagSet("oauth2_proxy", flag.ExitOnError)
2121
2222 emailDomains := StringArray{}
@@ -75,8 +75,21 @@ func main() {
7575 flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag")
7676 flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag")
7777
78− flagSet.Bool("request-logging", true, "Log requests to stdout")
79− flagSet.String("request-logging-format", defaultRequestLoggingFormat, "Template for log lines")
78+ flagSet.String("logging-filename", "", "File to log requests to, empty for stdout")
79+ flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation")
80+ flagSet.Int("logging-max-age", 7, "Maximum number of days to retain old log files")
81+ flagSet.Int("logging-max-backups", 0, "Maximum number of old log files to retain; 0 to disable")
82+ flagSet.Bool("logging-local-time", true, "If the time in log files and backup filenames are local or UTC time")
83+ flagSet.Bool("logging-compress", false, "Should rotated log files be compressed using gzip")
84+
85+ flagSet.Bool("standard-logging", true, "Log standard runtime information")
86+ flagSet.String("standard-logging-format", logger.DefaultStandardLoggingFormat, "Template for standard log lines")
87+
88+ flagSet.Bool("request-logging", true, "Log HTTP requests")
89+ flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines")
90+
91+ flagSet.Bool("auth-logging", true, "Log authentication attempts")
92+ flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines")
8093
8194 flagSet.String("provider", "google", "OAuth provider")
8295 flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
@@ -110,7 +123,7 @@ func main() {
110123 if *config != "" {
111124 _, err := toml.DecodeFile(*config, &cfg)
112125 if err != nil {
113− log.Fatalf("ERROR: failed to load config file %s - %s", *config, err)
126+ logger.Fatalf("ERROR: failed to load config file %s - %s", *config, err)
114127 }
115128 }
116129 cfg.LoadEnvForStruct(opts)
@@ -118,9 +131,10 @@ func main() {
118131
119132 err := opts.Validate()
120133 if err != nil {
121− log.Printf("%s", err)
134+ logger.Printf("%s", err)
122135 os.Exit(1)
123136 }
137+
124138 validator := NewValidator(opts.EmailDomains, opts.AuthenticatedEmailsFile)
125139 oauthproxy := NewOAuthProxy(opts, validator)
126140
@@ -133,11 +147,11 @@ func main() {
133147 }
134148
135149 if opts.HtpasswdFile != "" {
136− log.Printf("using htpasswd file %s", opts.HtpasswdFile)
150+ logger.Printf("using htpasswd file %s", opts.HtpasswdFile)
137151 oauthproxy.HtpasswdFile, err = NewHtpasswdFromFile(opts.HtpasswdFile)
138152 oauthproxy.DisplayHtpasswdForm = opts.DisplayHtpasswdForm
139153 if err != nil {
140− log.Fatalf("FATAL: unable to open %s %s", opts.HtpasswdFile, err)
154+ logger.Fatalf("FATAL: unable to open %s %s", opts.HtpasswdFile, err)
141155 }
142156 }
143157
@@ -145,9 +159,9 @@ func main() {
145159
146160 var handler http.Handler
147161 if opts.GCPHealthChecks {
148− handler = gcpHealthcheck(LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging, opts.RequestLoggingFormat))
162+ handler = gcpHealthcheck(LoggingHandler(oauthproxy))
149163 } else {
150− handler = LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging, opts.RequestLoggingFormat)
164+ handler = LoggingHandler(oauthproxy)
151165 }
152166 s := &Server{
153167 Handler: handler,
oauthproxy.go+33−25
@@ -5,7 +5,6 @@ import (
55 "errors"
66 "fmt"
77 "html/template"
8− "log"
98 "net"
109 "net/http"
1110 "net/http/httputil"
@@ -16,6 +15,7 @@ import (
1615
1716 "github.com/mbland/hmacauth"
1817 "github.com/pusher/oauth2_proxy/cookie"
18+ "github.com/pusher/oauth2_proxy/logger"
1919 "github.com/pusher/oauth2_proxy/providers"
2020 "github.com/yhat/wsutil"
2121)
@@ -185,7 +185,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
185185 path := u.Path
186186 switch u.Scheme {
187187 case httpScheme, httpsScheme:
188− log.Printf("mapping path %q => upstream %q", path, u)
188+ logger.Printf("mapping path %q => upstream %q", path, u)
189189 proxy := NewWebSocketOrRestReverseProxy(u, opts, auth)
190190 serveMux.Handle(path, proxy)
191191
@@ -193,7 +193,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
193193 if u.Fragment != "" {
194194 path = u.Fragment
195195 }
196− log.Printf("mapping path %q => file system %q", path, u.Path)
196+ logger.Printf("mapping path %q => file system %q", path, u.Path)
197197 proxy := NewFileServer(path, u.Path)
198198 serveMux.Handle(path, &UpstreamProxy{path, proxy, nil, nil})
199199 default:
@@ -201,7 +201,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
201201 }
202202 }
203203 for _, u := range opts.CompiledRegex {
204− log.Printf("compiled skip-auth-regex => %q", u)
204+ logger.Printf("compiled skip-auth-regex => %q", u)
205205 }
206206
207207 redirectURL := opts.redirectURL
@@ -209,20 +209,20 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
209209 redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
210210 }
211211
212− log.Printf("OAuthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID)
212+ logger.Printf("OAuthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID)
213213 refresh := "disabled"
214214 if opts.CookieRefresh != time.Duration(0) {
215215 refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
216216 }
217217
218− log.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s path:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, opts.CookiePath, refresh)
218+ logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s path:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, opts.CookiePath, refresh)
219219
220220 var cipher *cookie.Cipher
221221 if opts.PassAccessToken || opts.SetAuthorization || opts.PassAuthorization || (opts.CookieRefresh != time.Duration(0)) {
222222 var err error
223223 cipher, err = cookie.NewCipher(secretBytes(opts.CookieSecret))
224224 if err != nil {
225− log.Fatal("cookie-secret error: ", err)
225+ logger.Fatal("cookie-secret error: ", err)
226226 }
227227 }
228228
@@ -425,7 +425,7 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
425425 domain = h
426426 }
427427 if !strings.HasSuffix(domain, p.CookieDomain) {
428− log.Printf("Warning: request host is %q but using configured cookie domain of %q", domain, p.CookieDomain)
428+ logger.Printf("Warning: request host is %q but using configured cookie domain of %q", domain, p.CookieDomain)
429429 }
430430 }
431431
@@ -529,7 +529,6 @@ func (p *OAuthProxy) PingPage(rw http.ResponseWriter) {
529529
530530// ErrorPage writes an error response
531531func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
532− log.Printf("ErrorPage %d %s %s", code, title, message)
533532 rw.WriteHeader(code)
534533 t := struct {
535534 Title string
@@ -588,9 +587,10 @@ func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (st
588587 }
589588 // check auth
590589 if p.HtpasswdFile.Validate(user, passwd) {
591− log.Printf("authenticated %q via HtpasswdFile", user)
590+ logger.PrintAuthf(user, req, logger.AuthSuccess, "Authenticated via HtpasswdFile")
592591 return user, true
593592 }
593+ logger.PrintAuthf(user, req, logger.AuthFailure, "Invalid authentication via HtpasswdFile")
594594 return "", false
595595}
596596
@@ -686,6 +686,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
686686func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
687687 redirect, err := p.GetRedirect(req)
688688 if err != nil {
689+ logger.Printf("Error obtaining redirect: %s", err.Error())
689690 p.ErrorPage(rw, 500, "Internal Error", err.Error())
690691 return
691692 }
@@ -714,12 +715,14 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
714715func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
715716 nonce, err := cookie.Nonce()
716717 if err != nil {
718+ logger.Printf("Error obtaining nonce: %s", err.Error())
717719 p.ErrorPage(rw, 500, "Internal Error", err.Error())
718720 return
719721 }
720722 p.SetCSRFCookie(rw, req, nonce)
721723 redirect, err := p.GetRedirect(req)
722724 if err != nil {
725+ logger.Printf("Error obtaining redirect: %s", err.Error())
723726 p.ErrorPage(rw, 500, "Internal Error", err.Error())
724727 return
725728 }
@@ -735,24 +738,27 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
735738 // finish the oauth cycle
736739 err := req.ParseForm()
737740 if err != nil {
741+ logger.Printf("Error while parsing OAuth2 callback: %s" + err.Error())
738742 p.ErrorPage(rw, 500, "Internal Error", err.Error())
739743 return
740744 }
741745 errorString := req.Form.Get("error")
742746 if errorString != "" {
747+ logger.Printf("Error while parsing OAuth2 callback: %s ", errorString)
743748 p.ErrorPage(rw, 403, "Permission Denied", errorString)
744749 return
745750 }
746751
747752 session, err := p.redeemCode(req.Host, req.Form.Get("code"))
748753 if err != nil {
749− log.Printf("%s error redeeming code %s", remoteAddr, err)
754+ logger.Printf("Error redeeming code during OAuth2 callback: %s ", errorString)
750755 p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
751756 return
752757 }
753758
754759 s := strings.SplitN(req.Form.Get("state"), ":", 2)
755760 if len(s) != 2 {
761+ logger.Printf("Error while parsing OAuth2 state: invalid length")
756762 p.ErrorPage(rw, 500, "Internal Error", "Invalid State")
757763 return
758764 }
@@ -760,12 +766,13 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
760766 redirect := s[1]
761767 c, err := req.Cookie(p.CSRFCookieName)
762768 if err != nil {
769+ logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unable too obtain CSRF cookie")
763770 p.ErrorPage(rw, 403, "Permission Denied", err.Error())
764771 return
765772 }
766773 p.ClearCSRFCookie(rw, req)
767774 if c.Value != nonce {
768− log.Printf("%s csrf token mismatch, potential attack", remoteAddr)
775+ logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: csrf token mismatch, potential attack")
769776 p.ErrorPage(rw, 403, "Permission Denied", "csrf failed")
770777 return
771778 }
@@ -776,16 +783,16 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
776783
777784 // set cookie, or deny
778785 if p.Validator(session.Email) && p.provider.ValidateGroup(session.Email) {
779− log.Printf("%s authentication complete %s", remoteAddr, session)
786+ logger.PrintAuthf(session.Email, req, logger.AuthSuccess, "Authenticated via OAuth2: %s", session)
780787 err := p.SaveSession(rw, req, session)
781788 if err != nil {
782− log.Printf("%s %s", remoteAddr, err)
789+ logger.Printf("%s %s", remoteAddr, err)
783790 p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
784791 return
785792 }
786793 http.Redirect(rw, req, redirect, 302)
787794 } else {
788− log.Printf("%s Permission Denied: %q is unauthorized", remoteAddr, session.Email)
795+ logger.PrintAuthf(session.Email, req, logger.AuthSuccess, "Invalid authentication via OAuth2: unauthorized")
789796 p.ErrorPage(rw, 403, "Permission Denied", "Invalid Account")
790797 }
791798}
@@ -827,16 +834,16 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
827834
828835 session, sessionAge, err := p.LoadCookiedSession(req)
829836 if err != nil {
830− log.Printf("%s %s", remoteAddr, err)
837+ logger.Printf("Error loading cookied session: %s", err)
831838 }
832839 if session != nil && sessionAge > p.CookieRefresh && p.CookieRefresh != time.Duration(0) {
833− log.Printf("%s refreshing %s old session cookie for %s (refresh after %s)", remoteAddr, sessionAge, session, p.CookieRefresh)
840+ logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", sessionAge, session, p.CookieRefresh)
834841 saveSession = true
835842 }
836843
837844 var ok bool
838845 if ok, err = p.provider.RefreshSessionIfNeeded(session); err != nil {
839− log.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session)
846+ logger.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session)
840847 clearSession = true
841848 session = nil
842849 } else if ok {
@@ -845,7 +852,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
845852 }
846853
847854 if session != nil && session.IsExpired() {
848− log.Printf("%s removing session. token expired %s", remoteAddr, session)
855+ logger.Printf("Removing session: token expired %s", session)
849856 session = nil
850857 saveSession = false
851858 clearSession = true
@@ -853,7 +860,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
853860
854861 if saveSession && !revalidated && session != nil && session.AccessToken != "" {
855862 if !p.provider.ValidateSessionState(session) {
856− log.Printf("%s removing session. error validating %s", remoteAddr, session)
863+ logger.Printf("Removing session: error validating %s", session)
857864 saveSession = false
858865 session = nil
859866 clearSession = true
@@ -861,7 +868,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
861868 }
862869
863870 if session != nil && session.Email != "" && !p.Validator(session.Email) {
864− log.Printf("%s Permission Denied: removing session %s", remoteAddr, session)
871+ logger.Printf(session.Email, req, logger.AuthFailure, "Invalid authentication via session: removing session %s", session)
865872 session = nil
866873 saveSession = false
867874 clearSession = true
@@ -870,7 +877,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
870877 if saveSession && session != nil {
871878 err = p.SaveSession(rw, req, session)
872879 if err != nil {
873− log.Printf("%s %s", remoteAddr, err)
880+ logger.PrintAuthf(session.Email, req, logger.AuthError, "Save session error %s", err)
874881 return http.StatusInternalServerError
875882 }
876883 }
@@ -882,7 +889,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
882889 if session == nil {
883890 session, err = p.CheckBasicAuth(req)
884891 if err != nil {
885− log.Printf("%s %s", remoteAddr, err)
892+ logger.Printf("Error during basic auth validation: %s", err)
886893 }
887894 }
888895
@@ -958,10 +965,11 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState,
958965 return nil, fmt.Errorf("invalid format %s", b)
959966 }
960967 if p.HtpasswdFile.Validate(pair[0], pair[1]) {
961− log.Printf("authenticated %q via basic auth", pair[0])
968+ logger.PrintAuthf(pair[0], req, logger.AuthSuccess, "Authenticated via basic auth and HTpasswd File")
962969 return &providers.SessionState{User: pair[0]}, nil
963970 }
964− return nil, fmt.Errorf("%s not in HtpasswdFile", pair[0])
971+ logger.PrintAuthf(pair[0], req, logger.AuthFailure, "Invalid authentication via basic auth: not in Htpasswd File")
972+ return nil, nil
965973}
966974
967975// isAjax checks if a request is an ajax request
oauthproxy_test.go+4−4
@@ -5,7 +5,6 @@ import (
55 "encoding/base64"
66 "io"
77 "io/ioutil"
8− "log"
98 "net"
109 "net/http"
1110 "net/http/httptest"
@@ -16,6 +15,7 @@ import (
1615 "time"
1716
1817 "github.com/mbland/hmacauth"
18+ "github.com/pusher/oauth2_proxy/logger"
1919 "github.com/pusher/oauth2_proxy/providers"
2020 "github.com/stretchr/testify/assert"
2121 "github.com/stretchr/testify/require"
@@ -23,7 +23,7 @@ import (
2323)
2424
2525func init() {
26− log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
26+ logger.SetFlags(logger.Lshortfile)
2727
2828}
2929
@@ -263,7 +263,7 @@ func (tp *TestProvider) ValidateSessionState(session *providers.SessionState) bo
263263
264264func TestBasicAuthPassword(t *testing.T) {
265265 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
266− log.Printf("%#v", r)
266+ logger.Printf("%#v", r)
267267 var payload string
268268 switch r.URL.Path {
269269 case "/oauth/token":
@@ -355,7 +355,7 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
355355
356356 t.providerServer = httptest.NewServer(
357357 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
358− log.Printf("%#v", r)
358+ logger.Printf("%#v", r)
359359 var payload string
360360 switch r.URL.Path {
361361 case "/oauth/token":
options.go+94−24
@@ -17,7 +17,9 @@ import (
1717 oidc "github.com/coreos/go-oidc"
1818 "github.com/dgrijalva/jwt-go"
1919 "github.com/mbland/hmacauth"
20+ "github.com/pusher/oauth2_proxy/logger"
2021 "github.com/pusher/oauth2_proxy/providers"
22+ "gopkg.in/natefinch/lumberjack.v2"
2123)
2224
2325// Options holds Configuration Options that can be set by Command Line Flag,
@@ -85,8 +87,19 @@ type Options struct {
8587 Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"`
8688 ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"`
8789
88− RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"`
89− RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"`
90+ // Configuration values for logging
91+ LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_LOGGING_FILENAME"`
92+ LoggingMaxSize int `flag:"logging-max-size" cfg:"logging_max_size" env:"OAUTH2_LOGGING_MAX_SIZE"`
93+ LoggingMaxAge int `flag:"logging-max-age" cfg:"logging_max_age" env:"OAUTH2_LOGGING_MAX_AGE"`
94+ LoggingMaxBackups int `flag:"logging-max-backups" cfg:"logging_max_backups" env:"OAUTH2_LOGGING_MAX_BACKUPS"`
95+ LoggingLocalTime bool `flag:"logging-local-time" cfg:"logging_local_time" env:"OAUTH2_LOGGING_LOCAL_TIME"`
96+ LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress" env:"OAUTH2_LOGGING_COMPRESS"`
97+ StandardLogging bool `flag:"standard-logging" cfg:"standard_logging" env:"OAUTH2_STANDARD_LOGGING"`
98+ StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_STANDARD_LOGGING_FORMAT"`
99+ RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_REQUEST_LOGGING"`
100+ RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_REQUEST_LOGGING_FORMAT"`
101+ AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_LOGGING_AUTH_LOGGING"`
102+ AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_AUTH_LOGGING_FORMAT"`
90103
91104 SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"`
92105 AcrValues string `flag:"acr-values" cfg:"acr_values" env:"OAUTH2_PROXY_ACR_VALUES"`
@@ -113,28 +126,38 @@ type SignatureData struct {
113126// NewOptions constructs a new Options with defaulted values
114127func NewOptions() *Options {
115128 return &Options{
116− ProxyPrefix: "/oauth2",
117− ProxyWebSockets: true,
118− HTTPAddress: "127.0.0.1:4180",
119− HTTPSAddress: ":443",
120− DisplayHtpasswdForm: true,
121− CookieName: "_oauth2_proxy",
122− CookieSecure: true,
123− CookieHTTPOnly: true,
124− CookieExpire: time.Duration(168) * time.Hour,
125− CookieRefresh: time.Duration(0),
126− SetXAuthRequest: false,
127− SkipAuthPreflight: false,
128− PassBasicAuth: true,
129− PassUserHeaders: true,
130− PassAccessToken: false,
131− PassHostHeader: true,
132− SetAuthorization: false,
133− PassAuthorization: false,
134− ApprovalPrompt: "force",
135− RequestLogging: true,
136− SkipOIDCDiscovery: false,
137− RequestLoggingFormat: defaultRequestLoggingFormat,
129+ ProxyPrefix: "/oauth2",
130+ ProxyWebSockets: true,
131+ HTTPAddress: "127.0.0.1:4180",
132+ HTTPSAddress: ":443",
133+ DisplayHtpasswdForm: true,
134+ CookieName: "_oauth2_proxy",
135+ CookieSecure: true,
136+ CookieHTTPOnly: true,
137+ CookieExpire: time.Duration(168) * time.Hour,
138+ CookieRefresh: time.Duration(0),
139+ SetXAuthRequest: false,
140+ SkipAuthPreflight: false,
141+ PassBasicAuth: true,
142+ PassUserHeaders: true,
143+ PassAccessToken: false,
144+ PassHostHeader: true,
145+ SetAuthorization: false,
146+ PassAuthorization: false,
147+ ApprovalPrompt: "force",
148+ SkipOIDCDiscovery: false,
149+ LoggingFilename: "",
150+ LoggingMaxSize: 100,
151+ LoggingMaxAge: 7,
152+ LoggingMaxBackups: 0,
153+ LoggingLocalTime: true,
154+ LoggingCompress: false,
155+ StandardLogging: true,
156+ StandardLoggingFormat: logger.DefaultStandardLoggingFormat,
157+ RequestLogging: true,
158+ RequestLoggingFormat: logger.DefaultRequestLoggingFormat,
159+ AuthLogging: true,
160+ AuthLoggingFormat: logger.DefaultAuthLoggingFormat,
138161 }
139162}
140163
@@ -285,6 +308,7 @@ func (o *Options) Validate() error {
285308
286309 msgs = parseSignatureKey(o, msgs)
287310 msgs = validateCookieName(o, msgs)
311+ msgs = setupLogger(o, msgs)
288312
289313 if len(msgs) != 0 {
290314 return fmt.Errorf("Invalid configuration:\n %s",
@@ -414,3 +438,49 @@ func secretBytes(secret string) []byte {
414438 }
415439 return []byte(secret)
416440}
441+
442+func setupLogger(o *Options, msgs []string) []string {
443+ // Setup the log file
444+ if len(o.LoggingFilename) > 0 {
445+ // Validate that the file/dir can be written
446+ file, err := os.OpenFile(o.LoggingFilename, os.O_WRONLY|os.O_CREATE, 0666)
447+ if err != nil {
448+ if os.IsPermission(err) {
449+ return append(msgs, "unable to write to log file: "+o.LoggingFilename)
450+ }
451+ }
452+ file.Close()
453+
454+ logger.Printf("Redirecting logging to file: %s", o.LoggingFilename)
455+
456+ logWriter := &lumberjack.Logger{
457+ Filename: o.LoggingFilename,
458+ MaxSize: o.LoggingMaxSize, // megabytes
459+ MaxAge: o.LoggingMaxAge, // days
460+ MaxBackups: o.LoggingMaxBackups,
461+ LocalTime: o.LoggingLocalTime,
462+ Compress: o.LoggingCompress,
463+ }
464+
465+ logger.SetOutput(logWriter)
466+ }
467+
468+ // Supply a sanity warning to the logger if all logging is disabled
469+ if !o.StandardLogging && !o.AuthLogging && !o.RequestLogging {
470+ logger.Print("Warning: Logging disabled. No further logs will be shown.")
471+ }
472+
473+ // Pass configuration values to the standard logger
474+ logger.SetStandardEnabled(o.StandardLogging)
475+ logger.SetAuthEnabled(o.AuthLogging)
476+ logger.SetReqEnabled(o.RequestLogging)
477+ logger.SetStandardTemplate(o.StandardLoggingFormat)
478+ logger.SetAuthTemplate(o.AuthLoggingFormat)
479+ logger.SetReqTemplate(o.RequestLoggingFormat)
480+
481+ if !o.LoggingLocalTime {
482+ logger.SetFlags(logger.Flags() | logger.LUTC)
483+ }
484+
485+ return msgs
486+}
providers/azure.go+3−3
@@ -3,12 +3,12 @@ package providers
33import (
44 "errors"
55 "fmt"
6− "log"
76 "net/http"
87 "net/url"
98
109 "github.com/bitly/go-simplejson"
1110 "github.com/pusher/oauth2_proxy/api"
11+ "github.com/pusher/oauth2_proxy/logger"
1212)
1313
1414// AzureProvider represents an Azure based Identity Provider
@@ -116,12 +116,12 @@ func (p *AzureProvider) GetEmailAddress(s *SessionState) (string, error) {
116116 email, err = json.Get("userPrincipalName").String()
117117
118118 if err != nil {
119− log.Printf("failed making request %s", err)
119+ logger.Printf("failed making request %s", err)
120120 return "", err
121121 }
122122
123123 if email == "" {
124− log.Printf("failed to get email address")
124+ logger.Printf("failed to get email address")
125125 return "", err
126126 }
127127
providers/github.go+9−8
@@ -4,12 +4,13 @@ import (
44 "encoding/json"
55 "fmt"
66 "io/ioutil"
7− "log"
87 "net/http"
98 "net/url"
109 "path"
1110 "strconv"
1211 "strings"
12+
13+ "github.com/pusher/oauth2_proxy/logger"
1314)
1415
1516// GitHubProvider represents an GitHub based Identity Provider
@@ -116,13 +117,13 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) {
116117 var presentOrgs []string
117118 for _, org := range orgs {
118119 if p.Org == org.Login {
119− log.Printf("Found Github Organization: %q", org.Login)
120+ logger.Printf("Found Github Organization: %q", org.Login)
120121 return true, nil
121122 }
122123 presentOrgs = append(presentOrgs, org.Login)
123124 }
124125
125− log.Printf("Missing Organization:%q in %v", p.Org, presentOrgs)
126+ logger.Printf("Missing Organization:%q in %v", p.Org, presentOrgs)
126127 return false, nil
127128}
128129
@@ -179,7 +180,7 @@ func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) {
179180 ts := strings.Split(p.Team, ",")
180181 for _, t := range ts {
181182 if t == team.Slug {
182− log.Printf("Found Github Organization:%q Team:%q (Name:%q)", team.Org.Login, team.Slug, team.Name)
183+ logger.Printf("Found Github Organization:%q Team:%q (Name:%q)", team.Org.Login, team.Slug, team.Name)
183184 return true, nil
184185 }
185186 }
@@ -187,13 +188,13 @@ func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) {
187188 }
188189 }
189190 if hasOrg {
190− log.Printf("Missing Team:%q from Org:%q in teams: %v", p.Team, p.Org, presentTeams)
191+ logger.Printf("Missing Team:%q from Org:%q in teams: %v", p.Team, p.Org, presentTeams)
191192 } else {
192193 var allOrgs []string
193194 for org := range presentOrgs {
194195 allOrgs = append(allOrgs, org)
195196 }
196− log.Printf("Missing Organization:%q in %#v", p.Org, allOrgs)
197+ logger.Printf("Missing Organization:%q in %#v", p.Org, allOrgs)
197198 }
198199 return false, nil
199200}
@@ -242,7 +243,7 @@ func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) {
242243 resp.StatusCode, endpoint.String(), body)
243244 }
244245
245− log.Printf("got %d from %q %s", resp.StatusCode, endpoint.String(), body)
246+ logger.Printf("got %d from %q %s", resp.StatusCode, endpoint.String(), body)
246247
247248 if err := json.Unmarshal(body, &emails); err != nil {
248249 return "", fmt.Errorf("%s unmarshaling %s", err, body)
@@ -292,7 +293,7 @@ func (p *GitHubProvider) GetUserName(s *SessionState) (string, error) {
292293 resp.StatusCode, endpoint.String(), body)
293294 }
294295
295− log.Printf("got %d from %q %s", resp.StatusCode, endpoint.String(), body)
296+ logger.Printf("got %d from %q %s", resp.StatusCode, endpoint.String(), body)
296297
297298 if err := json.Unmarshal(body, &user); err != nil {
298299 return "", fmt.Errorf("%s unmarshaling %s", err, body)
providers/gitlab.go+3−3
@@ -1,11 +1,11 @@
11package providers
22
33import (
4− "log"
54 "net/http"
65 "net/url"
76
87 "github.com/pusher/oauth2_proxy/api"
8+ "github.com/pusher/oauth2_proxy/logger"
99)
1010
1111// GitLabProvider represents an GitLab based Identity Provider
@@ -49,12 +49,12 @@ func (p *GitLabProvider) GetEmailAddress(s *SessionState) (string, error) {
4949 req, err := http.NewRequest("GET",
5050 p.ValidateURL.String()+"?access_token="+s.AccessToken, nil)
5151 if err != nil {
52− log.Printf("failed building request %s", err)
52+ logger.Printf("failed building request %s", err)
5353 return "", err
5454 }
5555 json, err := api.Request(req)
5656 if err != nil {
57− log.Printf("failed making request %s", err)
57+ logger.Printf("failed making request %s", err)
5858 return "", err
5959 }
6060 return json.Get("email").String()
providers/google.go+8−8
@@ -8,12 +8,12 @@ import (
88 "fmt"
99 "io"
1010 "io/ioutil"
11− "log"
1211 "net/http"
1312 "net/url"
1413 "strings"
1514 "time"
1615
16+ "github.com/pusher/oauth2_proxy/logger"
1717 "golang.org/x/oauth2"
1818 "golang.org/x/oauth2/google"
1919 admin "google.golang.org/api/admin/directory/v1"
@@ -167,18 +167,18 @@ func (p *GoogleProvider) SetGroupRestriction(groups []string, adminEmail string,
167167func getAdminService(adminEmail string, credentialsReader io.Reader) *admin.Service {
168168 data, err := ioutil.ReadAll(credentialsReader)
169169 if err != nil {
170− log.Fatal("can't read Google credentials file:", err)
170+ logger.Fatal("can't read Google credentials file:", err)
171171 }
172172 conf, err := google.JWTConfigFromJSON(data, admin.AdminDirectoryUserReadonlyScope, admin.AdminDirectoryGroupReadonlyScope)
173173 if err != nil {
174− log.Fatal("can't load Google credentials file:", err)
174+ logger.Fatal("can't load Google credentials file:", err)
175175 }
176176 conf.Subject = adminEmail
177177
178178 client := conf.Client(oauth2.NoContext)
179179 adminService, err := admin.New(client)
180180 if err != nil {
181− log.Fatal(err)
181+ logger.Fatal(err)
182182 }
183183 return adminService
184184}
@@ -186,7 +186,7 @@ func getAdminService(adminEmail string, credentialsReader io.Reader) *admin.Serv
186186func userInGroup(service *admin.Service, groups []string, email string) bool {
187187 user, err := fetchUser(service, email)
188188 if err != nil {
189− log.Printf("error fetching user: %v", err)
189+ logger.Printf("error fetching user: %v", err)
190190 return false
191191 }
192192 id := user.Id
@@ -196,9 +196,9 @@ func userInGroup(service *admin.Service, groups []string, email string) bool {
196196 members, err := fetchGroupMembers(service, group)
197197 if err != nil {
198198 if err, ok := err.(*googleapi.Error); ok && err.Code == 404 {
199− log.Printf("error fetching members for group %s: group does not exist", group)
199+ logger.Printf("error fetching members for group %s: group does not exist", group)
200200 } else {
201− log.Printf("error fetching group members: %v", err)
201+ logger.Printf("error fetching group members: %v", err)
202202 return false
203203 }
204204 }
@@ -274,7 +274,7 @@ func (p *GoogleProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
274274 s.AccessToken = newToken
275275 s.IDToken = newIDToken
276276 s.ExpiresOn = time.Now().Add(duration).Truncate(time.Second)
277− log.Printf("refreshed access token %s (expired on %s)", s, origExpiration)
277+ logger.Printf("refreshed access token %s (expired on %s)", s, origExpiration)
278278 return true, nil
279279}
280280
providers/internal_util.go+7−7
@@ -2,11 +2,11 @@ package providers
22
33import (
44 "io/ioutil"
5− "log"
65 "net/http"
76 "net/url"
87
98 "github.com/pusher/oauth2_proxy/api"
9+ "github.com/pusher/oauth2_proxy/logger"
1010)
1111
1212// stripToken is a helper function to obfuscate "access_token"
@@ -24,14 +24,14 @@ func stripToken(endpoint string) string {
2424func stripParam(param, endpoint string) string {
2525 u, err := url.Parse(endpoint)
2626 if err != nil {
27− log.Printf("error attempting to strip %s: %s", param, err)
27+ logger.Printf("error attempting to strip %s: %s", param, err)
2828 return endpoint
2929 }
3030
3131 if u.RawQuery != "" {
3232 values, err := url.ParseQuery(u.RawQuery)
3333 if err != nil {
34− log.Printf("error attempting to strip %s: %s", param, err)
34+ logger.Printf("error attempting to strip %s: %s", param, err)
3535 return u.String()
3636 }
3737
@@ -57,18 +57,18 @@ func validateToken(p Provider, accessToken string, header http.Header) bool {
5757 }
5858 resp, err := api.RequestUnparsedResponse(endpoint, header)
5959 if err != nil {
60− log.Printf("GET %s", stripToken(endpoint))
61− log.Printf("token validation request failed: %s", err)
60+ logger.Printf("GET %s", stripToken(endpoint))
61+ logger.Printf("token validation request failed: %s", err)
6262 return false
6363 }
6464
6565 body, _ := ioutil.ReadAll(resp.Body)
6666 resp.Body.Close()
67− log.Printf("%d GET %s %s", resp.StatusCode, stripToken(endpoint), body)
67+ logger.Printf("%d GET %s %s", resp.StatusCode, stripToken(endpoint), body)
6868
6969 if resp.StatusCode == 200 {
7070 return true
7171 }
72− log.Printf("token validation request failed: status %d - %s", resp.StatusCode, body)
72+ logger.Printf("token validation request failed: status %d - %s", resp.StatusCode, body)
7373 return false
7474}
templates.go+6−5
@@ -2,18 +2,19 @@ package main
22
33import (
44 "html/template"
5− "log"
65 "path"
6+
7+ "github.com/pusher/oauth2_proxy/logger"
78)
89
910func loadTemplates(dir string) *template.Template {
1011 if dir == "" {
1112 return getTemplates()
1213 }
13− log.Printf("using custom template directory %q", dir)
14+ logger.Printf("using custom template directory %q", dir)
1415 t, err := template.New("").ParseFiles(path.Join(dir, "sign_in.html"), path.Join(dir, "error.html"))
1516 if err != nil {
16− log.Fatalf("failed parsing template %s", err)
17+ logger.Fatalf("failed parsing template %s", err)
1718 }
1819 return t
1920}
@@ -151,7 +152,7 @@ func getTemplates() *template.Template {
151152</html>
152153{{end}}`)
153154 if err != nil {
154− log.Fatalf("failed parsing template %s", err)
155+ logger.Fatalf("failed parsing template %s", err)
155156 }
156157
157158 t, err = t.Parse(`{{define "error.html"}}
@@ -169,7 +170,7 @@ func getTemplates() *template.Template {
169170</body>
170171</html>{{end}}`)
171172 if err != nil {
172− log.Fatalf("failed parsing template %s", err)
173+ logger.Fatalf("failed parsing template %s", err)
173174 }
174175 return t
175176}
validator.go+5−4
@@ -3,11 +3,12 @@ package main
33import (
44 "encoding/csv"
55 "fmt"
6− "log"
76 "os"
87 "strings"
98 "sync/atomic"
109 "unsafe"
10+
11+ "github.com/pusher/oauth2_proxy/logger"
1112)
1213
1314// UserMap holds information from the authenticated emails file
@@ -22,7 +23,7 @@ func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
2223 m := make(map[string]bool)
2324 atomic.StorePointer(&um.m, unsafe.Pointer(&m))
2425 if usersFile != "" {
25− log.Printf("using authenticated emails file %s", usersFile)
26+ logger.Printf("using authenticated emails file %s", usersFile)
2627 WatchForUpdates(usersFile, done, func() {
2728 um.LoadAuthenticatedEmailsFile()
2829 onUpdate()
@@ -44,7 +45,7 @@ func (um *UserMap) IsValid(email string) (result bool) {
4445func (um *UserMap) LoadAuthenticatedEmailsFile() {
4546 r, err := os.Open(um.usersFile)
4647 if err != nil {
47− log.Fatalf("failed opening authenticated-emails-file=%q, %s", um.usersFile, err)
48+ logger.Fatalf("failed opening authenticated-emails-file=%q, %s", um.usersFile, err)
4849 }
4950 defer r.Close()
5051 csvReader := csv.NewReader(r)
@@ -53,7 +54,7 @@ func (um *UserMap) LoadAuthenticatedEmailsFile() {
5354 csvReader.TrimLeadingSpace = true
5455 records, err := csvReader.ReadAll()
5556 if err != nil {
56− log.Printf("error reading authenticated-emails-file=%q, %s", um.usersFile, err)
57+ logger.Printf("error reading authenticated-emails-file=%q, %s", um.usersFile, err)
5758 return
5859 }
5960 updated := make(map[string]bool)
watcher.go+9−9
@@ -3,11 +3,11 @@
33package main
44
55import (
6− "log"
76 "os"
87 "path/filepath"
98 "time"
109
10+ "github.com/pusher/oauth2_proxy/logger"
1111 fsnotify "gopkg.in/fsnotify/fsnotify.v1"
1212)
1313
@@ -24,7 +24,7 @@ func WaitForReplacement(filename string, op fsnotify.Op,
2424 for {
2525 if _, err := os.Stat(filename); err == nil {
2626 if err := watcher.Add(filename); err == nil {
27− log.Printf("watching resumed for %s", filename)
27+ logger.Printf("watching resumed for %s", filename)
2828 return
2929 }
3030 }
@@ -37,14 +37,14 @@ func WatchForUpdates(filename string, done <-chan bool, action func()) {
3737 filename = filepath.Clean(filename)
3838 watcher, err := fsnotify.NewWatcher()
3939 if err != nil {
40− log.Fatal("failed to create watcher for ", filename, ": ", err)
40+ logger.Fatal("failed to create watcher for ", filename, ": ", err)
4141 }
4242 go func() {
4343 defer watcher.Close()
4444 for {
4545 select {
4646 case _ = <-done:
47− log.Printf("Shutting down watcher for: %s", filename)
47+ logger.Printf("Shutting down watcher for: %s", filename)
4848 return
4949 case event := <-watcher.Events:
5050 // On Arch Linux, it appears Chmod events precede Remove events,
@@ -53,19 +53,19 @@ func WatchForUpdates(filename string, done <-chan bool, action func()) {
5353 // UserMap.LoadAuthenticatedEmailsFile()) crashes when the file
5454 // can't be opened.
5555 if event.Op&(fsnotify.Remove|fsnotify.Rename|fsnotify.Chmod) != 0 {
56− log.Printf("watching interrupted on event: %s", event)
56+ logger.Printf("watching interrupted on event: %s", event)
5757 watcher.Remove(filename)
5858 WaitForReplacement(filename, event.Op, watcher)
5959 }
60− log.Printf("reloading after event: %s", event)
60+ logger.Printf("reloading after event: %s", event)
6161 action()
6262 case err = <-watcher.Errors:
63− log.Printf("error watching %s: %s", filename, err)
63+ logger.Printf("error watching %s: %s", filename, err)
6464 }
6565 }
6666 }()
6767 if err = watcher.Add(filename); err != nil {
68− log.Fatal("failed to add ", filename, " to watcher: ", err)
68+ logger.Fatal("failed to add ", filename, " to watcher: ", err)
6969 }
70− log.Printf("watching %s for updates", filename)
70+ logger.Printf("watching %s for updates", filename)
7171}
watcher_unsupported.go+2−4
@@ -2,11 +2,9 @@
22
33package main
44
5−import (
6− "log"
7−)
5+import "github.com/pusher/oauth2_proxy/logger"
86
97func WatchForUpdates(filename string, done <-chan bool, action func()) {
10− log.Printf("file watching not implemented on this platform")
8+ logger.Printf("file watching not implemented on this platform")
119 go func() { <-done }()
1210}