Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v17] UserTasks - DiscoverRDS issues: add and emit RDS known issues #51234

Open
wants to merge 3 commits into
base: branch/v17
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions api/types/usertasks/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,34 @@ func NewDiscoverEKSUserTask(spec *usertasksv1.UserTaskSpec, opts ...UserTaskOpti
return ut, nil
}

// NewDiscoverRDSUserTask creates a new DiscoverRDS User Task Type.
func NewDiscoverRDSUserTask(spec *usertasksv1.UserTaskSpec, opts ...UserTaskOption) (*usertasksv1.UserTask, error) {
taskName := TaskNameForDiscoverRDS(TaskNameForDiscoverRDSParts{
Integration: spec.GetIntegration(),
IssueType: spec.GetIssueType(),
AccountID: spec.GetDiscoverRds().GetAccountId(),
Region: spec.GetDiscoverRds().GetRegion(),
})

ut := &usertasksv1.UserTask{
Kind: types.KindUserTask,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: taskName,
},
Spec: spec,
}
for _, o := range opts {
o(ut)
}

if err := ValidateUserTask(ut); err != nil {
return nil, trace.Wrap(err)
}

return ut, nil
}

const (
// TaskStateOpen identifies an issue with an instance that is not yet resolved.
TaskStateOpen = "OPEN"
Expand All @@ -121,6 +149,11 @@ const (
// when an auto-enrollment of an EKS cluster fails.
// UserTasks that have this Task Type must include the DiscoverEKS field.
TaskTypeDiscoverEKS = "discover-eks"

// TaskTypeDiscoverRDS identifies a User Tasks that is created
// when an auto-enrollment of an RDS database fails or needs attention.
// UserTasks that have this Task Type must include the DiscoverRDS field.
TaskTypeDiscoverRDS = "discover-rds"
)

// List of Auto Discover EC2 issues identifiers.
Expand Down Expand Up @@ -197,6 +230,19 @@ var DiscoverEKSIssueTypes = []string{
AutoDiscoverEKSIssueAgentNotConnecting,
}

// List of Auto Discover RDS issues identifiers.
// This value is used to populate the UserTasks.Spec.IssueType for Discover RDS tasks.
const (
// AutoDiscoverRDSIssueIAMAuthenticationDisabled is used to identify databases that won't be
// accessible because IAM Authentication is not enabled.
AutoDiscoverRDSIssueIAMAuthenticationDisabled = "rds-iam-auth-disabled"
)

// DiscoverRDSIssueTypes is a list of issue types that can occur when trying to auto enroll RDS databases.
var DiscoverRDSIssueTypes = []string{
AutoDiscoverRDSIssueIAMAuthenticationDisabled,
}

// ValidateUserTask validates the UserTask object without modifying it.
func ValidateUserTask(ut *usertasksv1.UserTask) error {
switch {
Expand Down Expand Up @@ -225,6 +271,10 @@ func ValidateUserTask(ut *usertasksv1.UserTask) error {
if err := validateDiscoverEKSTaskType(ut); err != nil {
return trace.Wrap(err)
}
case TaskTypeDiscoverRDS:
if err := validateDiscoverRDSTaskType(ut); err != nil {
return trace.Wrap(err)
}
default:
return trace.BadParameter("task type %q is not valid", ut.Spec.TaskType)
}
Expand Down Expand Up @@ -345,6 +395,61 @@ func validateDiscoverEKSTaskType(ut *usertasksv1.UserTask) error {
return nil
}

func validateDiscoverRDSTaskType(ut *usertasksv1.UserTask) error {
if ut.GetSpec().Integration == "" {
return trace.BadParameter("integration is required")
}
if ut.GetSpec().GetDiscoverRds() == nil {
return trace.BadParameter("%s requires the discover_rds field", TaskTypeDiscoverRDS)
}
if ut.GetSpec().GetDiscoverRds().AccountId == "" {
return trace.BadParameter("%s requires the discover_rds.account_id field", TaskTypeDiscoverRDS)
}
if ut.GetSpec().GetDiscoverRds().Region == "" {
return trace.BadParameter("%s requires the discover_rds.region field", TaskTypeDiscoverRDS)
}

expectedTaskName := TaskNameForDiscoverRDS(TaskNameForDiscoverRDSParts{
Integration: ut.GetSpec().Integration,
IssueType: ut.GetSpec().IssueType,
AccountID: ut.GetSpec().GetDiscoverRds().AccountId,
Region: ut.GetSpec().GetDiscoverRds().Region,
})
if ut.GetMetadata().GetName() != expectedTaskName {
return trace.BadParameter("task name is pre-defined for discover-rds types, expected %s, got %s",
expectedTaskName,
ut.Metadata.GetName(),
)
}

if !slices.Contains(DiscoverRDSIssueTypes, ut.GetSpec().GetIssueType()) {
return trace.BadParameter("invalid issue type state, allowed values: %v", DiscoverRDSIssueTypes)
}

if len(ut.GetSpec().GetDiscoverRds().GetDatabases()) == 0 {
return trace.BadParameter("at least one database is required")
}
for databaseIdentifier, databaseIssue := range ut.GetSpec().GetDiscoverRds().GetDatabases() {
if databaseIdentifier == "" {
return trace.BadParameter("database identifier in discover_rds.databases map is required")
}
if databaseIssue.Name == "" {
return trace.BadParameter("database identifier in discover_rds.databases field is required")
}
if databaseIdentifier != databaseIssue.Name {
return trace.BadParameter("database identifier in discover_rds.databases map and field are different")
}
if databaseIssue.DiscoveryConfig == "" {
return trace.BadParameter("discovery config in discover_rds.databases field is required")
}
if databaseIssue.DiscoveryGroup == "" {
return trace.BadParameter("discovery group in discover_rds.databases field is required")
}
}

return nil
}

// TaskNameForDiscoverEC2Parts are the fields that deterministically compute a Discover EC2 task name.
// To be used with TaskNameForDiscoverEC2 function.
type TaskNameForDiscoverEC2Parts struct {
Expand Down Expand Up @@ -408,3 +513,30 @@ func TaskNameForDiscoverEKS(parts TaskNameForDiscoverEKSParts) string {

// discoverEKSNamespace is an UUID that represents the name space to be used for generating UUIDs for DiscoverEKS User Task names.
var discoverEKSNamespace = uuid.NewSHA1(uuid.Nil, []byte("discover-eks"))

// TaskNameForDiscoverRDSParts are the fields that deterministically compute a Discover RDS task name.
// To be used with TaskNameForDiscoverRDS function.
type TaskNameForDiscoverRDSParts struct {
Integration string
IssueType string
AccountID string
Region string
}

// TaskNameForDiscoverRDS returns a deterministic name for the DiscoverRDS task type.
// This method is used to ensure a single UserTask is created to report issues in enrolling RDS databases for a given integration, issue type, account id and region.
func TaskNameForDiscoverRDS(parts TaskNameForDiscoverRDSParts) string {
var bs []byte
bs = append(bs, binary.LittleEndian.AppendUint64(nil, uint64(len(parts.Integration)))...)
bs = append(bs, []byte(parts.Integration)...)
bs = append(bs, binary.LittleEndian.AppendUint64(nil, uint64(len(parts.IssueType)))...)
bs = append(bs, []byte(parts.IssueType)...)
bs = append(bs, binary.LittleEndian.AppendUint64(nil, uint64(len(parts.AccountID)))...)
bs = append(bs, []byte(parts.AccountID)...)
bs = append(bs, binary.LittleEndian.AppendUint64(nil, uint64(len(parts.Region)))...)
bs = append(bs, []byte(parts.Region)...)
return uuid.NewSHA1(discoverRDSNamespace, bs).String()
}

// discoverRDSNamespace is an UUID that represents the name space to be used for generating UUIDs for DiscoverRDS User Task names.
var discoverRDSNamespace = uuid.NewSHA1(uuid.Nil, []byte("discover-rds"))
200 changes: 200 additions & 0 deletions api/types/usertasks/object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,32 @@ func TestValidateUserTask(t *testing.T) {
return userTask
}

exampleDatabaseName := "my-db"
baseRDSDiscoverTask := func(t *testing.T) *usertasksv1.UserTask {
userTask, err := usertasks.NewDiscoverRDSUserTask(&usertasksv1.UserTaskSpec{
Integration: "my-integration",
TaskType: "discover-rds",
IssueType: "rds-iam-auth-disabled",
State: "OPEN",
DiscoverRds: &usertasksv1.DiscoverRDS{
AccountId: "123456789012",
Region: "us-east-1",
Databases: map[string]*usertasksv1.DiscoverRDSDatabase{
exampleDatabaseName: {
Name: exampleDatabaseName,
DiscoveryConfig: "dc01",
DiscoveryGroup: "dg01",
IsCluster: true,
Engine: "aurora-postgresql",
SyncTime: timestamppb.Now(),
},
},
},
})
require.NoError(t, err)
return userTask
}

tests := []struct {
name string
task func(t *testing.T) *usertasksv1.UserTask
Expand Down Expand Up @@ -338,6 +364,119 @@ func TestValidateUserTask(t *testing.T) {
},
wantErr: require.Error,
},
{
name: "DiscoverRDS: valid",
task: baseRDSDiscoverTask,
wantErr: require.NoError,
},
{
name: "DiscoverRDS: invalid issue type",
task: func(t *testing.T) *usertasksv1.UserTask {
ut := baseRDSDiscoverTask(t)
ut.Spec.IssueType = "unknown error"
return ut
},
wantErr: require.Error,
},
{
name: "DiscoverRDS: missing integration",
task: func(t *testing.T) *usertasksv1.UserTask {
ut := baseRDSDiscoverTask(t)
ut.Spec.Integration = ""
return ut
},
wantErr: require.Error,
},
{
name: "DiscoverRDS: missing discover rds field",
task: func(t *testing.T) *usertasksv1.UserTask {
ut := baseRDSDiscoverTask(t)
ut.Spec.DiscoverRds = nil
return ut
},
wantErr: require.Error,
},
{
name: "DiscoverRDS: wrong task name",
task: func(t *testing.T) *usertasksv1.UserTask {
ut := baseRDSDiscoverTask(t)
ut.Metadata.Name = "another-name"
return ut
},
wantErr: require.Error,
},
{
name: "DiscoverRDS: missing account id",
task: func(t *testing.T) *usertasksv1.UserTask {
ut := baseRDSDiscoverTask(t)
ut.Spec.DiscoverRds.AccountId = ""
return ut
},
wantErr: require.Error,
},
{
name: "DiscoverRDS: missing region",
task: func(t *testing.T) *usertasksv1.UserTask {
ut := baseRDSDiscoverTask(t)
ut.Spec.DiscoverRds.Region = ""
return ut
},
wantErr: require.Error,
},
{
name: "DiscoverRDS: databases - missing database name in map key",
task: func(t *testing.T) *usertasksv1.UserTask {
ut := baseRDSDiscoverTask(t)
origDatabasdeMetadata := ut.Spec.DiscoverRds.Databases[exampleDatabaseName]
ut.Spec.DiscoverRds.Databases[""] = origDatabasdeMetadata
return ut
},
wantErr: require.Error,
},
{
name: "DiscoverRDS: databases - missing database name in metadata",
task: func(t *testing.T) *usertasksv1.UserTask {
ut := baseRDSDiscoverTask(t)
origDatabasdeMetadata := ut.Spec.DiscoverRds.Databases[exampleDatabaseName]
origDatabasdeMetadata.Name = ""
ut.Spec.DiscoverRds.Databases[exampleDatabaseName] = origDatabasdeMetadata
return ut
},
wantErr: require.Error,
},
{
name: "DiscoverRDS: databases - different database name",
task: func(t *testing.T) *usertasksv1.UserTask {
ut := baseRDSDiscoverTask(t)
origDatabasdeMetadata := ut.Spec.DiscoverRds.Databases[exampleDatabaseName]
origDatabasdeMetadata.Name = "another-database"
ut.Spec.DiscoverRds.Databases[exampleDatabaseName] = origDatabasdeMetadata
return ut
},
wantErr: require.Error,
},
{
name: "DiscoverRDS: databases - missing discovery config",
task: func(t *testing.T) *usertasksv1.UserTask {
ut := baseRDSDiscoverTask(t)
origDatabasdeMetadata := ut.Spec.DiscoverRds.Databases[exampleDatabaseName]
origDatabasdeMetadata.DiscoveryConfig = ""
ut.Spec.DiscoverRds.Databases[exampleDatabaseName] = origDatabasdeMetadata
return ut
},
wantErr: require.Error,
},
{
name: "DiscoverRDS: databases - missing discovery group",
task: func(t *testing.T) *usertasksv1.UserTask {
ut := baseRDSDiscoverTask(t)
origDatabasdeMetadata := ut.Spec.DiscoverRds.Databases[exampleDatabaseName]
origDatabasdeMetadata.DiscoveryGroup = ""
ut.Spec.DiscoverRds.Databases[exampleDatabaseName] = origDatabasdeMetadata
return ut
},
wantErr: require.Error,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -465,3 +604,64 @@ func TestNewDiscoverEKSUserTask(t *testing.T) {
})
}
}

func TestNewDiscoverRDSUserTask(t *testing.T) {
t.Parallel()

userTaskExpirationTime := time.Now()
userTaskExpirationTimestamp := timestamppb.New(userTaskExpirationTime)
databaseSyncTimestamp := userTaskExpirationTimestamp

baseRDSDiscoverTaskSpec := &usertasksv1.UserTaskSpec{
Integration: "my-integration",
TaskType: "discover-rds",
IssueType: "rds-iam-auth-disabled",
State: "OPEN",
DiscoverRds: &usertasksv1.DiscoverRDS{
AccountId: "123456789012",
Region: "us-east-1",
Databases: map[string]*usertasksv1.DiscoverRDSDatabase{
"my-database": {
Name: "my-database",
DiscoveryConfig: "dc01",
DiscoveryGroup: "dg01",
SyncTime: databaseSyncTimestamp,
IsCluster: true,
Engine: "aurora-postgresql",
},
},
},
}

tests := []struct {
name string
taskSpec *usertasksv1.UserTaskSpec
taskOption []usertasks.UserTaskOption
expectedTask *usertasksv1.UserTask
}{
{
name: "options are applied task type",
taskSpec: baseRDSDiscoverTaskSpec,
expectedTask: &usertasksv1.UserTask{
Kind: "user_task",
Version: "v1",
Metadata: &headerv1.Metadata{
Name: "8c6014e2-8275-54d7-b285-31e0194b7835",
Expires: userTaskExpirationTimestamp,
},
Spec: baseRDSDiscoverTaskSpec,
},
taskOption: []usertasks.UserTaskOption{
usertasks.WithExpiration(userTaskExpirationTime),
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotTask, err := usertasks.NewDiscoverRDSUserTask(tt.taskSpec, tt.taskOption...)
require.NoError(t, err)
require.Equal(t, tt.expectedTask, gotTask)
})
}
}
2 changes: 2 additions & 0 deletions lib/auth/usertasks/usertasksv1/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ func userTaskToUserTaskStateEvent(ut *usertasksv1.UserTask) *usagereporter.UserT
ret.InstancesCount = int32(len(ut.GetSpec().GetDiscoverEc2().GetInstances()))
case usertasks.TaskTypeDiscoverEKS:
ret.InstancesCount = int32(len(ut.GetSpec().GetDiscoverEks().GetClusters()))
case usertasks.TaskTypeDiscoverRDS:
ret.InstancesCount = int32(len(ut.GetSpec().GetDiscoverRds().GetDatabases()))
}
return ret
}
Expand Down
Loading
Loading