diff --git a/lib/auth/init.go b/lib/auth/init.go index 154b11daed3f..56ff44319cc3 100644 --- a/lib/auth/init.go +++ b/lib/auth/init.go @@ -1131,6 +1131,25 @@ func createPresetDatabaseObjectImportRule(ctx context.Context, rules services.Da return trace.Wrap(err, "failed listing available database object import rules") } if len(importRules) > 0 { + // If the single rule is the old preset, we assume the user hasn't used + // DB DAC feature yet since the old preset alone is usually not enough + // to make things work. Replace it with the new preset. + // + // Creating and updating the database object import is handled on a + // best-effort basis, so it’s not included in backend migrations. + // + // TODO(greedy52) DELETE in 18.0 + if len(importRules) == 1 && databaseobjectimportrule.IsOldImportAllObjectsRulePreset(importRules[0]) { + rule := databaseobjectimportrule.NewPresetImportAllObjectsRule() + if rule == nil { + return nil + } + + _, err = rules.UpsertDatabaseObjectImportRule(ctx, rule) + if err != nil { + return trace.Wrap(err, "failed to update the default database object import rule") + } + } return nil } diff --git a/lib/auth/init_test.go b/lib/auth/init_test.go index 8070a9f73a3b..ed37f1145450 100644 --- a/lib/auth/init_test.go +++ b/lib/auth/init_test.go @@ -43,7 +43,9 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/constants" + dbobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/label" apisshutils "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/entitlements" "github.com/gravitational/teleport/lib" @@ -58,6 +60,7 @@ import ( "github.com/gravitational/teleport/lib/observability/tracing" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services/suite" + "github.com/gravitational/teleport/lib/srv/db/common/databaseobjectimportrule" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/proxy" @@ -2098,3 +2101,108 @@ func TestTeleportProcessAuthVersionUpgradeCheck(t *testing.T) { }) } } + +type mockDatabaseObjectImportRules struct { + services.DatabaseObjectImportRules + listRules []*dbobjectimportrulev1.DatabaseObjectImportRule + created *dbobjectimportrulev1.DatabaseObjectImportRule + upserted *dbobjectimportrulev1.DatabaseObjectImportRule +} + +func (m *mockDatabaseObjectImportRules) ListDatabaseObjectImportRules(context.Context, int, string) ([]*dbobjectimportrulev1.DatabaseObjectImportRule, string, error) { + return m.listRules, "", nil +} +func (m *mockDatabaseObjectImportRules) CreateDatabaseObjectImportRule(ctx context.Context, rule *dbobjectimportrulev1.DatabaseObjectImportRule) (*dbobjectimportrulev1.DatabaseObjectImportRule, error) { + m.created = rule + return rule, nil +} +func (m *mockDatabaseObjectImportRules) UpsertDatabaseObjectImportRule(ctx context.Context, rule *dbobjectimportrulev1.DatabaseObjectImportRule) (*dbobjectimportrulev1.DatabaseObjectImportRule, error) { + m.upserted = rule + return rule, nil +} + +func Test_createPresetDatabaseObjectImportRule(t *testing.T) { + presetRule := databaseobjectimportrule.NewPresetImportAllObjectsRule() + require.NotNil(t, presetRule) + + customRule, err := databaseobjectimportrule.NewDatabaseObjectImportRule("dev_rule", &dbobjectimportrulev1.DatabaseObjectImportRuleSpec{ + Priority: 100, + DatabaseLabels: label.FromMap(map[string][]string{"env": {"dev"}}), + Mappings: []*dbobjectimportrulev1.DatabaseObjectImportRuleMapping{{ + Match: &dbobjectimportrulev1.DatabaseObjectImportMatch{ + TableNames: []string{"*"}, + }, + AddLabels: map[string]string{ + "env": "dev", + }, + Scope: &dbobjectimportrulev1.DatabaseObjectImportScope{ + SchemaNames: []string{"public"}, + }, + }}, + }) + require.NoError(t, err) + + oldPresetRule, err := databaseobjectimportrule.NewDatabaseObjectImportRule("import_all_objects", &dbobjectimportrulev1.DatabaseObjectImportRuleSpec{ + DatabaseLabels: label.FromMap(map[string][]string{"*": {"*"}}), + Mappings: []*dbobjectimportrulev1.DatabaseObjectImportRuleMapping{ + { + Match: &dbobjectimportrulev1.DatabaseObjectImportMatch{TableNames: []string{"*"}}, + AddLabels: map[string]string{"kind": "table"}, + }, + { + Match: &dbobjectimportrulev1.DatabaseObjectImportMatch{ViewNames: []string{"*"}}, + AddLabels: map[string]string{"kind": "view"}, + }, + { + Match: &dbobjectimportrulev1.DatabaseObjectImportMatch{ProcedureNames: []string{"*"}}, + AddLabels: map[string]string{"kind": "procedure"}, + }, + }, + }) + require.NoError(t, err) + + tests := []struct { + name string + existingRules []*dbobjectimportrulev1.DatabaseObjectImportRule + expectCreate *dbobjectimportrulev1.DatabaseObjectImportRule + expectUpsert *dbobjectimportrulev1.DatabaseObjectImportRule + }{ + { + name: "create preset in new cluster", + expectCreate: presetRule, + }, + { + name: "no action with custom rule", + existingRules: []*dbobjectimportrulev1.DatabaseObjectImportRule{customRule}, + }, + { + name: "no action with old preset and custom rule", + existingRules: []*dbobjectimportrulev1.DatabaseObjectImportRule{oldPresetRule, customRule}, + }, + { + name: "no action with preset rule", + existingRules: []*dbobjectimportrulev1.DatabaseObjectImportRule{presetRule}, + }, + { + name: "migrate old preset to new", + existingRules: []*dbobjectimportrulev1.DatabaseObjectImportRule{oldPresetRule}, + expectUpsert: presetRule, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + m := &mockDatabaseObjectImportRules{ + listRules: test.existingRules, + } + + err := createPresetDatabaseObjectImportRule(context.Background(), m) + require.NoError(t, err) + require.Equal(t, test.expectCreate, m.created) + require.Equal(t, test.expectUpsert, m.upserted) + }) + } +} diff --git a/lib/srv/db/common/databaseobjectimportrule/preset.go b/lib/srv/db/common/databaseobjectimportrule/preset.go index d627e23d0f95..53bc8cb970da 100644 --- a/lib/srv/db/common/databaseobjectimportrule/preset.go +++ b/lib/srv/db/common/databaseobjectimportrule/preset.go @@ -17,10 +17,12 @@ package databaseobjectimportrule import ( - log "github.com/sirupsen/logrus" + "context" + "log/slog" dbobjectimportrulev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1" "github.com/gravitational/teleport/api/types/label" + "google.golang.org/protobuf/proto" ) // NewPresetImportAllObjectsRule creates new "import_all_objects" database object import rule, which applies `kind: ` label to all database objects. @@ -49,8 +51,40 @@ func NewPresetImportAllObjectsRule() *dbobjectimportrulev1pb.DatabaseObjectImpor }) if err != nil { - log.WithError(err).Warn("failed to create import_all_objects database object import rule") + slog.WarnContext(context.Background(), "failed to create import_all_objects database object import rule", "error", err) return nil } return rule } + +// IsOldImportAllObjectsRulePreset checks if the provided rule is the "old" preset. +func IsOldImportAllObjectsRulePreset(cur *dbobjectimportrulev1pb.DatabaseObjectImportRule) bool { + // Make the old preset from https://github.com/gravitational/teleport/pull/37808 + old, err := NewDatabaseObjectImportRule("import_all_objects", &dbobjectimportrulev1pb.DatabaseObjectImportRuleSpec{ + Priority: 0, + DatabaseLabels: label.FromMap(map[string][]string{"*": {"*"}}), + Mappings: []*dbobjectimportrulev1pb.DatabaseObjectImportRuleMapping{ + { + Match: &dbobjectimportrulev1pb.DatabaseObjectImportMatch{TableNames: []string{"*"}}, + AddLabels: map[string]string{"kind": ObjectKindTable}, + }, + { + Match: &dbobjectimportrulev1pb.DatabaseObjectImportMatch{ViewNames: []string{"*"}}, + AddLabels: map[string]string{"kind": ObjectKindView}, + }, + { + Match: &dbobjectimportrulev1pb.DatabaseObjectImportMatch{ProcedureNames: []string{"*"}}, + AddLabels: map[string]string{"kind": ObjectKindProcedure}, + }, + }, + }) + if err != nil { + slog.WarnContext(context.Background(), "failed to create old import_all_objects database object import rule", "error", err) + return false + } + + // Ignore these fields. + old.Metadata.Revision = cur.Metadata.Revision + old.Metadata.Namespace = cur.Metadata.Namespace + return proto.Equal(old, cur) +}