14 "github.com/coreos/go-oidc"
15 "github.com/dgrijalva/jwt-go"
16 "github.com/mbland/hmacauth"
17 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
18 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
19 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
20 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
21 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
22 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
25// Validate checks that required options are set and validates those that they
26// are of the correct format
27func Validate(o *options.Options) error {
28 msgs := validateCookie(o.Cookie)
29 msgs = append(msgs, validateSessionCookieMinimal(o)...)
30 msgs = append(msgs, validateRedisSessionStore(o)...)
31 msgs = append(msgs, prefixValues("injectRequestHeaders: ", validateHeaders(o.InjectRequestHeaders)...)...)
32 msgs = append(msgs, prefixValues("injectResponseHeaders: ", validateHeaders(o.InjectResponseHeaders)...)...)
34 if o.SSLInsecureSkipVerify {
35 // InsecureSkipVerify is a configurable option we allow
37 insecureTransport := &http.Transport{
38 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
40 http.DefaultClient = &http.Client{Transport: insecureTransport}
41 } else if len(o.ProviderCAFiles) > 0 {
42 pool, err := util.GetCertPool(o.ProviderCAFiles)
44 transport := &http.Transport{
45 TLSClientConfig: &tls.Config{
50 http.DefaultClient = &http.Client{Transport: transport}
52 msgs = append(msgs, fmt.Sprintf("unable to load provider CA file(s): %v", err))
57 msgs = append(msgs, "missing setting: client-id")
59 // login.gov uses a signed JWT to authenticate, not a client-secret
60 if o.ProviderType != "login.gov" {
61 if o.ClientSecret == "" && o.ClientSecretFile == "" {
62 msgs = append(msgs, "missing setting: client-secret or client-secret-file")
64 if o.ClientSecret == "" && o.ClientSecretFile != "" {
65 _, err := ioutil.ReadFile(o.ClientSecretFile)
67 msgs = append(msgs, "could not read client secret file: "+o.ClientSecretFile)
71 if o.AuthenticatedEmailsFile == "" && len(o.EmailDomains) == 0 && o.HtpasswdFile == "" {
72 msgs = append(msgs, "missing setting for email validation: email-domain or authenticated-emails-file required."+
73 "\n use email-domain=* to authorize all email addresses")
76 if o.OIDCIssuerURL != "" {
78 ctx := context.Background()
80 if o.InsecureOIDCSkipIssuerVerification && !o.SkipOIDCDiscovery {
81 // go-oidc doesn't let us pass bypass the issuer check this in the oidc.NewProvider call
82 // (which uses discovery to get the URLs), so we'll do a quick check ourselves and if
83 // we get the URLs, we'll just use the non-discovery path.
85 logger.Printf("Performing OIDC Discovery...")
87 requestURL := strings.TrimSuffix(o.OIDCIssuerURL, "/") + "/.well-known/openid-configuration"
88 body, err := requests.New(requestURL).
93 logger.Errorf("error: failed to discover OIDC configuration: %v", err)
95 // Prefer manually configured URLs. It's a bit unclear
96 // why you'd be doing discovery and also providing the URLs
97 // explicitly though...
99 o.LoginURL = body.Get("authorization_endpoint").MustString()
102 if o.RedeemURL == "" {
103 o.RedeemURL = body.Get("token_endpoint").MustString()
106 if o.OIDCJwksURL == "" {
107 o.OIDCJwksURL = body.Get("jwks_uri").MustString()
110 if o.ProfileURL == "" {
111 o.ProfileURL = body.Get("userinfo_endpoint").MustString()
114 o.SkipOIDCDiscovery = true
118 // Construct a manual IDTokenVerifier from issuer URL & JWKS URI
119 // instead of metadata discovery if we enable -skip-oidc-discovery.
120 // In this case we need to make sure the required endpoints for
121 // the provider are configured.
122 if o.SkipOIDCDiscovery {
123 if o.LoginURL == "" {
124 msgs = append(msgs, "missing setting: login-url")
126 if o.RedeemURL == "" {
127 msgs = append(msgs, "missing setting: redeem-url")
129 if o.OIDCJwksURL == "" {
130 msgs = append(msgs, "missing setting: oidc-jwks-url")
132 keySet := oidc.NewRemoteKeySet(ctx, o.OIDCJwksURL)
133 o.SetOIDCVerifier(oidc.NewVerifier(o.OIDCIssuerURL, keySet, &oidc.Config{
134 ClientID: o.ClientID,
135 SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
138 // Configure discoverable provider data.
139 provider, err := oidc.NewProvider(ctx, o.OIDCIssuerURL)
143 o.SetOIDCVerifier(provider.Verifier(&oidc.Config{
144 ClientID: o.ClientID,
145 SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
148 o.LoginURL = provider.Endpoint().AuthURL
149 o.RedeemURL = provider.Endpoint().TokenURL
152 o.Scope = "openid email profile"
154 if len(o.AllowedGroups) > 0 {
160 if o.SkipJwtBearerTokens {
161 // Configure extra issuers
162 if len(o.ExtraJwtIssuers) > 0 {
163 var jwtIssuers []jwtIssuer
164 jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs)
165 for _, jwtIssuer := range jwtIssuers {
166 verifier, err := newVerifierFromJwtIssuer(jwtIssuer)
168 msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err))
170 o.SetJWTBearerVerifiers(append(o.GetJWTBearerVerifiers(), verifier))
175 var redirectURL *url.URL
176 redirectURL, msgs = parseURL(o.RawRedirectURL, "redirect", msgs)
177 o.SetRedirectURL(redirectURL)
179 msgs = append(msgs, validateUpstreams(o.UpstreamServers)...)
180 msgs = parseProviderInfo(o, msgs)
182 if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" {
183 if len(o.GoogleGroups) < 1 {
184 msgs = append(msgs, "missing setting: google-group")
186 if o.GoogleAdminEmail == "" {
187 msgs = append(msgs, "missing setting: google-admin-email")
189 if o.GoogleServiceAccountJSON == "" {
190 msgs = append(msgs, "missing setting: google-service-account-json")
194 msgs = parseSignatureKey(o, msgs)
195 msgs = configureLogger(o.Logging, msgs)
198 parser, err := ip.GetRealClientIPParser(o.RealClientIPHeader)
200 msgs = append(msgs, fmt.Sprintf("real_client_ip_header (%s) not accepted parameter value: %v", o.RealClientIPHeader, err))
202 o.SetRealClientIPParser(parser)
204 // Allow the logger to get client IPs
205 logger.SetGetClientFunc(func(r *http.Request) string {
206 return ip.GetClientString(o.GetRealClientIPParser(), r, false)
210 // Do this after ReverseProxy validation for TrustedIP coordinated checks
211 msgs = append(msgs, validateAllowlists(o)...)
214 return fmt.Errorf("invalid configuration:\n %s",
215 strings.Join(msgs, "\n "))
220func parseProviderInfo(o *options.Options, msgs []string) []string {
221 p := &providers.ProviderData{
223 ClientID: o.ClientID,
224 ClientSecret: o.ClientSecret,
225 ClientSecretFile: o.ClientSecretFile,
227 ApprovalPrompt: o.ApprovalPrompt,
228 AcrValues: o.AcrValues,
230 p.LoginURL, msgs = parseURL(o.LoginURL, "login", msgs)
231 p.RedeemURL, msgs = parseURL(o.RedeemURL, "redeem", msgs)
232 p.ProfileURL, msgs = parseURL(o.ProfileURL, "profile", msgs)
233 p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs)
234 p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs)
236 // Make the OIDC options available to all providers that support it
237 p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
238 p.EmailClaim = o.OIDCEmailClaim
239 p.GroupsClaim = o.OIDCGroupsClaim
240 p.Verifier = o.GetOIDCVerifier()
242 // TODO (@NickMeves) - Remove This
243 // Backwards Compatibility for Deprecated UserIDClaim option
244 if o.OIDCEmailClaim == providers.OIDCEmailClaim &&
245 o.UserIDClaim != providers.OIDCEmailClaim {
246 p.EmailClaim = o.UserIDClaim
249 p.SetAllowedGroups(o.AllowedGroups)
251 provider := providers.New(o.ProviderType, p)
253 msgs = append(msgs, fmt.Sprintf("invalid setting: provider '%s' is not available", o.ProviderType))
256 o.SetProvider(provider)
258 switch p := o.GetProvider().(type) {
259 case *providers.AzureProvider:
260 p.Configure(o.AzureTenant)
261 case *providers.GitHubProvider:
262 p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam)
263 p.SetRepo(o.GitHubRepo, o.GitHubToken)
264 p.SetUsers(o.GitHubUsers)
265 case *providers.KeycloakProvider:
266 // Backwards compatibility with `--keycloak-group` option
267 if len(o.KeycloakGroups) > 0 {
268 p.SetAllowedGroups(o.KeycloakGroups)
270 case *providers.GoogleProvider:
271 if o.GoogleServiceAccountJSON != "" {
272 file, err := os.Open(o.GoogleServiceAccountJSON)
274 msgs = append(msgs, "invalid Google credentials file: "+o.GoogleServiceAccountJSON)
276 groups := o.AllowedGroups
277 // Backwards compatibility with `--google-group` option
278 if len(o.GoogleGroups) > 0 {
279 groups = o.GoogleGroups
280 p.SetAllowedGroups(groups)
282 p.SetGroupRestriction(groups, o.GoogleAdminEmail, file)
285 case *providers.BitbucketProvider:
286 p.SetTeam(o.BitbucketTeam)
287 p.SetRepository(o.BitbucketRepository)
288 case *providers.OIDCProvider:
289 if p.Verifier == nil {
290 msgs = append(msgs, "oidc provider requires an oidc issuer URL")
292 case *providers.GitLabProvider:
293 p.Groups = o.GitLabGroup
294 err := p.AddProjects(o.GitlabProjects)
296 msgs = append(msgs, "failed to setup gitlab project access level")
298 p.SetAllowedGroups(p.PrefixAllowedGroups())
301 if p.Verifier == nil {
302 // Initialize with default verifier for gitlab.com
303 ctx := context.Background()
305 provider, err := oidc.NewProvider(ctx, "https://gitlab.com")
307 msgs = append(msgs, "failed to initialize oidc provider for gitlab.com")
309 p.Verifier = provider.Verifier(&oidc.Config{
310 ClientID: o.ClientID,
313 p.LoginURL, msgs = parseURL(provider.Endpoint().AuthURL, "login", msgs)
314 p.RedeemURL, msgs = parseURL(provider.Endpoint().TokenURL, "redeem", msgs)
317 case *providers.LoginGovProvider:
318 p.PubJWKURL, msgs = parseURL(o.PubJWKURL, "pubjwk", msgs)
320 // JWT key can be supplied via env variable or file in the filesystem, but not both.
322 case o.JWTKey != "" && o.JWTKeyFile != "":
323 msgs = append(msgs, "cannot set both jwt-key and jwt-key-file options")
324 case o.JWTKey == "" && o.JWTKeyFile == "":
325 msgs = append(msgs, "login.gov provider requires a private key for signing JWTs")
327 // The JWT Key is in the commandline argument
328 signKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(o.JWTKey))
330 msgs = append(msgs, "could not parse RSA Private Key PEM")
334 case o.JWTKeyFile != "":
335 // The JWT key is in the filesystem
336 keyData, err := ioutil.ReadFile(o.JWTKeyFile)
338 msgs = append(msgs, "could not read key file: "+o.JWTKeyFile)
340 signKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
342 msgs = append(msgs, "could not parse private key from PEM file:"+o.JWTKeyFile)
351func parseSignatureKey(o *options.Options, msgs []string) []string {
352 if o.SignatureKey == "" {
356 components := strings.Split(o.SignatureKey, ":")
357 if len(components) != 2 {
358 return append(msgs, "invalid signature hash:key spec: "+
362 algorithm, secretKey := components[0], components[1]
365 if hash, err = hmacauth.DigestNameToCryptoHash(algorithm); err != nil {
366 return append(msgs, "unsupported signature hash algorithm: "+
369 o.SetSignatureData(&options.SignatureData{Hash: hash, Key: secretKey})
373// parseJwtIssuers takes in an array of strings in the form of issuer=audience
374// and parses to an array of jwtIssuer structs.
375func parseJwtIssuers(issuers []string, msgs []string) ([]jwtIssuer, []string) {
376 parsedIssuers := make([]jwtIssuer, 0, len(issuers))
377 for _, jwtVerifier := range issuers {
378 components := strings.Split(jwtVerifier, "=")
379 if len(components) < 2 {
380 msgs = append(msgs, fmt.Sprintf("invalid jwt verifier uri=audience spec: %s", jwtVerifier))
383 uri, audience := components[0], strings.Join(components[1:], "=")
384 parsedIssuers = append(parsedIssuers, jwtIssuer{issuerURI: uri, audience: audience})
386 return parsedIssuers, msgs
389// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns
390// a verifier for that issuer.
391func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error) {
392 config := &oidc.Config{
393 ClientID: jwtIssuer.audience,
395 // Try as an OpenID Connect Provider first
396 var verifier *oidc.IDTokenVerifier
397 provider, err := oidc.NewProvider(context.Background(), jwtIssuer.issuerURI)
400 jwksURI := strings.TrimSuffix(jwtIssuer.issuerURI, "/") + "/.well-known/jwks.json"
401 if err := requests.New(jwksURI).Do().Error(); err != nil {
405 verifier = oidc.NewVerifier(jwtIssuer.issuerURI, oidc.NewRemoteKeySet(context.Background(), jwksURI), config)
407 verifier = provider.Verifier(config)
412// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
413type jwtIssuer struct {
418func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string) {
419 parsed, err := url.Parse(toParse)
421 return nil, append(msgs, fmt.Sprintf(
422 "error parsing %s-url=%q %s", urltype, toParse, err))
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list