Skip to content

Commit

Permalink
sql,ccl: add auth behaviors for authorization
Browse files Browse the repository at this point in the history
informs cockroachdb#125087
informs cockroachdb#128498
informs cockroachdb#129707
fixes CRDB-41622
Epic CRDB-33829

We currently lack auth behavior functions for authorization which can be
configured as part of AuthMethod itself. This PR adds these AuthBehaviors for
authorization, specifically:
1. Authorizer which retrieves authorization information from an external
authorization system and is a component of the AuthMethod for that auth system.
It also maps the information it to sql identities like roles or subject role
option.
2. RolesGrantFn which defines how the mapped sql identities can be fetched as
sql group roles and be granted to the sql user session.

Release note: None
  • Loading branch information
souravcrl committed Aug 30, 2024
1 parent dafb6dd commit 04ad50e
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 15 deletions.
11 changes: 0 additions & 11 deletions pkg/ccl/ldapccl/authorization_ldap.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,6 @@ func (authManager *ldapAuthManager) validateLDAPAuthZOptions() error {
// contain sensitive information we do not want to send to sql clients but still
// want to log it). We do not want to send any information back to client which
// was not provided by the client.
//
// Example authorization example for obtaining LDAP groups for LDAP user:
// if ldapGroups, detailedErrors, authError := ldapManager.m.FetchLDAPGroups(ctx, execCfg.Settings, externalUserDN, entry, identMap); authError != nil {
// errForLog := authError
// if detailedErrors != "" {
// errForLog = errors.Join(errForLog, errors.Newf("%s", detailedErrors))
// }
// log.Warningf(ctx, "error retrieving ldap groups for authZ: %+v", errForLog)
// } else {
// log.Infof(ctx, "LDAP authorization: retrieved ldap groups are %+v", ldapGroups)
// }
func (authManager *ldapAuthManager) FetchLDAPGroups(
ctx context.Context,
st *cluster.Settings,
Expand Down
1 change: 1 addition & 0 deletions pkg/sql/pgwire/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ go_library(
"auth_behaviors.go",
"auth_methods.go",
"authenticator.go",
"authorizer.go",
"command_result.go",
"conn.go",
"hba_conf.go",
Expand Down
9 changes: 9 additions & 0 deletions pkg/sql/pgwire/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,15 @@ func (c *conn) handleAuthentication(
return connClose, c.sendError(ctx, err)
}

// Since authentication was successful for the user session, we try to perform
// additional authorization for the user. This delegates to the selected
// AuthMethod implementation to complete the authorization.
if err := behaviors.MaybeAuthorize(ctx, systemIdentity, true /* public */); err != nil {
ac.LogAuthFailed(ctx, eventpb.AuthFailReason_UNKNOWN, err)
err = pgerror.WithCandidateCode(err, pgcode.InvalidAuthorizationSpecification)
return connClose, c.sendError(ctx, err)
}

// Add all the defaults to this session's defaults. If there is an
// error (e.g., a setting that no longer exists, or bad input),
// log a warning instead of preventing login.
Expand Down
44 changes: 41 additions & 3 deletions pkg/sql/pgwire/auth_behaviors.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ type AuthBehaviors struct {
replacementIdentity username.SQLUsername
replacedIdentity bool
roleMapper RoleMapper
authorizer Authorizer
roleGranter RoleGranter
}

// Ensure that an AuthBehaviors is easily composable with itself.
var _ Authenticator = (*AuthBehaviors)(nil).Authenticate
var _ func() = (*AuthBehaviors)(nil).ConnClose
var _ RoleMapper = (*AuthBehaviors)(nil).MapRole
var _ RoleGranter = (*AuthBehaviors)(nil).GrantRole

// This is a hack for the unused-symbols linter. These two functions
// are, at present, only called by the GSSAPI integration. The code
Expand All @@ -44,9 +47,8 @@ var _ RoleMapper = (*AuthBehaviors)(nil).MapRole
var _ = (*AuthBehaviors)(nil).SetConnClose
var _ = (*AuthBehaviors)(nil).SetReplacementIdentity

// Authenticate delegates to the Authenticator passed to
// SetAuthenticator or returns an error if SetAuthenticator has not been
// called.
// Authenticate delegates to the Authenticator passed to SetAuthenticator or
// returns an error if SetAuthenticator has not been called.
func (b *AuthBehaviors) Authenticate(
ctx context.Context,
systemIdentity username.SQLUsername,
Expand Down Expand Up @@ -110,3 +112,39 @@ func (b *AuthBehaviors) MapRole(
func (b *AuthBehaviors) SetRoleMapper(m RoleMapper) {
b.roleMapper = m
}

// SetAuthorizer updates the SetAuthorizer to be used.
func (b *AuthBehaviors) SetAuthorizer(a Authorizer) {
b.authorizer = a
}

// MaybeAuthorize delegates to the Authorizer passed to SetAuthorizer and if
// successful obtains the grants using RoleGranter passed to SetRoleGranter.
func (b *AuthBehaviors) MaybeAuthorize(
ctx context.Context, systemIdentity username.SQLUsername, clientConnection bool,
) error {
if b.authorizer != nil {
sqlGroups, err := b.authorizer(ctx, systemIdentity, clientConnection)
if err != nil {
return err
}
return b.GrantRole(ctx, systemIdentity, sqlGroups)
}
return nil
}

// GrantRole delegates to the RoleGranter passed to SetRoleGranter or
// returns an error if SetRoleGranter has not been called.
func (b *AuthBehaviors) GrantRole(
ctx context.Context, systemIdentity username.SQLUsername, sqlGroups []username.SQLUsername,
) error {
if found := b.roleGranter; found != nil {
return found(ctx, systemIdentity, sqlGroups)
}
return errors.New("no RoleGranter provided to AuthBehaviors")
}

// SetRoleGranter updates the RoleMapper to be used.
func (b *AuthBehaviors) SetRoleGranter(g RoleGranter) {
b.roleGranter = g
}
26 changes: 25 additions & 1 deletion pkg/sql/pgwire/auth_methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -975,7 +975,31 @@ func authLDAP(
}
return nil
})
// TODO(souravcrl): add authorizer auth behavior b.SetAuthorizer() for syncing LDAP groups

b.SetAuthorizer(func(ctx context.Context, user username.SQLUsername, clientConnection bool) ([]username.SQLUsername, error) {
c.LogAuthInfof(ctx, "LDAP authentication succeeded; attempting authorization")
if ldapGroups, detailedErrors, authError := ldapManager.m.FetchLDAPGroups(ctx, execCfg.Settings, ldapUserDN, user, entry, identMap); authError != nil {
errForLog := authError
if detailedErrors != "" {
errForLog = errors.Join(errForLog, errors.Newf("%s", detailedErrors))
}
log.Warningf(ctx, "LDAP authorization: error retrieving ldap groups for authorization: %+v", errForLog)
return nil, authError
} else {
log.Infof(ctx, "LDAP authorization sync succeeded; attempting to assign roles")
sqlGroupRoles := make([]username.SQLUsername, len(ldapGroups))
for idx := range ldapGroups {
var err error
if sqlGroupRoles[idx], err = username.MakeSQLUsernameFromUserInput(ldapGroups[idx].String(), username.PurposeValidation); err != nil {
return nil, errors.Wrapf(err, "LDAP authorization: error creating group role for DN %s", ldapGroups[idx].String())
}
}
return sqlGroupRoles, nil
}
})
b.SetRoleGranter(func(ctx context.Context, user username.SQLUsername, sqlGroups []username.SQLUsername) error {
return nil
})

return b, nil
}
45 changes: 45 additions & 0 deletions pkg/sql/pgwire/authorizer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2024 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package pgwire

import (
"context"

"github.com/cockroachdb/cockroach/pkg/security/username"
)

// Authorizer is a component of an AuthMethod that adds additional system
// privilege information for the client session, specifically when we want to
// synchronize this information from some external authorization system (e.g.:
// LDAP groups, JWT claims or X.509 SAN or other fields, etc). It returns list
// of system identities which map to roles created specifically to assign the
// privileges to the session and could be either the subject distinguished names
// defined in role options or the role itself. Authorizer is intended to be used
// with GrantRolesFn which assigns it to intended groups(roles).
type Authorizer = func(
ctx context.Context,
systemIdentity username.SQLUsername,
clientConnection bool,
) ([]username.SQLUsername, error)

// RoleGranter defines a mechanism by which an AuthMethod associated with an
// incoming connection may grant additional roles obtained from an external
// authorization systems (e.g.: LDAP groups, JWT claims or X.509 SAN or other
// fields, etc.) to a sql session. It is expected that at this point we have
// created these groups(roles) with requisite privileges which are retrieved
// during the granting process and assigned to the sql session identified by the
// systemIdentity. Both Authorizer and RoleGranter are executed as part of
// (*AuthBehaviors).MaybeAuthorize().
type RoleGranter = func(
ctx context.Context,
systemIdentity username.SQLUsername,
sqlGroups []username.SQLUsername,
) error

0 comments on commit 04ad50e

Please sign in to comment.