git.iko.soy/typeable/oauth2-proxy
on v5.1.0 help sign in
655 lines 28 kB history raw
1package main
3import (
4 "context"
5 "crypto"
6 "crypto/tls"
7 "encoding/base64"
8 "fmt"
9 "io/ioutil"
10 "net/http"
11 "net/url"
12 "os"
13 "regexp"
14 "strings"
15 "time"
17 oidc "github.com/coreos/go-oidc"
18 "github.com/dgrijalva/jwt-go"
19 "github.com/mbland/hmacauth"
20 "github.com/pusher/oauth2_proxy/pkg/apis/options"
21 sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
22 "github.com/pusher/oauth2_proxy/pkg/encryption"
23 "github.com/pusher/oauth2_proxy/pkg/logger"
24 "github.com/pusher/oauth2_proxy/pkg/sessions"
25 "github.com/pusher/oauth2_proxy/providers"
26 "gopkg.in/natefinch/lumberjack.v2"
29// Options holds Configuration Options that can be set by Command Line Flag,
30// or Config File
31type Options struct {
32 ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy_prefix" env:"OAUTH2_PROXY_PROXY_PREFIX"`
33 PingPath string `flag:"ping-path" cfg:"ping_path" env:"OAUTH2_PROXY_PING_PATH"`
34 ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets" env:"OAUTH2_PROXY_PROXY_WEBSOCKETS"`
35 HTTPAddress string `flag:"http-address" cfg:"http_address" env:"OAUTH2_PROXY_HTTP_ADDRESS"`
36 HTTPSAddress string `flag:"https-address" cfg:"https_address" env:"OAUTH2_PROXY_HTTPS_ADDRESS"`
37 ReverseProxy bool `flag:"reverse-proxy" cfg:"reverse_proxy" env:"OAUTH2_PROXY_REVERSE_PROXY"`
38 ForceHTTPS bool `flag:"force-https" cfg:"force_https" env:"OAUTH2_PROXY_FORCE_HTTPS"`
39 RedirectURL string `flag:"redirect-url" cfg:"redirect_url" env:"OAUTH2_PROXY_REDIRECT_URL"`
40 ClientID string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"`
41 ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"`
42 ClientSecretFile string `flag:"client-secret-file" cfg:"client_secret_file" env:"OAUTH2_PROXY_CLIENT_SECRET_FILE"`
43 TLSCertFile string `flag:"tls-cert-file" cfg:"tls_cert_file" env:"OAUTH2_PROXY_TLS_CERT_FILE"`
44 TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file" env:"OAUTH2_PROXY_TLS_KEY_FILE"`
46 AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file" env:"OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE"`
47 KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group" env:"OAUTH2_PROXY_KEYCLOAK_GROUP"`
48 AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant" env:"OAUTH2_PROXY_AZURE_TENANT"`
49 BitbucketTeam string `flag:"bitbucket-team" cfg:"bitbucket_team" env:"OAUTH2_PROXY_BITBUCKET_TEAM"`
50 BitbucketRepository string `flag:"bitbucket-repository" cfg:"bitbucket_repository" env:"OAUTH2_PROXY_BITBUCKET_REPOSITORY"`
51 EmailDomains []string `flag:"email-domain" cfg:"email_domains" env:"OAUTH2_PROXY_EMAIL_DOMAINS"`
52 WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_domains" env:"OAUTH2_PROXY_WHITELIST_DOMAINS"`
53 GitHubOrg string `flag:"github-org" cfg:"github_org" env:"OAUTH2_PROXY_GITHUB_ORG"`
54 GitHubTeam string `flag:"github-team" cfg:"github_team" env:"OAUTH2_PROXY_GITHUB_TEAM"`
55 GitLabGroup string `flag:"gitlab-group" cfg:"gitlab_group" env:"OAUTH2_PROXY_GITLAB_GROUP"`
56 GoogleGroups []string `flag:"google-group" cfg:"google_group" env:"OAUTH2_PROXY_GOOGLE_GROUPS"`
57 GoogleAdminEmail string `flag:"google-admin-email" cfg:"google_admin_email" env:"OAUTH2_PROXY_GOOGLE_ADMIN_EMAIL"`
58 GoogleServiceAccountJSON string `flag:"google-service-account-json" cfg:"google_service_account_json" env:"OAUTH2_PROXY_GOOGLE_SERVICE_ACCOUNT_JSON"`
59 HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file" env:"OAUTH2_PROXY_HTPASSWD_FILE"`
60 DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form" env:"OAUTH2_PROXY_DISPLAY_HTPASSWD_FORM"`
61 CustomTemplatesDir string `flag:"custom-templates-dir" cfg:"custom_templates_dir" env:"OAUTH2_PROXY_CUSTOM_TEMPLATES_DIR"`
62 Banner string `flag:"banner" cfg:"banner" env:"OAUTH2_PROXY_BANNER"`
63 Footer string `flag:"footer" cfg:"footer" env:"OAUTH2_PROXY_FOOTER"`
65 // Embed CookieOptions
66 options.CookieOptions
68 // Embed SessionOptions
69 options.SessionOptions
71 Upstreams []string `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"`
72 SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"`
73 SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens" env:"OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS"`
74 ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers" env:"OAUTH2_PROXY_EXTRA_JWT_ISSUERS"`
75 PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth" env:"OAUTH2_PROXY_PASS_BASIC_AUTH"`
76 PreferEmailToUser bool `flag:"prefer-email-to-user" cfg:"prefer_email_to_user" env:"OAUTH2_PROXY_PREFER_EMAIL_TO_USER"`
77 BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password" env:"OAUTH2_PROXY_BASIC_AUTH_PASSWORD"`
78 PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN"`
79 PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header" env:"OAUTH2_PROXY_PASS_HOST_HEADER"`
80 SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button" env:"OAUTH2_PROXY_SKIP_PROVIDER_BUTTON"`
81 PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers" env:"OAUTH2_PROXY_PASS_USER_HEADERS"`
82 SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY"`
83 SSLUpstreamInsecureSkipVerify bool `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_UPSTREAM_INSECURE_SKIP_VERIFY"`
84 SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest" env:"OAUTH2_PROXY_SET_XAUTHREQUEST"`
85 SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header" env:"OAUTH2_PROXY_SET_AUTHORIZATION_HEADER"`
86 PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header" env:"OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER"`
87 SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight" env:"OAUTH2_PROXY_SKIP_AUTH_PREFLIGHT"`
88 FlushInterval time.Duration `flag:"flush-interval" cfg:"flush_interval" env:"OAUTH2_PROXY_FLUSH_INTERVAL"`
90 // These options allow for other providers besides Google, with
91 // potential overrides.
92 Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"`
93 ProviderName string `flag:"provider-display-name" cfg:"provider_display_name" env:"OAUTH2_PROXY_PROVIDER_DISPLAY_NAME"`
94 OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"`
95 InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email" env:"OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL"`
96 SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_PROXY_SKIP_OIDC_DISCOVERY"`
97 OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_PROXY_OIDC_JWKS_URL"`
98 LoginURL string `flag:"login-url" cfg:"login_url" env:"OAUTH2_PROXY_LOGIN_URL"`
99 RedeemURL string `flag:"redeem-url" cfg:"redeem_url" env:"OAUTH2_PROXY_REDEEM_URL"`
100 ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"`
101 ProtectedResource string `flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE"`
102 ValidateURL string `flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL"`
103 Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"`
104 Prompt string `flag:"prompt" cfg:"prompt" env:"OAUTH2_PROXY_PROMPT"`
105 ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"` // Deprecated by OIDC 1.0
107 // Configuration values for logging
108 LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_PROXY_LOGGING_FILENAME"`
109 LoggingMaxSize int `flag:"logging-max-size" cfg:"logging_max_size" env:"OAUTH2_PROXY_LOGGING_MAX_SIZE"`
110 LoggingMaxAge int `flag:"logging-max-age" cfg:"logging_max_age" env:"OAUTH2_PROXY_LOGGING_MAX_AGE"`
111 LoggingMaxBackups int `flag:"logging-max-backups" cfg:"logging_max_backups" env:"OAUTH2_PROXY_LOGGING_MAX_BACKUPS"`
112 LoggingLocalTime bool `flag:"logging-local-time" cfg:"logging_local_time" env:"OAUTH2_PROXY_LOGGING_LOCAL_TIME"`
113 LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress" env:"OAUTH2_PROXY_LOGGING_COMPRESS"`
114 StandardLogging bool `flag:"standard-logging" cfg:"standard_logging" env:"OAUTH2_PROXY_STANDARD_LOGGING"`
115 StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_PROXY_STANDARD_LOGGING_FORMAT"`
116 RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"`
117 RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"`
118 ExcludeLoggingPaths string `flag:"exclude-logging-paths" cfg:"exclude_logging_paths" env:"OAUTH2_PROXY_EXCLUDE_LOGGING_PATHS"`
119 SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING"`
120 AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_PROXY_LOGGING_AUTH_LOGGING"`
121 AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_PROXY_AUTH_LOGGING_FORMAT"`
122 SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"`
123 AcrValues string `flag:"acr-values" cfg:"acr_values" env:"OAUTH2_PROXY_ACR_VALUES"`
124 JWTKey string `flag:"jwt-key" cfg:"jwt_key" env:"OAUTH2_PROXY_JWT_KEY"`
125 JWTKeyFile string `flag:"jwt-key-file" cfg:"jwt_key_file" env:"OAUTH2_PROXY_JWT_KEY_FILE"`
126 PubJWKURL string `flag:"pubjwk-url" cfg:"pubjwk_url" env:"OAUTH2_PROXY_PUBJWK_URL"`
127 GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks" env:"OAUTH2_PROXY_GCP_HEALTHCHECKS"`
129 // internal values that are set after config validation
130 redirectURL *url.URL
131 proxyURLs []*url.URL
132 CompiledRegex []*regexp.Regexp
133 provider providers.Provider
134 sessionStore sessionsapi.SessionStore
135 signatureData *SignatureData
136 oidcVerifier *oidc.IDTokenVerifier
137 jwtBearerVerifiers []*oidc.IDTokenVerifier
140// SignatureData holds hmacauth signature hash and key
141type SignatureData struct {
142 hash crypto.Hash
143 key string
146// NewOptions constructs a new Options with defaulted values
147func NewOptions() *Options {
148 return &Options{
149 ProxyPrefix: "/oauth2",
150 PingPath: "/ping",
151 ProxyWebSockets: true,
152 HTTPAddress: "127.0.0.1:4180",
153 HTTPSAddress: ":443",
154 ForceHTTPS: false,
155 DisplayHtpasswdForm: true,
156 CookieOptions: options.CookieOptions{
157 CookieName: "_oauth2_proxy",
158 CookieSecure: true,
159 CookieHTTPOnly: true,
160 CookieExpire: time.Duration(168) * time.Hour,
161 CookieRefresh: time.Duration(0),
162 },
163 SessionOptions: options.SessionOptions{
164 Type: "cookie",
165 },
166 SetXAuthRequest: false,
167 SkipAuthPreflight: false,
168 PassBasicAuth: true,
169 PassUserHeaders: true,
170 PassAccessToken: false,
171 PassHostHeader: true,
172 SetAuthorization: false,
173 PassAuthorization: false,
174 PreferEmailToUser: false,
175 Prompt: "", // Change to "login" when ApprovalPrompt officially deprecated
176 ApprovalPrompt: "force",
177 InsecureOIDCAllowUnverifiedEmail: false,
178 SkipOIDCDiscovery: false,
179 LoggingFilename: "",
180 LoggingMaxSize: 100,
181 LoggingMaxAge: 7,
182 LoggingMaxBackups: 0,
183 LoggingLocalTime: true,
184 LoggingCompress: false,
185 ExcludeLoggingPaths: "",
186 SilencePingLogging: false,
187 StandardLogging: true,
188 StandardLoggingFormat: logger.DefaultStandardLoggingFormat,
189 RequestLogging: true,
190 RequestLoggingFormat: logger.DefaultRequestLoggingFormat,
191 AuthLogging: true,
192 AuthLoggingFormat: logger.DefaultAuthLoggingFormat,
193 }
196// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
197type jwtIssuer struct {
198 issuerURI string
199 audience string
202func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string) {
203 parsed, err := url.Parse(toParse)
204 if err != nil {
205 return nil, append(msgs, fmt.Sprintf(
206 "error parsing %s-url=%q %s", urltype, toParse, err))
207 }
208 return parsed, msgs
211// Validate checks that required options are set and validates those that they
212// are of the correct format
213func (o *Options) Validate() error {
214 if o.SSLInsecureSkipVerify {
215 // TODO: Accept a certificate bundle.
216 insecureTransport := &http.Transport{
217 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
218 }
219 http.DefaultClient = &http.Client{Transport: insecureTransport}
220 }
222 msgs := make([]string, 0)
223 if o.CookieSecret == "" {
224 msgs = append(msgs, "missing setting: cookie-secret")
225 }
226 if o.ClientID == "" {
227 msgs = append(msgs, "missing setting: client-id")
228 }
229 // login.gov uses a signed JWT to authenticate, not a client-secret
230 if o.Provider != "login.gov" {
231 if o.ClientSecret == "" && o.ClientSecretFile == "" {
232 msgs = append(msgs, "missing setting: client-secret or client-secret-file")
233 }
234 if o.ClientSecret == "" && o.ClientSecretFile != "" {
235 _, err := ioutil.ReadFile(o.ClientSecretFile)
236 if err != nil {
237 msgs = append(msgs, "could not read client secret file: "+o.ClientSecretFile)
238 }
239 }
240 }
241 if o.AuthenticatedEmailsFile == "" && len(o.EmailDomains) == 0 && o.HtpasswdFile == "" {
242 msgs = append(msgs, "missing setting for email validation: email-domain or authenticated-emails-file required."+
243 "\n use email-domain=* to authorize all email addresses")
244 }
246 if o.OIDCIssuerURL != "" {
248 ctx := context.Background()
250 // Construct a manual IDTokenVerifier from issuer URL & JWKS URI
251 // instead of metadata discovery if we enable -skip-oidc-discovery.
252 // In this case we need to make sure the required endpoints for
253 // the provider are configured.
254 if o.SkipOIDCDiscovery {
255 if o.LoginURL == "" {
256 msgs = append(msgs, "missing setting: login-url")
257 }
258 if o.RedeemURL == "" {
259 msgs = append(msgs, "missing setting: redeem-url")
260 }
261 if o.OIDCJwksURL == "" {
262 msgs = append(msgs, "missing setting: oidc-jwks-url")
263 }
264 keySet := oidc.NewRemoteKeySet(ctx, o.OIDCJwksURL)
265 o.oidcVerifier = oidc.NewVerifier(o.OIDCIssuerURL, keySet, &oidc.Config{
266 ClientID: o.ClientID,
267 })
268 } else {
269 // Configure discoverable provider data.
270 provider, err := oidc.NewProvider(ctx, o.OIDCIssuerURL)
271 if err != nil {
272 return err
273 }
274 o.oidcVerifier = provider.Verifier(&oidc.Config{
275 ClientID: o.ClientID,
276 })
278 o.LoginURL = provider.Endpoint().AuthURL
279 o.RedeemURL = provider.Endpoint().TokenURL
280 }
281 if o.Scope == "" {
282 o.Scope = "openid email profile"
283 }
284 }
286 if o.PreferEmailToUser == true && o.PassBasicAuth == false && o.PassUserHeaders == false {
287 msgs = append(msgs, "PreferEmailToUser should only be used with PassBasicAuth or PassUserHeaders")
288 }
290 if o.SkipJwtBearerTokens {
291 // If we are using an oidc provider, go ahead and add that provider to the list
292 if o.oidcVerifier != nil {
293 o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, o.oidcVerifier)
294 }
295 // Configure extra issuers
296 if len(o.ExtraJwtIssuers) > 0 {
297 var jwtIssuers []jwtIssuer
298 jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs)
299 for _, jwtIssuer := range jwtIssuers {
300 verifier, err := newVerifierFromJwtIssuer(jwtIssuer)
301 if err != nil {
302 msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err))
303 }
304 o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, verifier)
305 }
306 }
307 }
309 o.redirectURL, msgs = parseURL(o.RedirectURL, "redirect", msgs)
311 for _, u := range o.Upstreams {
312 upstreamURL, err := url.Parse(u)
313 if err != nil {
314 msgs = append(msgs, fmt.Sprintf("error parsing upstream: %s", err))
315 } else {
316 if upstreamURL.Path == "" {
317 upstreamURL.Path = "/"
318 }
319 o.proxyURLs = append(o.proxyURLs, upstreamURL)
320 }
321 }
323 for _, u := range o.SkipAuthRegex {
324 CompiledRegex, err := regexp.Compile(u)
325 if err != nil {
326 msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err))
327 continue
328 }
329 o.CompiledRegex = append(o.CompiledRegex, CompiledRegex)
330 }
331 msgs = parseProviderInfo(o, msgs)
333 var cipher *encryption.Cipher
334 if o.PassAccessToken || o.SetAuthorization || o.PassAuthorization || (o.CookieRefresh != time.Duration(0)) {
335 validCookieSecretSize := false
336 for _, i := range []int{16, 24, 32} {
337 if len(secretBytes(o.CookieSecret)) == i {
338 validCookieSecretSize = true
339 }
340 }
341 var decoded bool
342 if string(secretBytes(o.CookieSecret)) != o.CookieSecret {
343 decoded = true
344 }
345 if validCookieSecretSize == false {
346 var suffix string
347 if decoded {
348 suffix = fmt.Sprintf(" note: cookie secret was base64 decoded from %q", o.CookieSecret)
349 }
350 msgs = append(msgs, fmt.Sprintf(
351 "cookie_secret must be 16, 24, or 32 bytes "+
352 "to create an AES cipher when "+
353 "pass_access_token == true or "+
354 "cookie_refresh != 0, but is %d bytes.%s",
355 len(secretBytes(o.CookieSecret)), suffix))
356 } else {
357 var err error
358 cipher, err = encryption.NewCipher(secretBytes(o.CookieSecret))
359 if err != nil {
360 msgs = append(msgs, fmt.Sprintf("cookie-secret error: %v", err))
361 }
362 }
363 }
365 o.SessionOptions.Cipher = cipher
366 sessionStore, err := sessions.NewSessionStore(&o.SessionOptions, &o.CookieOptions)
367 if err != nil {
368 msgs = append(msgs, fmt.Sprintf("error initialising session storage: %v", err))
369 } else {
370 o.sessionStore = sessionStore
371 }
373 if o.CookieRefresh >= o.CookieExpire {
374 msgs = append(msgs, fmt.Sprintf(
375 "cookie_refresh (%s) must be less than "+
376 "cookie_expire (%s)",
377 o.CookieRefresh.String(),
378 o.CookieExpire.String()))
379 }
381 if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" {
382 if len(o.GoogleGroups) < 1 {
383 msgs = append(msgs, "missing setting: google-group")
384 }
385 if o.GoogleAdminEmail == "" {
386 msgs = append(msgs, "missing setting: google-admin-email")
387 }
388 if o.GoogleServiceAccountJSON == "" {
389 msgs = append(msgs, "missing setting: google-service-account-json")
390 }
391 }
393 switch o.CookieSameSite {
394 case "", "none", "lax", "strict":
395 default:
396 msgs = append(msgs, fmt.Sprintf("cookie_samesite (%s) must be one of ['', 'lax', 'strict', 'none']", o.CookieSameSite))
397 }
399 msgs = parseSignatureKey(o, msgs)
400 msgs = validateCookieName(o, msgs)
401 msgs = setupLogger(o, msgs)
403 if len(msgs) != 0 {
404 return fmt.Errorf("Invalid configuration:\n %s",
405 strings.Join(msgs, "\n "))
406 }
407 return nil
410func parseProviderInfo(o *Options, msgs []string) []string {
411 p := &providers.ProviderData{
412 Scope: o.Scope,
413 ClientID: o.ClientID,
414 ClientSecret: o.ClientSecret,
415 ClientSecretFile: o.ClientSecretFile,
416 Prompt: o.Prompt,
417 ApprovalPrompt: o.ApprovalPrompt,
418 AcrValues: o.AcrValues,
419 }
420 p.LoginURL, msgs = parseURL(o.LoginURL, "login", msgs)
421 p.RedeemURL, msgs = parseURL(o.RedeemURL, "redeem", msgs)
422 p.ProfileURL, msgs = parseURL(o.ProfileURL, "profile", msgs)
423 p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs)
424 p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs)
426 o.provider = providers.New(o.Provider, p)
427 switch p := o.provider.(type) {
428 case *providers.AzureProvider:
429 p.Configure(o.AzureTenant)
430 case *providers.GitHubProvider:
431 p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam)
432 case *providers.KeycloakProvider:
433 p.SetGroup(o.KeycloakGroup)
434 case *providers.GoogleProvider:
435 if o.GoogleServiceAccountJSON != "" {
436 file, err := os.Open(o.GoogleServiceAccountJSON)
437 if err != nil {
438 msgs = append(msgs, "invalid Google credentials file: "+o.GoogleServiceAccountJSON)
439 } else {
440 p.SetGroupRestriction(o.GoogleGroups, o.GoogleAdminEmail, file)
441 }
442 }
443 case *providers.BitbucketProvider:
444 p.SetTeam(o.BitbucketTeam)
445 p.SetRepository(o.BitbucketRepository)
446 case *providers.OIDCProvider:
447 p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
448 if o.oidcVerifier == nil {
449 msgs = append(msgs, "oidc provider requires an oidc issuer URL")
450 } else {
451 p.Verifier = o.oidcVerifier
452 }
453 case *providers.GitLabProvider:
454 p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
455 p.Group = o.GitLabGroup
456 p.EmailDomains = o.EmailDomains
458 if o.oidcVerifier != nil {
459 p.Verifier = o.oidcVerifier
460 } else {
461 // Initialize with default verifier for gitlab.com
462 ctx := context.Background()
464 provider, err := oidc.NewProvider(ctx, "https://gitlab.com")
465 if err != nil {
466 msgs = append(msgs, "failed to initialize oidc provider for gitlab.com")
467 } else {
468 p.Verifier = provider.Verifier(&oidc.Config{
469 ClientID: o.ClientID,
470 })
472 p.LoginURL, msgs = parseURL(provider.Endpoint().AuthURL, "login", msgs)
473 p.RedeemURL, msgs = parseURL(provider.Endpoint().TokenURL, "redeem", msgs)
474 }
475 }
476 case *providers.LoginGovProvider:
477 p.PubJWKURL, msgs = parseURL(o.PubJWKURL, "pubjwk", msgs)
479 // JWT key can be supplied via env variable or file in the filesystem, but not both.
480 switch {
481 case o.JWTKey != "" && o.JWTKeyFile != "":
482 msgs = append(msgs, "cannot set both jwt-key and jwt-key-file options")
483 case o.JWTKey == "" && o.JWTKeyFile == "":
484 msgs = append(msgs, "login.gov provider requires a private key for signing JWTs")
485 case o.JWTKey != "":
486 // The JWT Key is in the commandline argument
487 signKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(o.JWTKey))
488 if err != nil {
489 msgs = append(msgs, "could not parse RSA Private Key PEM")
490 } else {
491 p.JWTKey = signKey
492 }
493 case o.JWTKeyFile != "":
494 // The JWT key is in the filesystem
495 keyData, err := ioutil.ReadFile(o.JWTKeyFile)
496 if err != nil {
497 msgs = append(msgs, "could not read key file: "+o.JWTKeyFile)
498 }
499 signKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
500 if err != nil {
501 msgs = append(msgs, "could not parse private key from PEM file:"+o.JWTKeyFile)
502 } else {
503 p.JWTKey = signKey
504 }
505 }
506 }
507 return msgs
510func parseSignatureKey(o *Options, msgs []string) []string {
511 if o.SignatureKey == "" {
512 return msgs
513 }
515 components := strings.Split(o.SignatureKey, ":")
516 if len(components) != 2 {
517 return append(msgs, "invalid signature hash:key spec: "+
518 o.SignatureKey)
519 }
521 algorithm, secretKey := components[0], components[1]
522 var hash crypto.Hash
523 var err error
524 if hash, err = hmacauth.DigestNameToCryptoHash(algorithm); err != nil {
525 return append(msgs, "unsupported signature hash algorithm: "+
526 o.SignatureKey)
527 }
528 o.signatureData = &SignatureData{hash: hash, key: secretKey}
529 return msgs
532// parseJwtIssuers takes in an array of strings in the form of issuer=audience
533// and parses to an array of jwtIssuer structs.
534func parseJwtIssuers(issuers []string, msgs []string) ([]jwtIssuer, []string) {
535 var parsedIssuers []jwtIssuer
536 for _, jwtVerifier := range issuers {
537 components := strings.Split(jwtVerifier, "=")
538 if len(components) < 2 {
539 msgs = append(msgs, fmt.Sprintf("invalid jwt verifier uri=audience spec: %s", jwtVerifier))
540 continue
541 }
542 uri, audience := components[0], strings.Join(components[1:], "=")
543 parsedIssuers = append(parsedIssuers, jwtIssuer{issuerURI: uri, audience: audience})
544 }
545 return parsedIssuers, msgs
548// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns
549// a verifier for that issuer.
550func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error) {
551 config := &oidc.Config{
552 ClientID: jwtIssuer.audience,
553 }
554 // Try as an OpenID Connect Provider first
555 var verifier *oidc.IDTokenVerifier
556 provider, err := oidc.NewProvider(context.Background(), jwtIssuer.issuerURI)
557 if err != nil {
558 // Try as JWKS URI
559 jwksURI := strings.TrimSuffix(jwtIssuer.issuerURI, "/") + "/.well-known/jwks.json"
560 _, err := http.NewRequest("GET", jwksURI, nil)
561 if err != nil {
562 return nil, err
563 }
564 verifier = oidc.NewVerifier(jwtIssuer.issuerURI, oidc.NewRemoteKeySet(context.Background(), jwksURI), config)
565 } else {
566 verifier = provider.Verifier(config)
567 }
568 return verifier, nil
571func validateCookieName(o *Options, msgs []string) []string {
572 cookie := &http.Cookie{Name: o.CookieName}
573 if cookie.String() == "" {
574 return append(msgs, fmt.Sprintf("invalid cookie name: %q", o.CookieName))
575 }
576 return msgs
579func addPadding(secret string) string {
580 padding := len(secret) % 4
581 switch padding {
582 case 1:
583 return secret + "==="
584 case 2:
585 return secret + "=="
586 case 3:
587 return secret + "="
588 default:
589 return secret
590 }
593// secretBytes attempts to base64 decode the secret, if that fails it treats the secret as binary
594func secretBytes(secret string) []byte {
595 b, err := base64.URLEncoding.DecodeString(addPadding(secret))
596 if err == nil {
597 return []byte(addPadding(string(b)))
598 }
599 return []byte(secret)
602func setupLogger(o *Options, msgs []string) []string {
603 // Setup the log file
604 if len(o.LoggingFilename) > 0 {
605 // Validate that the file/dir can be written
606 file, err := os.OpenFile(o.LoggingFilename, os.O_WRONLY|os.O_CREATE, 0666)
607 if err != nil {
608 if os.IsPermission(err) {
609 return append(msgs, "unable to write to log file: "+o.LoggingFilename)
610 }
611 }
612 file.Close()
614 logger.Printf("Redirecting logging to file: %s", o.LoggingFilename)
616 logWriter := &lumberjack.Logger{
617 Filename: o.LoggingFilename,
618 MaxSize: o.LoggingMaxSize, // megabytes
619 MaxAge: o.LoggingMaxAge, // days
620 MaxBackups: o.LoggingMaxBackups,
621 LocalTime: o.LoggingLocalTime,
622 Compress: o.LoggingCompress,
623 }
625 logger.SetOutput(logWriter)
626 }
628 // Supply a sanity warning to the logger if all logging is disabled
629 if !o.StandardLogging && !o.AuthLogging && !o.RequestLogging {
630 logger.Print("Warning: Logging disabled. No further logs will be shown.")
631 }
633 // Pass configuration values to the standard logger
634 logger.SetStandardEnabled(o.StandardLogging)
635 logger.SetAuthEnabled(o.AuthLogging)
636 logger.SetReqEnabled(o.RequestLogging)
637 logger.SetStandardTemplate(o.StandardLoggingFormat)
638 logger.SetAuthTemplate(o.AuthLoggingFormat)
639 logger.SetReqTemplate(o.RequestLoggingFormat)
640 logger.SetReverseProxy(o.ReverseProxy)
642 excludePaths := make([]string, 0)
643 excludePaths = append(excludePaths, strings.Split(o.ExcludeLoggingPaths, ",")...)
644 if o.SilencePingLogging {
645 excludePaths = append(excludePaths, o.PingPath)
646 }
648 logger.SetExcludePaths(excludePaths)
650 if !o.LoggingLocalTime {
651 logger.SetFlags(logger.Flags() | logger.LUTC)
652 }
654 return msgs