Merge 6e24e8498c into 09979d458a
This commit is contained in:
commit
627444dc24
|
|
@ -2,8 +2,13 @@
|
|||
|
||||
## Release Highlights
|
||||
|
||||
- Logging now uses Go's `log/slog`, adding configurable log levels and optional structured JSON output while keeping text output as the default.
|
||||
|
||||
## Important Notes
|
||||
|
||||
- The new `--logging-level` flag controls log verbosity with `debug`, `info`, `warn`, and `error` levels.
|
||||
- The new `--logging-format` flag supports `text` and `json`; `text` remains the default, and existing text template options continue to apply to text output.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
## Changes since v7.15.3
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
# ]
|
||||
|
||||
## Logging configuration
|
||||
#logging_level = "info"
|
||||
#logging_format = "text"
|
||||
#logging_filename = ""
|
||||
#logging_max_size = 100
|
||||
#logging_max_age = 7
|
||||
|
|
@ -29,10 +31,10 @@
|
|||
#logging_compress = false
|
||||
#standard_logging = true
|
||||
#standard_logging_format = "[{{.Timestamp}}] [{{.File}}] {{.Message}}"
|
||||
#request_logging = true
|
||||
#request_logging_format = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
|
||||
#auth_logging = true
|
||||
#auth_logging_format = "{{.Client}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}"
|
||||
#auth_logging_format = "{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}"
|
||||
#request_logging = true
|
||||
#request_logging_format = "{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
|
||||
|
||||
## pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
|
||||
# pass_basic_auth = true
|
||||
|
|
|
|||
|
|
@ -154,22 +154,24 @@ Provider specific options can be found on their respective subpages.
|
|||
|
||||
| Flag / Config Field | Type | Description | Default |
|
||||
| --------------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------- | --------------------------------------------------- |
|
||||
| flag: `--auth-logging-format`<br/>toml: `auth_logging_format` | string | Template for authentication log lines | see [Logging Configuration](#logging-configuration) |
|
||||
| flag: `--logging-level`<br/>toml: `logging_level` | string | Log level: `debug`, `info`, `warn`, `error` | `"info"` |
|
||||
| flag: `--logging-format`<br/>toml: `logging_format` | string | Log format: `text`, `json` | `"text"` |
|
||||
| flag: `--auth-logging-format`<br/>toml: `auth_logging_format` | string | Template for authentication log lines when `logging_format` is `text` | see [Logging Configuration](#logging-configuration) |
|
||||
| flag: `--auth-logging`<br/>toml: `auth_logging` | bool | Log authentication attempts | true |
|
||||
| flag: `--errors-to-info-log`<br/>toml: `errors_to_info_log` | bool | redirects error-level logging to default log channel instead of stderr | false |
|
||||
| flag: `--exclude-logging-path`<br/>toml: `exclude_logging_paths` | string | comma separated list of paths to exclude from logging, e.g. `"/ping,/path2"` | `""` (no paths excluded) |
|
||||
| flag: `--logging-compress`<br/>toml: `logging_compress` | bool | Should rotated log files be compressed using gzip | false |
|
||||
| flag: `--request-logging-format`<br/>toml: `request_logging_format` | string | Template for request log lines when `logging_format` is `text` | see [Logging Configuration](#logging-configuration) |
|
||||
| flag: `--request-logging`<br/>toml: `request_logging` | bool | Log HTTP requests | true |
|
||||
| flag: `--standard-logging-format`<br/>toml: `standard_logging_format` | string | Template for standard log lines when `logging_format` is `text` | see [Logging Configuration](#logging-configuration) |
|
||||
| flag: `--standard-logging`<br/>toml: `standard_logging` | bool | Log standard runtime information | true |
|
||||
| flag: `--errors-to-info-log`<br/>toml: `errors_to_info_log` | bool | Redirect error-level logging to the standard log channel instead of stderr | false |
|
||||
| flag: `--exclude-logging-path`<br/>toml: `exclude_logging_paths` | string | Comma separated list of paths to exclude from logging, e.g. `"/ping,/path2"` | `""` (no paths excluded) |
|
||||
| flag: `--silence-ping-logging`<br/>toml: `silence_ping_logging` | bool | Disable logging of requests to ping & ready endpoints | false |
|
||||
| flag: `--request-id-header`<br/>toml: `request_id_header` | string | Request header to use as the request ID in logging | `"X-Request-Id"` |
|
||||
| flag: `--logging-filename`<br/>toml: `logging_filename` | string | File to log requests to, empty for `stdout` | `""` (stdout) |
|
||||
| flag: `--logging-local-time`<br/>toml: `logging_local_time` | bool | Use local time in log files and backup filenames instead of UTC | true (local time) |
|
||||
| flag: `--logging-max-size`<br/>toml: `logging_max_size` | int | Maximum size in megabytes of the log file before rotation | 100 |
|
||||
| flag: `--logging-max-age`<br/>toml: `logging_max_age` | int | Maximum number of days to retain old log files | 7 |
|
||||
| flag: `--logging-max-backups`<br/>toml: `logging_max_backups` | int | Maximum number of old log files to retain; 0 to disable | 0 |
|
||||
| flag: `--logging-max-size`<br/>toml: `logging_max_size` | int | Maximum size in megabytes of the log file before rotation | 100 |
|
||||
| flag: `--request-id-header`<br/>toml: `request_id_header` | string | Request header to use as the request ID in logging | X-Request-Id |
|
||||
| flag: `--request-logging-format`<br/>toml: `request_logging_format` | string | Template for request log lines | see [Logging Configuration](#logging-configuration) |
|
||||
| flag: `--request-logging`<br/>toml: `request_logging` | bool | Log requests | true |
|
||||
| flag: `--silence-ping-logging`<br/>toml: `silence_ping_logging` | bool | disable logging of requests to ping & ready endpoints | false |
|
||||
| flag: `--standard-logging-format`<br/>toml: `standard_logging_format` | string | Template for standard log lines | see [Logging Configuration](#logging-configuration) |
|
||||
| flag: `--standard-logging`<br/>toml: `standard_logging` | bool | Log standard runtime information | true |
|
||||
| flag: `--logging-local-time`<br/>toml: `logging_local_time` | bool | Use local time in log files and backup filenames instead of UTC | true (local time) |
|
||||
| flag: `--logging-compress`<br/>toml: `logging_compress` | bool | Should rotated log files be compressed using gzip | false |
|
||||
|
||||
### Page Template Options
|
||||
|
||||
|
|
@ -349,19 +351,47 @@ Please check the type for each [config option](#config-options) first.
|
|||
|
||||
## Logging Configuration
|
||||
|
||||
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.
|
||||
OAuth2 Proxy uses Go's standard `log/slog` package for log levels and structured logging support. The default output format is `text`, preserving the existing template-based log lines. Set `--logging-format=json` to emit structured JSON logs for log aggregation systems.
|
||||
|
||||
By default, OAuth2 Proxy logs standard informational output to stdout and standard warning/error output to stderr. Logging can be configured to output to a rotating log file using the `--logging-filename` command.
|
||||
|
||||
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`).
|
||||
|
||||
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`.
|
||||
|
||||
Each type of logging has its own configurable format and variables. By default, these formats are similar to the Apache Combined Log.
|
||||
### Log Format
|
||||
|
||||
Logging of requests to the `/ping` endpoint (or using `--ping-user-agent`) and the `/ready` endpoint can be disabled with `--silence-ping-logging` reducing log volume.
|
||||
Two log formats are supported via `--logging-format`:
|
||||
|
||||
## Auth Log Format
|
||||
- **`text`** (default): Human-readable log lines using the standard, authentication, and request templates documented below.
|
||||
- **`json`**: Machine-readable JSON, one object per line. The template format options are ignored in JSON mode because fields are emitted as structured attributes.
|
||||
|
||||
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:
|
||||
Example text output:
|
||||
|
||||
```
|
||||
127.0.0.1:59742 - 6cccb6ca - user@example.com [2025/01/15 10:30:00] app.example.com GET - "/oauth2/callback" HTTP/1.1 "Mozilla/5.0" 200 12 0.001
|
||||
```
|
||||
|
||||
Example JSON output:
|
||||
|
||||
```json
|
||||
{"time":"2025-01-15T10:30:00Z","level":"INFO","source":{"function":"main.main","file":"main.go","line":42},"msg":"request","user":"user@example.com","client":"127.0.0.1:59742","host":"app.example.com","method":"GET","uri":"/oauth2/callback","protocol":"HTTP/1.1","status_code":200,"response_size":12,"duration_s":"0.001","request_id":"6cccb6ca"}
|
||||
```
|
||||
|
||||
### Log Levels
|
||||
|
||||
Four levels are supported via `--logging-level`:
|
||||
|
||||
| Level | Description |
|
||||
| ------- | ------------------------------------------------------------------------ |
|
||||
| `debug` | Verbose output for troubleshooting |
|
||||
| `info` | Normal operational messages (default) |
|
||||
| `warn` | Warning conditions, including explicit authentication failures |
|
||||
| `error` | Error conditions written to stderr unless `--errors-to-info-log` is set |
|
||||
|
||||
### Auth Log Format
|
||||
|
||||
Authentication logs are logs which are guaranteed to contain a username or email address of a user attempting to authenticate. In `text` format, these logs are output by default in the below format:
|
||||
|
||||
```
|
||||
<REMOTE_ADDRESS> - <REQUEST ID> - <user@domain.com> [2015/03/19 17:20:19] [<STATUS>] <MESSAGE>
|
||||
|
|
@ -373,8 +403,7 @@ The status block will contain one of the below strings:
|
|||
- `AuthFailure` If the user failed to authenticate explicitly
|
||||
- `AuthError` If there was an unexpected error during authentication
|
||||
|
||||
If you require a different format than that, you can configure it with the `--auth-logging-format` flag.
|
||||
The default format is configured as follows:
|
||||
If you require a different text format, you can configure it with the `--auth-logging-format` flag. The default format is configured as follows:
|
||||
|
||||
```
|
||||
{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}
|
||||
|
|
@ -395,16 +424,15 @@ Available variables for auth logging:
|
|||
| Username | username@email.com | The email or username of the auth request. |
|
||||
| Status | AuthSuccess | The status of the auth request. See above for details. |
|
||||
|
||||
## Request Log Format
|
||||
### Request Log Format
|
||||
|
||||
HTTP request logs will output by default in the below format:
|
||||
HTTP request logs will output by default in the below text format:
|
||||
|
||||
```
|
||||
<REMOTE_ADDRESS> - <REQUEST ID> - <user@domain.com> [2015/03/19 17:20:19] <HOST_HEADER> GET <UPSTREAM_HOST> "/path/" HTTP/1.1 "<USER_AGENT>" <RESPONSE_CODE> <RESPONSE_BYTES> <REQUEST_DURATION>
|
||||
```
|
||||
|
||||
If you require a different format than that, you can configure it with the `--request-logging-format` flag.
|
||||
The default format is configured as follows:
|
||||
If you require a different text format, you can configure it with the `--request-logging-format` flag. The default format is configured as follows:
|
||||
|
||||
```
|
||||
{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}
|
||||
|
|
@ -428,15 +456,15 @@ Available variables for request logging:
|
|||
| UserAgent | - | The full user agent as reported by the requesting client. |
|
||||
| Username | username@email.com | The email or username of the auth request. |
|
||||
|
||||
## Standard Log Format
|
||||
### Standard Log Format
|
||||
|
||||
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:
|
||||
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 text format is below:
|
||||
|
||||
```
|
||||
[2015/03/19 17:20:19] [main.go:40] <MESSAGE>
|
||||
```
|
||||
|
||||
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:
|
||||
If you require a different text format, you can configure it with the `--standard-logging-format` flag. The default format is configured as follows:
|
||||
|
||||
```
|
||||
[{{.Timestamp}}] [{{.File}}] {{.Message}}
|
||||
|
|
@ -449,3 +477,47 @@ Available variables for standard logging:
|
|||
| Timestamp | 2015/03/19 17:20:19 | The date and time of the logging event. |
|
||||
| File | main.go:40 | The file and line number of the logging statement. |
|
||||
| Message | HTTP: listening on 127.0.0.1:4180 | The details of the log statement. |
|
||||
|
||||
### JSON Fields
|
||||
|
||||
When `--logging-format=json` is configured, log entries automatically include structured fields relevant to the log type.
|
||||
|
||||
Authentication logs include:
|
||||
|
||||
| Field | Description |
|
||||
| ------------ | -------------------------------------------------------- |
|
||||
| `user` | The email or username of the auth request |
|
||||
| `status` | `AuthSuccess`, `AuthFailure`, or `AuthError` |
|
||||
| `client` | The client/remote IP address |
|
||||
| `request_id` | The request ID from `--request-id-header`, if available |
|
||||
| `host` | The Host header value |
|
||||
| `method` | The HTTP request method |
|
||||
| `protocol` | The request protocol |
|
||||
| `user_agent` | The client User-Agent header |
|
||||
|
||||
Request logs include:
|
||||
|
||||
| Field | Description |
|
||||
| --------------- | ------------------------------------------------------- |
|
||||
| `user` | The email or username |
|
||||
| `upstream` | The upstream backend that handled the request |
|
||||
| `client` | The client/remote IP address |
|
||||
| `request_id` | The request ID from `--request-id-header`, if available |
|
||||
| `host` | The Host header value |
|
||||
| `method` | The HTTP request method |
|
||||
| `uri` | The request URI path |
|
||||
| `protocol` | The request protocol |
|
||||
| `user_agent` | The client User-Agent header |
|
||||
| `status_code` | The HTTP response status code |
|
||||
| `response_size` | The response size in bytes |
|
||||
| `duration_s` | The request duration in seconds |
|
||||
|
||||
### Log Routing
|
||||
|
||||
By default, standard log messages at `INFO` and below are written to stdout, while `WARN` and `ERROR` messages are written to stderr. Use `--errors-to-info-log` to redirect error output to stdout.
|
||||
|
||||
When `--logging-filename` is configured, logs are written to the specified file with automatic rotation support via `--logging-max-size`, `--logging-max-age`, `--logging-max-backups`, and `--logging-compress`.
|
||||
|
||||
### Filtering
|
||||
|
||||
Logging of requests to the `/ping` and `/ready` endpoints can be disabled with `--silence-ping-logging` to reduce log volume. Additional paths can be excluded with `--exclude-logging-path`.
|
||||
|
|
|
|||
19
main.go
19
main.go
|
|
@ -14,7 +14,6 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
logger.SetFlags(logger.Lshortfile)
|
||||
|
||||
configFlagSet := pflag.NewFlagSet("oauth2-proxy", pflag.ContinueOnError)
|
||||
|
||||
|
|
@ -35,21 +34,21 @@ func main() {
|
|||
}
|
||||
|
||||
if *convertConfig && *alphaConfig != "" {
|
||||
logger.Fatal("cannot use alpha-config and convert-config-to-alpha together")
|
||||
logger.FatalMsg("cannot use alpha-config and convert-config-to-alpha together")
|
||||
}
|
||||
|
||||
if *configTest && *convertConfig {
|
||||
logger.Fatal("cannot use config-test and convert-config-to-alpha together")
|
||||
logger.FatalMsg("cannot use config-test and convert-config-to-alpha together")
|
||||
}
|
||||
|
||||
opts, err := loadConfiguration(*config, *alphaConfig, configFlagSet, os.Args[1:])
|
||||
if err != nil {
|
||||
logger.Fatalf("ERROR: %v", err)
|
||||
logger.FatalMsg("failed to load configuration", "error", err)
|
||||
}
|
||||
|
||||
if *configTest {
|
||||
if err = validation.Validate(opts); err != nil {
|
||||
logger.Errorf("%s", err)
|
||||
logger.ErrMsgf("%s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("configuration is valid")
|
||||
|
|
@ -58,23 +57,23 @@ func main() {
|
|||
|
||||
if *convertConfig {
|
||||
if err := printConvertedConfig(opts); err != nil {
|
||||
logger.Fatalf("ERROR: could not convert config: %v", err)
|
||||
logger.FatalMsg("could not convert config", "error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err = validation.Validate(opts); err != nil {
|
||||
logger.Fatalf("%s", err)
|
||||
logger.FatalMsg("invalid configuration", "error", err)
|
||||
}
|
||||
|
||||
validator := NewValidator(opts.EmailDomains, opts.AuthenticatedEmailsFile)
|
||||
oauthproxy, err := NewOAuthProxy(opts, validator)
|
||||
if err != nil {
|
||||
logger.Fatalf("ERROR: Failed to initialise OAuth2 Proxy: %v", err)
|
||||
logger.FatalMsg("failed to initialise OAuth2 Proxy", "error", err)
|
||||
}
|
||||
|
||||
if err := oauthproxy.Start(); err != nil {
|
||||
logger.Fatalf("ERROR: Failed to start OAuth2 Proxy: %v", err)
|
||||
logger.FatalMsg("failed to start OAuth2 Proxy", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +87,7 @@ func loadConfiguration(config, yamlConfig string, extraFlags *pflag.FlagSet, arg
|
|||
}
|
||||
|
||||
if yamlConfig != "" {
|
||||
logger.Printf("WARNING: You are using alpha configuration. The structure in this configuration file may change without notice. You MUST remove conflicting options from your existing configuration.")
|
||||
logger.Warn("alpha configuration in use: the structure in this configuration file may change without notice, remove conflicting options from your existing configuration")
|
||||
opts, err = loadYamlOptions(yamlConfig, config, extraFlags, args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load yaml options: %w", err)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -9,8 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestMainSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Main Suite")
|
||||
|
|
|
|||
117
oauthproxy.go
117
oauthproxy.go
|
|
@ -129,7 +129,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||
|
||||
var basicAuthValidator basic.Validator
|
||||
if opts.HtpasswdFile != "" {
|
||||
logger.Printf("using htpasswd file: %s", opts.HtpasswdFile)
|
||||
logger.Info("using htpasswd file", "path", opts.HtpasswdFile)
|
||||
var err error
|
||||
basicAuthValidator, err = basic.NewHTPasswdValidator(opts.HtpasswdFile)
|
||||
if err != nil {
|
||||
|
|
@ -163,12 +163,12 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||
}
|
||||
|
||||
if opts.SkipJwtBearerTokens {
|
||||
logger.Printf("Skipping JWT tokens from configured OIDC issuer: %q", opts.Providers[0].OIDCConfig.IssuerURL)
|
||||
logger.Info("skipping JWT tokens from configured OIDC issuer", "issuer", opts.Providers[0].OIDCConfig.IssuerURL)
|
||||
for _, issuer := range opts.ExtraJwtIssuers {
|
||||
logger.Printf("Skipping JWT tokens from extra JWT issuer: %q", issuer)
|
||||
logger.Info("skipping JWT tokens from extra JWT issuer", "issuer", issuer)
|
||||
}
|
||||
if !opts.BearerTokenLoginFallback {
|
||||
logger.Println("Denying requests with invalid JWT tokens")
|
||||
logger.Info("denying requests with invalid JWT tokens")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -177,13 +177,22 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||
redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
|
||||
}
|
||||
|
||||
logger.Printf("OAuthProxy configured for %s Client ID: %s", provider.Data().ProviderName, opts.Providers[0].ClientID)
|
||||
logger.Info("OAuthProxy configured", "provider", provider.Data().ProviderName, "client_id", opts.Providers[0].ClientID)
|
||||
refresh := "disabled"
|
||||
if opts.Cookie.Refresh != time.Duration(0) {
|
||||
refresh = fmt.Sprintf("after %s", opts.Cookie.Refresh)
|
||||
}
|
||||
|
||||
logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domains:%s path:%s samesite:%s refresh:%s", opts.Cookie.Name, opts.Cookie.Secure, opts.Cookie.HTTPOnly, opts.Cookie.Expire, strings.Join(opts.Cookie.Domains, ","), opts.Cookie.Path, opts.Cookie.SameSite, refresh)
|
||||
logger.Info("cookie settings",
|
||||
"name", opts.Cookie.Name,
|
||||
"secure", opts.Cookie.Secure,
|
||||
"httponly", opts.Cookie.HTTPOnly,
|
||||
"expiry", opts.Cookie.Expire,
|
||||
"domains", strings.Join(opts.Cookie.Domains, ","),
|
||||
"path", opts.Cookie.Path,
|
||||
"samesite", opts.Cookie.SameSite,
|
||||
"refresh", refresh,
|
||||
)
|
||||
|
||||
trustedIPs, err := ip.ParseNetSet(opts.TrustedIPs)
|
||||
if err != nil {
|
||||
|
|
@ -372,7 +381,7 @@ func buildPreAuthChain(opts *options.Options, sessionStore sessionsapi.SessionSt
|
|||
healthCheckPaths := []string{opts.PingPath}
|
||||
healthCheckUserAgents := []string{opts.PingUserAgent}
|
||||
if opts.GCPHealthChecks {
|
||||
logger.Printf("WARNING: GCP HealthChecks are now deprecated: Reconfigure apps to use the ping path for liveness and readiness checks, set the ping user agent to \"GoogleHC/1.0\" to preserve existing behaviour")
|
||||
logger.Warn("GCP HealthChecks are now deprecated: reconfigure apps to use the ping path for liveness and readiness checks, set the ping user agent to GoogleHC/1.0 to preserve existing behaviour")
|
||||
healthCheckPaths = append(healthCheckPaths, "/liveness_check", "/readiness_check")
|
||||
healthCheckUserAgents = append(healthCheckUserAgents, "GoogleHC/1.0")
|
||||
}
|
||||
|
|
@ -401,7 +410,7 @@ func buildPreAuthChain(opts *options.Options, sessionStore sessionsapi.SessionSt
|
|||
func buildTrustedProxyNetSet(opts *options.Options) (*ip.NetSet, error) {
|
||||
trustedProxyIPs := opts.TrustedProxyIPs
|
||||
if opts.ReverseProxy && len(trustedProxyIPs) == 0 {
|
||||
logger.Print("WARNING: --reverse-proxy is enabled but no --trusted-proxy-ip CIDRs were configured. All connecting IPs are trusted to supply X-Forwarded-* headers by default (0.0.0.0/0, ::/0). This preserves backwards compatibility but is a potential security risk; configure --trusted-proxy-ip to match your reverse proxy addresses.")
|
||||
logger.Warn("--reverse-proxy is enabled but no --trusted-proxy-ip CIDRs were configured. All connecting IPs are trusted to supply X-Forwarded-* headers by default (0.0.0.0/0, ::/0). This preserves backwards compatibility but is a potential security risk; configure --trusted-proxy-ip to match your reverse proxy addresses.")
|
||||
trustedProxyIPs = defaultTrustedProxyIPs
|
||||
}
|
||||
|
||||
|
|
@ -489,7 +498,7 @@ func buildRoutesAllowlist(opts *options.Options) ([]allowedRoute, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Printf("Skipping auth - Method: ALL | Path: %s", path)
|
||||
logger.Info("skipping auth", "method", "ALL", "path", path)
|
||||
routes = append(routes, allowedRoute{
|
||||
method: "",
|
||||
pathRegex: compiledRegex,
|
||||
|
|
@ -516,7 +525,7 @@ func buildRoutesAllowlist(opts *options.Options) ([]allowedRoute, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Printf("Skipping auth - Method: %s | Path: %s", method, path)
|
||||
logger.Info("skipping auth", "method", method, "path", path)
|
||||
routes = append(routes, allowedRoute{
|
||||
method: method,
|
||||
negate: negate,
|
||||
|
|
@ -536,7 +545,7 @@ func buildAPIRoutes(opts *options.Options) ([]apiRoute, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Printf("API route - Path: %s", path)
|
||||
logger.Info("API route", "path", path)
|
||||
routes = append(routes, apiRoute{
|
||||
pathRegex: compiledRegex,
|
||||
})
|
||||
|
|
@ -569,7 +578,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, req *http.Request, code int, appError string, messages ...interface{}) {
|
||||
redirectURL, err := p.appDirector.GetRedirect(req)
|
||||
if err != nil {
|
||||
logger.Errorf("Error obtaining redirect: %v", err)
|
||||
logger.ErrMsgf("error obtaining redirect: %v", err)
|
||||
}
|
||||
if redirectURL == p.SignInPath || redirectURL == "" {
|
||||
redirectURL = "/"
|
||||
|
|
@ -632,7 +641,7 @@ func (p *OAuthProxy) isTrustedIP(req *http.Request) bool {
|
|||
|
||||
remoteAddr, err := ip.GetClientIP(p.realClientIPParser, req)
|
||||
if err != nil {
|
||||
logger.Errorf("Error obtaining real IP for trusted IP list: %v", err)
|
||||
logger.ErrMsgf("error obtaining real IP for trusted IP list: %v", err)
|
||||
// Possibly spoofed X-Real-IP header
|
||||
return false
|
||||
}
|
||||
|
|
@ -649,13 +658,13 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
|
|||
prepareNoCache(rw)
|
||||
|
||||
if err := p.ClearSessionCookie(rw, req); err != nil {
|
||||
logger.Printf("Error clearing session cookie: %v", err)
|
||||
logger.ErrMsgf("error clearing session cookie: %v", err)
|
||||
}
|
||||
rw.WriteHeader(code)
|
||||
|
||||
redirectURL, err := p.appDirector.GetRedirect(req)
|
||||
if err != nil {
|
||||
logger.Errorf("Error obtaining redirect: %v", err)
|
||||
logger.ErrMsgf("error obtaining redirect: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
@ -679,10 +688,10 @@ func (p *OAuthProxy) ManualSignIn(req *http.Request) (string, bool, int) {
|
|||
}
|
||||
// check auth
|
||||
if p.basicAuthValidator.Validate(user, passwd) {
|
||||
logger.PrintAuthf(user, req, logger.AuthSuccess, "Authenticated via HtpasswdFile")
|
||||
logger.LogAuth(user, req, logger.AuthSuccess, "authenticated via HtpasswdFile")
|
||||
return user, true, http.StatusOK
|
||||
}
|
||||
logger.PrintAuthf(user, req, logger.AuthFailure, "Invalid authentication via HtpasswdFile")
|
||||
logger.LogAuth(user, req, logger.AuthFailure, "invalid authentication via HtpasswdFile")
|
||||
return "", false, http.StatusUnauthorized
|
||||
}
|
||||
|
||||
|
|
@ -690,7 +699,7 @@ func (p *OAuthProxy) ManualSignIn(req *http.Request) (string, bool, int) {
|
|||
func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
|
||||
redirect, err := p.appDirector.GetRedirect(req)
|
||||
if err != nil {
|
||||
logger.Errorf("Error obtaining redirect: %v", err)
|
||||
logger.ErrMsgf("error obtaining redirect: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
@ -700,7 +709,7 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
|
|||
session := &sessionsapi.SessionState{User: user, Groups: p.basicAuthGroups}
|
||||
err = p.SaveSession(rw, req, session)
|
||||
if err != nil {
|
||||
logger.Printf("Error saving session: %v", err)
|
||||
logger.ErrMsgf("error saving session: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
@ -727,7 +736,7 @@ func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
|
|||
rw.WriteHeader(http.StatusOK)
|
||||
if session == nil {
|
||||
if _, err := rw.Write([]byte("{}")); err != nil {
|
||||
logger.Printf("Error encoding empty user info: %v", err)
|
||||
logger.ErrMsgf("error encoding empty user info: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return
|
||||
|
|
@ -748,7 +757,7 @@ func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
if err := json.NewEncoder(rw).Encode(userInfo); err != nil {
|
||||
logger.Printf("Error encoding user info: %v", err)
|
||||
logger.ErrMsgf("error encoding user info: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
}
|
||||
|
|
@ -757,7 +766,7 @@ func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
|
|||
func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
|
||||
redirect, err := p.appDirector.GetRedirect(req)
|
||||
if err != nil {
|
||||
logger.Errorf("Error obtaining redirect: %v", err)
|
||||
logger.ErrMsgf("error obtaining redirect: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
@ -765,7 +774,7 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
|
|||
if strings.Contains(redirect, idTokenPlaceholder) {
|
||||
session, err := p.getAuthenticatedSession(rw, req)
|
||||
if err != nil {
|
||||
logger.Errorf("error getting authenticated session during SignOut, won't replace id_token placeholder in redirect URL: %v", err)
|
||||
logger.ErrMsgf("error getting authenticated session during SignOut, won't replace id_token placeholder in redirect URL: %v", err)
|
||||
} else {
|
||||
redirect = strings.ReplaceAll(redirect, idTokenPlaceholder, session.IDToken)
|
||||
}
|
||||
|
|
@ -777,7 +786,7 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
|
|||
|
||||
err = p.ClearSessionCookie(rw, req)
|
||||
if err != nil {
|
||||
logger.Errorf("Error clearing session cookie: %v", err)
|
||||
logger.ErrMsgf("error clearing session cookie: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
@ -788,7 +797,7 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
|
|||
func (p *OAuthProxy) backendLogout(rw http.ResponseWriter, req *http.Request) {
|
||||
session, err := p.getAuthenticatedSession(rw, req)
|
||||
if err != nil {
|
||||
logger.Errorf("error getting authenticated session during backend logout: %v", err)
|
||||
logger.ErrMsgf("error getting authenticated session during backend logout: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -806,13 +815,13 @@ func (p *OAuthProxy) backendLogout(rw http.ResponseWriter, req *http.Request) {
|
|||
// base is not end-user provided but comes from configuration somewhat secure
|
||||
resp, err := http.Get(backendLogoutURL) // #nosec G107
|
||||
if err != nil {
|
||||
logger.Errorf("error while calling backend logout: %v", err)
|
||||
logger.ErrMsgf("error while calling backend logout: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
|
||||
logger.Errorf("error while calling backend logout url, returned error code %v", resp.StatusCode)
|
||||
logger.ErrMsgf("error while calling backend logout url, returned error code %v", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -834,14 +843,14 @@ func (p *OAuthProxy) doOAuthStart(rw http.ResponseWriter, req *http.Request, ove
|
|||
codeChallengeMethod = p.provider.Data().CodeChallengeMethod
|
||||
codeVerifier, err = encryption.GenerateCodeVerifierString(96)
|
||||
if err != nil {
|
||||
logger.Errorf("Unable to build random ASCII string for code verifier: %v", err)
|
||||
logger.ErrMsgf("unable to build random ASCII string for code verifier: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
codeChallenge, err = encryption.GenerateCodeChallenge(p.provider.Data().CodeChallengeMethod, codeVerifier)
|
||||
if err != nil {
|
||||
logger.Errorf("Error creating code challenge: %v", err)
|
||||
logger.ErrMsgf("error creating code challenge: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
@ -852,14 +861,14 @@ func (p *OAuthProxy) doOAuthStart(rw http.ResponseWriter, req *http.Request, ove
|
|||
|
||||
csrf, err := cookies.NewCSRF(p.CookieOptions, codeVerifier)
|
||||
if err != nil {
|
||||
logger.Errorf("Error creating CSRF nonce: %v", err)
|
||||
logger.ErrMsgf("error creating CSRF nonce: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
appRedirect, err := p.appDirector.GetRedirect(req)
|
||||
if err != nil {
|
||||
logger.Errorf("Error obtaining application redirect: %v", err)
|
||||
logger.ErrMsgf("error obtaining application redirect: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
@ -873,7 +882,7 @@ func (p *OAuthProxy) doOAuthStart(rw http.ResponseWriter, req *http.Request, ove
|
|||
)
|
||||
cookies.ClearExtraCsrfCookies(p.CookieOptions, rw, req)
|
||||
if _, err := csrf.SetCookie(rw, req); err != nil {
|
||||
logger.Errorf("Error setting CSRF cookie: %v", err)
|
||||
logger.ErrMsgf("error setting CSRF cookie: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
@ -890,13 +899,13 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||
// unlikely to be hit in practice.
|
||||
err := req.ParseForm()
|
||||
if err != nil {
|
||||
logger.Errorf("Error while parsing OAuth2 callback: %v", err)
|
||||
logger.ErrMsgf("error while parsing OAuth2 callback: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
errorString := req.Form.Get("error")
|
||||
if errorString != "" {
|
||||
logger.Errorf("Error while parsing OAuth2 callback: %s", errorString)
|
||||
logger.ErrMsg("error in OAuth2 callback from upstream identity provider", "error", errorString)
|
||||
message := fmt.Sprintf("Login Failed: The upstream identity provider returned an error: %s", errorString)
|
||||
// Set the debug message and override the non debug message to be the same for this case
|
||||
p.ErrorPage(rw, req, http.StatusForbidden, message, message)
|
||||
|
|
@ -905,7 +914,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||
|
||||
nonce, appRedirect, err := decodeState(req.Form.Get("state"), p.encodeState)
|
||||
if err != nil {
|
||||
logger.Errorf("Error while parsing OAuth2 state: %v", err)
|
||||
logger.ErrMsgf("error while parsing OAuth2 state: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
@ -918,21 +927,21 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||
// There are a lot of issues opened complaining about missing CSRF cookies.
|
||||
// Try to log the INs and OUTs of OAuthProxy, to be easier to analyse these issues.
|
||||
LoggingCSRFCookiesInOAuthCallback(req, cookieName)
|
||||
logger.Println(req, logger.AuthFailure, "Invalid authentication via OAuth2: unable to obtain CSRF cookie: %s (state=%s)", err, nonce)
|
||||
logger.Warn("invalid authentication via OAuth2: unable to obtain CSRF cookie", "error", err, "state", nonce)
|
||||
p.ErrorPage(rw, req, http.StatusForbidden, err.Error(), "Login Failed: Unable to find a valid CSRF token. Please try again.")
|
||||
return
|
||||
}
|
||||
|
||||
session, err := p.redeemCode(req, csrf.GetCodeVerifier())
|
||||
if err != nil {
|
||||
logger.Errorf("Error redeeming code during OAuth2 callback: %v", err)
|
||||
logger.ErrMsgf("error redeeming code during OAuth2 callback: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = p.enrichSessionState(req.Context(), session)
|
||||
if err != nil {
|
||||
logger.Errorf("Error creating session during OAuth2 callback: %v", err)
|
||||
logger.ErrMsgf("error creating session during OAuth2 callback: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
@ -940,14 +949,14 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||
csrf.ClearCookie(rw, req)
|
||||
|
||||
if !csrf.CheckOAuthState(nonce) {
|
||||
logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: CSRF token mismatch, potential attack")
|
||||
logger.LogAuth(session.Email, req, logger.AuthFailure, "invalid authentication via OAuth2: CSRF token mismatch, potential attack")
|
||||
p.ErrorPage(rw, req, http.StatusForbidden, "CSRF token mismatch, potential attack", "Login Failed: Unable to find a valid CSRF token. Please try again.")
|
||||
return
|
||||
}
|
||||
|
||||
csrf.SetSessionNonce(session)
|
||||
if !p.provider.ValidateSession(req.Context(), session) {
|
||||
logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Session validation failed: %s", session)
|
||||
logger.LogAuth(session.Email, req, logger.AuthFailure, fmt.Sprintf("session validation failed: %s", session))
|
||||
p.ErrorPage(rw, req, http.StatusForbidden, "Session validation failed")
|
||||
return
|
||||
}
|
||||
|
|
@ -959,19 +968,19 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||
// set cookie, or deny
|
||||
authorized, err := p.provider.Authorize(req.Context(), session)
|
||||
if err != nil {
|
||||
logger.Errorf("Error with authorization: %v", err)
|
||||
logger.ErrMsgf("error with authorization: %v", err)
|
||||
}
|
||||
if p.Validator(session.Email) && authorized {
|
||||
logger.PrintAuthf(session.Email, req, logger.AuthSuccess, "Authenticated via OAuth2: %s", session)
|
||||
logger.LogAuth(session.Email, req, logger.AuthSuccess, fmt.Sprintf("authenticated via OAuth2: %s", session))
|
||||
err := p.SaveSession(rw, req, session)
|
||||
if err != nil {
|
||||
logger.Errorf("Error saving session state for %s: %v", remoteAddr, err)
|
||||
logger.ErrMsg("error saving session state", "remote_addr", remoteAddr, "error", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
http.Redirect(rw, req, appRedirect, http.StatusFound)
|
||||
} else {
|
||||
logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unauthorized")
|
||||
logger.LogAuth(session.Email, req, logger.AuthFailure, "invalid authentication via OAuth2: unauthorized")
|
||||
p.ErrorPage(rw, req, http.StatusForbidden, "Invalid session: unauthorized")
|
||||
}
|
||||
}
|
||||
|
|
@ -1055,13 +1064,13 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
|||
case ErrNeedsLogin:
|
||||
// we need to send the user to a login screen
|
||||
if p.forceJSONErrors || isAjax(req) || p.isAPIPath(req) {
|
||||
logger.Printf("No valid authentication in request. Access Denied.")
|
||||
logger.Warn("no valid authentication in request, access denied")
|
||||
// no point redirecting an AJAX request
|
||||
p.errorJSON(rw, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Printf("No valid authentication in request. Initiating login.")
|
||||
logger.Info("no valid authentication in request, initiating login")
|
||||
if p.SkipProviderButton {
|
||||
// start OAuth flow, but only with the default login URL params - do not
|
||||
// consider this request's query params as potential overrides, since
|
||||
|
|
@ -1080,7 +1089,7 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
|||
|
||||
default:
|
||||
// unknown error
|
||||
logger.Errorf("Unexpected internal error: %v", err)
|
||||
logger.ErrMsgf("unexpected internal error: %v", err)
|
||||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
}
|
||||
|
|
@ -1154,7 +1163,7 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R
|
|||
invalidEmail := session.Email != "" && !p.Validator(session.Email)
|
||||
authorized, err := p.provider.Authorize(req.Context(), session)
|
||||
if err != nil {
|
||||
logger.Errorf("Error with authorization: %v", err)
|
||||
logger.ErrMsgf("error with authorization: %v", err)
|
||||
}
|
||||
|
||||
if invalidEmail || !authorized {
|
||||
|
|
@ -1163,11 +1172,11 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R
|
|||
cause = "invalid email"
|
||||
}
|
||||
|
||||
logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authorization via session (%s): removing session %s", cause, session)
|
||||
logger.LogAuth(session.Email, req, logger.AuthFailure, fmt.Sprintf("invalid authorization via session (%s): removing session %s", cause, session))
|
||||
// Invalid session, clear it
|
||||
err := p.ClearSessionCookie(rw, req)
|
||||
if err != nil {
|
||||
logger.Errorf("Error clearing session cookie: %v", err)
|
||||
logger.ErrMsgf("error clearing session cookie: %v", err)
|
||||
}
|
||||
return nil, ErrAccessDenied
|
||||
}
|
||||
|
|
@ -1349,21 +1358,21 @@ func (p *OAuthProxy) errorJSON(rw http.ResponseWriter, code int) {
|
|||
func LoggingCSRFCookiesInOAuthCallback(req *http.Request, cookieName string) {
|
||||
cookies := req.Cookies()
|
||||
if len(cookies) == 0 {
|
||||
logger.Println(req, logger.AuthFailure, "No cookies were found in OAuth callback.")
|
||||
logger.Warn("no cookies were found in OAuth callback")
|
||||
return
|
||||
}
|
||||
|
||||
for _, c := range cookies {
|
||||
if cookieName == c.Name {
|
||||
logger.Println(req, logger.AuthFailure, "CSRF cookie %s was found in OAuth callback.", c.Name)
|
||||
logger.Warn("CSRF cookie found in OAuth callback", "cookie_name", c.Name)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasSuffix(c.Name, "_csrf") {
|
||||
logger.Println(req, logger.AuthFailure, "CSRF cookie %s was found in OAuth callback, but it is not the expected one (%s).", c.Name, cookieName)
|
||||
logger.Warn("unexpected CSRF cookie found in OAuth callback", "cookie_name", c.Name, "expected", cookieName)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
logger.Println(req, logger.AuthFailure, "Cookies were found in OAuth callback, but none was a CSRF cookie.")
|
||||
logger.Warn("cookies were found in OAuth callback, but none was a CSRF cookie")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,10 +40,6 @@ const (
|
|||
clientSecret = "gv3498mfc9t23y23974dm2394dm9"
|
||||
)
|
||||
|
||||
func init() {
|
||||
logger.SetFlags(logger.Lshortfile)
|
||||
}
|
||||
|
||||
func TestRobotsTxt(t *testing.T) {
|
||||
opts := baseTestOptions()
|
||||
err := validation.Validate(opts)
|
||||
|
|
@ -178,7 +174,7 @@ func Test_enrichSession(t *testing.T) {
|
|||
|
||||
func TestBasicAuthPassword(t *testing.T) {
|
||||
providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Printf("%#v", r)
|
||||
logger.Infof("%#v", r)
|
||||
var payload string
|
||||
switch r.URL.Path {
|
||||
case "/oauth/token":
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package middleware_test
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -12,8 +13,7 @@ import (
|
|||
// to prevent circular imports with the `logger` package which uses
|
||||
// this functionality
|
||||
func TestMiddlewareSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Middleware API")
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ func (c *Cookie) GetSecret() (secret string, err error) {
|
|||
|
||||
fileSecret, err := os.ReadFile(c.SecretFile)
|
||||
if err != nil {
|
||||
logger.Errorf("error reading cookie secret file %s: %s", c.SecretFile, err)
|
||||
logger.ErrMsgf("error reading cookie secret file %s: %s", c.SecretFile, err)
|
||||
return "", errors.New("could not read cookie secret file")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
|
|||
case "static":
|
||||
responseCode, err := strconv.Atoi(u.Host)
|
||||
if err != nil {
|
||||
logger.Errorf("unable to convert %q to int, use default \"200\"", u.Host)
|
||||
logger.ErrMsgf("unable to convert %q to int, use default \"200\"", u.Host)
|
||||
responseCode = 200
|
||||
}
|
||||
upstream.Static = ptr.To(true)
|
||||
|
|
@ -782,8 +782,8 @@ func (l *LegacyProvider) convert() (Providers, error) {
|
|||
case "google":
|
||||
if len(l.GoogleGroupsLegacy) != 0 && !reflect.DeepEqual(l.GoogleGroupsLegacy, l.GoogleGroups) {
|
||||
// Log the deprecation notice
|
||||
logger.Error(
|
||||
"WARNING: The 'OAUTH2_PROXY_GOOGLE_GROUP' environment variable is deprecated and will likely be removed in the next major release. Use 'OAUTH2_PROXY_GOOGLE_GROUPS' instead.",
|
||||
logger.Warn(
|
||||
"the 'OAUTH2_PROXY_GOOGLE_GROUP' environment variable is deprecated and will likely be removed in the next major release. Use 'OAUTH2_PROXY_GOOGLE_GROUPS' instead.",
|
||||
)
|
||||
l.GoogleGroups = l.GoogleGroupsLegacy
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import (
|
|||
|
||||
// Logging contains all options required for configuring the logging
|
||||
type Logging struct {
|
||||
Level string `flag:"logging-level" cfg:"logging_level"`
|
||||
Format string `flag:"logging-format" cfg:"logging_format"`
|
||||
AuthEnabled bool `flag:"auth-logging" cfg:"auth_logging"`
|
||||
AuthFormat string `flag:"auth-logging-format" cfg:"auth_logging_format"`
|
||||
RequestEnabled bool `flag:"request-logging" cfg:"request_logging"`
|
||||
|
|
@ -33,13 +35,15 @@ type LogFileOptions struct {
|
|||
func loggingFlagSet() *pflag.FlagSet {
|
||||
flagSet := pflag.NewFlagSet("logging", pflag.ExitOnError)
|
||||
|
||||
flagSet.String("logging-level", "info", "Log level: debug, info, warn, error")
|
||||
flagSet.String("logging-format", "text", "Log format: text, json")
|
||||
flagSet.Bool("auth-logging", true, "Log authentication attempts")
|
||||
flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines")
|
||||
flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines when logging-format=text")
|
||||
flagSet.Bool("standard-logging", true, "Log standard runtime information")
|
||||
flagSet.String("standard-logging-format", logger.DefaultStandardLoggingFormat, "Template for standard log lines")
|
||||
flagSet.String("standard-logging-format", logger.DefaultStandardLoggingFormat, "Template for standard log lines when logging-format=text")
|
||||
flagSet.Bool("request-logging", true, "Log HTTP requests")
|
||||
flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines")
|
||||
flagSet.Bool("errors-to-info-log", false, "Log errors to the standard logging channel instead of stderr")
|
||||
flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines when logging-format=text")
|
||||
flagSet.Bool("errors-to-info-log", false, "Log errors to the standard logging channel instead of stderr")
|
||||
|
||||
flagSet.StringSlice("exclude-logging-path", []string{}, "Exclude logging requests to paths (eg: '/path1,/path2,/path3')")
|
||||
flagSet.Bool("logging-local-time", true, "If the time in log files and backup filenames are local or UTC time")
|
||||
|
|
@ -58,6 +62,8 @@ func loggingFlagSet() *pflag.FlagSet {
|
|||
// loggingDefaults creates a Logging structure, populating each field with its default value
|
||||
func loggingDefaults() Logging {
|
||||
return Logging{
|
||||
Level: "info",
|
||||
Format: "text",
|
||||
ExcludePaths: nil,
|
||||
LocalTime: true,
|
||||
SilencePing: false,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package options
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -9,8 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestOptionsSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Options Suite")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -9,8 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestUtilSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Options Util Suite")
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ func (e *errorPageWriter) WriteErrorPage(rw http.ResponseWriter, opts ErrorPageO
|
|||
}
|
||||
|
||||
if err := e.template.Execute(rw, data); err != nil {
|
||||
logger.Printf("Error rendering error template: %v", err)
|
||||
logger.ErrMsgf("error rendering error template: %v", err)
|
||||
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
|
@ -88,7 +88,7 @@ func (e *errorPageWriter) WriteErrorPage(rw http.ResponseWriter, opts ErrorPageO
|
|||
// when there are issues with upstream servers.
|
||||
// It is expected to always render a bad gateway error.
|
||||
func (e *errorPageWriter) ProxyErrorHandler(rw http.ResponseWriter, req *http.Request, proxyErr error) {
|
||||
logger.Errorf("Error proxying to upstream server: %v", proxyErr)
|
||||
logger.ErrMsgf("error proxying to upstream server: %v", proxyErr)
|
||||
scope := middlewareapi.GetRequestScope(req)
|
||||
e.WriteErrorPage(rw, ErrorPageOpts{
|
||||
Status: http.StatusBadGateway,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package pagewriter
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -11,8 +12,7 @@ import (
|
|||
const testRequestID = "11111111-2222-4333-8444-555555555555"
|
||||
|
||||
func TestOptionsSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "App Suite")
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ func (s *signInPageWriter) WriteSignInPage(rw http.ResponseWriter, req *http.Req
|
|||
|
||||
err := s.template.Execute(rw, t)
|
||||
if err != nil {
|
||||
logger.Printf("Error rendering sign-in template: %v", err)
|
||||
logger.ErrMsgf("error rendering sign-in template: %v", err)
|
||||
scope := middlewareapi.GetRequestScope(req)
|
||||
s.errorPageWriter.WriteErrorPage(rw, ErrorPageOpts{
|
||||
Status: http.StatusInternalServerError,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func (s *staticPageWriter) WriteRobotsTxt(rw http.ResponseWriter, req *http.Requ
|
|||
func (s *staticPageWriter) writePage(rw http.ResponseWriter, req *http.Request, pageName string) {
|
||||
_, err := rw.Write(s.pageGetter.getPage(pageName))
|
||||
if err != nil {
|
||||
logger.Printf("Error writing %q: %v", pageName, err)
|
||||
logger.ErrMsgf("error writing %q: %v", pageName, err)
|
||||
scope := middlewareapi.GetRequestScope(req)
|
||||
s.errorPageWriter.WriteErrorPage(rw, ErrorPageOpts{
|
||||
Status: http.StatusInternalServerError,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ func addTemplate(t *template.Template, customDir, fileName, defaultTemplate stri
|
|||
if err != nil {
|
||||
// This should not happen.
|
||||
// Default templates should be tested and so should never fail to parse.
|
||||
logger.Panic("Could not parse defaultTemplate: ", err)
|
||||
logger.PanicMsg("could not parse defaultTemplate", "error", err)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ func addTemplate(t *template.Template, customDir, fileName, defaultTemplate stri
|
|||
func isFile(fileName string) bool {
|
||||
info, err := os.Stat(fileName)
|
||||
if err != nil {
|
||||
logger.Errorf("Could not load file %s: %v, will use default template", fileName, err)
|
||||
logger.ErrMsgf("could not load file %s: %v, will use default template", fileName, err)
|
||||
return false
|
||||
}
|
||||
return info.Mode().IsRegular()
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ func (a *appDirector) validateRedirect(redirect string, errorFormat string) stri
|
|||
return redirect
|
||||
}
|
||||
if redirect != "" {
|
||||
logger.Errorf(errorFormat, redirect)
|
||||
logger.ErrMsgf(errorFormat, redirect)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package redirect
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -9,8 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestOptionsSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Redirect Suite")
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func (v *validator) IsValidRedirect(redirect string) bool {
|
|||
case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"):
|
||||
redirectURL, err := url.Parse(redirect)
|
||||
if err != nil {
|
||||
logger.Printf("Rejecting invalid redirect %q: scheme unsupported or missing", redirect)
|
||||
logger.Infof("rejecting invalid redirect %q: scheme unsupported or missing", redirect)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -57,10 +57,10 @@ func (v *validator) IsValidRedirect(redirect string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
logger.Printf("Rejecting invalid redirect %q: domain / port not in whitelist", redirect)
|
||||
logger.Infof("rejecting invalid redirect %q: domain / port not in whitelist", redirect)
|
||||
return false
|
||||
default:
|
||||
logger.Printf("Rejecting invalid redirect %q: not an absolute or relative URL", redirect)
|
||||
logger.Infof("rejecting invalid redirect %q: not an absolute or relative URL", redirect)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package basic
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -9,8 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestBasicSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Basic")
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func NewHTPasswdValidator(path string) (Validator, error) {
|
|||
if err := watcher.WatchFileForUpdates(path, nil, func() {
|
||||
err := h.loadHTPasswdFile(path)
|
||||
if err != nil {
|
||||
logger.Errorf("%v: no changes were made to the current htpasswd map", err)
|
||||
logger.ErrMsgf("%v: no changes were made to the current htpasswd map", err)
|
||||
}
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("could not watch htpasswd file: %v", err)
|
||||
|
|
@ -61,7 +61,7 @@ func (h *htpasswdMap) loadHTPasswdFile(filename string) error {
|
|||
defer func(c io.Closer) {
|
||||
cerr := c.Close()
|
||||
if cerr != nil {
|
||||
logger.Fatalf("error closing the htpasswd file: %v", cerr)
|
||||
logger.FatalMsgf("error closing the htpasswd file: %v", cerr)
|
||||
}
|
||||
}(r)
|
||||
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ func MakeCookieFromOptions(req *http.Request, opts *CookieOptions) *http.Cookie
|
|||
domain := GetCookieDomain(req, opts.Domains)
|
||||
// If nothing matches, create the cookie with the shortest domain
|
||||
if domain == "" && len(opts.Domains) > 0 {
|
||||
logger.Errorf("Warning: request host %q did not match any of the specific cookie domains of %q",
|
||||
requestutil.GetRequestHost(req),
|
||||
strings.Join(opts.Domains, ","),
|
||||
logger.Warn("request host did not match any specific cookie domains",
|
||||
"host", requestutil.GetRequestHost(req),
|
||||
"domains", strings.Join(opts.Domains, ","),
|
||||
)
|
||||
domain = opts.Domains[len(opts.Domains)-1]
|
||||
}
|
||||
|
|
@ -96,6 +96,6 @@ func warnInvalidDomain(c *http.Cookie, req *http.Request) {
|
|||
host = h
|
||||
}
|
||||
if !strings.HasSuffix(host, c.Domain) {
|
||||
logger.Errorf("Warning: request host is %q but using configured cookie domain of %q", host, c.Domain)
|
||||
logger.Warn("request host does not match configured cookie domain", "host", host, "domain", c.Domain)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package cookies
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -25,7 +26,7 @@ const (
|
|||
)
|
||||
|
||||
func TestProviderSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Cookies")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package header
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
|
@ -15,8 +16,7 @@ var (
|
|||
)
|
||||
|
||||
func TestHeaderSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Header")
|
||||
|
|
|
|||
1130
pkg/logger/logger.go
1130
pkg/logger/logger.go
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,594 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
)
|
||||
|
||||
// resetLogger resets the logger to defaults for test isolation.
|
||||
func resetLogger(t *testing.T) {
|
||||
t.Helper()
|
||||
logLevel.Set(slog.LevelInfo)
|
||||
logFormat = "text"
|
||||
standardEnabled = true
|
||||
localTime = true
|
||||
flags = LstdFlags
|
||||
stdLogTemplate = template.Must(template.New("std-log").Parse(DefaultStandardLoggingFormat))
|
||||
authTemplate = template.Must(template.New("auth-log").Parse(DefaultAuthLoggingFormat))
|
||||
reqTemplate = template.Must(template.New("req-log").Parse(DefaultRequestLoggingFormat))
|
||||
authEnabled = true
|
||||
reqEnabled = true
|
||||
excludePaths = nil
|
||||
getClientFunc = func(r *http.Request) string { return r.RemoteAddr }
|
||||
errToInfo = false
|
||||
}
|
||||
|
||||
// parseJSON parses a JSON log line into a map.
|
||||
func parseJSON(t *testing.T, data []byte) map[string]any {
|
||||
t.Helper()
|
||||
// Take only the first line if there are multiple
|
||||
line := strings.TrimSpace(strings.Split(string(data), "\n")[0])
|
||||
if line == "" {
|
||||
t.Fatal("empty log output")
|
||||
}
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal([]byte(line), &m); err != nil {
|
||||
t.Fatalf("failed to parse JSON log: %v\nraw: %s", err, line)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func TestSetup_JSONFormat(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "json", buf, errBuf)
|
||||
|
||||
Info("hello", "key", "val")
|
||||
|
||||
m := parseJSON(t, buf.Bytes())
|
||||
if m["msg"] != "hello" {
|
||||
t.Errorf("expected msg=hello, got %v", m["msg"])
|
||||
}
|
||||
if m["key"] != "val" {
|
||||
t.Errorf("expected key=val, got %v", m["key"])
|
||||
}
|
||||
if m["level"] != "INFO" {
|
||||
t.Errorf("expected level=INFO, got %v", m["level"])
|
||||
}
|
||||
if _, ok := m["time"]; !ok {
|
||||
t.Error("expected time field in JSON output")
|
||||
}
|
||||
if _, ok := m["source"]; !ok {
|
||||
t.Error("expected source field in JSON output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetup_TextFormat(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "text", buf, errBuf)
|
||||
|
||||
Info("hello", "key", "val")
|
||||
|
||||
out := buf.String()
|
||||
if strings.Contains(out, "level=INFO") {
|
||||
t.Errorf("did not expect slog key=value text output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "hello key=val") {
|
||||
t.Errorf("expected message and attrs in template output, got: %s", out)
|
||||
}
|
||||
if !strings.HasSuffix(out, "\n") {
|
||||
t.Errorf("expected text output to end with newline, got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextFormat_StandardTemplate(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "text", buf, errBuf)
|
||||
SetStandardTemplate("{{.Message}}")
|
||||
|
||||
Info("hello", "key", "val")
|
||||
|
||||
if got, want := buf.String(), "hello key=val\n"; got != want {
|
||||
t.Errorf("expected custom standard template output %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextFormat_StandardTemplateFileUsesCaller(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "text", buf, errBuf)
|
||||
SetStandardTemplate("{{.File}}|{{.Message}}")
|
||||
|
||||
Info("hello")
|
||||
|
||||
out := buf.String()
|
||||
if !strings.Contains(out, "logger_test.go:") {
|
||||
t.Errorf("expected caller file in text output, got: %s", out)
|
||||
}
|
||||
if strings.Contains(out, "logger.go:") {
|
||||
t.Errorf("expected external caller file, got logger wrapper file: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetFlags_ControlsStandardTemplateFile(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "text", buf, errBuf)
|
||||
SetStandardTemplate("{{.File}}")
|
||||
SetFlags(0)
|
||||
|
||||
Info("hello")
|
||||
|
||||
out := buf.String()
|
||||
if !strings.Contains(out, "/pkg/logger/logger_test.go:") {
|
||||
t.Errorf("expected full caller file when Lshortfile is disabled, got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextFormat_StandardLoggingDisabled(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "text", buf, errBuf)
|
||||
SetStandardEnabled(false)
|
||||
|
||||
Info("hidden")
|
||||
Warn("also hidden")
|
||||
|
||||
if buf.Len() > 0 || errBuf.Len() > 0 {
|
||||
t.Errorf("expected standard logs to be disabled, stdout=%q stderr=%q", buf.String(), errBuf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLevelFiltering(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "json", buf, errBuf)
|
||||
|
||||
Debug("should not appear")
|
||||
if buf.Len() > 0 {
|
||||
t.Error("Debug message should be filtered at Info level")
|
||||
}
|
||||
|
||||
Info("should appear")
|
||||
if buf.Len() == 0 {
|
||||
t.Error("Info message should appear at Info level")
|
||||
}
|
||||
m := parseJSON(t, buf.Bytes())
|
||||
if m["msg"] != "should appear" {
|
||||
t.Errorf("expected msg='should appear', got %v", m["msg"])
|
||||
}
|
||||
|
||||
// Error should go to errBuf
|
||||
ErrMsg("error msg")
|
||||
if errBuf.Len() == 0 {
|
||||
t.Error("Error message should appear in errBuf")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetLevel_Runtime(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "json", buf, errBuf)
|
||||
|
||||
Debug("hidden")
|
||||
if buf.Len() > 0 {
|
||||
t.Error("Debug should be hidden at Info level")
|
||||
}
|
||||
|
||||
SetLevel(slog.LevelDebug)
|
||||
|
||||
Debug("visible")
|
||||
if buf.Len() == 0 {
|
||||
t.Error("Debug should be visible after SetLevel(Debug)")
|
||||
}
|
||||
m := parseJSON(t, buf.Bytes())
|
||||
if m["msg"] != "visible" {
|
||||
t.Errorf("expected msg='visible', got %v", m["msg"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLevelSplitHandler(t *testing.T) {
|
||||
resetLogger(t)
|
||||
stdBuf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelDebug, "json", stdBuf, errBuf)
|
||||
|
||||
Info("info msg")
|
||||
if stdBuf.Len() == 0 {
|
||||
t.Error("Info should go to stdout")
|
||||
}
|
||||
if errBuf.Len() > 0 {
|
||||
t.Error("Info should NOT go to stderr")
|
||||
}
|
||||
|
||||
stdBuf.Reset()
|
||||
|
||||
Warn("warn msg")
|
||||
if errBuf.Len() == 0 {
|
||||
t.Error("Warn should go to stderr")
|
||||
}
|
||||
if stdBuf.Len() > 0 {
|
||||
t.Error("Warn should NOT go to stdout")
|
||||
}
|
||||
|
||||
errBuf.Reset()
|
||||
|
||||
ErrMsg("error msg")
|
||||
if errBuf.Len() == 0 {
|
||||
t.Error("Error should go to stderr")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrToInfo(t *testing.T) {
|
||||
resetLogger(t)
|
||||
stdBuf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "json", stdBuf, errBuf)
|
||||
|
||||
SetErrToInfo(true)
|
||||
|
||||
ErrMsg("goes to stdout")
|
||||
if stdBuf.Len() == 0 {
|
||||
t.Error("Error should go to stdout when ErrToInfo is true")
|
||||
}
|
||||
if errBuf.Len() > 0 {
|
||||
t.Error("Error should NOT go to stderr when ErrToInfo is true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogAuth_Success(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelDebug, "json", buf, errBuf)
|
||||
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
req.RemoteAddr = "192.168.1.1:1234"
|
||||
scope := &middlewareapi.RequestScope{RequestID: "test-request-id"}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
LogAuth("user@test.com", req, AuthSuccess, "authenticated via OAuth2")
|
||||
|
||||
m := parseJSON(t, buf.Bytes())
|
||||
if m["level"] != "INFO" {
|
||||
t.Errorf("AuthSuccess should log at INFO, got %v", m["level"])
|
||||
}
|
||||
if m["user"] != "user@test.com" {
|
||||
t.Errorf("expected user=user@test.com, got %v", m["user"])
|
||||
}
|
||||
if m["status"] != "AuthSuccess" {
|
||||
t.Errorf("expected status=AuthSuccess, got %v", m["status"])
|
||||
}
|
||||
if m["request_id"] != "test-request-id" {
|
||||
t.Errorf("expected request_id=test-request-id, got %v", m["request_id"])
|
||||
}
|
||||
if m["msg"] != "authenticated via OAuth2" {
|
||||
t.Errorf("expected msg='authenticated via OAuth2', got %v", m["msg"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogAuth_Failure(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelDebug, "json", buf, errBuf)
|
||||
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
scope := &middlewareapi.RequestScope{RequestID: "req-id"}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
LogAuth("bad-user", req, AuthFailure, "invalid credentials")
|
||||
|
||||
// AuthFailure → Warn → goes to errBuf
|
||||
m := parseJSON(t, errBuf.Bytes())
|
||||
if m["level"] != "WARN" {
|
||||
t.Errorf("AuthFailure should log at WARN, got %v", m["level"])
|
||||
}
|
||||
if m["status"] != "AuthFailure" {
|
||||
t.Errorf("expected status=AuthFailure, got %v", m["status"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogAuth_Error(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelDebug, "json", buf, errBuf)
|
||||
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
scope := &middlewareapi.RequestScope{RequestID: "req-id"}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
LogAuth("user", req, AuthError, "internal error")
|
||||
|
||||
m := parseJSON(t, errBuf.Bytes())
|
||||
if m["level"] != "ERROR" {
|
||||
t.Errorf("AuthError should log at ERROR, got %v", m["level"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogAuth_Disabled(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelDebug, "json", buf, errBuf)
|
||||
SetAuthEnabled(false)
|
||||
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
scope := &middlewareapi.RequestScope{RequestID: "req-id"}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
LogAuth("user", req, AuthSuccess, "should not appear")
|
||||
|
||||
if buf.Len() > 0 || errBuf.Len() > 0 {
|
||||
t.Error("LogAuth should produce no output when auth logging is disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogAuth_TextTemplate(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelDebug, "text", buf, errBuf)
|
||||
SetAuthTemplate("{{.Client}}|{{.RequestID}}|{{.Username}}|{{.Status}}|{{.Message}}")
|
||||
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
req.RemoteAddr = "192.168.1.1:1234"
|
||||
scope := &middlewareapi.RequestScope{RequestID: "req-id"}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
LogAuth("user@test.com", req, AuthSuccess, "authenticated", "provider", "oidc")
|
||||
|
||||
if got, want := buf.String(), "192.168.1.1:1234|req-id|user@test.com|AuthSuccess|authenticated provider=oidc\n"; got != want {
|
||||
t.Errorf("expected custom auth template output %q, got %q", want, got)
|
||||
}
|
||||
if errBuf.Len() > 0 {
|
||||
t.Errorf("expected text auth log to use standard writer, got stderr %q", errBuf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogRequest(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelDebug, "json", buf, errBuf)
|
||||
|
||||
req := httptest.NewRequest("GET", "/foo/bar", nil)
|
||||
req.RemoteAddr = "127.0.0.1:5678"
|
||||
scope := &middlewareapi.RequestScope{RequestID: "req-123"}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
reqURL := *req.URL
|
||||
LogRequest("testuser", "backend", req, reqURL, time.Now(), 200, 1024)
|
||||
|
||||
// LogRequest uses time.Now() internally so we can't easily test duration_s exactly.
|
||||
// Just parse and check fields.
|
||||
m := parseJSON(t, buf.Bytes())
|
||||
if m["msg"] != "request" {
|
||||
t.Errorf("expected msg=request, got %v", m["msg"])
|
||||
}
|
||||
if m["level"] != "INFO" {
|
||||
t.Errorf("expected level=INFO, got %v", m["level"])
|
||||
}
|
||||
if m["user"] != "testuser" {
|
||||
t.Errorf("expected user=testuser, got %v", m["user"])
|
||||
}
|
||||
if m["upstream"] != "backend" {
|
||||
t.Errorf("expected upstream=backend, got %v", m["upstream"])
|
||||
}
|
||||
if m["method"] != "GET" {
|
||||
t.Errorf("expected method=GET, got %v", m["method"])
|
||||
}
|
||||
// status_code comes as float64 from JSON
|
||||
if sc, ok := m["status_code"].(float64); !ok || int(sc) != 200 {
|
||||
t.Errorf("expected status_code=200, got %v", m["status_code"])
|
||||
}
|
||||
if rs, ok := m["response_size"].(float64); !ok || int(rs) != 1024 {
|
||||
t.Errorf("expected response_size=1024, got %v", m["response_size"])
|
||||
}
|
||||
if _, ok := m["duration_s"]; !ok {
|
||||
t.Error("expected duration_s field")
|
||||
}
|
||||
if m["request_id"] != "req-123" {
|
||||
t.Errorf("expected request_id=req-123, got %v", m["request_id"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogRequest_ExcludePaths(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelDebug, "json", buf, errBuf)
|
||||
SetExcludePaths([]string{"/healthz", "/ping"})
|
||||
|
||||
req := httptest.NewRequest("GET", "/healthz", nil)
|
||||
scope := &middlewareapi.RequestScope{RequestID: "req-id"}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
reqURL := *req.URL
|
||||
LogRequest("user", "-", req, reqURL, time.Now(), 200, 0)
|
||||
|
||||
if buf.Len() > 0 {
|
||||
t.Error("LogRequest should not log excluded paths")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogRequest_Disabled(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelDebug, "json", buf, errBuf)
|
||||
SetReqEnabled(false)
|
||||
|
||||
req := httptest.NewRequest("GET", "/foo", nil)
|
||||
scope := &middlewareapi.RequestScope{RequestID: "req-id"}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
reqURL := *req.URL
|
||||
LogRequest("user", "-", req, reqURL, time.Now(), 200, 0)
|
||||
|
||||
if buf.Len() > 0 {
|
||||
t.Error("LogRequest should produce no output when request logging is disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogRequest_TextTemplate(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelDebug, "text", buf, errBuf)
|
||||
SetReqTemplate("{{.Username}}|{{.Upstream}}|{{.RequestMethod}}|{{.RequestURI}}|{{.StatusCode}}|{{.ResponseSize}}|{{.RequestID}}")
|
||||
|
||||
req := httptest.NewRequest("GET", "/foo/bar?x=1", nil)
|
||||
req.RemoteAddr = "127.0.0.1:5678"
|
||||
scope := &middlewareapi.RequestScope{RequestID: "req-123"}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
reqURL := *req.URL
|
||||
LogRequest("testuser", "backend", req, reqURL, time.Now(), 204, 42)
|
||||
|
||||
if got, want := buf.String(), "testuser|backend|GET|\"/foo/bar?x=1\"|204|42|req-123\n"; got != want {
|
||||
t.Errorf("expected custom request template output %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFatalMsg(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "json", buf, errBuf)
|
||||
|
||||
// Override exitFunc to capture the exit code
|
||||
var exitCode int
|
||||
exitFunc = func(code int) { exitCode = code }
|
||||
defer func() { exitFunc = nil }() // will panic on real exit if not restored
|
||||
|
||||
FatalMsg("fatal error")
|
||||
|
||||
if exitCode != 1 {
|
||||
t.Errorf("expected exit code 1, got %d", exitCode)
|
||||
}
|
||||
if errBuf.Len() == 0 {
|
||||
t.Error("Fatal should produce error-level output")
|
||||
}
|
||||
m := parseJSON(t, errBuf.Bytes())
|
||||
if m["level"] != "ERROR" {
|
||||
t.Errorf("expected level=ERROR, got %v", m["level"])
|
||||
}
|
||||
|
||||
// Restore
|
||||
exitFunc = func(code int) {}
|
||||
}
|
||||
|
||||
func TestInfof(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "json", buf, errBuf)
|
||||
|
||||
Infof("hello %s", "world")
|
||||
|
||||
m := parseJSON(t, buf.Bytes())
|
||||
if m["msg"] != "hello world" {
|
||||
t.Errorf("expected msg='hello world', got %v", m["msg"])
|
||||
}
|
||||
if m["level"] != "INFO" {
|
||||
t.Errorf("expected level=INFO, got %v", m["level"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrMsgf(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "json", buf, errBuf)
|
||||
|
||||
ErrMsgf("error: %s", "something")
|
||||
|
||||
m := parseJSON(t, errBuf.Bytes())
|
||||
if m["msg"] != "error: something" {
|
||||
t.Errorf("expected msg='error: something', got %v", m["msg"])
|
||||
}
|
||||
if m["level"] != "ERROR" {
|
||||
t.Errorf("expected level=ERROR, got %v", m["level"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogAuth(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelDebug, "json", buf, errBuf)
|
||||
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
scope := &middlewareapi.RequestScope{RequestID: "req-id"}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
LogAuth("user@test.com", req, AuthSuccess, "authenticated via OAuth2")
|
||||
|
||||
m := parseJSON(t, buf.Bytes())
|
||||
if m["msg"] != "authenticated via OAuth2" {
|
||||
t.Errorf("expected msg='authenticated via OAuth2', got %v", m["msg"])
|
||||
}
|
||||
if m["user"] != "user@test.com" {
|
||||
t.Errorf("expected user=user@test.com, got %v", m["user"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetOutput(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf1 := &bytes.Buffer{}
|
||||
errBuf := &bytes.Buffer{}
|
||||
Setup(slog.LevelInfo, "json", buf1, errBuf)
|
||||
|
||||
Info("before")
|
||||
if buf1.Len() == 0 {
|
||||
t.Error("expected output in buf1")
|
||||
}
|
||||
|
||||
buf2 := &bytes.Buffer{}
|
||||
SetOutput(buf2)
|
||||
buf1.Reset()
|
||||
|
||||
Info("after")
|
||||
if buf2.Len() == 0 {
|
||||
t.Error("expected output in buf2 after SetOutput")
|
||||
}
|
||||
if buf1.Len() > 0 {
|
||||
t.Error("buf1 should have no new output after SetOutput")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLevel(t *testing.T) {
|
||||
resetLogger(t)
|
||||
buf := &bytes.Buffer{}
|
||||
Setup(slog.LevelWarn, "json", buf, buf)
|
||||
|
||||
if GetLevel() != slog.LevelWarn {
|
||||
t.Errorf("expected LevelWarn, got %v", GetLevel())
|
||||
}
|
||||
|
||||
SetLevel(slog.LevelDebug)
|
||||
if GetLevel() != slog.LevelDebug {
|
||||
t.Errorf("expected LevelDebug, got %v", GetLevel())
|
||||
}
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ func loadBasicAuthSession(validator basic.Validator, sessionGroups []string, pre
|
|||
|
||||
session, err := getSession(validator, sessionGroups, req)
|
||||
if err != nil {
|
||||
logger.Errorf("Error retrieving session from token in Authorization header: %v", err)
|
||||
logger.ErrMsgf("error retrieving session from token in Authorization header: %v", err)
|
||||
}
|
||||
|
||||
// Add the session to the scope if it was found
|
||||
|
|
@ -75,12 +75,12 @@ func getBasicSession(validator basic.Validator, sessionGroups []string, req *htt
|
|||
}
|
||||
|
||||
if validator.Validate(user, password) {
|
||||
logger.PrintAuthf(user, req, logger.AuthSuccess, "Authenticated via basic auth and HTpasswd File")
|
||||
logger.LogAuth(user, req, logger.AuthSuccess, "authenticated via basic auth and HTpasswd File")
|
||||
|
||||
return &sessionsapi.SessionState{User: user, Groups: sessionGroups}, nil
|
||||
}
|
||||
|
||||
logger.PrintAuthf(user, req, logger.AuthFailure, "Invalid authentication via basic auth: not in Htpasswd File")
|
||||
logger.LogAuth(user, req, logger.AuthFailure, "invalid authentication via basic auth: not in Htpasswd File")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func (j *jwtSessionLoader) loadSession(next http.Handler) http.Handler {
|
|||
|
||||
session, err := j.getJwtSession(req)
|
||||
if err != nil {
|
||||
logger.Errorf("Error retrieving session from token in Authorization header: %v", err)
|
||||
logger.ErrMsgf("error retrieving session from token in Authorization header: %v", err)
|
||||
if j.denyInvalidJWTs {
|
||||
http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
|
|
@ -11,8 +12,7 @@ import (
|
|||
)
|
||||
|
||||
func TestMiddlewareSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Middleware")
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ func requestLogger(next http.Handler) http.Handler {
|
|||
scope := middlewareapi.GetRequestScope(req)
|
||||
// If scope is nil, this will panic.
|
||||
// A scope should always be injected before this handler is called.
|
||||
logger.PrintReq(
|
||||
logger.LogRequest(
|
||||
getUser(scope),
|
||||
scope.Upstream,
|
||||
req,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package middleware
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
|
|
@ -12,23 +14,32 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const RequestLoggingFormatWithoutTime = "{{.Client}} - {{.RequestID}} - {{.Username}} [TIMELESS] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
|
||||
|
||||
var _ = Describe("Request logger suite", func() {
|
||||
type expectedFields struct {
|
||||
User string
|
||||
Client string
|
||||
Host string
|
||||
Method string
|
||||
URI string
|
||||
Protocol string
|
||||
Upstream string
|
||||
StatusCode float64
|
||||
Size float64
|
||||
RequestID string
|
||||
}
|
||||
|
||||
type requestLoggerTableInput struct {
|
||||
Format string
|
||||
ExpectedLogMessage string
|
||||
Path string
|
||||
ExcludePaths []string
|
||||
Upstream string
|
||||
Session *sessions.SessionState
|
||||
Expected *expectedFields // nil means no output expected
|
||||
Path string
|
||||
ExcludePaths []string
|
||||
Upstream string
|
||||
Session *sessions.SessionState
|
||||
}
|
||||
|
||||
DescribeTable("when service a request",
|
||||
func(in *requestLoggerTableInput) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
logger.SetOutput(buf)
|
||||
logger.SetReqTemplate(in.Format)
|
||||
logger.Setup(slog.LevelDebug, "json", buf, buf)
|
||||
logger.SetExcludePaths(in.ExcludePaths)
|
||||
|
||||
req, err := http.NewRequest("GET", in.Path, nil)
|
||||
|
|
@ -45,79 +56,119 @@ var _ = Describe("Request logger suite", func() {
|
|||
handler := NewRequestLogger()(testUpstreamHandler(in.Upstream))
|
||||
handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
Expect(buf.String()).To(Equal(in.ExpectedLogMessage))
|
||||
if in.Expected == nil {
|
||||
Expect(buf.String()).To(BeEmpty())
|
||||
return
|
||||
}
|
||||
|
||||
var logEntry map[string]interface{}
|
||||
Expect(json.Unmarshal(buf.Bytes(), &logEntry)).To(Succeed())
|
||||
|
||||
Expect(logEntry).To(HaveKeyWithValue("level", "INFO"))
|
||||
Expect(logEntry).To(HaveKeyWithValue("msg", "request"))
|
||||
Expect(logEntry).To(HaveKey("time"))
|
||||
Expect(logEntry).To(HaveKeyWithValue("user", in.Expected.User))
|
||||
Expect(logEntry).To(HaveKeyWithValue("client", in.Expected.Client))
|
||||
Expect(logEntry).To(HaveKeyWithValue("host", in.Expected.Host))
|
||||
Expect(logEntry).To(HaveKeyWithValue("method", in.Expected.Method))
|
||||
Expect(logEntry).To(HaveKeyWithValue("uri", in.Expected.URI))
|
||||
Expect(logEntry).To(HaveKeyWithValue("protocol", in.Expected.Protocol))
|
||||
Expect(logEntry).To(HaveKeyWithValue("upstream", in.Expected.Upstream))
|
||||
Expect(logEntry).To(HaveKeyWithValue("status_code", in.Expected.StatusCode))
|
||||
Expect(logEntry).To(HaveKeyWithValue("response_size", in.Expected.Size))
|
||||
Expect(logEntry).To(HaveKeyWithValue("request_id", in.Expected.RequestID))
|
||||
Expect(logEntry).To(HaveKey("duration_s"))
|
||||
},
|
||||
Entry("standard request", &requestLoggerTableInput{
|
||||
Format: RequestLoggingFormatWithoutTime,
|
||||
ExpectedLogMessage: "127.0.0.1 - 11111111-2222-4333-8444-555555555555 - standard.user [TIMELESS] test-server GET standard \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n",
|
||||
Path: "/foo/bar",
|
||||
ExcludePaths: []string{},
|
||||
Upstream: "standard",
|
||||
Session: &sessions.SessionState{User: "standard.user"},
|
||||
Expected: &expectedFields{
|
||||
User: "standard.user", Client: "127.0.0.1", Host: "test-server",
|
||||
Method: "GET", URI: "/foo/bar", Protocol: "HTTP/1.1",
|
||||
Upstream: "standard", StatusCode: 200, Size: 4,
|
||||
RequestID: "11111111-2222-4333-8444-555555555555",
|
||||
},
|
||||
Path: "/foo/bar",
|
||||
ExcludePaths: []string{},
|
||||
Upstream: "standard",
|
||||
Session: &sessions.SessionState{User: "standard.user"},
|
||||
}),
|
||||
Entry("with unrelated path excluded", &requestLoggerTableInput{
|
||||
Format: RequestLoggingFormatWithoutTime,
|
||||
ExpectedLogMessage: "127.0.0.1 - 11111111-2222-4333-8444-555555555555 - unrelated.exclusion [TIMELESS] test-server GET unrelated \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n",
|
||||
Path: "/foo/bar",
|
||||
ExcludePaths: []string{"/ping"},
|
||||
Upstream: "unrelated",
|
||||
Session: &sessions.SessionState{User: "unrelated.exclusion"},
|
||||
Expected: &expectedFields{
|
||||
User: "unrelated.exclusion", Client: "127.0.0.1", Host: "test-server",
|
||||
Method: "GET", URI: "/foo/bar", Protocol: "HTTP/1.1",
|
||||
Upstream: "unrelated", StatusCode: 200, Size: 4,
|
||||
RequestID: "11111111-2222-4333-8444-555555555555",
|
||||
},
|
||||
Path: "/foo/bar",
|
||||
ExcludePaths: []string{"/ping"},
|
||||
Upstream: "unrelated",
|
||||
Session: &sessions.SessionState{User: "unrelated.exclusion"},
|
||||
}),
|
||||
Entry("with path as the sole exclusion", &requestLoggerTableInput{
|
||||
Format: RequestLoggingFormatWithoutTime,
|
||||
ExpectedLogMessage: "",
|
||||
Path: "/foo/bar",
|
||||
ExcludePaths: []string{"/foo/bar"},
|
||||
Expected: nil,
|
||||
Path: "/foo/bar",
|
||||
ExcludePaths: []string{"/foo/bar"},
|
||||
}),
|
||||
Entry("ping path", &requestLoggerTableInput{
|
||||
Format: RequestLoggingFormatWithoutTime,
|
||||
ExpectedLogMessage: "127.0.0.1 - 11111111-2222-4333-8444-555555555555 - mr.ping [TIMELESS] test-server GET - \"/ping\" HTTP/1.1 \"\" 200 4 0.000\n",
|
||||
Path: "/ping",
|
||||
ExcludePaths: []string{},
|
||||
Upstream: "",
|
||||
Session: &sessions.SessionState{User: "mr.ping"},
|
||||
Expected: &expectedFields{
|
||||
User: "mr.ping", Client: "127.0.0.1", Host: "test-server",
|
||||
Method: "GET", URI: "/ping", Protocol: "HTTP/1.1",
|
||||
Upstream: "-", StatusCode: 200, Size: 4,
|
||||
RequestID: "11111111-2222-4333-8444-555555555555",
|
||||
},
|
||||
Path: "/ping",
|
||||
ExcludePaths: []string{},
|
||||
Upstream: "",
|
||||
Session: &sessions.SessionState{User: "mr.ping"},
|
||||
}),
|
||||
Entry("ping path but excluded", &requestLoggerTableInput{
|
||||
Format: RequestLoggingFormatWithoutTime,
|
||||
ExpectedLogMessage: "",
|
||||
Path: "/ping",
|
||||
ExcludePaths: []string{"/ping"},
|
||||
Upstream: "",
|
||||
Session: &sessions.SessionState{User: "mr.ping"},
|
||||
Expected: nil,
|
||||
Path: "/ping",
|
||||
ExcludePaths: []string{"/ping"},
|
||||
Upstream: "",
|
||||
Session: &sessions.SessionState{User: "mr.ping"},
|
||||
}),
|
||||
Entry("ping path and excluded in list", &requestLoggerTableInput{
|
||||
Format: RequestLoggingFormatWithoutTime,
|
||||
ExpectedLogMessage: "",
|
||||
Path: "/ping",
|
||||
ExcludePaths: []string{"/foo/bar", "/ping"},
|
||||
Expected: nil,
|
||||
Path: "/ping",
|
||||
ExcludePaths: []string{"/foo/bar", "/ping"},
|
||||
}),
|
||||
Entry("custom format", &requestLoggerTableInput{
|
||||
Format: "{{.RequestMethod}} {{.Username}} {{.Upstream}}",
|
||||
ExpectedLogMessage: "GET custom.format custom\n",
|
||||
Path: "/foo/bar",
|
||||
ExcludePaths: []string{""},
|
||||
Upstream: "custom",
|
||||
Session: &sessions.SessionState{User: "custom.format"},
|
||||
Entry("request with no session", &requestLoggerTableInput{
|
||||
Expected: &expectedFields{
|
||||
User: "-", Client: "127.0.0.1", Host: "test-server",
|
||||
Method: "GET", URI: "/foo/bar", Protocol: "HTTP/1.1",
|
||||
Upstream: "custom", StatusCode: 200, Size: 4,
|
||||
RequestID: "11111111-2222-4333-8444-555555555555",
|
||||
},
|
||||
Path: "/foo/bar",
|
||||
ExcludePaths: []string{""},
|
||||
Upstream: "custom",
|
||||
}),
|
||||
Entry("custom format with unrelated exclusion", &requestLoggerTableInput{
|
||||
Format: "{{.RequestMethod}} {{.Username}} {{.Upstream}}",
|
||||
ExpectedLogMessage: "GET custom.format custom\n",
|
||||
Path: "/foo/bar",
|
||||
ExcludePaths: []string{"/ping"},
|
||||
Upstream: "custom",
|
||||
Session: &sessions.SessionState{User: "custom.format"},
|
||||
Entry("request with user session", &requestLoggerTableInput{
|
||||
Expected: &expectedFields{
|
||||
User: "custom.format", Client: "127.0.0.1", Host: "test-server",
|
||||
Method: "GET", URI: "/foo/bar", Protocol: "HTTP/1.1",
|
||||
Upstream: "custom", StatusCode: 200, Size: 4,
|
||||
RequestID: "11111111-2222-4333-8444-555555555555",
|
||||
},
|
||||
Path: "/foo/bar",
|
||||
ExcludePaths: []string{"/ping"},
|
||||
Upstream: "custom",
|
||||
Session: &sessions.SessionState{User: "custom.format"},
|
||||
}),
|
||||
Entry("custom format ping path", &requestLoggerTableInput{
|
||||
Format: "{{.RequestMethod}}",
|
||||
ExpectedLogMessage: "GET\n",
|
||||
Path: "/ping",
|
||||
ExcludePaths: []string{""},
|
||||
Entry("request with empty upstream", &requestLoggerTableInput{
|
||||
Expected: &expectedFields{
|
||||
User: "-", Client: "127.0.0.1", Host: "test-server",
|
||||
Method: "GET", URI: "/ping", Protocol: "HTTP/1.1",
|
||||
Upstream: "-", StatusCode: 200, Size: 4,
|
||||
RequestID: "11111111-2222-4333-8444-555555555555",
|
||||
},
|
||||
Path: "/ping",
|
||||
ExcludePaths: []string{""},
|
||||
}),
|
||||
Entry("custom format ping path excluded", &requestLoggerTableInput{
|
||||
Format: "{{.RequestMethod}}",
|
||||
ExpectedLogMessage: "",
|
||||
Path: "/ping",
|
||||
ExcludePaths: []string{"/ping"},
|
||||
Entry("excluded path not matched", &requestLoggerTableInput{
|
||||
Expected: nil,
|
||||
Path: "/ping",
|
||||
ExcludePaths: []string{"/ping"},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -119,10 +119,10 @@ func (s *storedSessionLoader) loadSession(next http.Handler) http.Handler {
|
|||
if err != nil && !errors.Is(err, http.ErrNoCookie) {
|
||||
// In the case when there was an error loading the session,
|
||||
// we should clear the session
|
||||
logger.Errorf("Error loading cookied session: %v, removing session", err)
|
||||
logger.ErrMsgf("error loading cookied session: %v, removing session", err)
|
||||
err = s.store.Clear(rw, req)
|
||||
if err != nil {
|
||||
logger.Errorf("Error removing session: %v", err)
|
||||
logger.ErrMsgf("error removing session: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ func (s *storedSessionLoader) refreshSessionIfNeeded(rw http.ResponseWriter, req
|
|||
return
|
||||
}
|
||||
if err := session.ReleaseLock(req.Context()); err != nil {
|
||||
logger.Errorf("unable to release lock: %v", err)
|
||||
logger.ErrMsgf("unable to release lock: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
@ -214,18 +214,20 @@ func (s *storedSessionLoader) refreshSessionIfNeeded(rw http.ResponseWriter, req
|
|||
}
|
||||
|
||||
// We are holding the lock and the session needs a refresh
|
||||
logger.Printf("Refreshing session - User: %s; SessionAge: %s", session.User, session.Age())
|
||||
logger.Info("refreshing session", "user", session.User, "session_age", session.Age())
|
||||
if err := s.refreshSession(rw, req, session); err != nil {
|
||||
logger.Errorf("Unable to refresh session: %v", err)
|
||||
// If a preemptive refresh fails, we still keep the session
|
||||
// if validateSession succeeds.
|
||||
logger.ErrMsgf("unable to refresh session: %v", err)
|
||||
|
||||
// Check if this is a fatal error that indicates the session is revoked
|
||||
// or no longer valid at the provider level
|
||||
if isFatalRefreshError(err) {
|
||||
logger.Printf("Fatal refresh error detected (session revoked or invalid), clearing session for user: %s", session.User)
|
||||
logger.Warn("fatal refresh error detected; clearing session", "user", session.User, "error", err)
|
||||
|
||||
// Clear the session from storage (Redis) and remove the cookie
|
||||
if err := s.store.Clear(rw, req); err != nil {
|
||||
logger.Errorf("failed clearing session: %v", err)
|
||||
logger.ErrMsgf("failed clearing session: %v", err)
|
||||
}
|
||||
|
||||
// Return error immediately to force re-authentication
|
||||
|
|
@ -265,7 +267,7 @@ func (s *storedSessionLoader) refreshSession(rw http.ResponseWriter, req *http.R
|
|||
|
||||
// Session not refreshed, nothing to persist.
|
||||
if !refreshed {
|
||||
logger.Printf("Session not refreshed - User: %s; no refresh token available or provider returned false", session.User)
|
||||
logger.Debug("session not refreshed", "user", session.User, "reason", "no refresh token available or provider returned false")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -276,7 +278,7 @@ func (s *storedSessionLoader) refreshSession(rw http.ResponseWriter, req *http.R
|
|||
// Because the session was refreshed, make sure to save it
|
||||
err = s.store.Save(rw, req, session)
|
||||
if err != nil {
|
||||
logger.PrintAuthf(session.Email, req, logger.AuthError, "error saving session: %v", err)
|
||||
logger.LogAuth(session.Email, req, logger.AuthError, fmt.Sprintf("error saving session: %v", err))
|
||||
return fmt.Errorf("error saving session: %v", err)
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -9,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestOIDCSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "OIDC")
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ func NewProvider(ctx context.Context, issuerURL string, skipIssuerVerification b
|
|||
// (which uses discovery to get the URLs), so we'll do a quick check ourselves and if
|
||||
// we get the URLs, we'll just use the non-discovery path.
|
||||
|
||||
logger.Printf("Performing OIDC Discovery...")
|
||||
logger.Info("performing OIDC Discovery...")
|
||||
|
||||
var p providerJSON
|
||||
requestURL := strings.TrimSuffix(issuerURL, "/") + "/.well-known/openid-configuration"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -9,8 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestProviderUtilSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Provider Utils")
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
|
|
@ -22,8 +23,7 @@ var ipv6CertDataSource, ipv6KeyDataSource options.SecretSource
|
|||
var transport *http.Transport
|
||||
|
||||
func TestHTTPSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "HTTP")
|
||||
|
|
|
|||
|
|
@ -346,11 +346,11 @@ func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
|
|||
}
|
||||
err = tc.SetKeepAlive(true)
|
||||
if err != nil {
|
||||
logger.Errorf("Error setting Keep-Alive: %v", err)
|
||||
logger.ErrMsgf("error setting Keep-Alive: %v", err)
|
||||
}
|
||||
err = tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||
if err != nil {
|
||||
logger.Printf("Error setting Keep-Alive period: %v", err)
|
||||
logger.ErrMsgf("error setting Keep-Alive period: %v", err)
|
||||
}
|
||||
return tc, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -20,8 +21,7 @@ var (
|
|||
)
|
||||
|
||||
func TestRequetsSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
log.SetOutput(GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package util_test
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -12,8 +13,7 @@ import (
|
|||
// to prevent circular imports with the `logger` package which uses
|
||||
// this functionality
|
||||
func TestRequestUtilSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Request Utils")
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ func splitCookie(c *http.Cookie) []*http.Cookie {
|
|||
return []*http.Cookie{c}
|
||||
}
|
||||
|
||||
logger.Errorf("WARNING: Multiple cookies are required for this session as it exceeds the 4kb cookie limit. Please use server side session storage (eg. Redis) instead.")
|
||||
logger.Warn("multiple cookies are required for this session as it exceeds the 4kb cookie limit. Please use server side session storage (eg. Redis) instead.")
|
||||
|
||||
cookies := []*http.Cookie{}
|
||||
valueBytes := []byte(c.Value)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package cookie
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
mathrand "math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
|
@ -18,8 +19,7 @@ import (
|
|||
)
|
||||
|
||||
func TestSessionStore(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Cookie SessionStore")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package persistence
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -9,8 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestPersistenceSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Persistence")
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ func setupTLSConfig(opts options.RedisStoreOptions, opt *redis.Options) error {
|
|||
if opts.CAPath != "" {
|
||||
rootCAs, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
logger.Errorf("failed to load system cert pool for redis connection, falling back to empty cert pool")
|
||||
logger.ErrMsg("failed to load system cert pool for redis connection, falling back to empty cert pool")
|
||||
}
|
||||
if rootCAs == nil {
|
||||
rootCAs = x509.NewCertPool()
|
||||
|
|
@ -212,7 +212,7 @@ func setupTLSConfig(opts options.RedisStoreOptions, opt *redis.Options) error {
|
|||
|
||||
// Append our cert to the system pool
|
||||
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
|
||||
logger.Errorf("no certs appended, using system certs only")
|
||||
logger.ErrMsg("no certs appended, using system certs only")
|
||||
}
|
||||
|
||||
if opt.TLSConfig == nil {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"crypto/tls"
|
||||
"encoding/pem"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
|
|
@ -32,8 +33,7 @@ var (
|
|||
)
|
||||
|
||||
func TestRedis(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
redisLogger := &wrappedRedisLogger{Logger: log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile)}
|
||||
redisLogger.SetOutput(GinkgoWriter)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package sessions_test
|
|||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"log/slog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -17,8 +18,7 @@ import (
|
|||
)
|
||||
|
||||
func TestSessionStore(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "SessionStore")
|
||||
|
|
|
|||
|
|
@ -77,19 +77,19 @@ func (m *multiUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request
|
|||
|
||||
// registerStaticResponseHandler registers a static response handler with at the given path.
|
||||
func (m *multiUpstreamProxy) registerStaticResponseHandler(upstream options.Upstream, writer pagewriter.Writer) error {
|
||||
logger.Printf("mapping path %q => static response %d", upstream.Path, ptr.Deref(upstream.StaticCode, options.DefaultUpstreamStaticCode))
|
||||
logger.Infof("mapping path %q => static response %d", upstream.Path, ptr.Deref(upstream.StaticCode, options.DefaultUpstreamStaticCode))
|
||||
return m.registerHandler(upstream, newStaticResponseHandler(upstream.ID, upstream.StaticCode), writer)
|
||||
}
|
||||
|
||||
// registerFileServer registers a new fileServer based on the configuration given.
|
||||
func (m *multiUpstreamProxy) registerFileServer(upstream options.Upstream, u *url.URL, writer pagewriter.Writer) error {
|
||||
logger.Printf("mapping path %q => file system %q", upstream.Path, u.Path)
|
||||
logger.Infof("mapping path %q => file system %q", upstream.Path, u.Path)
|
||||
return m.registerHandler(upstream, newFileServer(upstream, u.Path), writer)
|
||||
}
|
||||
|
||||
// registerHTTPUpstreamProxy registers a new httpUpstreamProxy based on the configuration given.
|
||||
func (m *multiUpstreamProxy) registerHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, writer pagewriter.Writer) error {
|
||||
logger.Printf("mapping path %q => upstream %q", upstream.Path, upstream.URI)
|
||||
logger.Infof("mapping path %q => upstream %q", upstream.Path, upstream.URI)
|
||||
return m.registerHandler(upstream, newHTTPUpstreamProxy(upstream, u, sigData, writer.ProxyErrorHandler), writer)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ func rewritePath(rewriteRegExp *regexp.Regexp, rewriteTarget string, writer page
|
|||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
reqURL, err := url.ParseRequestURI(req.RequestURI)
|
||||
if err != nil {
|
||||
logger.Errorf("could not parse request URI: %v", err)
|
||||
logger.ErrMsgf("could not parse request URI: %v", err)
|
||||
writer.WriteErrorPage(rw, pagewriter.ErrorPageOpts{
|
||||
Status: http.StatusInternalServerError,
|
||||
RequestID: middleware.GetRequestScope(req).RequestID,
|
||||
|
|
@ -40,7 +40,7 @@ func rewritePath(rewriteRegExp *regexp.Regexp, rewriteTarget string, writer page
|
|||
newURI := rewriteRegExp.ReplaceAllString(reqURL.Path, rewriteTarget)
|
||||
reqURL.Path, reqURL.RawQuery, err = splitPathAndQuery(reqURL.Query(), newURI)
|
||||
if err != nil {
|
||||
logger.Errorf("could not parse rewrite URI: %v", err)
|
||||
logger.ErrMsgf("could not parse rewrite URI: %v", err)
|
||||
writer.WriteErrorPage(rw, pagewriter.ErrorPageOpts{
|
||||
Status: http.StatusInternalServerError,
|
||||
RequestID: middleware.GetRequestScope(req).RequestID,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,6 @@ func (s *staticResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Requ
|
|||
rw.WriteHeader(s.code)
|
||||
_, err := fmt.Fprintf(rw, "Authenticated")
|
||||
if err != nil {
|
||||
logger.Errorf("Error writing static response: %v", err)
|
||||
logger.ErrMsgf("error writing static response: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
|
@ -28,8 +29,7 @@ var (
|
|||
)
|
||||
|
||||
func TestUpstreamSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
log.SetOutput(GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,49 @@
|
|||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
// parseLogLevel converts a string log level to slog.Level.
|
||||
func parseLogLevel(s string) (slog.Level, error) {
|
||||
switch strings.ToLower(s) {
|
||||
case "debug":
|
||||
return slog.LevelDebug, nil
|
||||
case "info":
|
||||
return slog.LevelInfo, nil
|
||||
case "warn", "warning":
|
||||
return slog.LevelWarn, nil
|
||||
case "error":
|
||||
return slog.LevelError, nil
|
||||
default:
|
||||
return slog.LevelInfo, fmt.Errorf("invalid log level %q: must be one of debug, info, warn, error", s)
|
||||
}
|
||||
}
|
||||
|
||||
// configureLogger is responsible for configuring the logger based on the options given
|
||||
func configureLogger(o options.Logging, msgs []string) []string {
|
||||
// Setup the log file
|
||||
// Parse and validate log level
|
||||
level, err := parseLogLevel(o.Level)
|
||||
if err != nil {
|
||||
msgs = append(msgs, err.Error())
|
||||
return msgs
|
||||
}
|
||||
|
||||
// Validate log format
|
||||
format := strings.ToLower(o.Format)
|
||||
if format != "json" && format != "text" {
|
||||
msgs = append(msgs, fmt.Sprintf("invalid log format %q: must be one of json, text", o.Format))
|
||||
return msgs
|
||||
}
|
||||
|
||||
// Determine output writers
|
||||
if len(o.File.Filename) > 0 {
|
||||
// Validate that the file/dir can be written
|
||||
file, err := os.OpenFile(o.File.Filename, os.O_WRONLY|os.O_CREATE, 0600)
|
||||
|
|
@ -23,9 +56,14 @@ func configureLogger(o options.Logging, msgs []string) []string {
|
|||
if err != nil {
|
||||
return append(msgs, "error closing the log file: "+o.File.Filename)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Printf("Redirecting logging to file: %s", o.File.Filename)
|
||||
// Setup writers
|
||||
var stdWriter, errWriter *os.File
|
||||
stdWriter = os.Stdout
|
||||
errWriter = os.Stderr
|
||||
|
||||
if len(o.File.Filename) > 0 {
|
||||
logWriter := &lumberjack.Logger{
|
||||
Filename: o.File.Filename,
|
||||
MaxSize: o.File.MaxSize, // megabytes
|
||||
|
|
@ -35,28 +73,48 @@ func configureLogger(o options.Logging, msgs []string) []string {
|
|||
Compress: o.File.Compress,
|
||||
}
|
||||
|
||||
logger.SetOutput(logWriter)
|
||||
// Setup with lumberjack writer
|
||||
errW := errWriter
|
||||
if o.ErrToInfo {
|
||||
logger.Setup(level, format, logWriter, logWriter)
|
||||
} else {
|
||||
logger.Setup(level, format, logWriter, errW)
|
||||
}
|
||||
|
||||
logger.Info("logging redirected to file", "filename", o.File.Filename)
|
||||
} else {
|
||||
// Setup with stdout/stderr
|
||||
if o.ErrToInfo {
|
||||
logger.Setup(level, format, stdWriter, stdWriter)
|
||||
} else {
|
||||
logger.Setup(level, format, stdWriter, errWriter)
|
||||
}
|
||||
}
|
||||
|
||||
// Supply a sanity warning to the logger if all logging is disabled
|
||||
if !o.StandardEnabled && !o.AuthEnabled && !o.RequestEnabled {
|
||||
logger.Error("Warning: Logging disabled. No further logs will be shown.")
|
||||
}
|
||||
|
||||
// Pass configuration values to the standard logger
|
||||
logger.SetStandardEnabled(o.StandardEnabled)
|
||||
logger.SetErrToInfo(o.ErrToInfo)
|
||||
logger.SetAuthEnabled(o.AuthEnabled)
|
||||
logger.SetReqEnabled(o.RequestEnabled)
|
||||
logger.SetLocalTime(o.LocalTime)
|
||||
logger.SetStandardTemplate(o.StandardFormat)
|
||||
logger.SetAuthTemplate(o.AuthFormat)
|
||||
logger.SetReqTemplate(o.RequestFormat)
|
||||
logger.SetErrToInfo(o.ErrToInfo)
|
||||
logger.SetStandardEnabled(true)
|
||||
|
||||
logger.SetExcludePaths(o.ExcludePaths)
|
||||
|
||||
if !o.LocalTime {
|
||||
logger.SetFlags(logger.Flags() | logger.LUTC)
|
||||
// Supply a sanity warning to the logger if all logging is disabled
|
||||
if !o.StandardEnabled && !o.AuthEnabled && !o.RequestEnabled {
|
||||
logger.Warn("logging disabled: standard, auth, and request logging are all off")
|
||||
}
|
||||
|
||||
logger.SetStandardEnabled(o.StandardEnabled)
|
||||
|
||||
// Configure categorical logging
|
||||
logger.SetAuthEnabled(o.AuthEnabled)
|
||||
logger.SetReqEnabled(o.RequestEnabled)
|
||||
|
||||
// Configure exclude paths
|
||||
excludePaths := o.ExcludePaths
|
||||
if o.SilencePing {
|
||||
excludePaths = append(excludePaths, "/ping", "/ready")
|
||||
}
|
||||
logger.SetExcludePaths(excludePaths)
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ func Validate(o *options.Options) error {
|
|||
redirectURL, msgs = parseURL(o.RawRedirectURL, "redirect", msgs)
|
||||
o.SetRedirectURL(redirectURL)
|
||||
if o.RawRedirectURL == "" && !o.Cookie.Secure && !o.ReverseProxy {
|
||||
logger.Print("WARNING: no explicit redirect URL: redirects will default to insecure HTTP")
|
||||
logger.Warn("no explicit redirect URL: redirects will default to insecure HTTP")
|
||||
}
|
||||
|
||||
msgs = append(msgs, validateUpstreams(o.UpstreamServers)...)
|
||||
|
|
@ -108,7 +108,7 @@ func parseSignatureKey(o *options.Options, msgs []string) []string {
|
|||
return msgs
|
||||
}
|
||||
|
||||
logger.Print("WARNING: `--signature-key` is deprecated. It will be removed in a future release")
|
||||
logger.Warn("`--signature-key` is deprecated. It will be removed in a future release")
|
||||
|
||||
components := strings.Split(o.SignatureKey, ":")
|
||||
if len(components) != 2 {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package validation
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -9,8 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestValidationSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Validation Suite")
|
||||
|
|
|
|||
|
|
@ -25,19 +25,19 @@ func WatchFileForUpdates(filename string, done <-chan bool, action func()) error
|
|||
for {
|
||||
select {
|
||||
case <-done:
|
||||
logger.Printf("shutting down watcher for: %s", filename)
|
||||
logger.Infof("shutting down watcher for: %s", filename)
|
||||
return
|
||||
case event := <-watcher.Events:
|
||||
filterEvent(watcher, event, filename, action)
|
||||
case err = <-watcher.Errors:
|
||||
logger.Errorf("error watching '%s': %s", filename, err)
|
||||
logger.ErrMsgf("error watching '%s': %s", filename, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if err := watcher.Add(filename); err != nil {
|
||||
return fmt.Errorf("failed to add '%s' to watcher: %v", filename, err)
|
||||
}
|
||||
logger.Printf("watching '%s' for updates", filename)
|
||||
logger.Infof("watching '%s' for updates", filename)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -51,11 +51,11 @@ func filterEvent(watcher *fsnotify.Watcher, event fsnotify.Event, filename strin
|
|||
// In Kubernetes the file path is a symlink, so we should take action
|
||||
// when the ConfigMap/Secret is replaced.
|
||||
case event.Op&fsnotify.Remove != 0:
|
||||
logger.Printf("watching interrupted on event: %s", event)
|
||||
logger.Infof("watching interrupted on event: %s", event)
|
||||
WaitForReplacement(filename, event.Op, watcher)
|
||||
action()
|
||||
case event.Op&(fsnotify.Create|fsnotify.Write) != 0:
|
||||
logger.Printf("reloading after event: %s", event)
|
||||
logger.Infof("reloading after event: %s", event)
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@ func WaitForReplacement(filename string, op fsnotify.Op, watcher *fsnotify.Watch
|
|||
for {
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
if err := watcher.Add(filename); err == nil {
|
||||
logger.Printf("watching resumed for '%s'", filename)
|
||||
logger.Infof("watching resumed for '%s'", filename)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ func NewAzureProvider(p *ProviderData, opts options.AzureOptions) *AzureProvider
|
|||
azureV2GraphScope := fmt.Sprintf("https://%s/.default", p.ProfileURL.Host)
|
||||
|
||||
if strings.Contains(p.Scope, " groups") {
|
||||
logger.Print("WARNING: `groups` scope is not an accepted scope when using Azure OAuth V2 endpoint. Removing it from the scope list")
|
||||
logger.Warn("`groups` scope is not an accepted scope when using Azure OAuth V2 endpoint. Removing it from the scope list")
|
||||
p.Scope = strings.ReplaceAll(p.Scope, " groups", "")
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ func NewAzureProvider(p *ProviderData, opts options.AzureOptions) *AzureProvider
|
|||
}
|
||||
|
||||
if p.ProtectedResource != nil && p.ProtectedResource.String() != "" {
|
||||
logger.Print("WARNING: `--resource` option has no effect when using the Azure OAuth V2 endpoint.")
|
||||
logger.Warn("`--resource` option has no effect when using the Azure OAuth V2 endpoint.")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -197,7 +197,7 @@ func (p *AzureProvider) EnrichSession(ctx context.Context, session *sessions.Ses
|
|||
err := p.extractClaimsIntoSession(ctx, session)
|
||||
|
||||
if err != nil {
|
||||
logger.Printf("unable to get email and/or groups claims from token: %v", err)
|
||||
logger.Infof("unable to get email and/or groups claims from token: %v", err)
|
||||
}
|
||||
|
||||
if session.Email == "" {
|
||||
|
|
@ -288,7 +288,7 @@ func (p *AzureProvider) verifySessionToken(ctx context.Context, session *session
|
|||
|
||||
if session.IDToken != "" {
|
||||
if _, err := p.Verifier.Verify(ctx, session.IDToken); err != nil {
|
||||
logger.Printf("unable to verify ID token, fallback to access token: %v", err)
|
||||
logger.Infof("unable to verify ID token, fallback to access token: %v", err)
|
||||
if _, err = p.Verifier.Verify(ctx, session.AccessToken); err != nil {
|
||||
return fmt.Errorf("unable to verify access token: %v", err)
|
||||
}
|
||||
|
|
@ -353,7 +353,7 @@ func (p *AzureProvider) redeemRefreshToken(ctx context.Context, s *sessions.Sess
|
|||
err = p.extractClaimsIntoSession(ctx, s)
|
||||
|
||||
if err != nil {
|
||||
logger.Printf("unable to get email and/or groups claims from token: %v", err)
|
||||
logger.Infof("unable to get email and/or groups claims from token: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -445,7 +445,7 @@ func getEmailFromJSON(json *simplejson.Json) (string, error) {
|
|||
if err != nil || email == "" {
|
||||
email, err = json.Get("userPrincipalName").String()
|
||||
if err != nil {
|
||||
logger.Errorf("unable to find userPrincipalName: %s", err)
|
||||
logger.ErrMsgf("unable to find userPrincipalName: %s", err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ func (p *BitbucketProvider) GetEmailAddress(ctx context.Context, s *sessions.Ses
|
|||
Do().
|
||||
UnmarshalInto(&emails)
|
||||
if err != nil {
|
||||
logger.Errorf("failed making request: %v", err)
|
||||
logger.ErrMsgf("failed making request: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ func (p *BitbucketProvider) GetEmailAddress(ctx context.Context, s *sessions.Ses
|
|||
Do().
|
||||
UnmarshalInto(&teams)
|
||||
if err != nil {
|
||||
logger.Errorf("failed requesting teams membership: %v", err)
|
||||
logger.ErrMsgf("failed requesting teams membership: %v", err)
|
||||
return "", err
|
||||
}
|
||||
var found = false
|
||||
|
|
@ -144,7 +144,7 @@ func (p *BitbucketProvider) GetEmailAddress(ctx context.Context, s *sessions.Ses
|
|||
}
|
||||
}
|
||||
if !found {
|
||||
logger.Error("team membership test failed, access denied")
|
||||
logger.ErrMsg("team membership test failed, access denied")
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
|
@ -163,7 +163,7 @@ func (p *BitbucketProvider) GetEmailAddress(ctx context.Context, s *sessions.Ses
|
|||
Do().
|
||||
UnmarshalInto(&repositories)
|
||||
if err != nil {
|
||||
logger.Errorf("failed checking repository access: %v", err)
|
||||
logger.ErrMsgf("failed checking repository access: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ func (p *BitbucketProvider) GetEmailAddress(ctx context.Context, s *sessions.Ses
|
|||
}
|
||||
}
|
||||
if !found {
|
||||
logger.Error("repository access test failed, access denied")
|
||||
logger.ErrMsg("repository access test failed, access denied")
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ func (p *CIDAASProvider) EnrichSession(ctx context.Context, s *sessions.SessionS
|
|||
|
||||
// Try to get missing emails or groups from a profileURL
|
||||
if err := p.enrichFromUserinfoEndpoint(ctx, s); err != nil {
|
||||
logger.Errorf("Warning: Profile URL request failed: %s", err)
|
||||
logger.Warn("profile URL request failed", "error", err)
|
||||
}
|
||||
|
||||
// If a mandatory email wasn't set, error at this point.
|
||||
|
|
|
|||
|
|
@ -165,13 +165,13 @@ func (p *GitHubProvider) hasOrg(s *sessions.SessionState) error {
|
|||
presentOrgs := make([]string, 0, len(orgs))
|
||||
for _, org := range orgs {
|
||||
if p.Org == org {
|
||||
logger.Printf("Found Github Organization:%q", org)
|
||||
logger.Info("found Github organization", "org", org)
|
||||
return nil
|
||||
}
|
||||
presentOrgs = append(presentOrgs, org)
|
||||
}
|
||||
|
||||
logger.Printf("Missing Organization:%q in %v", p.Org, presentOrgs)
|
||||
logger.Info("missing organization", "required_org", p.Org, "present_orgs", presentOrgs)
|
||||
return errors.New("user is missing required organization")
|
||||
}
|
||||
|
||||
|
|
@ -204,7 +204,7 @@ func (p *GitHubProvider) hasOrgAndTeam(s *sessions.SessionState) error {
|
|||
teams := strings.Split(p.Team, ",")
|
||||
for _, team := range teams {
|
||||
if strings.EqualFold(strings.TrimSpace(team), ot.Team) {
|
||||
logger.Printf("Found Github Organization/Team:%q/%q", ot.Org, ot.Team)
|
||||
logger.Info("found Github organization/team", "org", ot.Org, "team", ot.Team)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -213,11 +213,11 @@ func (p *GitHubProvider) hasOrgAndTeam(s *sessions.SessionState) error {
|
|||
}
|
||||
|
||||
if hasOrg {
|
||||
logger.Printf("Missing Team:%q from Org:%q in teams: %v", p.Team, p.Org, presentTeams)
|
||||
logger.Info("missing team from org", "team", p.Team, "org", p.Org, "present_teams", presentTeams)
|
||||
return errors.New("user is missing required team")
|
||||
}
|
||||
|
||||
logger.Printf("Missing Organization:%q in %#v", p.Org, maps.Keys(presentOrgs))
|
||||
logger.Info("missing organization", "org", p.Org, "present_orgs", maps.Keys(presentOrgs))
|
||||
return errors.New("user is missing required organization")
|
||||
}
|
||||
|
||||
|
|
@ -236,19 +236,19 @@ func (p *GitHubProvider) hasTeam(s *sessions.SessionState) error {
|
|||
allowedTeams := strings.Split(p.Team, ",")
|
||||
for _, team := range allowedTeams {
|
||||
if !strings.Contains(team, orgTeamSeparator) {
|
||||
logger.Printf("Please use fully qualified team names (org:team-slug) if you omit the organisation. Current Team name: %s", team)
|
||||
logger.Warn("please use fully qualified team names (org:team-slug) if you omit the organisation", "team", team)
|
||||
return errors.New("team name is invalid")
|
||||
}
|
||||
|
||||
if strings.EqualFold(strings.TrimSpace(team), ot) {
|
||||
logger.Printf("Found Github Organization/Team:%s", ot)
|
||||
logger.Info("found Github organization/team", "team", ot)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
presentTeams = append(presentTeams, ot)
|
||||
}
|
||||
|
||||
logger.Printf("Missing Team:%q in teams: %v", p.Team, presentTeams)
|
||||
logger.Info("missing team", "team", p.Team, "present_teams", presentTeams)
|
||||
return errors.New("user is missing required team")
|
||||
}
|
||||
|
||||
|
|
@ -329,7 +329,7 @@ func (p *GitHubProvider) isCollaborator(ctx context.Context, username, accessTok
|
|||
result.StatusCode(), endpoint.String(), result.Body())
|
||||
}
|
||||
|
||||
logger.Printf("got %d from %q %s", result.StatusCode(), endpoint.String(), result.Body())
|
||||
logger.Infof("got %d from %q %s", result.StatusCode(), endpoint.String(), result.Body())
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
|
@ -497,10 +497,10 @@ func (p *GitHubProvider) getOrgs(ctx context.Context, s *sessions.SessionState)
|
|||
var orgName string
|
||||
if len(org.Login) > 0 {
|
||||
orgName = org.Login
|
||||
logger.Printf("Member of Github Organization: %q", orgName)
|
||||
logger.Info("member of Github organization", "org", orgName)
|
||||
} else {
|
||||
orgName = org.Name
|
||||
logger.Printf("Member of Gitea Organization: %q", orgName)
|
||||
logger.Info("member of Gitea organization", "org", orgName)
|
||||
}
|
||||
|
||||
s.Groups = append(s.Groups, orgName)
|
||||
|
|
@ -551,11 +551,11 @@ func (p *GitHubProvider) getTeams(ctx context.Context, s *sessions.SessionState)
|
|||
if len(team.Org.Login) > 0 {
|
||||
orgName = team.Org.Login
|
||||
teamName = team.Slug
|
||||
logger.Printf("Member of Github Organization/Team: %q/%q", orgName, teamName)
|
||||
logger.Info("member of Github organization/team", "org", orgName, "team", teamName)
|
||||
} else {
|
||||
orgName = team.Org.Name
|
||||
teamName = team.Name
|
||||
logger.Printf("Member of Gitea Organization/Team: %q/%q", orgName, teamName)
|
||||
logger.Info("member of Gitea organization/team", "org", orgName, "team", teamName)
|
||||
}
|
||||
|
||||
s.Groups = append(s.Groups, fmt.Sprintf("%s%s%s", orgName, orgTeamSeparator, teamName))
|
||||
|
|
|
|||
|
|
@ -185,12 +185,12 @@ func (p *GitLabProvider) addProjectsToSession(ctx context.Context, s *sessions.S
|
|||
for _, project := range p.allowedProjects {
|
||||
projectInfo, err := p.getProjectInfo(ctx, s, project.Name)
|
||||
if err != nil {
|
||||
logger.Errorf("Warning: project info request failed: %v", err)
|
||||
logger.Warn("project info request failed", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if projectInfo.Archived {
|
||||
logger.Errorf("Warning: project %s is archived", project.Name)
|
||||
logger.Warn("project is archived", "project", project.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -200,17 +200,17 @@ func (p *GitLabProvider) addProjectsToSession(ctx context.Context, s *sessions.S
|
|||
perms = projectInfo.Permissions.GroupAccess
|
||||
// group project access is not set for this user then we give up
|
||||
if perms == nil {
|
||||
logger.Errorf("Warning: user %q has no project level access to %s",
|
||||
s.Email, project.Name)
|
||||
logger.Warn("user has no project level access",
|
||||
"user", s.Email, "project", project.Name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if perms.AccessLevel < project.AccessLevel {
|
||||
logger.Errorf(
|
||||
"Warning: user %q does not have the minimum required access level for project %q",
|
||||
s.Email,
|
||||
project.Name,
|
||||
logger.Warn(
|
||||
"user does not have the minimum required access level for project",
|
||||
"user", s.Email,
|
||||
"project", project.Name,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ func (p *GoogleProvider) populateAllGroups(adminService *admin.Service) func(s *
|
|||
// Get all groups of the user
|
||||
groups, err := getUserGroups(adminService, s.Email)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to get user groups for %s: %v", s.Email, err)
|
||||
logger.ErrMsgf("failed to get user groups for %s: %v", s.Email, err)
|
||||
s.Groups = []string{}
|
||||
return true // Allow access even if we can't get groups
|
||||
}
|
||||
|
|
@ -311,24 +311,24 @@ func getOauth2TokenSource(ctx context.Context, opts options.GoogleOptions, scope
|
|||
Subject: opts.AdminEmail,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Fatal("failed to fetch application default credentials: ", err)
|
||||
logger.FatalMsg("failed to fetch application default credentials", "error", err)
|
||||
}
|
||||
return ts
|
||||
}
|
||||
|
||||
credentialsReader, err := os.Open(opts.ServiceAccountJSON)
|
||||
if err != nil {
|
||||
logger.Fatal("couldn't open Google credentials file: ", err)
|
||||
logger.FatalMsg("couldn't open Google credentials file", "error", err)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(credentialsReader)
|
||||
if err != nil {
|
||||
logger.Fatal("can't read Google credentials file:", err)
|
||||
logger.FatalMsg("can't read Google credentials file", "error", err)
|
||||
}
|
||||
|
||||
conf, err := google.JWTConfigFromJSON(data, scope)
|
||||
if err != nil {
|
||||
logger.Fatal("can't load Google credentials file:", err)
|
||||
logger.FatalMsg("can't load Google credentials file", "error", err)
|
||||
}
|
||||
|
||||
conf.Subject = opts.AdminEmail
|
||||
|
|
@ -357,24 +357,24 @@ func getAdminService(opts options.GoogleOptions) *admin.Service {
|
|||
retrieveErrBody := map[string]interface{}{}
|
||||
|
||||
if err := json.Unmarshal(retrieveErr.Body, &retrieveErrBody); err != nil {
|
||||
logger.Fatal("error unmarshalling retrieveErr body:", err)
|
||||
logger.FatalMsg("error unmarshalling retrieveErr body", "error", err)
|
||||
}
|
||||
|
||||
if retrieveErrBody["error"] == "unauthorized_client" && retrieveErrBody["error_description"] == "Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested." {
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Fatal("error retrieving token:", err)
|
||||
logger.FatalMsg("error retrieving token", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
if client == nil {
|
||||
logger.Fatal("error: google credentials do not have enough permissions to access admin API scope")
|
||||
logger.FatalMsg("error: google credentials do not have enough permissions to access admin API scope")
|
||||
}
|
||||
|
||||
adminService, err := admin.NewService(ctx, option.WithHTTPClient(client))
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
logger.FatalMsg("failed to create admin service", "error", err)
|
||||
}
|
||||
return adminService
|
||||
}
|
||||
|
|
@ -385,26 +385,26 @@ func getTargetPrincipal(ctx context.Context, opts options.GoogleOptions) (target
|
|||
if targetPrincipal != "" {
|
||||
return targetPrincipal
|
||||
}
|
||||
logger.Print("INFO: no target principal set, trying to automatically determine one instead.")
|
||||
logger.Info("no target principal set, trying to automatically determine one instead.")
|
||||
credential, err := google.FindDefaultCredentials(ctx)
|
||||
if err != nil {
|
||||
logger.Fatal("failed to fetch application default credentials: ", err)
|
||||
logger.FatalMsg("failed to fetch application default credentials", "error", err)
|
||||
}
|
||||
content := map[string]interface{}{}
|
||||
|
||||
err = json.Unmarshal(credential.JSON, &content)
|
||||
switch {
|
||||
case err != nil && !metadata.OnGCE():
|
||||
logger.Fatal("unable to unmarshal Application Default Credentials JSON", err)
|
||||
logger.FatalMsg("unable to unmarshal Application Default Credentials JSON", "error", err)
|
||||
case content["client_email"] != nil:
|
||||
targetPrincipal = fmt.Sprintf("%v", content["client_email"])
|
||||
case metadata.OnGCE():
|
||||
targetPrincipal, err = metadata.EmailWithContext(ctx, "")
|
||||
if err != nil {
|
||||
logger.Fatal("error while calling the GCE metadata server", err)
|
||||
logger.FatalMsg("error while calling the GCE metadata server", "error", err)
|
||||
}
|
||||
default:
|
||||
logger.Fatal("unable to determine Application Default Credentials TargetPrincipal, try overriding with --target-principal instead.")
|
||||
logger.FatalMsg("unable to determine Application Default Credentials TargetPrincipal, try overriding with --target-principal instead.")
|
||||
}
|
||||
return targetPrincipal
|
||||
}
|
||||
|
|
@ -476,7 +476,7 @@ func userInGroup(service *admin.Service, group string, email string) bool {
|
|||
gerr, ok := err.(*googleapi.Error)
|
||||
switch {
|
||||
case ok && gerr.Code == 404:
|
||||
logger.Errorf("error checking membership in group %s: group does not exist", group)
|
||||
logger.ErrMsg("error checking membership in group: group does not exist", "group", group)
|
||||
case ok && gerr.Code == 400:
|
||||
// It is possible for Members.HasMember to return false even if the email is a group member.
|
||||
// One case that can cause this is if the user email is from a different domain than the group,
|
||||
|
|
@ -485,7 +485,7 @@ func userInGroup(service *admin.Service, group string, email string) bool {
|
|||
req := service.Members.Get(group, email)
|
||||
r, err := req.Do()
|
||||
if err != nil {
|
||||
logger.Errorf("error using get API to check member %s of google group %s: user not in the group", email, group)
|
||||
logger.ErrMsg("error using get API to check member of google group: user not in the group", "email", email, "group", group)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -495,7 +495,7 @@ func userInGroup(service *admin.Service, group string, email string) bool {
|
|||
return true
|
||||
}
|
||||
default:
|
||||
logger.Errorf("error checking group membership: %v", err)
|
||||
logger.ErrMsgf("error checking group membership: %v", err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,14 +24,14 @@ func stripToken(endpoint string) string {
|
|||
func stripParam(param, endpoint string) string {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
logger.Errorf("error attempting to strip %s: %s", param, err)
|
||||
logger.ErrMsgf("error attempting to strip %s: %s", param, err)
|
||||
return endpoint
|
||||
}
|
||||
|
||||
if u.RawQuery != "" {
|
||||
values, err := url.ParseQuery(u.RawQuery)
|
||||
if err != nil {
|
||||
logger.Errorf("error attempting to strip %s: %s", param, err)
|
||||
logger.ErrMsgf("error attempting to strip %s: %s", param, err)
|
||||
return u.String()
|
||||
}
|
||||
|
||||
|
|
@ -66,17 +66,17 @@ func validateToken(ctx context.Context, p Provider, accessToken string, header h
|
|||
WithHeaders(header).
|
||||
Do()
|
||||
if result.Error() != nil {
|
||||
logger.Errorf("GET %s", stripToken(endpoint))
|
||||
logger.Errorf("token validation request failed: %s", result.Error())
|
||||
logger.ErrMsgf("GET %s", stripToken(endpoint))
|
||||
logger.ErrMsgf("token validation request failed: %s", result.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
logger.Printf("%d GET %s %s", result.StatusCode(), stripToken(endpoint), result.Body())
|
||||
logger.Infof("%d GET %s %s", result.StatusCode(), stripToken(endpoint), result.Body())
|
||||
|
||||
if result.StatusCode() == 200 {
|
||||
return true
|
||||
}
|
||||
logger.Errorf("token validation request failed: status %d - %s", result.StatusCode(), result.Body())
|
||||
logger.ErrMsgf("token validation request failed: status %d - %s", result.StatusCode(), result.Body())
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ func (p *KeycloakProvider) EnrichSession(ctx context.Context, s *sessions.Sessio
|
|||
Do().
|
||||
UnmarshalSimpleJSON()
|
||||
if err != nil {
|
||||
logger.Errorf("failed making request %v", err)
|
||||
logger.ErrMsgf("failed making request %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func (p *MicrosoftEntraIDProvider) EnrichSession(ctx context.Context, session *s
|
|||
}
|
||||
|
||||
if hasGroupOverage {
|
||||
logger.Printf("entra overage found, reading groups from Graph API")
|
||||
logger.Info("entra overage found, reading groups from Graph API")
|
||||
if err = p.addGraphGroupsToSession(ctx, session); err != nil {
|
||||
return fmt.Errorf("unable to enrich session: %v", err)
|
||||
}
|
||||
|
|
@ -83,17 +83,17 @@ func (p *MicrosoftEntraIDProvider) EnrichSession(ctx context.Context, session *s
|
|||
func (p *MicrosoftEntraIDProvider) ValidateSession(ctx context.Context, session *sessions.SessionState) bool {
|
||||
tenant, err := p.getTenantFromToken(session)
|
||||
if err != nil {
|
||||
logger.Errorf("unable to retrieve entra tenant from token: %v", err)
|
||||
logger.ErrMsgf("unable to retrieve entra tenant from token: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if len(p.multiTenantAllowedTenants) > 0 {
|
||||
tenantAllowed := p.checkTenantMatchesTenantList(tenant, p.multiTenantAllowedTenants)
|
||||
if !tenantAllowed {
|
||||
logger.Printf("entra: tenant %s is not specified in the list of allowed tenants", tenant)
|
||||
logger.Info("entra: tenant not in allowed list", "tenant", tenant)
|
||||
return false
|
||||
}
|
||||
logger.Printf("entra: tenant %s is allowed", tenant)
|
||||
logger.Info("entra: tenant is allowed", "tenant", tenant)
|
||||
}
|
||||
|
||||
return p.OIDCProvider.ValidateSession(ctx, session)
|
||||
|
|
@ -247,7 +247,7 @@ func (p *MicrosoftEntraIDProvider) addGraphGroupsToSession(ctx context.Context,
|
|||
UnmarshalSimpleJSON()
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("invalid response from microsoft graph, no groups added to session: %v", err)
|
||||
logger.ErrMsgf("invalid response from microsoft graph, no groups added to session: %v", err)
|
||||
return nil
|
||||
}
|
||||
reqGroups := response.Get("value").MustArray()
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func (p *NextcloudProvider) EnrichSession(ctx context.Context, s *sessions.Sessi
|
|||
Do().
|
||||
UnmarshalSimpleJSON()
|
||||
if err != nil {
|
||||
logger.Errorf("failed making request %v", err)
|
||||
logger.ErrMsgf("failed making request %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,14 +120,14 @@ func (p *OIDCProvider) ValidateSession(ctx context.Context, s *sessions.SessionS
|
|||
if s.Refreshed {
|
||||
validateEndpointAvailable := p.Data().ValidateURL != nil && p.Data().ValidateURL.String() != ""
|
||||
if validateEndpointAvailable && !validateToken(ctx, p, s.AccessToken, makeOIDCHeader(s.AccessToken)) {
|
||||
logger.Errorf("access_token validation failed")
|
||||
logger.ErrMsg("access_token validation failed")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if _, err := p.Verifier.Verify(ctx, s.IDToken); err != nil {
|
||||
logger.Errorf("id_token verification failed: %v", err)
|
||||
logger.ErrMsgf("id_token verification failed: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ func (p *OIDCProvider) ValidateSession(ctx context.Context, s *sessions.SessionS
|
|||
}
|
||||
|
||||
if err := p.checkNonce(s); err != nil {
|
||||
logger.Errorf("nonce verification failed: %v", err)
|
||||
logger.ErrMsgf("nonce verification failed: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func (p *ProviderData) GetClientSecret() (clientSecret string, err error) {
|
|||
// Getting ClientSecret can fail in runtime so we need to report it without returning the file name to the user
|
||||
fileClientSecret, err := os.ReadFile(p.ClientSecretFile)
|
||||
if err != nil {
|
||||
logger.Errorf("error reading client secret file %s: %s", p.ClientSecretFile, err)
|
||||
logger.ErrMsgf("error reading client secret file %s: %s", p.ClientSecretFile, err)
|
||||
return "", errors.New("could not read client secret file")
|
||||
}
|
||||
return string(fileClientSecret), nil
|
||||
|
|
@ -331,7 +331,7 @@ func (p *ProviderData) extractAdditionalClaims(extractor util.ClaimExtractor, ss
|
|||
for _, claim := range p.AdditionalClaims {
|
||||
value, exists, err := extractor.GetClaim(claim)
|
||||
if err != nil {
|
||||
logger.Printf("error extracting additional claim %q: %v", claim, err)
|
||||
logger.Warn("error extracting additional claim", "claim", claim, "error", err)
|
||||
continue
|
||||
}
|
||||
if exists {
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData,
|
|||
// Set PKCE enabled or disabled based on discovery and force options
|
||||
p.CodeChallengeMethod = parseCodeChallengeMethod(providerConfig)
|
||||
if len(p.SupportedCodeChallengeMethods) != 0 && p.CodeChallengeMethod == "" {
|
||||
logger.Printf("Warning: Your provider supports PKCE methods %+q, but you have not enabled one with --code-challenge-method", p.SupportedCodeChallengeMethods)
|
||||
logger.Warn("provider supports PKCE but no code-challenge-method is enabled", "supported_methods", p.SupportedCodeChallengeMethods)
|
||||
}
|
||||
|
||||
if providerConfig.OIDCConfig.UserIDClaim == "" {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package providers_test
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -9,8 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestProviderSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
logger.Setup(slog.LevelDebug, "text", GinkgoWriter, GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Providers")
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ func (p *SourceHutProvider) EnrichSession(ctx context.Context, s *sessions.Sessi
|
|||
Do().
|
||||
UnmarshalSimpleJSON()
|
||||
if err != nil {
|
||||
logger.Errorf("failed making request %v", err)
|
||||
logger.ErrMsgf("failed making request %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
|
|||
m := make(map[string]bool)
|
||||
atomic.StorePointer(&um.m, unsafe.Pointer(&m)) // #nosec G103
|
||||
if usersFile != "" {
|
||||
logger.Printf("using authenticated emails file %s", usersFile)
|
||||
logger.Info("using authenticated emails file", "path", usersFile)
|
||||
watcher.WatchFileForUpdates(usersFile, done, func() {
|
||||
um.LoadAuthenticatedEmailsFile()
|
||||
onUpdate()
|
||||
|
|
@ -48,12 +48,12 @@ func (um *UserMap) IsValid(email string) (result bool) {
|
|||
func (um *UserMap) LoadAuthenticatedEmailsFile() {
|
||||
r, err := os.Open(um.usersFile)
|
||||
if err != nil {
|
||||
logger.Fatalf("failed opening authenticated-emails-file=%q, %s", um.usersFile, err)
|
||||
logger.FatalMsgf("failed opening authenticated-emails-file=%q, %s", um.usersFile, err)
|
||||
}
|
||||
defer func(c io.Closer) {
|
||||
cerr := c.Close()
|
||||
if cerr != nil {
|
||||
logger.Fatalf("Error closing authenticated emails file: %s", cerr)
|
||||
logger.FatalMsgf("error closing authenticated emails file: %s", cerr)
|
||||
}
|
||||
}(r)
|
||||
csvReader := csv.NewReader(r)
|
||||
|
|
@ -62,7 +62,7 @@ func (um *UserMap) LoadAuthenticatedEmailsFile() {
|
|||
csvReader.TrimLeadingSpace = true
|
||||
records, err := csvReader.ReadAll()
|
||||
if err != nil {
|
||||
logger.Errorf("error reading authenticated-emails-file=%q, %s", um.usersFile, err)
|
||||
logger.ErrMsgf("error reading authenticated-emails-file=%q, %s", um.usersFile, err)
|
||||
return
|
||||
}
|
||||
updated := make(map[string]bool)
|
||||
|
|
|
|||
Loading…
Reference in New Issue