Authn: resolve issues with setting up a nil identity (#92620)

This commit is contained in:
Charandas
2024-08-29 00:49:41 +03:00
committed by GitHub
parent 692280cd32
commit 4f024d94d8
7 changed files with 71 additions and 44 deletions
+30 -28
View File
@@ -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",
+5
View File
@@ -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}
}