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