Skip to content

Commit

Permalink
UserTasks - Discover RDS issues: add RDS known issues
Browse files Browse the repository at this point in the history
This PR adds a single RDS issue so that it's easier to review.
Other issues will be added later on.
  • Loading branch information
marcoandredinis committed Jan 14, 2025
1 parent 08d60ed commit bbd67e6
Show file tree
Hide file tree
Showing 9 changed files with 476 additions and 2 deletions.
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().DiscoverRds == nil {
return trace.BadParameter("%s requires the discover_rds field", TaskTypeDiscoverRDS)
}
if ut.GetSpec().DiscoverRds.AccountId == "" {
return trace.BadParameter("%s requires the discover_rds.account_id field", TaskTypeDiscoverRDS)
}
if ut.GetSpec().DiscoverRds.Region == "" {
return trace.BadParameter("%s requires the discover_rds.region field", TaskTypeDiscoverRDS)
}

expectedTaskName := TaskNameForDiscoverRDS(TaskNameForDiscoverRDSParts{
Integration: ut.Spec.Integration,
IssueType: ut.Spec.IssueType,
AccountID: ut.Spec.DiscoverRds.AccountId,
Region: ut.Spec.DiscoverRds.Region,
})
if ut.Metadata.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().IssueType) {
return trace.BadParameter("invalid issue type state, allowed values: %v", DiscoverRDSIssueTypes)
}

if len(ut.Spec.DiscoverRds.Databases) == 0 {
return trace.BadParameter("at least one database is required")
}
for databaseIdentifier, databaseIssue := range ut.Spec.DiscoverRds.Databases {
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

0 comments on commit bbd67e6

Please sign in to comment.