feat(security): added the ability to use a different claim field to match against the allow-subjects list.

In this way you can use 'email' or 'preferred_username' to identify the users.
If not specified the default is 'subject'.
This commit is contained in:
fulvius
2024-05-14 14:50:22 +02:00
committed by Fulvius
parent f2c5f5911c
commit 4c3d062ae8
2 changed files with 31 additions and 6 deletions

View File

@@ -1518,6 +1518,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:
@@ -1529,6 +1530,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).

View File

@@ -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)
}