Skip to content

Commit

Permalink
auditloggingccl: migrate role-based audit logging as a CCL feature
Browse files Browse the repository at this point in the history
This change moves the existing role-based audit logging logic to be
consumed as a CCL (enterprise) feature.

https://cockroachlabs.atlassian.net/browse/DOC-1355 #Informs:
cockroachdb#33316 #Epic: CRDB-8035

(sql change): #Release note (ui change): #Release note (security
update): #Release note (general change):
(performance improvement): #Release note (bug fix):
  • Loading branch information
Thomas Hardy committed Jun 6, 2023
1 parent 530e2ef commit c921005
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package auditlogging
package auditloggingccl

import (
"context"
"github.com/cockroachdb/cockroach/pkg/sql/auditlogging"

"github.com/cockroachdb/cockroach/pkg/settings"
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
Expand Down Expand Up @@ -51,11 +52,11 @@ func validateAuditLogConfig(_ *settings.Values, input string) error {
return nil
}
// Ensure it can be parsed.
conf, err := parse(input)
conf, err := auditlogging.Parse(input)
if err != nil {
return err
}
if len(conf.settings) == 0 {
if len(conf.Settings) == 0 {
// The string was not empty, but we were unable to parse any settings.
return errors.WithHint(errors.New("no entries"),
"To use the default configuration, assign the empty string ('').")
Expand All @@ -66,17 +67,34 @@ func validateAuditLogConfig(_ *settings.Values, input string) error {
// UpdateAuditConfigOnChange initializes the local
// node's audit configuration each time the cluster setting
// is updated.
func UpdateAuditConfigOnChange(ctx context.Context, acl *AuditConfigLock, st *cluster.Settings) {
func UpdateAuditConfigOnChange(ctx context.Context, acl *auditlogging.AuditConfigLock, st *cluster.Settings) {
val := UserAuditLogConfig.Get(&st.SV)
config, err := parse(val)
config, err := auditlogging.Parse(val)
if err != nil {
// We encounter an error parsing (i.e. invalid config), fallback
// to an empty config.
log.Ops.Warningf(ctx, "invalid audit log config (sql.log.user_audit): %v\n"+
"falling back to empty audit config", err)
config = EmptyAuditConfig()
config = auditlogging.EmptyAuditConfig()
}
acl.Lock()
acl.Config = config
acl.Unlock()
}

var ConfigureRoleBasedAuditClusterSettings = func(ctx context.Context, acl *auditlogging.AuditConfigLock, st *cluster.Settings, sv *settings.Values) {
UserAuditLogConfig.SetOnChange(
sv, func(ctx context.Context) {
UpdateAuditConfigOnChange(ctx, acl, st)
})
UpdateAuditConfigOnChange(ctx, acl, st)
}

var UserAuditLogConfigEmpty = func(sv *settings.Values) bool {
return UserAuditLogConfig.Get(sv) == ""
}

func init() {
auditlogging.ConfigureRoleBasedAuditClusterSettings = ConfigureRoleBasedAuditClusterSettings
auditlogging.UserAuditLogConfigEmpty = UserAuditLogConfigEmpty
}
6 changes: 1 addition & 5 deletions pkg/server/server_sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -1374,11 +1374,7 @@ func newSQLServer(ctx context.Context, cfg sqlServerArgs) (*SQLServer, error) {
vmoduleSetting.SetOnChange(&cfg.Settings.SV, fn)
fn(ctx)

auditlogging.UserAuditLogConfig.SetOnChange(
&execCfg.Settings.SV, func(ctx context.Context) {
auditlogging.UpdateAuditConfigOnChange(ctx, execCfg.SessionInitCache.AuditConfig, execCfg.Settings)
})
auditlogging.UpdateAuditConfigOnChange(ctx, execCfg.SessionInitCache.AuditConfig, execCfg.Settings)
auditlogging.ConfigureRoleBasedAuditClusterSettings(ctx, execCfg.SessionInitCache.AuditConfig, execCfg.Settings, &execCfg.Settings.SV)

return &SQLServer{
ambientCtx: cfg.BaseConfig.AmbientCtx,
Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/audit_logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,5 @@ func (p *planner) shouldNotRoleBasedAudit() bool {
// Do not do audit work if the cluster setting is empty.
// Do not emit audit events for reserved users/roles. This does not omit the root user.
// Do not emit audit events for internal planners.
return auditlogging.UserAuditLogConfig.Get(&p.execCfg.Settings.SV) == "" || p.User().IsReserved() || p.isInternalPlanner
return auditlogging.UserAuditLogConfigEmpty(&p.execCfg.Settings.SV) || p.User().IsReserved() || p.isInternalPlanner
}
31 changes: 22 additions & 9 deletions pkg/sql/auditlogging/audit_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ package auditlogging
import (
"context"
"fmt"
"github.com/cockroachdb/cockroach/pkg/settings"
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
"strings"

"github.com/cockroachdb/cockroach/pkg/kv"
Expand All @@ -24,6 +26,17 @@ import (
"github.com/olekukonko/tablewriter"
)

// ConfigureRoleBasedAuditClusterSettings is a noop global var injected by CCL hook.
// See corresponding ConfigureRoleBasedAuditClusterSettings in auditloggingccl.
var ConfigureRoleBasedAuditClusterSettings = func(ctx context.Context, acl *AuditConfigLock, st *cluster.Settings, sv *settings.Values) {
return
}

// UserAuditLogConfigEmpty is a noop global var injected by CCL hook.
var UserAuditLogConfigEmpty = func(sv *settings.Values) bool {
return true
}

// Auditor is an interface used to check and build different audit events.
type Auditor interface {
GetQualifiedTableNameByID(ctx context.Context, id int64, requiredType tree.RequiredTableKind) (*tree.TableName, error)
Expand Down Expand Up @@ -74,16 +87,16 @@ func (cl *AuditConfigLock) GetMatchingAuditSetting(

// AuditConfig is a parsed configuration.
type AuditConfig struct {
// settings are the collection of AuditSettings that make up the AuditConfig.
settings []AuditSetting
// allRoleAuditSettingIdx is an index corresponding to an AuditSetting in settings that applies to all
// Settings are the collection of AuditSettings that make up the AuditConfig.
Settings []AuditSetting
// allRoleAuditSettingIdx is an index corresponding to an AuditSetting in Settings that applies to all
// users, if it exists. Default value -1 (defaultAllAuditSettingIdx).
allRoleAuditSettingIdx int
}

const defaultAllAuditSettingIdx = -1

// EmptyAuditConfig returns an audit configuration with no audit settings.
// EmptyAuditConfig returns an audit configuration with no audit Settings.
func EmptyAuditConfig() *AuditConfig {
return &AuditConfig{
allRoleAuditSettingIdx: defaultAllAuditSettingIdx,
Expand All @@ -96,7 +109,7 @@ func (c AuditConfig) getMatchingAuditSetting(
userRoles map[username.SQLUsername]bool, name username.SQLUsername,
) *AuditSetting {
// If the user matches any Setting, return the corresponding filter.
for idx, filter := range c.settings {
for idx, filter := range c.Settings {
// If we have matched an audit setting by role, return the audit setting.
_, exists := userRoles[filter.Role]
if exists {
Expand All @@ -116,13 +129,13 @@ func (c AuditConfig) getMatchingAuditSetting(
}

func (c AuditConfig) String() string {
if len(c.settings) == 0 {
if len(c.Settings) == 0 {
return "# (empty configuration)\n"
}

var sb strings.Builder
sb.WriteString("# Original configuration:\n")
for _, setting := range c.settings {
for _, setting := range c.Settings {
fmt.Fprintf(&sb, "# %s\n", setting.input)
}
sb.WriteString("#\n# Interpreted configuration:\n")
Expand All @@ -138,7 +151,7 @@ func (c AuditConfig) String() string {

row := []string{"# ROLE", "STATEMENT_FILTER"}
table.Append(row)
for _, setting := range c.settings {
for _, setting := range c.Settings {
row[0] = setting.Role.Normalized()
row[1] = writeStatementFilter(setting.IncludeStatements)
table.Append(row)
Expand Down Expand Up @@ -167,5 +180,5 @@ type AuditSetting struct {
}

func (s AuditSetting) String() string {
return AuditConfig{settings: []AuditSetting{s}}.String()
return AuditConfig{Settings: []AuditSetting{s}}.String()
}
4 changes: 2 additions & 2 deletions pkg/sql/auditlogging/audit_log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import (
)

func TestParse(t *testing.T) {
datadriven.RunTest(t, datapathutils.TestDataPath(t, "parse"),
datadriven.RunTest(t, datapathutils.TestDataPath(t, "Parse"),
func(t *testing.T, td *datadriven.TestData) string {
switch td.Cmd {
case "multiline":
config, err := parse(td.Input)
config, err := Parse(td.Input)
if err != nil {
return fmt.Sprintf("error: %v\n", err)
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/sql/auditlogging/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ import (
"github.com/cockroachdb/errors"
)

// parse parses the provided audit logging configuration.
func parse(input string) (*AuditConfig, error) {
// Parse parses the provided audit logging configuration.
func Parse(input string) (*AuditConfig, error) {
tokens, err := rulebasedscanner.Tokenize(input)
if err != nil {
return nil, err
}

config := EmptyAuditConfig()
config.settings = make([]AuditSetting, len(tokens.Lines))
config.Settings = make([]AuditSetting, len(tokens.Lines))
// settingsRoleMap keeps track of the roles we've already written in the config
settingsRoleMap := make(map[username.SQLUsername]interface{}, len(tokens.Lines))
for i, line := range tokens.Lines {
Expand All @@ -42,7 +42,7 @@ func parse(input string) (*AuditConfig, error) {
return nil, errors.Newf("duplicate role listed: %v", setting.Role)
}
settingsRoleMap[setting.Role] = i
config.settings[i] = setting
config.Settings[i] = setting
if setting.Role.Normalized() == allUserRole {
config.allRoleAuditSettingIdx = i
}
Expand Down

0 comments on commit c921005

Please sign in to comment.