mirror of
https://github.com/juanfont/headscale.git
synced 2026-11-04 04:34:07 +09:00
oidc: refuse logins whose subject the provider identifier would alter
This commit is contained in:
1 parent
b22dd41b51
commit
ffc668d48e
4 files changed
+74
No files matched your search
@@ -370,6 +370,13 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler(
|
||||
return
|
||||
}
|
||||
|
||||
if !claims.IdentifierKeepsSubject() {
|
||||
log.Warn().Str("subject", claims.Sub).Msg("OIDC subject would be altered by provider identifier cleaning")
|
||||
httpUserError(writer, NewHTTPError(http.StatusForbidden, "OIDC subject cannot be used as a provider identifier", nil))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
user, err := a.createOrUpdateUserFromClaim(&claims)
|
||||
if err != nil {
|
||||
httpUserError(writer, NewHTTPError(
|
||||
|
||||
@@ -1449,6 +1449,27 @@ func TestMergeEmailClaims(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOIDCRefusesAlteredSubject(t *testing.T) {
|
||||
b := newOIDCBrowser(t)
|
||||
b.idp.QueueUser(&mockoidc.MockUser{Subject: "alice", PreferredUsername: "alice"})
|
||||
_, registerURL := b.pendingNode(t)
|
||||
status, _, _ := b.get(t, registerURL)
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
|
||||
before, err := b.app.state.GetUserByOIDCIdentifier(b.idp.Issuer() + "/alice")
|
||||
require.NoError(t, err)
|
||||
|
||||
b.idp.QueueUser(&mockoidc.MockUser{Subject: "alice/", PreferredUsername: "renamed"})
|
||||
_, registerURL = b.pendingNode(t)
|
||||
status, _, _ = b.get(t, registerURL)
|
||||
assert.Equal(t, http.StatusForbidden, status)
|
||||
|
||||
users, err := b.app.state.ListAllUsers()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, users, 1)
|
||||
assert.Equal(t, *before, users[0], "a refused subject must not update an existing row")
|
||||
}
|
||||
|
||||
func TestOIDCRegistrationRejectsOversizedMergedHostinfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -354,6 +354,13 @@ func (c *OIDCClaims) Identifier() string {
|
||||
return CleanIdentifier(result)
|
||||
}
|
||||
|
||||
// IdentifierKeepsSubject reports whether the stored key preserves the subject.
|
||||
// Refusing altered subjects avoids changing existing provider identifiers.
|
||||
func (c *OIDCClaims) IdentifierKeepsSubject() bool {
|
||||
return c.Identifier() == CleanIdentifier(c.Iss)+"/"+
|
||||
(&url.URL{Path: c.Sub, RawPath: c.Sub}).EscapedPath()
|
||||
}
|
||||
|
||||
// CleanIdentifier cleans a potentially malformed identifier by removing double slashes
|
||||
// while preserving protocol specifications like http://. This function will:
|
||||
// - Trim all whitespace from the beginning and end of the identifier
|
||||
|
||||
@@ -552,3 +552,42 @@ func TestOIDCClaimsJSONToUser(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOIDCClaimsIdentifierKeepsSubject(t *testing.T) {
|
||||
kept := []string{
|
||||
"c8872e9e-9a4f-4b8f-b111-c6191595d450",
|
||||
"1234567890", "0123456789abcdef", "ab_C-dEF0123",
|
||||
"ab/cd+ef==", "auth0|12345", "google-oauth2|12345",
|
||||
"samlp|connection|alice@example.com", "f:realm:alice",
|
||||
"alice@example.com", "\u7528\u6237", "alice smith", ".", "..",
|
||||
}
|
||||
refused := []string{
|
||||
"", "/alice", "alice/", "alice//smith", " alice", "alice ",
|
||||
"alice/ smith", "alice /smith", "alice#", "alice?x", "alice%23",
|
||||
"alice%3Fx", "alice%2F", "alice%252F", "alice%20", "alice%2520",
|
||||
"alice%zz", "a\x01b", "a\nb", "a\tb",
|
||||
}
|
||||
|
||||
for _, issuer := range []string{
|
||||
"https://sso.example.com",
|
||||
"https://sso.example.com/realm/",
|
||||
"https://login.microsoftonline.com/tenant/v2.0",
|
||||
} {
|
||||
t.Run(issuer, func(t *testing.T) {
|
||||
identifiers := make(map[string]string)
|
||||
|
||||
for _, subject := range kept {
|
||||
claims := OIDCClaims{Iss: issuer, Sub: subject}
|
||||
assert.True(t, claims.IdentifierKeepsSubject(), "subject %q", subject)
|
||||
identifier := claims.Identifier()
|
||||
assert.NotContains(t, identifiers, identifier, "subject %q", subject)
|
||||
identifiers[identifier] = subject
|
||||
}
|
||||
|
||||
for _, subject := range refused {
|
||||
claims := OIDCClaims{Iss: issuer, Sub: subject}
|
||||
assert.False(t, claims.IdentifierKeepsSubject(), "subject %q", subject)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user