git.iko.soy/typeable/oauth2-proxy
on master help sign in
Auth and standard logging with file rolling
commit8ec025f536a76be4e5af6d7368cade2afac5cdf3 authorMisterWil <wilrader@gmail.com> dateSun 10 Feb 2019, 08:37 (8 years ago) parentee4ebe5
23 files changed+799−200
Gopkg.lock+9−1 Gopkg.toml+4 README.md+58−4 api/api.go+5−5 contrib/oauth2_proxy.cfg.example+12−2 htpasswd.go+2−2 http.go+11−10 logger/logger.go+447 logging_handler.go+7−87 logging_handler_test.go+5−1 main.go+27−9 oauthproxy.go+46−24 oauthproxy_test.go+4−4 options.go+110 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
Gopkg.lock+9−1
@@ -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"
@@ -214,7 +222,6 @@
214222 "github.com/mreiferson/go-options",
215223 "github.com/stretchr/testify/assert",
216224 "github.com/stretchr/testify/require",
217− "github.com/yhat/wsutil",
218225 "golang.org/x/crypto/bcrypt",
219226 "golang.org/x/net/websocket",
220227 "golang.org/x/oauth2",
@@ -222,6 +229,7 @@
222229 "google.golang.org/api/admin/directory/v1",
223230 "google.golang.org/api/googleapi",
224231 "gopkg.in/fsnotify/fsnotify.v1",
232+ "gopkg.in/natefinch/lumberjack.v2",
225233 ]
226234 solver-name = "gps-cdcl"
227235 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+58−4
@@ -262,6 +262,8 @@ An example [oauth2_proxy.cfg](contrib/oauth2_proxy.cfg.example) config file is i
262262```
263263Usage of oauth2_proxy:
264264 -approval-prompt string: OAuth approval_prompt (default "force")
265+ -auth-logging: Log authentication attempts (default true)
266+ -auth-logging-format string: Template for authentication log lines (see "Logging Configuration" paragraph below)
265267 -authenticated-emails-file string: authenticate against emails via file (one per line)
266268 -azure-tenant string: go to a tenant-specific or common (tenant-independent) endpoint. (default "common")
267269 -basic-auth-password string: the password to set when passing the HTTP Basic Auth header
@@ -290,6 +292,12 @@ Usage of oauth2_proxy:
290292 -htpasswd-file string: additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
291293 -http-address string: [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients (default "127.0.0.1:4180")
292294 -https-address string: <addr>:<port> to listen on for HTTPS clients (default ":443")
295+ -logging-compress: Should rotated log files be compressed using gzip (default false)
296+ -logging-filename string: File to log requests to, empty for stdout (default to stdout)
297+ -logging-local-time: If the time in log files and backup filenames are local or UTC time (default true)
298+ -logging-max-age int: Maximum number of days to retain old log files (default 7)
299+ -logging-max-backups int: Maximum number of old log files to retain; 0 to disable (default 0)
300+ -logging-max-size int: Maximum size in megabytes of the log file before rotation (default 100)
293301 -login-url string: Authentication endpoint
294302 -oidc-issuer-url: the OpenID Connect issuer URL. ie: "https://accounts.google.com"
295303 -oidc-jwks-url string: OIDC JWKS URI for token verification; required if OIDC discovery is disabled
@@ -305,7 +313,7 @@ Usage of oauth2_proxy:
305313 -redeem-url string: Token redemption endpoint
306314 -redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
307315 -request-logging: Log requests to stdout (default true)
308− -request-logging-format: Template for request log lines (see "Logging Format" paragraph below)
316+ -request-logging-format: Template for request log lines (see "Logging Configuration" paragraph below)
309317 -resource string: The resource that is protected (Azure AD only)
310318 -scope string: OAuth scope specification
311319 -set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)
@@ -316,6 +324,8 @@ Usage of oauth2_proxy:
316324 -skip-oidc-discovery: bypass OIDC endpoint discovery. login-url, redeem-url and oidc-jwks-url must be configured in this case
317325 -skip-provider-button: will skip sign-in-page to directly reach the next step: oauth/start
318326 -ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS
327+ -standard-logging: Log standard runtime information (default true)
328+ -standard-logging-format string: Template for standard log lines (see "Logging Configuration" paragraph below)
319329 -tls-cert string: path to certificate file
320330 -tls-key string: path to private key file
321331 -upstream value: the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path
@@ -446,9 +456,40 @@ following:
446456- [rc3.org: Using HMAC to authenticate Web service
447457 requests](http://rc3.org/2011/12/02/using-hmac-to-authenticate-web-service-requests/)
448458
449−## Logging Format
459+## Logging Configuration
450460
451−By default, OAuth2 Proxy logs requests to stdout in a format similar to Apache Combined Log.
461+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.
462+
463+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`).
464+
465+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`.
466+
467+Each type of logging has their own configurable format and variables. By default these formats are similar to the Apache Combined Log.
468+
469+### Auth Log Format
470+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:
471+
472+```
473+<REMOTE_ADDRESS> - <user@domain.com> [19/Mar/2015:17:20:19 -0400] [<STATUS>] <MESSAGE>
474+```
475+
476+The status block will contain one of the below strings:
477+
478+- `AuthSuccess` If a user has authenticated successfully by any method
479+- `AuthFailure` If the user failed to authenticate explicitly
480+- `AuthError` If there was an unexpected error during authentication
481+
482+If you require a different format than that, you can configure it with the `-auth-logging-format` flag.
483+The default format is configured as follows:
484+
485+```
486+{{.Client}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}
487+```
488+
489+[See `authLogMessageData` in `logging_handler.go`](./logger/logger.go) for all available variables.
490+
491+### Request Log Format
492+HTTP request logs will output by default in the below format:
452493
453494```
454495<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>
@@ -461,7 +502,20 @@ The default format is configured as follows:
461502{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}
462503```
463504
464−[See `logMessageData` in `logging_handler.go`](./logging_handler.go) for all available variables.
505+[See `requestLogMessageData` in `logging_handler.go`](./logger/logger.go) for all available variables.
506+
507+### Standard Log Format
508+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:
509+
510+```
511+[19/Mar/2015:17:20:19 -0400] [main.go:40] <MESSAGE>
512+```
513+
514+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:
515+
516+```
517+[{{.Timestamp}}] [{{.File}}] {{.Message}}
518+```
465519
466520## Adding a new Provider
467521
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+447
@@ -0,0 +1,447 @@
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+type AuthStatus string
17+
18+const (
19+ // Default logging formats
20+ DefaultStandardLoggingFormat = "[{{.Timestamp}}] [{{.File}}] {{.Message}}"
21+ DefaultAuthLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}"
22+ DefaultRequestLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
23+
24+ // Auth statuses
25+ AuthSuccess AuthStatus = "AuthSuccess"
26+ AuthFailure AuthStatus = "AuthFailure"
27+ AuthError AuthStatus = "AuthError"
28+
29+ // General logging config
30+ Llongfile = 1 << iota // full file name and line number: /a/b/c/d.go:23
31+ Lshortfile // final file name element and line number: d.go:23. overrides Llongfile
32+ LUTC // use UTC rather than the local time zone
33+ LstdFlags = Lshortfile // initial values for the standard logger
34+)
35+
36+// These are the containers for all values that are available as variables in the logging formats.
37+// All values are pre-formatted strings so it is easy to use them in the format string.
38+type stdLogMessageData struct {
39+ Timestamp,
40+ File,
41+ Message string
42+}
43+
44+type authLogMessageData struct {
45+ Client,
46+ Host,
47+ Protocol,
48+ RequestMethod,
49+ Timestamp,
50+ UserAgent,
51+ Username,
52+ Status,
53+ Message string
54+}
55+
56+type reqLogMessageData struct {
57+ Client,
58+ Host,
59+ Protocol,
60+ RequestDuration,
61+ RequestMethod,
62+ RequestURI,
63+ ResponseSize,
64+ StatusCode,
65+ Timestamp,
66+ Upstream,
67+ UserAgent,
68+ Username string
69+}
70+
71+type Logger struct {
72+ mu sync.Mutex
73+ flag int
74+ writer io.Writer
75+ stdEnabled bool
76+ authEnabled bool
77+ reqEnabled bool
78+ stdLogTemplate *template.Template
79+ authTemplate *template.Template
80+ reqTemplate *template.Template
81+}
82+
83+// New creates a new Standarderr Logger.
84+func New(flag int) *Logger {
85+ return &Logger{
86+ writer: os.Stderr,
87+ flag: flag,
88+ stdEnabled: true,
89+ authEnabled: true,
90+ reqEnabled: true,
91+ stdLogTemplate: template.Must(template.New("std-log").Parse(DefaultStandardLoggingFormat)),
92+ authTemplate: template.Must(template.New("auth-log").Parse(DefaultAuthLoggingFormat)),
93+ reqTemplate: template.Must(template.New("req-log").Parse(DefaultRequestLoggingFormat)),
94+ }
95+}
96+
97+var std = New(LstdFlags)
98+
99+// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
100+func itoa(buf *[]byte, i int, wid int) {
101+ // Assemble decimal in reverse order.
102+ var b [20]byte
103+ bp := len(b) - 1
104+ for i >= 10 || wid > 1 {
105+ wid--
106+ q := i / 10
107+ b[bp] = byte('0' + i - q*10)
108+ bp--
109+ i = q
110+ }
111+ // i < 10
112+ b[bp] = byte('0' + i)
113+ *buf = append(*buf, b[bp:]...)
114+}
115+
116+// Output a standard log template with a simple message.
117+// Write a final newline at the end of every message.
118+func (l *Logger) Output(calldepth int, message string) {
119+ if !l.stdEnabled {
120+ return
121+ }
122+
123+ now := time.Now()
124+ file := "???:0"
125+
126+ if l.flag&(Lshortfile|Llongfile) != 0 {
127+ file = l.GetFileLineString(calldepth + 1)
128+ }
129+
130+ l.mu.Lock()
131+ defer l.mu.Unlock()
132+
133+ l.stdLogTemplate.Execute(l.writer, stdLogMessageData{
134+ Timestamp: FormatTimestamp(now),
135+ File: file,
136+ Message: message,
137+ })
138+
139+ l.writer.Write([]byte("\n"))
140+}
141+
142+// Print auth details to the logger. Requires an http.Request to log details.
143+// Remaining arguments are handled in the manner of fmt.Sprintf.
144+// Writes a final newline to the end of every message.
145+func (l *Logger) PrintAuth(username string, req *http.Request, status AuthStatus, format string, a ...interface{}) {
146+ if !l.authEnabled {
147+ return
148+ }
149+
150+ now := time.Now()
151+
152+ if username == "" {
153+ username = "-"
154+ }
155+
156+ client := GetClient(req)
157+
158+ l.mu.Lock()
159+ defer l.mu.Unlock()
160+
161+ l.authTemplate.Execute(l.writer, authLogMessageData{
162+ Client: client,
163+ Host: req.Host,
164+ Protocol: req.Proto,
165+ RequestMethod: req.Method,
166+ Timestamp: FormatTimestamp(now),
167+ UserAgent: fmt.Sprintf("%q", req.UserAgent()),
168+ Username: username,
169+ Status: fmt.Sprintf("%s", status),
170+ Message: fmt.Sprintf(format, a...),
171+ })
172+
173+ l.writer.Write([]byte("\n"))
174+}
175+
176+// Print HTTP request details to the Logger.
177+// Writes a final newline to the end of every message.
178+func (l *Logger) PrintReq(username, upstream string, req *http.Request, url url.URL, ts time.Time, status int, size int) {
179+ if !l.reqEnabled {
180+ return
181+ }
182+
183+ duration := float64(time.Now().Sub(ts)) / float64(time.Second)
184+
185+ if username == "" {
186+ username = "-"
187+ }
188+
189+ if upstream == "" {
190+ upstream = "-"
191+ }
192+
193+ if url.User != nil && username == "-" {
194+ if name := url.User.Username(); name != "" {
195+ username = name
196+ }
197+ }
198+
199+ client := GetClient(req)
200+
201+ l.mu.Lock()
202+ defer l.mu.Unlock()
203+
204+ l.reqTemplate.Execute(l.writer, reqLogMessageData{
205+ Client: client,
206+ Host: req.Host,
207+ Protocol: req.Proto,
208+ RequestDuration: fmt.Sprintf("%0.3f", duration),
209+ RequestMethod: req.Method,
210+ RequestURI: fmt.Sprintf("%q", url.RequestURI()),
211+ ResponseSize: fmt.Sprintf("%d", size),
212+ StatusCode: fmt.Sprintf("%d", status),
213+ Timestamp: FormatTimestamp(ts),
214+ Upstream: upstream,
215+ UserAgent: fmt.Sprintf("%q", req.UserAgent()),
216+ Username: username,
217+ })
218+
219+ l.writer.Write([]byte("\n"))
220+}
221+
222+// Obtains file and line number info for the standard output log
223+func (l *Logger) GetFileLineString(calldepth int) string {
224+ var file string
225+ var line int
226+ var ok bool
227+
228+ _, file, line, ok = runtime.Caller(calldepth)
229+ if !ok {
230+ file = "???"
231+ line = 0
232+ }
233+
234+ if l.flag&Lshortfile != 0 {
235+ short := file
236+ for i := len(file) - 1; i > 0; i-- {
237+ if file[i] == '/' {
238+ short = file[i+1:]
239+ break
240+ }
241+ }
242+ file = short
243+ }
244+
245+ return fmt.Sprintf("%s:%d", file, line)
246+}
247+
248+// Parse an HTTP request for the client/remote IP
249+func GetClient(req *http.Request) string {
250+ client := req.Header.Get("X-Real-IP")
251+ if client == "" {
252+ client = req.RemoteAddr
253+ }
254+
255+ if c, _, err := net.SplitHostPort(client); err == nil {
256+ client = c
257+ }
258+
259+ return client
260+}
261+
262+// Returns a formatted timestamp
263+func (l *Logger) FormatTimestamp(ts time.Time) string {
264+ if l.flag&LUTC != 0 {
265+ ts = ts.UTC()
266+ }
267+
268+ return ts.Format("2006/01/02 15:04:05")
269+}
270+
271+// Flags returns the output flags for the logger.
272+func (l *Logger) Flags() int {
273+ l.mu.Lock()
274+ defer l.mu.Unlock()
275+ return l.flag
276+}
277+
278+// SetFlags sets the output flags for the logger.
279+func (l *Logger) SetFlags(flag int) {
280+ l.mu.Lock()
281+ defer l.mu.Unlock()
282+ l.flag = flag
283+}
284+
285+// Set all logging enabled for the logger.
286+func (l *Logger) SetStandardEnabled(e bool) {
287+ l.mu.Lock()
288+ defer l.mu.Unlock()
289+ l.stdEnabled = e
290+}
291+
292+// Set auth logging enabled for the standard logger
293+func (l *Logger) SetAuthEnabled(e bool) {
294+ l.mu.Lock()
295+ defer l.mu.Unlock()
296+ l.authEnabled = e
297+}
298+
299+// Set request logging enabled for the standard logger
300+func (l *Logger) SetReqEnabled(e bool) {
301+ l.mu.Lock()
302+ defer l.mu.Unlock()
303+ l.reqEnabled = e
304+}
305+
306+// Set the output template for the standard logger
307+func (l *Logger) SetStandardTemplate(t string) {
308+ l.mu.Lock()
309+ defer l.mu.Unlock()
310+ l.stdLogTemplate = template.Must(template.New("std-log").Parse(t))
311+}
312+
313+// Set the auth template for the standard logger
314+func (l *Logger) SetAuthTemplate(t string) {
315+ l.mu.Lock()
316+ defer l.mu.Unlock()
317+ l.authTemplate = template.Must(template.New("auth-log").Parse(t))
318+}
319+
320+// Set the request template for the standard logger
321+func (l *Logger) SetReqTemplate(t string) {
322+ l.mu.Lock()
323+ defer l.mu.Unlock()
324+ l.reqTemplate = template.Must(template.New("req-log").Parse(t))
325+}
326+
327+// These functions utilize the standard logger.
328+
329+// Returns a formatted timestamp for the standard logger.
330+func FormatTimestamp(ts time.Time) string {
331+ return std.FormatTimestamp(ts)
332+}
333+
334+// Flags returns the output flags for the standard logger.
335+func Flags() int {
336+ return std.Flags()
337+}
338+
339+// SetFlags sets the output flags for the standard logger.
340+func SetFlags(flag int) {
341+ std.SetFlags(flag)
342+}
343+
344+// SetOutput sets the output destination for the standard logger.
345+func SetOutput(w io.Writer) {
346+ std.mu.Lock()
347+ defer std.mu.Unlock()
348+ std.writer = w
349+}
350+
351+// Set output logging enabled for the standard logger
352+func SetStandardEnabled(e bool) {
353+ std.SetStandardEnabled(e)
354+}
355+
356+// Set auth logging enabled for the standard logger
357+func SetAuthEnabled(e bool) {
358+ std.SetAuthEnabled(e)
359+}
360+
361+// Set request logging enabled for the standard logger
362+func SetReqEnabled(e bool) {
363+ std.SetReqEnabled(e)
364+}
365+
366+// Set the output template for the standard logger
367+func SetStandardTemplate(t string) {
368+ std.SetStandardTemplate(t)
369+}
370+
371+// Set the auth template for the standard logger
372+func SetAuthTemplate(t string) {
373+ std.SetAuthTemplate(t)
374+}
375+
376+// Set the request template for the standard logger
377+func SetReqTemplate(t string) {
378+ std.SetReqTemplate(t)
379+}
380+
381+// Print calls Output to print to the standard logger.
382+// Arguments are handled in the manner of fmt.Print.
383+func Print(v ...interface{}) {
384+ std.Output(2, fmt.Sprint(v...))
385+}
386+
387+// Printf calls Output to print to the standard logger.
388+// Arguments are handled in the manner of fmt.Printf.
389+func Printf(format string, v ...interface{}) {
390+ std.Output(2, fmt.Sprintf(format, v...))
391+}
392+
393+// Println calls Output to print to the standard logger.
394+// Arguments are handled in the manner of fmt.Println.
395+func Println(v ...interface{}) {
396+ std.Output(2, fmt.Sprintln(v...))
397+}
398+
399+// Fatal is equivalent to Print() followed by a call to os.Exit(1).
400+func Fatal(v ...interface{}) {
401+ std.Output(2, fmt.Sprint(v...))
402+ os.Exit(1)
403+}
404+
405+// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
406+func Fatalf(format string, v ...interface{}) {
407+ std.Output(2, fmt.Sprintf(format, v...))
408+ os.Exit(1)
409+}
410+
411+// Fatalln is equivalent to Println() followed by a call to os.Exit(1).
412+func Fatalln(v ...interface{}) {
413+ std.Output(2, fmt.Sprintln(v...))
414+ os.Exit(1)
415+}
416+
417+// Panic is equivalent to Print() followed by a call to panic().
418+func Panic(v ...interface{}) {
419+ s := fmt.Sprint(v...)
420+ std.Output(2, s)
421+ panic(s)
422+}
423+
424+// Panicf is equivalent to Printf() followed by a call to panic().
425+func Panicf(format string, v ...interface{}) {
426+ s := fmt.Sprintf(format, v...)
427+ std.Output(2, s)
428+ panic(s)
429+}
430+
431+// Panicln is equivalent to Println() followed by a call to panic().
432+func Panicln(v ...interface{}) {
433+ s := fmt.Sprintln(v...)
434+ std.Output(2, s)
435+ panic(s)
436+}
437+
438+// PrintAuthf writes authentication details to the standard logger.
439+// Arguments are handled in the manner of fmt.Printf.
440+func PrintAuthf(username string, req *http.Request, status AuthStatus, format string, a ...interface{}) {
441+ std.PrintAuth(username, req, status, format, a...)
442+}
443+
444+// PrintAuthf writes request details to the standard logger.
445+func PrintReq(username, upstream string, req *http.Request, url url.URL, ts time.Time, status int, size int) {
446+ std.PrintReq(username, upstream, req, url, ts, status, size)
447+}
logging_handler.go+7−87
@@ -4,19 +4,10 @@
44package main
55
66import (
7− "bufio"
8− "errors"
9− "fmt"
10− "io"
11− "net"
127 "net/http"
13− "net/url"
14− "text/template"
158 "time"
16−)
179
18−const (
19− defaultRequestLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
10+ "github.com/pusher/oauth2_proxy/logger"
2011)
2112
2213// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status
@@ -92,93 +83,22 @@ func (l *responseLogger) Flush() {
9283 }
9384}
9485
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−
11286// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
11387type loggingHandler struct {
114− writer io.Writer
115− handler http.Handler
116− enabled bool
117− logTemplate *template.Template
88+ handler http.Handler
11889}
11990
12091// 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 {
92+func LoggingHandler(h http.Handler) http.Handler {
12293 return loggingHandler{
123− writer: out,
124− handler: h,
125− enabled: v,
126− logTemplate: template.Must(template.New("request-log").Parse(requestLoggingTpl)),
94+ handler: h,
12795 }
12896}
12997
13098func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
13199 t := time.Now()
132100 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"))
101+ responseLogger := &responseLogger{w: w}
102+ h.handler.ServeHTTP(responseLogger, req)
103+ logger.PrintReq(responseLogger.authInfo, responseLogger.upstream, req, url, t, responseLogger.Status(), responseLogger.Size())
184104}
logging_handler_test.go+5−1
@@ -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) {
@@ -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+27−9
@@ -16,11 +16,11 @@ import (
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{}
23− whitelistDomains := StringArray{}
23+ whitelistandardomains := StringArray{}
2424 upstreams := StringArray{}
2525 skipAuthRegex := StringArray{}
2626 googleGroups := StringArray{}
@@ -49,7 +49,7 @@ func main() {
4949 flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses")
5050
5151 flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email")
52− flagSet.Var(&whitelistDomains, "whitelist-domain", "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)")
52+ flagSet.Var(&whitelistandardomains, "whitelist-domain", "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)")
5353 flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.")
5454 flagSet.String("github-org", "", "restrict logins to members of this organisation")
5555 flagSet.String("github-team", "", "restrict logins to members of this team")
@@ -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)")
@@ -109,7 +122,7 @@ func main() {
109122 if *config != "" {
110123 _, err := toml.DecodeFile(*config, &cfg)
111124 if err != nil {
112− log.Fatalf("ERROR: failed to load config file %s - %s", *config, err)
125+ logger.Fatalf("ERROR: failed to load config file %s - %s", *config, err)
113126 }
114127 }
115128 cfg.LoadEnvForStruct(opts)
@@ -117,9 +130,10 @@ func main() {
117130
118131 err := opts.Validate()
119132 if err != nil {
120− log.Printf("%s", err)
133+ logger.Printf("%s", err)
121134 os.Exit(1)
122135 }
136+
123137 validator := NewValidator(opts.EmailDomains, opts.AuthenticatedEmailsFile)
124138 oauthproxy := NewOAuthProxy(opts, validator)
125139
@@ -132,11 +146,11 @@ func main() {
132146 }
133147
134148 if opts.HtpasswdFile != "" {
135− log.Printf("using htpasswd file %s", opts.HtpasswdFile)
149+ logger.Printf("using htpasswd file %s", opts.HtpasswdFile)
136150 oauthproxy.HtpasswdFile, err = NewHtpasswdFromFile(opts.HtpasswdFile)
137151 oauthproxy.DisplayHtpasswdForm = opts.DisplayHtpasswdForm
138152 if err != nil {
139− log.Fatalf("FATAL: unable to open %s %s", opts.HtpasswdFile, err)
153+ logger.Fatalf("FATAL: unable to open %s %s", opts.HtpasswdFile, err)
140154 }
141155 }
142156
@@ -149,7 +163,11 @@ func main() {
149163 handler = LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging, opts.RequestLoggingFormat)
150164 }
151165 s := &Server{
166+<<<<<<< HEAD
152167 Handler: handler,
168+=======
169+ Handler: LoggingHandler(oauthproxy),
170+>>>>>>> Auth and standard logging with file rolling
153171 Opts: opts,
154172 }
155173 s.ListenAndServe()
oauthproxy.go+46−24
@@ -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,15 +185,28 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
185185 path := u.Path
186186 switch u.Scheme {
187187 case httpScheme, httpsScheme:
188+<<<<<<< HEAD
188189 log.Printf("mapping path %q => upstream %q", path, u)
189190 proxy := NewWebSocketOrRestReverseProxy(u, opts, auth)
190191 serveMux.Handle(path, proxy)
191192
193+=======
194+ u.Path = ""
195+ logger.Printf("mapping path %q => upstream %q", path, u)
196+ proxy := NewReverseProxy(u, opts.FlushInterval)
197+ if !opts.PassHostHeader {
198+ setProxyUpstreamHostHeader(proxy, u)
199+ } else {
200+ setProxyDirector(proxy)
201+ }
202+ serveMux.Handle(path,
203+ &UpstreamProxy{u.Host, proxy, auth})
204+>>>>>>> Auth and standard logging with file rolling
192205 case "file":
193206 if u.Fragment != "" {
194207 path = u.Fragment
195208 }
196− log.Printf("mapping path %q => file system %q", path, u.Path)
209+ logger.Printf("mapping path %q => file system %q", path, u.Path)
197210 proxy := NewFileServer(path, u.Path)
198211 serveMux.Handle(path, &UpstreamProxy{path, proxy, nil, nil})
199212 default:
@@ -201,7 +214,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
201214 }
202215 }
203216 for _, u := range opts.CompiledRegex {
204− log.Printf("compiled skip-auth-regex => %q", u)
217+ logger.Printf("compiled skip-auth-regex => %q", u)
205218 }
206219
207220 redirectURL := opts.redirectURL
@@ -209,20 +222,24 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
209222 redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
210223 }
211224
212− log.Printf("OAuthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID)
225+ logger.Printf("OAuthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID)
213226 refresh := "disabled"
214227 if opts.CookieRefresh != time.Duration(0) {
215228 refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
216229 }
217230
231+<<<<<<< HEAD
218232 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)
233+=======
234+ logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, refresh)
235+>>>>>>> Auth and standard logging with file rolling
219236
220237 var cipher *cookie.Cipher
221238 if opts.PassAccessToken || opts.SetAuthorization || opts.PassAuthorization || (opts.CookieRefresh != time.Duration(0)) {
222239 var err error
223240 cipher, err = cookie.NewCipher(secretBytes(opts.CookieSecret))
224241 if err != nil {
225− log.Fatal("cookie-secret error: ", err)
242+ logger.Fatal("cookie-secret error: ", err)
226243 }
227244 }
228245
@@ -425,7 +442,7 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
425442 domain = h
426443 }
427444 if !strings.HasSuffix(domain, p.CookieDomain) {
428− log.Printf("Warning: request host is %q but using configured cookie domain of %q", domain, p.CookieDomain)
445+ logger.Printf("Warning: request host is %q but using configured cookie domain of %q", domain, p.CookieDomain)
429446 }
430447 }
431448
@@ -529,7 +546,6 @@ func (p *OAuthProxy) PingPage(rw http.ResponseWriter) {
529546
530547// ErrorPage writes an error response
531548func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
532− log.Printf("ErrorPage %d %s %s", code, title, message)
533549 rw.WriteHeader(code)
534550 t := struct {
535551 Title string
@@ -588,9 +604,10 @@ func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (st
588604 }
589605 // check auth
590606 if p.HtpasswdFile.Validate(user, passwd) {
591− log.Printf("authenticated %q via HtpasswdFile", user)
607+ logger.PrintAuthf(user, req, logger.AuthSuccess, "Successful authentication via HtpasswdFile")
592608 return user, true
593609 }
610+ logger.PrintAuthf(user, req, logger.AuthFailure, "Failed authentication via HtpasswdFile; unauthorized")
594611 return "", false
595612}
596613
@@ -686,6 +703,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
686703func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
687704 redirect, err := p.GetRedirect(req)
688705 if err != nil {
706+ logger.Printf("Error obtaining redirect: %s", err.Error())
689707 p.ErrorPage(rw, 500, "Internal Error", err.Error())
690708 return
691709 }
@@ -714,12 +732,14 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
714732func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
715733 nonce, err := cookie.Nonce()
716734 if err != nil {
735+ logger.Printf("Error obtaining nonce: %s", err.Error())
717736 p.ErrorPage(rw, 500, "Internal Error", err.Error())
718737 return
719738 }
720739 p.SetCSRFCookie(rw, req, nonce)
721740 redirect, err := p.GetRedirect(req)
722741 if err != nil {
742+ logger.Printf("Error obtaining redirect: %s", err.Error())
723743 p.ErrorPage(rw, 500, "Internal Error", err.Error())
724744 return
725745 }
@@ -735,24 +755,27 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
735755 // finish the oauth cycle
736756 err := req.ParseForm()
737757 if err != nil {
758+ logger.Printf("Error while parsing OAuth callback: %s" + err.Error())
738759 p.ErrorPage(rw, 500, "Internal Error", err.Error())
739760 return
740761 }
741762 errorString := req.Form.Get("error")
742763 if errorString != "" {
764+ logger.Printf("Error while parsing OAuth callback: %s ", errorString)
743765 p.ErrorPage(rw, 403, "Permission Denied", errorString)
744766 return
745767 }
746768
747769 session, err := p.redeemCode(req.Host, req.Form.Get("code"))
748770 if err != nil {
749− log.Printf("%s error redeeming code %s", remoteAddr, err)
771+ logger.Printf("Error while parsing OAuth callback: %s ", errorString)
750772 p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
751773 return
752774 }
753775
754776 s := strings.SplitN(req.Form.Get("state"), ":", 2)
755777 if len(s) != 2 {
778+ logger.Printf("Error while parsing OAuth state; invalid length")
756779 p.ErrorPage(rw, 500, "Internal Error", "Invalid State")
757780 return
758781 }
@@ -760,12 +783,13 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
760783 redirect := s[1]
761784 c, err := req.Cookie(p.CSRFCookieName)
762785 if err != nil {
786+ logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Failed authentication via oauth2; unable too obtain CSRF cookie")
763787 p.ErrorPage(rw, 403, "Permission Denied", err.Error())
764788 return
765789 }
766790 p.ClearCSRFCookie(rw, req)
767791 if c.Value != nonce {
768− log.Printf("%s csrf token mismatch, potential attack", remoteAddr)
792+ logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Failed authentication via oauth2; csrf token mismatch, potential attack")
769793 p.ErrorPage(rw, 403, "Permission Denied", "csrf failed")
770794 return
771795 }
@@ -776,16 +800,16 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
776800
777801 // set cookie, or deny
778802 if p.Validator(session.Email) && p.provider.ValidateGroup(session.Email) {
779− log.Printf("%s authentication complete %s", remoteAddr, session)
803+ logger.PrintAuthf(session.Email, req, logger.AuthSuccess, "Successful authentication via oauth2; %s", session)
780804 err := p.SaveSession(rw, req, session)
781805 if err != nil {
782− log.Printf("%s %s", remoteAddr, err)
806+ logger.Printf("%s %s", remoteAddr, err)
783807 p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
784808 return
785809 }
786810 http.Redirect(rw, req, redirect, 302)
787811 } else {
788− log.Printf("%s Permission Denied: %q is unauthorized", remoteAddr, session.Email)
812+ logger.PrintAuthf(session.Email, req, logger.AuthSuccess, "Failed authentication via oauth2; unauthorized")
789813 p.ErrorPage(rw, 403, "Permission Denied", "Invalid Account")
790814 }
791815}
@@ -827,16 +851,16 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
827851
828852 session, sessionAge, err := p.LoadCookiedSession(req)
829853 if err != nil {
830− log.Printf("%s %s", remoteAddr, err)
854+ logger.Printf("Error loading cookied session: %s", err)
831855 }
832856 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)
857+ logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", sessionAge, session, p.CookieRefresh)
834858 saveSession = true
835859 }
836860
837861 var ok bool
838862 if ok, err = p.provider.RefreshSessionIfNeeded(session); err != nil {
839− log.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session)
863+ logger.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session)
840864 clearSession = true
841865 session = nil
842866 } else if ok {
@@ -845,7 +869,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
845869 }
846870
847871 if session != nil && session.IsExpired() {
848− log.Printf("%s removing session. token expired %s", remoteAddr, session)
872+ logger.Printf("Removing session; token expired %s", session)
849873 session = nil
850874 saveSession = false
851875 clearSession = true
@@ -853,7 +877,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
853877
854878 if saveSession && !revalidated && session != nil && session.AccessToken != "" {
855879 if !p.provider.ValidateSessionState(session) {
856− log.Printf("%s removing session. error validating %s", remoteAddr, session)
880+ logger.Printf("Removing session; error validating %s", session)
857881 saveSession = false
858882 session = nil
859883 clearSession = true
@@ -861,7 +885,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
861885 }
862886
863887 if session != nil && session.Email != "" && !p.Validator(session.Email) {
864− log.Printf("%s Permission Denied: removing session %s", remoteAddr, session)
888+ logger.Printf(session.Email, req, logger.AuthFailure, "Failed authentication via session; removing session %s", session)
865889 session = nil
866890 saveSession = false
867891 clearSession = true
@@ -870,7 +894,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
870894 if saveSession && session != nil {
871895 err = p.SaveSession(rw, req, session)
872896 if err != nil {
873− log.Printf("%s %s", remoteAddr, err)
897+ logger.PrintAuthf(session.Email, req, logger.AuthError, "Save session error %s", err)
874898 return http.StatusInternalServerError
875899 }
876900 }
@@ -881,9 +905,6 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
881905
882906 if session == nil {
883907 session, err = p.CheckBasicAuth(req)
884− if err != nil {
885− log.Printf("%s %s", remoteAddr, err)
886− }
887908 }
888909
889910 if session == nil {
@@ -958,9 +979,10 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState,
958979 return nil, fmt.Errorf("invalid format %s", b)
959980 }
960981 if p.HtpasswdFile.Validate(pair[0], pair[1]) {
961− log.Printf("authenticated %q via basic auth", pair[0])
982+ logger.PrintAuthf(pair[0], req, logger.AuthSuccess, "Successful authentication via basic auth")
962983 return &providers.SessionState{User: pair[0]}, nil
963984 }
985+ logger.PrintAuthf(pair[0], req, logger.AuthFailure, "Failed authentication via basic auth; not in Htpasswd file")
964986 return nil, fmt.Errorf("%s not in HtpasswdFile", pair[0])
965987}
966988
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+110
@@ -16,7 +16,9 @@ import (
1616 oidc "github.com/coreos/go-oidc"
1717 "github.com/dgrijalva/jwt-go"
1818 "github.com/mbland/hmacauth"
19+ "github.com/pusher/oauth2_proxy/logger"
1920 "github.com/pusher/oauth2_proxy/providers"
21+ lumberjack "gopkg.in/natefinch/lumberjack.v2"
2022)
2123
2224// Options holds Configuration Options that can be set by Command Line Flag,
@@ -72,6 +74,7 @@ type Options struct {
7274
7375 // These options allow for other providers besides Google, with
7476 // potential overrides.
77+<<<<<<< HEAD
7578 Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"`
7679 OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"`
7780 SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_SKIP_OIDC_DISCOVERY"`
@@ -92,6 +95,33 @@ type Options struct {
9295 JWTKey string `flag:"jwt-key" cfg:"jwt_key" env:"OAUTH2_PROXY_JWT_KEY"`
9396 PubJWKURL string `flag:"pubjwk-url" cfg:"pubjwk_url" env:"OAUTH2_PROXY_PUBJWK_URL"`
9497 GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks" env:"OAUTH2_PROXY_GCP_HEALTHCHECKS"`
98+=======
99+ Provider string `flag:"provider" cfg:"provider"`
100+ OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url"`
101+ LoginURL string `flag:"login-url" cfg:"login_url"`
102+ RedeemURL string `flag:"redeem-url" cfg:"redeem_url"`
103+ ProfileURL string `flag:"profile-url" cfg:"profile_url"`
104+ ProtectedResource string `flag:"resource" cfg:"resource"`
105+ ValidateURL string `flag:"validate-url" cfg:"validate_url"`
106+ Scope string `flag:"scope" cfg:"scope"`
107+ ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"`
108+
109+ // Configuration values for logging
110+ LoggingFilename string `flag:"logging-filename" cfg:"logging_filename"`
111+ LoggingMaxSize int `flag:"logging-max-size" cfg:"logging_max_size"`
112+ LoggingMaxAge int `flag:"logging-max-age" cfg:"logging_max_age"`
113+ LoggingMaxBackups int `flag:"logging-max-backups" cfg:"logging_max_backups"`
114+ LoggingLocalTime bool `flag:"logging-local-time" cfg:"logging_local_time"`
115+ LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress"`
116+ StandardLogging bool `flag:"standard-logging" cfg:"standard_logging"`
117+ StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format"`
118+ RequestLogging bool `flag:"request-logging" cfg:"request_logging"`
119+ RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format"`
120+ AuthLogging bool `flag:"auth-logging" cfg:"auth_logging"`
121+ AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format"`
122+
123+ SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"`
124+>>>>>>> Auth and standard logging with file rolling
95125
96126 // internal values that are set after config validation
97127 redirectURL *url.URL
@@ -111,6 +141,7 @@ type SignatureData struct {
111141// NewOptions constructs a new Options with defaulted values
112142func NewOptions() *Options {
113143 return &Options{
144+<<<<<<< HEAD
114145 ProxyPrefix: "/oauth2",
115146 ProxyWebSockets: true,
116147 HTTPAddress: "127.0.0.1:4180",
@@ -133,6 +164,38 @@ func NewOptions() *Options {
133164 RequestLogging: true,
134165 SkipOIDCDiscovery: false,
135166 RequestLoggingFormat: defaultRequestLoggingFormat,
167+=======
168+ ProxyPrefix: "/oauth2",
169+ HTTPAddress: "127.0.0.1:4180",
170+ HTTPSAddress: ":443",
171+ DisplayHtpasswdForm: true,
172+ CookieName: "_oauth2_proxy",
173+ CookieSecure: true,
174+ CookieHTTPOnly: true,
175+ CookieExpire: time.Duration(168) * time.Hour,
176+ CookieRefresh: time.Duration(0),
177+ SetXAuthRequest: false,
178+ SkipAuthPreflight: false,
179+ PassBasicAuth: true,
180+ PassUserHeaders: true,
181+ PassAccessToken: false,
182+ PassHostHeader: true,
183+ SetAuthorization: false,
184+ PassAuthorization: false,
185+ ApprovalPrompt: "force",
186+ LoggingFilename: "",
187+ LoggingMaxSize: 100,
188+ LoggingMaxAge: 7,
189+ LoggingMaxBackups: 0,
190+ LoggingLocalTime: true,
191+ LoggingCompress: false,
192+ StandardLogging: true,
193+ StandardLoggingFormat: logger.DefaultStandardLoggingFormat,
194+ RequestLogging: true,
195+ RequestLoggingFormat: logger.DefaultRequestLoggingFormat,
196+ AuthLogging: true,
197+ AuthLoggingFormat: logger.DefaultAuthLoggingFormat,
198+>>>>>>> Auth and standard logging with file rolling
136199 }
137200}
138201
@@ -283,6 +346,7 @@ func (o *Options) Validate() error {
283346
284347 msgs = parseSignatureKey(o, msgs)
285348 msgs = validateCookieName(o, msgs)
349+ msgs = setupLogger(o, msgs)
286350
287351 if len(msgs) != 0 {
288352 return fmt.Errorf("Invalid configuration:\n %s",
@@ -394,3 +458,49 @@ func secretBytes(secret string) []byte {
394458 }
395459 return []byte(secret)
396460}
461+
462+func setupLogger(o *Options, msgs []string) []string {
463+ // Setup the log file
464+ if len(o.LoggingFilename) > 0 {
465+ // Validate that the file/dir can be written
466+ file, err := os.OpenFile(o.LoggingFilename, os.O_WRONLY|os.O_CREATE, 0666)
467+ if err != nil {
468+ if os.IsPermission(err) {
469+ return append(msgs, "unable to write to log file: "+o.LoggingFilename)
470+ }
471+ }
472+ file.Close()
473+
474+ logger.Printf("Redirecting logging to file: %s", o.LoggingFilename)
475+
476+ logWriter := &lumberjack.Logger{
477+ Filename: o.LoggingFilename,
478+ MaxSize: o.LoggingMaxSize, // megabytes
479+ MaxAge: o.LoggingMaxAge, // days
480+ MaxBackups: o.LoggingMaxBackups,
481+ LocalTime: o.LoggingLocalTime,
482+ Compress: o.LoggingCompress,
483+ }
484+
485+ logger.SetOutput(logWriter)
486+ }
487+
488+ // Supply a sanity warning to the logger if all logging is disabled
489+ if !o.StandardLogging && !o.AuthLogging && !o.RequestLogging {
490+ logger.Print("Warning: Logging disabled. No further logs will be shown.")
491+ }
492+
493+ // Pass configuration values to the standard logger
494+ logger.SetStandardEnabled(o.StandardLogging)
495+ logger.SetAuthEnabled(o.AuthLogging)
496+ logger.SetReqEnabled(o.RequestLogging)
497+ logger.SetStandardTemplate(o.StandardLoggingFormat)
498+ logger.SetAuthTemplate(o.AuthLoggingFormat)
499+ logger.SetReqTemplate(o.RequestLoggingFormat)
500+
501+ if !o.LoggingLocalTime {
502+ logger.SetFlags(logger.Flags() | logger.LUTC)
503+ }
504+
505+ return msgs
506+}
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}