diff --git a/README.md b/README.md index 5d2a436e7..35447bd68 100644 --- a/README.md +++ b/README.md @@ -1519,6 +1519,7 @@ security: | `security.oidc.client-secret` | Client secret | Required `""` | | `security.oidc.scopes` | Scopes to request. The only scope you need is `openid`. | Required `[]` | | `security.oidc.allowed-subjects` | List of subjects to allow. If empty, all subjects are allowed. | `[]` | +| `security.oidc.claim-to-check` | Name of the field to use to match against `allowed-subjects` | `""` | ```yaml security: @@ -1530,6 +1531,8 @@ security: scopes: ["openid"] # You may optionally specify a list of allowed subjects. If this is not specified, all subjects will be allowed. #allowed-subjects: ["johndoe@example.com"] + # You can specify a different claim field to match against allowed-subjects. If not specified "subject" is used. + #claim-to-check: "preferred_username" ``` Confused? Read [Securing Gatus with OIDC using Auth0](https://twin.sh/articles/56/securing-gatus-with-oidc-using-auth0). diff --git a/security/oidc.go b/security/oidc.go index f1cc2c476..03fe9b566 100644 --- a/security/oidc.go +++ b/security/oidc.go @@ -21,6 +21,7 @@ type OIDCConfig struct { ClientSecret string `yaml:"client-secret"` Scopes []string `yaml:"scopes"` // e.g. ["openid"] AllowedSubjects []string `yaml:"allowed-subjects"` // e.g. ["user1@example.com"]. If empty, all subjects are allowed + ClaimToCheck string `yaml:"claim-to-check"` // e.g. email. If empty, subject is used oauth2Config oauth2.Config verifier *oidc.IDTokenVerifier @@ -117,14 +118,35 @@ func (c *OIDCConfig) callbackHandler(w http.ResponseWriter, r *http.Request) { / http.Redirect(w, r, "/", http.StatusFound) return } - for _, subject := range c.AllowedSubjects { - if strings.ToLower(subject) == strings.ToLower(idToken.Subject) { - c.setSessionCookie(w, idToken) - http.Redirect(w, r, "/", http.StatusFound) - return + + var claimToCheck = c.ClaimToCheck + if len(claimToCheck) > 0 { + var claimsMap map[string]interface{} + if err := idToken.Claims(&claimsMap); err == nil { + claimValue, ok := claimsMap[claimToCheck] + if ok { + for _, subject := range c.AllowedSubjects { + if claimValue == subject { + c.setSessionCookie(w, idToken) + http.Redirect(w, r, "/", http.StatusFound) + return + } + } + log.Printf("[security.callbackHandler] Value %s of claim %s doesn't match any element of the list of allowed subjects", claimValue, claimToCheck) + } else { + log.Printf("[security.callbackHandler] Claim doesn't contain the field %s", claimToCheck) + } + } + } else { + for _, subject := range c.AllowedSubjects { + if strings.ToLower(subject) == strings.ToLower(idToken.Subject) { + c.setSessionCookie(w, idToken) + http.Redirect(w, r, "/", http.StatusFound) + return + } } + log.Printf("[security.callbackHandler] Subject %s is not in the list of allowed subjects", idToken.Subject) } - log.Printf("[security.callbackHandler] Subject %s is not in the list of allowed subjects", idToken.Subject) http.Redirect(w, r, "/?error=access_denied", http.StatusFound) }