Authn: resolve issues with setting up a nil identity (#92620)
This commit is contained in:
@@ -74,13 +74,11 @@ type ExtendedJWT struct {
|
||||
func (s *ExtendedJWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) {
|
||||
jwtToken := s.retrieveAuthenticationToken(r.HTTPRequest)
|
||||
|
||||
accessToken, err := s.accessTokenVerifier.Verify(ctx, jwtToken)
|
||||
accessTokenClaims, err := s.accessTokenVerifier.Verify(ctx, jwtToken)
|
||||
if err != nil {
|
||||
return nil, errExtJWTInvalid.Errorf("failed to verify access token: %w", err)
|
||||
}
|
||||
|
||||
accessTokenClaims := authlib.NewAccessClaims(*accessToken)
|
||||
|
||||
idToken := s.retrieveAuthorizationToken(r.HTTPRequest)
|
||||
if idToken != "" {
|
||||
idTokenClaims, err := s.idTokenVerifier.Verify(ctx, idToken)
|
||||
@@ -88,10 +86,10 @@ func (s *ExtendedJWT) Authenticate(ctx context.Context, r *authn.Request) (*auth
|
||||
return nil, errExtJWTInvalid.Errorf("failed to verify id token: %w", err)
|
||||
}
|
||||
|
||||
return s.authenticateAsUser(authlib.NewIdentityClaims(*idTokenClaims), accessTokenClaims)
|
||||
return s.authenticateAsUser(*idTokenClaims, *accessTokenClaims)
|
||||
}
|
||||
|
||||
return s.authenticateAsService(accessTokenClaims)
|
||||
return s.authenticateAsService(*accessTokenClaims)
|
||||
}
|
||||
|
||||
func (s *ExtendedJWT) IsEnabled() bool {
|
||||
@@ -99,73 +97,75 @@ func (s *ExtendedJWT) IsEnabled() bool {
|
||||
}
|
||||
|
||||
func (s *ExtendedJWT) authenticateAsUser(
|
||||
idTokenClaims claims.IdentityClaims,
|
||||
accessTokenClaims claims.AccessClaims,
|
||||
idTokenClaims authlib.Claims[authlib.IDTokenClaims],
|
||||
accessTokenClaims authlib.Claims[authlib.AccessTokenClaims],
|
||||
) (*authn.Identity, error) {
|
||||
// Only allow id tokens signed for namespace configured for this instance.
|
||||
if allowedNamespace := s.namespaceMapper(s.getDefaultOrgID()); !claims.NamespaceMatches(idTokenClaims, allowedNamespace) {
|
||||
return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected id token namespace: %s", idTokenClaims.Namespace())
|
||||
if allowedNamespace := s.namespaceMapper(s.getDefaultOrgID()); !claims.NamespaceMatches(authlib.NewIdentityClaims(idTokenClaims), allowedNamespace) {
|
||||
return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected id token namespace: %s", idTokenClaims.Rest.Namespace)
|
||||
}
|
||||
|
||||
// Allow access tokens with either the same namespace as the validated id token namespace or wildcard (`*`).
|
||||
if !claims.NamespaceMatches(accessTokenClaims, idTokenClaims.Namespace()) {
|
||||
return nil, errExtJWTMisMatchedNamespaceClaims.Errorf("unexpected access token namespace: %s", accessTokenClaims.Namespace())
|
||||
if !claims.NamespaceMatches(authlib.NewAccessClaims(accessTokenClaims), idTokenClaims.Rest.Namespace) {
|
||||
return nil, errExtJWTMisMatchedNamespaceClaims.Errorf("unexpected access token namespace: %s", accessTokenClaims.Rest.Namespace)
|
||||
}
|
||||
|
||||
accessType, _, err := identity.ParseTypeAndID(accessTokenClaims.Subject())
|
||||
accessType, _, err := identity.ParseTypeAndID(accessTokenClaims.Subject)
|
||||
if err != nil {
|
||||
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessTokenClaims.Subject())
|
||||
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessTokenClaims.Subject)
|
||||
}
|
||||
|
||||
if !claims.IsIdentityType(accessType, claims.TypeAccessPolicy) {
|
||||
return nil, errExtJWTInvalid.Errorf("unexpected identity: %s", accessTokenClaims.Subject())
|
||||
return nil, errExtJWTInvalid.Errorf("unexpected identity: %s", accessTokenClaims.Subject)
|
||||
}
|
||||
|
||||
t, id, err := identity.ParseTypeAndID(idTokenClaims.Subject())
|
||||
t, id, err := identity.ParseTypeAndID(idTokenClaims.Subject)
|
||||
if err != nil {
|
||||
return nil, errExtJWTInvalid.Errorf("failed to parse id token subject: %w", err)
|
||||
}
|
||||
|
||||
if !claims.IsIdentityType(t, claims.TypeUser) {
|
||||
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", idTokenClaims.Subject())
|
||||
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", idTokenClaims.Subject)
|
||||
}
|
||||
|
||||
// For use in service layer, allow higher privilege
|
||||
allowedKubernetesNamespace := accessTokenClaims.Namespace()
|
||||
allowedKubernetesNamespace := accessTokenClaims.Rest.Namespace
|
||||
if len(s.cfg.StackID) > 0 {
|
||||
// For single-tenant cloud use, choose the lower of the two (id token will always have the specific namespace)
|
||||
allowedKubernetesNamespace = idTokenClaims.Namespace()
|
||||
allowedKubernetesNamespace = idTokenClaims.Rest.Namespace
|
||||
}
|
||||
|
||||
return &authn.Identity{
|
||||
ID: id,
|
||||
Type: t,
|
||||
OrgID: s.getDefaultOrgID(),
|
||||
AccessTokenClaims: &accessTokenClaims,
|
||||
IDTokenClaims: &idTokenClaims,
|
||||
AuthenticatedBy: login.ExtendedJWTModule,
|
||||
AuthID: accessTokenClaims.Subject(),
|
||||
AuthID: accessTokenClaims.Subject,
|
||||
AllowedKubernetesNamespace: allowedKubernetesNamespace,
|
||||
ClientParams: authn.ClientParams{
|
||||
SyncPermissions: true,
|
||||
FetchPermissionsParams: authn.FetchPermissionsParams{
|
||||
ActionsLookup: accessTokenClaims.DelegatedPermissions(),
|
||||
ActionsLookup: accessTokenClaims.Rest.DelegatedPermissions,
|
||||
},
|
||||
FetchSyncedUser: true,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (s *ExtendedJWT) authenticateAsService(accessTokenClaims claims.AccessClaims) (*authn.Identity, error) {
|
||||
func (s *ExtendedJWT) authenticateAsService(accessTokenClaims authlib.Claims[authlib.AccessTokenClaims]) (*authn.Identity, error) {
|
||||
// Allow access tokens with that has a wildcard namespace or a namespace matching this instance.
|
||||
if allowedNamespace := s.namespaceMapper(s.getDefaultOrgID()); !claims.NamespaceMatches(accessTokenClaims, allowedNamespace) {
|
||||
return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected access token namespace: %s", accessTokenClaims.Namespace())
|
||||
if allowedNamespace := s.namespaceMapper(s.getDefaultOrgID()); !claims.NamespaceMatches(authlib.NewAccessClaims(accessTokenClaims), allowedNamespace) {
|
||||
return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected access token namespace: %s", accessTokenClaims.Rest.Namespace)
|
||||
}
|
||||
|
||||
t, id, err := identity.ParseTypeAndID(accessTokenClaims.Subject())
|
||||
t, id, err := identity.ParseTypeAndID(accessTokenClaims.Subject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse access token subject: %w", err)
|
||||
}
|
||||
|
||||
if !claims.IsIdentityType(t, claims.TypeAccessPolicy) {
|
||||
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessTokenClaims.Subject())
|
||||
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessTokenClaims.Subject)
|
||||
}
|
||||
|
||||
return &authn.Identity{
|
||||
@@ -173,13 +173,15 @@ func (s *ExtendedJWT) authenticateAsService(accessTokenClaims claims.AccessClaim
|
||||
UID: id,
|
||||
Type: t,
|
||||
OrgID: s.getDefaultOrgID(),
|
||||
AccessTokenClaims: &accessTokenClaims,
|
||||
IDTokenClaims: nil,
|
||||
AuthenticatedBy: login.ExtendedJWTModule,
|
||||
AuthID: accessTokenClaims.Subject(),
|
||||
AllowedKubernetesNamespace: accessTokenClaims.Namespace(),
|
||||
AuthID: accessTokenClaims.Subject,
|
||||
AllowedKubernetesNamespace: accessTokenClaims.Rest.Namespace,
|
||||
ClientParams: authn.ClientParams{
|
||||
SyncPermissions: true,
|
||||
FetchPermissionsParams: authn.FetchPermissionsParams{
|
||||
Roles: accessTokenClaims.Permissions(),
|
||||
Roles: accessTokenClaims.Rest.Permissions,
|
||||
},
|
||||
FetchSyncedUser: false,
|
||||
},
|
||||
|
||||
@@ -230,6 +230,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
UID: "this-uid",
|
||||
Type: claims.TypeAccessPolicy,
|
||||
OrgID: 1,
|
||||
AccessTokenClaims: &validAccessTokenClaims,
|
||||
AllowedKubernetesNamespace: "default",
|
||||
AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid",
|
||||
@@ -247,6 +248,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
UID: "this-uid",
|
||||
Type: claims.TypeAccessPolicy,
|
||||
OrgID: 1,
|
||||
AccessTokenClaims: &validAccessTokenClaimsWildcard,
|
||||
AllowedKubernetesNamespace: "*",
|
||||
AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid",
|
||||
@@ -264,6 +266,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
ID: "2",
|
||||
Type: claims.TypeUser,
|
||||
OrgID: 1,
|
||||
AccessTokenClaims: &validAccessTokenClaims,
|
||||
IDTokenClaims: &validIDTokenClaims,
|
||||
AllowedKubernetesNamespace: "default",
|
||||
AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid",
|
||||
@@ -285,6 +289,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
ID: "2",
|
||||
Type: claims.TypeUser,
|
||||
OrgID: 1,
|
||||
AccessTokenClaims: &validAccessTokenClaimsWildcard,
|
||||
IDTokenClaims: &validIDTokenClaims,
|
||||
AllowedKubernetesNamespace: "*",
|
||||
AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid",
|
||||
@@ -311,6 +317,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
ID: "2",
|
||||
Type: claims.TypeUser,
|
||||
OrgID: 1,
|
||||
AccessTokenClaims: &validAccessTokenClaimsWildcard,
|
||||
IDTokenClaims: &validIDTokenClaimsWithStackSet,
|
||||
AllowedKubernetesNamespace: "stacks-1234",
|
||||
AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid",
|
||||
@@ -337,6 +345,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
UID: "this-uid",
|
||||
Type: claims.TypeAccessPolicy,
|
||||
OrgID: 1,
|
||||
AccessTokenClaims: &validAccessTokenClaimsWithStackSet,
|
||||
AllowedKubernetesNamespace: "stacks-1234",
|
||||
AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid",
|
||||
@@ -362,6 +371,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
UID: "this-uid",
|
||||
Type: claims.TypeAccessPolicy,
|
||||
OrgID: 1,
|
||||
AccessTokenClaims: &validAccessTokenClaimsWithDeprecatedStackClaimSet,
|
||||
AllowedKubernetesNamespace: "stack-1234",
|
||||
AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid",
|
||||
@@ -387,6 +397,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
ID: "2",
|
||||
Type: claims.TypeUser,
|
||||
OrgID: 1,
|
||||
AccessTokenClaims: &validAccessTokenClaimsWithDeprecatedStackClaimSet,
|
||||
IDTokenClaims: &validIDTokenClaimsWithDeprecatedStackClaimSet,
|
||||
AllowedKubernetesNamespace: "stack-1234",
|
||||
AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid",
|
||||
@@ -413,6 +425,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
ID: "2",
|
||||
Type: claims.TypeUser,
|
||||
OrgID: 1,
|
||||
AccessTokenClaims: &validAccessTokenClaimsWildcard,
|
||||
IDTokenClaims: &validIDTokenClaimsWithStackSet,
|
||||
AllowedKubernetesNamespace: "stacks-1234",
|
||||
AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid",
|
||||
|
||||
@@ -74,10 +74,15 @@ type Identity struct {
|
||||
// IDToken is a signed token representing the identity that can be forwarded to plugins and external services.
|
||||
IDToken string
|
||||
IDTokenClaims *authn.Claims[authn.IDTokenClaims]
|
||||
|
||||
AccessTokenClaims *authn.Claims[authn.AccessTokenClaims]
|
||||
}
|
||||
|
||||
// Access implements claims.AuthInfo.
|
||||
func (i *Identity) GetAccess() claims.AccessClaims {
|
||||
if i.AccessTokenClaims != nil {
|
||||
return authn.NewAccessClaims(*i.AccessTokenClaims)
|
||||
}
|
||||
return &identity.IDClaimsWrapper{Source: i}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user