diff --git a/go.mod b/go.mod index 2b29147da..711a4dbfd 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nats-io/nats.go v1.36.0 github.com/open-policy-agent/opa v0.69.0 - github.com/opengovern/og-util v1.1.0 + github.com/opengovern/og-util v1.1.1 github.com/opengovern/plugin-aws v0.7.3 github.com/opengovern/plugin-gcp v0.0.0-20241014134959-2c0f222fc07b github.com/opengovern/plugin-kubernetes-internal v0.18.12 diff --git a/go.sum b/go.sum index e802027f0..b995f24c1 100644 --- a/go.sum +++ b/go.sum @@ -989,6 +989,8 @@ github.com/opencontainers/runc v1.2.0 h1:qke7ZVCmJcKrJVY2iHJVC+0kql9uYdkusOPsQOO github.com/opencontainers/runc v1.2.0/go.mod h1:/PXzF0h531HTMsYQnmxXkBD7YaGShm/2zcRB79dksUc= github.com/opengovern/og-util v1.1.0 h1:bV2XKX8aIpJGC2CR4kh9CU/rJWPQed76gtVXM2zBDXM= github.com/opengovern/og-util v1.1.0/go.mod h1:dyn8rhmxq59o1jnbgGfmcUvW7iB/eN6OxoTUUx6jEHA= +github.com/opengovern/og-util v1.1.1 h1:SXnPCNuhR9WxxupEa1hCtIOIh2Sy+MF80qVdpoxc58k= +github.com/opengovern/og-util v1.1.1/go.mod h1:dyn8rhmxq59o1jnbgGfmcUvW7iB/eN6OxoTUUx6jEHA= github.com/opengovern/plugin-aws v0.7.3 h1:76hZOjulNlgn4uaq5lq1/pmGmgJqvX1ZQbgqcQn03gI= github.com/opengovern/plugin-aws v0.7.3/go.mod h1:zfTMswfCyXZ0gD6SDCsmKg55LseXzeFzOH4jXn2QJVo= github.com/opengovern/plugin-gcp v0.0.0-20241014134959-2c0f222fc07b h1:4xP98kDpOXUu6RcFJyZN63OeA2I26MLS+dEB9JWYQpY= diff --git a/pkg/describe/scheduler_describe.go b/pkg/describe/scheduler_describe.go index d9d12b86c..bc81b70c1 100644 --- a/pkg/describe/scheduler_describe.go +++ b/pkg/describe/scheduler_describe.go @@ -155,7 +155,7 @@ func (s *Scheduler) RunDescribeResourceJobCycle(ctx context.Context, manuals boo cred: credential, } wp.AddJob(func() (interface{}, error) { - err := s.enqueueCloudNativeDescribeJob(ctx, c.dc, c.cred.Secret) + err := s.enqueueCloudNativeDescribeJob(ctx, c.dc, c.cred.Secret, c.src) if err != nil { s.logger.Error("Failed to enqueueCloudNativeDescribeConnectionJob", zap.Error(err), zap.Uint("jobID", dc.ID)) DescribeResourceJobsCount.WithLabelValues("failure", "enqueue").Inc() @@ -367,7 +367,8 @@ func newDescribeConnectionJob(a integrationapi.Integration, resourceType string, } } -func (s *Scheduler) enqueueCloudNativeDescribeJob(ctx context.Context, dc model.DescribeIntegrationJob, cipherText string) error { +func (s *Scheduler) enqueueCloudNativeDescribeJob(ctx context.Context, dc model.DescribeIntegrationJob, cipherText string, + integration *integrationapi.Integration) error { ctx, span := otel.Tracer(opengovernanceTrace.JaegerTracerName).Start(ctx, opengovernanceTrace.GetCurrentFuncName()) defer span.End() @@ -395,15 +396,17 @@ func (s *Scheduler) enqueueCloudNativeDescribeJob(ctx context.Context, dc model. VaultConfig: s.conf.Vault, DescribeJob: describe.DescribeJob{ - JobID: dc.ID, - ResourceType: dc.ResourceType, - IntegrationID: dc.IntegrationID, - ProviderID: dc.ProviderID, - DescribedAt: dc.CreatedAt.UnixMilli(), - IntegrationType: dc.IntegrationType, - CipherText: cipherText, - TriggerType: dc.TriggerType, - RetryCounter: 0, + JobID: dc.ID, + ResourceType: dc.ResourceType, + IntegrationID: dc.IntegrationID, + ProviderID: dc.ProviderID, + DescribedAt: dc.CreatedAt.UnixMilli(), + IntegrationType: dc.IntegrationType, + CipherText: cipherText, + IntegrationLabels: integration.Labels, + IntegrationAnnotations: integration.Annotations, + TriggerType: dc.TriggerType, + RetryCounter: 0, }, } diff --git a/services/integration/api/integrations/api.go b/services/integration/api/integrations/api.go index cba40a2db..fe7b7fd57 100644 --- a/services/integration/api/integrations/api.go +++ b/services/integration/api/integrations/api.go @@ -201,30 +201,6 @@ func (h API) DiscoverIntegrations(c echo.Context) error { var integrationsAPI []models.Integration for _, i := range integrations { - annotations, err := integration.GetAnnotations(jsonData) - if err != nil { - h.logger.Error("failed to get annotations", zap.Error(err)) - } - annotationsJsonData, err := json.Marshal(annotations) - if err != nil { - return err - } - integrationAnnotationsJsonb := pgtype.JSONB{} - err = integrationAnnotationsJsonb.Set(annotationsJsonData) - i.Annotations = integrationAnnotationsJsonb - - labels, err := integration.GetLabels(jsonData) - if err != nil { - h.logger.Error("failed to get labels", zap.Error(err)) - } - labelsJsonData, err := json.Marshal(labels) - if err != nil { - return err - } - integrationLabelsJsonb := pgtype.JSONB{} - err = integrationLabelsJsonb.Set(labelsJsonData) - i.Labels = integrationLabelsJsonb - integrationAPI, err := i.ToApi() if err != nil { h.logger.Error("failed to create integration api", zap.Error(err)) @@ -316,34 +292,15 @@ func (h API) AddIntegrations(c echo.Context) error { i.CredentialID = credentialID - annotations, err := integration.GetAnnotations(jsonData) - if err != nil { - h.logger.Error("failed to get annotations", zap.Error(err)) - } - annotationsJsonData, err := json.Marshal(annotations) - if err != nil { - return err - } - integrationAnnotationsJsonb := pgtype.JSONB{} - err = integrationAnnotationsJsonb.Set(annotationsJsonData) - i.Annotations = integrationAnnotationsJsonb - - labels, err := integration.GetLabels(jsonData) - if err != nil { - h.logger.Error("failed to get labels", zap.Error(err)) - } - labelsJsonData, err := json.Marshal(labels) - if err != nil { - return err - } - integrationLabelsJsonb := pgtype.JSONB{} - err = integrationLabelsJsonb.Set(labelsJsonData) - i.Labels = integrationLabelsJsonb - healthcheckTime := time.Now() i.LastCheck = &healthcheckTime - healthy, err := integration.HealthCheck(jsonData, i.ProviderID, labels) + iApi, err := i.ToApi() + if err != nil { + h.logger.Error("failed to create integration api", zap.Error(err)) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to create integration api") + } + healthy, err := integration.HealthCheck(jsonData, i.ProviderID, iApi.Annotations) if err != nil || !healthy { h.logger.Info("integration is not healthy", zap.String("integration_id", i.IntegrationID.String()), zap.Error(err)) i.State = models2.IntegrationStateInactive diff --git a/services/integration/integration-type/aws-account/aws_account.go b/services/integration/integration-type/aws-account/aws_account.go index b6552fc23..bbcff08d2 100644 --- a/services/integration/integration-type/aws-account/aws_account.go +++ b/services/integration/integration-type/aws-account/aws_account.go @@ -2,6 +2,8 @@ package aws_account import ( "encoding/json" + "fmt" + "github.com/jackc/pgtype" awsDescriberLocal "github.com/opengovern/opengovernance/services/integration/integration-type/aws-account/configs" "github.com/opengovern/opengovernance/services/integration/integration-type/aws-account/discovery" "github.com/opengovern/opengovernance/services/integration/integration-type/aws-account/healthcheck" @@ -23,31 +25,19 @@ func (i *AwsCloudAccountIntegration) GetDescriberConfiguration() interfaces.Desc } } -func (i *AwsCloudAccountIntegration) GetAnnotations(jsonData []byte) (map[string]string, error) { - annotations := make(map[string]string) - - return annotations, nil -} - -func (i *AwsCloudAccountIntegration) GetLabels(jsonData []byte) (map[string]string, error) { - annotations := make(map[string]string) - - return annotations, nil -} - -func (i *AwsCloudAccountIntegration) HealthCheck(jsonData []byte, providerId string, labels map[string]string) (bool, error) { +func (i *AwsCloudAccountIntegration) HealthCheck(jsonData []byte, providerId string, labels map[string]string, annotations map[string]string) (bool, error) { var credentials awsDescriberLocal.IntegrationCredentials err := json.Unmarshal(jsonData, &credentials) if err != nil { return false, err } - return healthcheck.AWSIntegrationHealthCheck(healthcheck.Config{ - AWSAccessKeyID: credentials.AwsAccessKeyID, - AWSSecretAccessKey: credentials.AwsSecretAccessKey, - RoleToAssumeInMainAccount: credentials.RoleToAssumeInMainAccount, - CrossAccountRole: credentials.CrossAccountRoleName, - ExternalID: credentials.ExternalID, + return healthcheck.AWSIntegrationHealthCheck(healthcheck.AWSConfigInput{ + AccessKeyID: credentials.AwsAccessKeyID, + SecretAccessKey: credentials.AwsSecretAccessKey, + RoleNameInPrimaryAccount: credentials.RoleToAssumeInMainAccount, + CrossAccountRoleARN: credentials.CrossAccountRoleName, + ExternalID: credentials.ExternalID, }, providerId) } @@ -59,20 +49,38 @@ func (i *AwsCloudAccountIntegration) DiscoverIntegrations(jsonData []byte) ([]mo } var integrations []models.Integration - accounts, err := discovery.AWSIntegrationDiscovery(discovery.Config{ - AWSAccessKeyID: credentials.AwsAccessKeyID, - AWSSecretAccessKey: credentials.AwsSecretAccessKey, - RoleToAssumeInMainAccount: credentials.RoleToAssumeInMainAccount, - CrossAccountRole: credentials.CrossAccountRoleName, - ExternalID: credentials.ExternalID, + accounts := discovery.AWSIntegrationDiscovery(discovery.Config{ + AWSAccessKeyID: credentials.AwsAccessKeyID, + AWSSecretAccessKey: credentials.AwsSecretAccessKey, + RoleNameToAssumeInMainAccount: credentials.RoleToAssumeInMainAccount, + CrossAccountRoleName: credentials.CrossAccountRoleName, + ExternalID: credentials.ExternalID, }) - if err != nil { - return nil, err - } for _, a := range accounts { + if a.Details.Error != "" { + return nil, fmt.Errorf(a.Details.Error) + } + + labels := map[string]string{ + "RoleNameInMainAccount": a.Labels.RoleNameInMainAccount, + "AccountType": a.Labels.AccountType, + "CrossAccountRoleARN": a.Labels.CrossAccountRoleARN, + "ExternalID": a.Labels.ExternalID, + } + labelsJsonData, err := json.Marshal(labels) + if err != nil { + return nil, err + } + integrationLabelsJsonb := pgtype.JSONB{} + err = integrationLabelsJsonb.Set(labelsJsonData) + if err != nil { + return nil, err + } + integrations = append(integrations, models.Integration{ ProviderID: a.AccountID, Name: a.AccountName, + Labels: integrationLabelsJsonb, }) } diff --git a/services/integration/integration-type/aws-account/discovery/aws_integrations_discovery.go b/services/integration/integration-type/aws-account/discovery/aws_integrations_discovery.go index e7600ff76..71cb38133 100644 --- a/services/integration/integration-type/aws-account/discovery/aws_integrations_discovery.go +++ b/services/integration/integration-type/aws-account/discovery/aws_integrations_discovery.go @@ -20,53 +20,28 @@ const DefaultMaxAccounts = 500 // Config represents the configuration loaded from the JSON file. type Config struct { - AWSAccessKeyID string `json:"aws_access_key_id"` - AWSSecretAccessKey string `json:"aws_secret_access_key"` - RoleToAssumeInMainAccount string `json:"role_to_assume_in_main_account,omitempty"` - CrossAccountRole string `json:"cross_account_role,omitempty"` - ExternalID string `json:"external_id,omitempty"` - MaxAccounts int `json:"max_accounts,omitempty"` + AWSAccessKeyID string `json:"aws_access_key_id"` + AWSSecretAccessKey string `json:"aws_secret_access_key"` + RoleNameToAssumeInMainAccount string `json:"role_name_to_assume_in_main_account,omitempty"` + CrossAccountRoleName string `json:"cross_account_role_name,omitempty"` + ExternalID string `json:"external_id,omitempty"` + MaxAccounts int `json:"max_accounts,omitempty"` } -// AccountResult holds the result for each account. -type AccountResult struct { - AccountID string `json:"account_id"` - AccountName string `json:"account_name"` - AccountType string `json:"account_type"` // "Organization Master", "Organization Member", or "Standalone" - Details Details `json:"details"` +// AccountLabels holds the labels associated with each account. +type AccountLabels struct { + AccountType string `json:"account_type"` // "Organization Master", "Organization Member", or "Standalone" + CrossAccountRoleARN string `json:"cross_account_role_arn,omitempty"` + RoleNameInMainAccount string `json:"role_name_in_main_account,omitempty"` + ExternalID string `json:"external_id,omitempty"` } -func AWSIntegrationDiscovery(cfg Config) ([]AccountResult, error) { - credentialType := "Single Account" - - if cfg.CrossAccountRole != "" { - credentialType = "Multi-Account" - } - - if credentialType == "Multi-Account" { - // Set MaxAccounts - maxAccounts := cfg.MaxAccounts - if maxAccounts == 0 { - maxAccounts = DefaultMaxAccounts - } - - // Perform organization discovery - results, err := DiscoverOrganizationAccounts(cfg, maxAccounts) - if err != nil { - return nil, err - } - return results, nil - } else { - result, err := DiscoverSingleAccount(cfg) - if err != nil { - return nil, err - } - if result != nil { - return []AccountResult{*result}, nil - } else { - return []AccountResult{}, nil - } - } +// AccountResult holds the result for each account. +type AccountResult struct { + AccountID string `json:"account_id"` + AccountName string `json:"account_name"` + Labels AccountLabels `json:"labels"` + Details Details `json:"details"` } // Details holds detailed information for each account. @@ -88,14 +63,68 @@ var requiredPolicies = []string{ // Add more policy ARNs as needed } -// GenerateAWSConfig creates an AWS configuration using the provided credentials provider. -func GenerateAWSConfig(credsProvider aws.CredentialsProvider) (aws.Config, error) { +func AWSIntegrationDiscovery(cfg Config) []AccountResult { + // Determine credential type + credentialType := "Single Account" + + if cfg.CrossAccountRoleName != "" { + credentialType = "Multi-Account" + } + + if credentialType == "Multi-Account" { + results := DiscoverOrganizationAccounts(cfg) + return results + } else { + result := DiscoverSingleAccount(cfg) + return []AccountResult{result} + } +} + +// GenerateAWSConfig creates an AWS configuration using the provided credentials. +// It can assume a role if roleNameToAssume is provided. +func GenerateAWSConfig(awsAccessKeyID string, awsSecretAccessKey string, roleNameToAssume string, externalID string, accountID string) (aws.Config, error) { + credsProvider := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider( + awsAccessKeyID, + awsSecretAccessKey, + "", + )) cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithCredentialsProvider(credsProvider), + config.WithRegion("us-east-2"), ) if err != nil { return aws.Config{}, fmt.Errorf("failed to load configuration: %v", err) } + if roleNameToAssume != "" { + // Construct Role ARN + roleArn := fmt.Sprintf("arn:aws:iam::%s:role/%s", accountID, roleNameToAssume) + + // Use STS to assume the role + stsClient := sts.NewFromConfig(cfg) + input := &sts.AssumeRoleInput{ + RoleArn: aws.String(roleArn), + RoleSessionName: aws.String("GenerateAWSConfigSession"), + } + if externalID != "" { + input.ExternalId = aws.String(externalID) + } + assumeRoleOutput, err := stsClient.AssumeRole(context.TODO(), input) + if err != nil { + return aws.Config{}, fmt.Errorf("failed to assume role: %v", err) + } + credsProvider = aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider( + *assumeRoleOutput.Credentials.AccessKeyId, + *assumeRoleOutput.Credentials.SecretAccessKey, + *assumeRoleOutput.Credentials.SessionToken, + )) + cfg, err = config.LoadDefaultConfig(context.TODO(), + config.WithCredentialsProvider(credsProvider), + config.WithRegion("us-east-2"), + ) + if err != nil { + return aws.Config{}, fmt.Errorf("failed to load configuration with assumed role: %v", err) + } + } return cfg, nil } @@ -209,31 +238,56 @@ func parsePrincipalArn(principalArn string) (entityType string, entityName strin // DiscoverOrganizationAccounts retrieves all active accounts in an AWS Organization // and checks their accessibility and attached policies. -func DiscoverOrganizationAccounts(cfg Config, maxAccounts int) ([]AccountResult, error) { +func DiscoverOrganizationAccounts(cfg Config) []AccountResult { var results []AccountResult - // Create initial AWS Config using provided credentials - credsProvider := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider( + // First, get primary account ID using initial credentials + initialCfg, err := config.LoadDefaultConfig(context.TODO(), + config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider( + cfg.AWSAccessKeyID, + cfg.AWSSecretAccessKey, + "", + )), + config.WithRegion("us-east-2"), + ) + if err != nil { + fmt.Printf("Failed to load initial AWS config: %v\n", err) + return results + } + + stsClient := sts.NewFromConfig(initialCfg) + + identityOutput, err := stsClient.GetCallerIdentity(context.TODO(), &sts.GetCallerIdentityInput{}) + if err != nil { + fmt.Printf("Failed to get caller identity: %v\n", err) + return results + } + + mainAccountID := *identityOutput.Account + + // Create AWS Config using provided credentials and optional role in main account + awsCfg, err := GenerateAWSConfig( cfg.AWSAccessKeyID, cfg.AWSSecretAccessKey, - "", - )) - - awsCfg, err := GenerateAWSConfig(credsProvider) + cfg.RoleNameToAssumeInMainAccount, + cfg.ExternalID, + mainAccountID, + ) if err != nil { - return nil, err + fmt.Printf("Failed to generate AWS config: %v\n", err) + return results } // Initialize STS client - stsClient := sts.NewFromConfig(awsCfg) + stsClient = sts.NewFromConfig(awsCfg) // Retrieve Caller Identity to get main account details - identityOutput, err := stsClient.GetCallerIdentity(context.TODO(), &sts.GetCallerIdentityInput{}) + identityOutput, err = stsClient.GetCallerIdentity(context.TODO(), &sts.GetCallerIdentityInput{}) if err != nil { - return nil, err + fmt.Printf("Failed to get caller identity: %v\n", err) + return results } - mainAccountID := *identityOutput.Account mainAccountARN := *identityOutput.Arn // Set credential type as Multi-Account by default @@ -243,46 +297,10 @@ func DiscoverOrganizationAccounts(cfg Config, maxAccounts int) ([]AccountResult, orgClient := organizations.NewFromConfig(awsCfg) // Attempt to describe the organization - // Discard orgOutput since it's not used _, err = orgClient.DescribeOrganization(context.TODO(), &organizations.DescribeOrganizationInput{}) if err != nil { - return nil, err - } - - // If RoleToAssumeInMainAccount is provided, assume that role - if cfg.RoleToAssumeInMainAccount != "" { - // Construct Role ARN for main account - roleArn := fmt.Sprintf("arn:aws:iam::%s:role/%s", mainAccountID, cfg.RoleToAssumeInMainAccount) - assumeRoleInput := &sts.AssumeRoleInput{ - RoleArn: aws.String(roleArn), - RoleSessionName: aws.String("AssumeMainAccountRoleSession"), - } - - // Include ExternalID if provided - if cfg.ExternalID != "" { - assumeRoleInput.ExternalId = aws.String(cfg.ExternalID) - } - - // Attempt to assume the role - assumeRoleOutput, err := stsClient.AssumeRole(context.TODO(), assumeRoleInput) - if err != nil { - return nil, err - } - - // Create new AWS Config with assumed role credentials - credsProvider = aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider( - *assumeRoleOutput.Credentials.AccessKeyId, - *assumeRoleOutput.Credentials.SecretAccessKey, - *assumeRoleOutput.Credentials.SessionToken, - )) - - awsCfg, err = GenerateAWSConfig(credsProvider) - if err != nil { - return nil, err - } - - // Update STS client with new configuration - stsClient = sts.NewFromConfig(awsCfg) + fmt.Printf("This account is not an AWS Organizations management account or lacks permissions: %v\n", err) + return results } // Prepare to list accounts within the organization @@ -290,6 +308,11 @@ func DiscoverOrganizationAccounts(cfg Config, maxAccounts int) ([]AccountResult, MaxResults: aws.Int32(20), // Default page size } + maxAccounts := cfg.MaxAccounts + if maxAccounts == 0 { + maxAccounts = DefaultMaxAccounts + } + var accounts []organizationsTypes.Account // Initialize paginator for listing accounts @@ -320,6 +343,9 @@ func DiscoverOrganizationAccounts(cfg Config, maxAccounts int) ([]AccountResult, accountResult := AccountResult{ AccountID: *acct.Id, AccountName: *acct.Name, + Labels: AccountLabels{ + ExternalID: cfg.ExternalID, // Include ExternalID if provided + }, Details: Details{ RequiredPolicies: requiredPolicies, Email: *acct.Email, @@ -329,19 +355,24 @@ func DiscoverOrganizationAccounts(cfg Config, maxAccounts int) ([]AccountResult, // Determine the account type (Master or Member) if *acct.Id == mainAccountID { - accountResult.AccountType = "Organization Master" + accountResult.Labels.AccountType = "Organization Master" } else { - accountResult.AccountType = "Organization Member" + accountResult.Labels.AccountType = "Organization Member" } - // Handle the main account separately if no role assumption is needed - if cfg.RoleToAssumeInMainAccount == "" && *acct.Id == mainAccountID { + // Handle the main account + if *acct.Id == mainAccountID { // Mark account as accessible accountResult.Details.IsAccessible = true accountResult.Details.IamPrincipal = mainAccountARN // The IAM user's ARN - // Check policies attached to the IAM user - attachedPolicies, missingPolicies, err := GetAttachedPolicies(awsCfg, mainAccountARN, requiredPolicies) + // Set RoleNameInMainAccount if role was assumed + if cfg.RoleNameToAssumeInMainAccount != "" { + accountResult.Labels.RoleNameInMainAccount = cfg.RoleNameToAssumeInMainAccount + } + + // Check policies attached to the IAM user or assumed role + attachedPolicies, missingPolicies, err := GetAttachedPolicies(awsCfg, accountResult.Details.IamPrincipal, requiredPolicies) if err != nil { accountResult.Details.HasPolicies = false accountResult.Details.Error = fmt.Sprintf("Error checking policies: %v", err) @@ -367,7 +398,7 @@ func DiscoverOrganizationAccounts(cfg Config, maxAccounts int) ([]AccountResult, // For member accounts, attempt to assume the cross-account role // Construct Role ARN for the member account - roleArn := fmt.Sprintf("arn:aws:iam::%s:role/%s", *acct.Id, cfg.CrossAccountRole) + roleArn := fmt.Sprintf("arn:aws:iam::%s:role/%s", *acct.Id, cfg.CrossAccountRoleName) // Prepare AssumeRole input assumeRoleInput := &sts.AssumeRoleInput{ @@ -395,6 +426,7 @@ func DiscoverOrganizationAccounts(cfg Config, maxAccounts int) ([]AccountResult, // Mark account as accessible and record the assumed role ARN accountResult.Details.IsAccessible = true accountResult.Details.IamPrincipal = *assumeRoleOutput.AssumedRoleUser.Arn + accountResult.Labels.CrossAccountRoleARN = roleArn // Create AWS Config with assumed role credentials assumedCredsProvider := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider( @@ -403,7 +435,10 @@ func DiscoverOrganizationAccounts(cfg Config, maxAccounts int) ([]AccountResult, *assumeRoleOutput.Credentials.SessionToken, )) - assumedCfg, err := GenerateAWSConfig(assumedCredsProvider) + assumedCfg, err := config.LoadDefaultConfig(context.TODO(), + config.WithCredentialsProvider(assumedCredsProvider), + config.WithRegion("us-east-2"), // Default region + ) if err != nil { accountResult.Details.HasPolicies = false accountResult.Details.Error = fmt.Sprintf("Failed to generate AWS config with assumed role: %v", err) @@ -439,40 +474,58 @@ func DiscoverOrganizationAccounts(cfg Config, maxAccounts int) ([]AccountResult, results = append(results, accountResult) } - return results, nil + return results } // DiscoverSingleAccount handles the case when credentials are only for a single account. -func DiscoverSingleAccount(cfg Config) (*AccountResult, error) { +func DiscoverSingleAccount(cfg Config) AccountResult { var result AccountResult - // Create AWS Config - credsProvider := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider( - cfg.AWSAccessKeyID, - cfg.AWSSecretAccessKey, - "", - )) - - awsCfg, err := GenerateAWSConfig(credsProvider) + // First, get primary account ID using initial credentials + initialCfg, err := config.LoadDefaultConfig(context.TODO(), + config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider( + cfg.AWSAccessKeyID, + cfg.AWSSecretAccessKey, + "", + )), + config.WithRegion("us-east-2"), + ) if err != nil { - return nil, err + fmt.Printf("Failed to load initial AWS config: %v\n", err) + result.Details.Error = fmt.Sprintf("Failed to load initial AWS config: %v", err) + return result } - // Get STS client - stsClient := sts.NewFromConfig(awsCfg) + stsClient := sts.NewFromConfig(initialCfg) - // Get Caller Identity identityOutput, err := stsClient.GetCallerIdentity(context.TODO(), &sts.GetCallerIdentityInput{}) if err != nil { - return nil, err + fmt.Printf("Failed to get caller identity: %v\n", err) + result.Details.Error = fmt.Sprintf("Failed to get caller identity: %v", err) + return result } accountID := *identityOutput.Account accountARN := *identityOutput.Arn + // Create AWS Config + awsCfg, err := GenerateAWSConfig( + cfg.AWSAccessKeyID, + cfg.AWSSecretAccessKey, + cfg.RoleNameToAssumeInMainAccount, + cfg.ExternalID, + accountID, + ) + if err != nil { + fmt.Printf("Failed to generate AWS config: %v\n", err) + result.Details.Error = fmt.Sprintf("Failed to generate AWS config: %v", err) + return result + } + result.AccountID = accountID result.Details.RequiredPolicies = requiredPolicies result.Details.CredentialType = "Single Account" + result.Labels.ExternalID = cfg.ExternalID // Include ExternalID if provided // Try to get account alias as account name iamClient := iam.NewFromConfig(awsCfg) @@ -485,64 +538,41 @@ func DiscoverSingleAccount(cfg Config) (*AccountResult, error) { } // Check if the account is part of an organization + orgClient := organizations.NewFromConfig(awsCfg) if orgOutput, err := orgClient.DescribeOrganization(context.TODO(), &organizations.DescribeOrganizationInput{}); err == nil { // The account is part of an organization if orgOutput.Organization != nil && orgOutput.Organization.MasterAccountId != nil { if *orgOutput.Organization.MasterAccountId == accountID { - result.AccountType = "Organization Master" + result.Labels.AccountType = "Organization Master" } else { - result.AccountType = "Organization Member" + result.Labels.AccountType = "Organization Member" } } } else { if strings.Contains(err.Error(), "AWSOrganizationsNotInUseException") { - result.AccountType = "Standalone" + result.Labels.AccountType = "Standalone" } else { - result.AccountType = "Unknown" + result.Labels.AccountType = "Unknown" result.Details.Error = fmt.Sprintf("Error describing organization: %v", err) } } - // Handle role assumption if role_to_assume_in_main_account is provided - if cfg.RoleToAssumeInMainAccount != "" { - // Assume the role in main account - roleArn := fmt.Sprintf("arn:aws:iam::%s:role/%s", accountID, cfg.RoleToAssumeInMainAccount) - input := &sts.AssumeRoleInput{ - RoleArn: aws.String(roleArn), - RoleSessionName: aws.String("AssumeRoleSession"), - } - - if cfg.ExternalID != "" { - input.ExternalId = aws.String(cfg.ExternalID) - } - - assumeRoleOutput, err := stsClient.AssumeRole(context.TODO(), input) - if err != nil { - result.Details.IsAccessible = false - result.Details.HasPolicies = false - result.Details.Error = fmt.Sprintf("Failed to assume role: %v", err) - result.Details.Healthy = false - return &result, nil - } + // Handle role assumption if RoleNameToAssumeInMainAccount is provided + if cfg.RoleNameToAssumeInMainAccount != "" { + // Set RoleNameInMainAccount to the role used + result.Labels.RoleNameInMainAccount = cfg.RoleNameToAssumeInMainAccount - // Update AWS Config with assumed role credentials - credsProvider = aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider( - *assumeRoleOutput.Credentials.AccessKeyId, - *assumeRoleOutput.Credentials.SecretAccessKey, - *assumeRoleOutput.Credentials.SessionToken, - )) - - awsCfg, err = GenerateAWSConfig(credsProvider) + // Get the assumed role ARN + stsClient = sts.NewFromConfig(awsCfg) + identityOutput, err := stsClient.GetCallerIdentity(context.TODO(), &sts.GetCallerIdentityInput{}) if err != nil { - result.Details.Error = fmt.Sprintf("Failed to generate AWS config with assumed role: %v", err) - result.Details.IsAccessible = false - result.Details.HasPolicies = false - result.Details.Healthy = false - return &result, nil + fmt.Printf("Failed to get caller identity: %v\n", err) + result.Details.Error = fmt.Sprintf("Failed to get caller identity: %v", err) + return result } - result.Details.IamPrincipal = *assumeRoleOutput.AssumedRoleUser.Arn + result.Details.IamPrincipal = *identityOutput.Arn } else { result.Details.IamPrincipal = accountARN } @@ -550,12 +580,11 @@ func DiscoverSingleAccount(cfg Config) (*AccountResult, error) { // Check policies attached to the IAM user or assumed role attachedPolicies, missingPolicies, err := GetAttachedPolicies(awsCfg, result.Details.IamPrincipal, requiredPolicies) if err != nil { - return nil, err - //result.Details.HasPolicies = false - //result.Details.Error = fmt.Sprintf("Error checking policies: %v", err) - //result.Details.IsAccessible = false - //result.Details.Healthy = false - //return &result, nil + result.Details.HasPolicies = false + result.Details.Error = fmt.Sprintf("Error checking policies: %v", err) + result.Details.IsAccessible = false + result.Details.Healthy = false + return result } result.Details.AttachedPolicies = attachedPolicies @@ -569,5 +598,5 @@ func DiscoverSingleAccount(cfg Config) (*AccountResult, error) { result.Details.IsAccessible = true result.Details.Healthy = result.Details.IsAccessible && result.Details.HasPolicies - return &result, nil + return result } diff --git a/services/integration/integration-type/aws-account/healthcheck/aws_cloud_account_healthcheck.go b/services/integration/integration-type/aws-account/healthcheck/aws_cloud_account_healthcheck.go index 6184aea16..98bc4a3fd 100644 --- a/services/integration/integration-type/aws-account/healthcheck/aws_cloud_account_healthcheck.go +++ b/services/integration/integration-type/aws-account/healthcheck/aws_cloud_account_healthcheck.go @@ -2,52 +2,45 @@ package healthcheck import ( "context" + "errors" "fmt" "strings" "github.com/aws/aws-sdk-go-v2/aws" - awsarn "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" "github.com/aws/aws-sdk-go-v2/service/iam" "github.com/aws/aws-sdk-go-v2/service/sts" ) -// Config represents the configuration loaded from the JSON file. -type Config struct { - AWSAccessKeyID string `json:"aws_access_key_id"` - AWSSecretAccessKey string `json:"aws_secret_access_key"` - RoleToAssumeInMainAccount string `json:"role_to_assume_in_main_account,omitempty"` - CrossAccountRole string `json:"cross_account_role,omitempty"` - ExternalID string `json:"external_id,omitempty"` +// AWSConfigInput encapsulates all possible AWS credentials and role information. +type AWSConfigInput struct { + AccessKeyID string `json:"access_key_id"` // AWS Access Key ID. Leave empty to use default credentials. + SecretAccessKey string `json:"secret_access_key"` // AWS Secret Access Key. Leave empty to use default credentials. + RoleNameInPrimaryAccount string `json:"role_name_in_primary_account"` // Role ARN to assume in the primary account. Leave empty if not assuming a role. + CrossAccountRoleARN string `json:"cross_account_role_arn"` // Role ARN to assume in the cross account. Leave empty if not assuming a cross account role. + ExternalID string `json:"external_id"` // External ID required for assuming the cross account role. Leave empty if not required. + Region string `json:"region"` // AWS region. Defaults to "us-east-2" if empty. } -func AWSIntegrationHealthCheck(config Config, accountID string) (bool, error) { - result := ValidateAccount(accountID, config) - - var err error - if result.Details.Error != "" { - err = fmt.Errorf(result.Details.Error) - } - return result.Healthy, err -} - -// AccountResult holds the result for the account. +// AccountResult represents the outcome of validating an AWS account. type AccountResult struct { - AccountID string `json:"account_id"` - Healthy bool `json:"healthy"` - Details Details `json:"details"` + AccountID string `json:"account_id"` // The AWS account ID being validated. + Healthy bool `json:"healthy"` // Indicates if the account is healthy (accessible and has required policies). + Details PolicyDetails `json:"details"` // Detailed information about policies and accessibility. } -// Details holds detailed information for the account. -type Details struct { - IsAccessible bool `json:"isAccessible"` - HasPolicies bool `json:"hasPolicies"` - AttachedPolicies []string `json:"attached_policies,omitempty"` - RequiredPolicies []string `json:"required_policies,omitempty"` - IamPrincipal string `json:"iam_principal,omitempty"` - CredentialType string `json:"credential_type"` - Error string `json:"error,omitempty"` +// PolicyDetails provides detailed information about the policies attached to the IAM principal. +type PolicyDetails struct { + RequiredPolicies []string `json:"required_policies"` // List of required policy ARNs. + AttachedPolicies []string `json:"attached_policies"` // List of policies attached to the principal. + MissingPolicies []string `json:"missing_policies"` // List of required policies that are missing. + CredentialType string `json:"credential_type"` // Type of credentials used ("Single Account" or "Multi-Account"). + IsAccessible bool `json:"is_accessible"` // Indicates if the account is accessible. + IamPrincipal string `json:"iam_principal"` // ARN of the IAM principal. + HasPolicies bool `json:"has_policies"` // Indicates if required policies are attached. + Error string `json:"error,omitempty"` // Error message, if any. } // List of required policy ARNs. Modify this list as needed. @@ -56,25 +49,238 @@ var requiredPolicies = []string{ // Add more policy ARNs as needed } -// GenerateAWSConfig creates an AWS configuration using the provided credentials provider. -func GenerateAWSConfig(credsProvider aws.CredentialsProvider) (aws.Config, error) { - cfg, err := config.LoadDefaultConfig(context.TODO(), - config.WithCredentialsProvider(credsProvider), +func AWSIntegrationHealthCheck(creds AWSConfigInput, accountID string) (bool, error) { + // Perform account validation + result := ValidateIntegrationHealth(accountID, creds) + if result.Details.Error != "" { + return false, errors.New(result.Details.Error) + } + + return result.Healthy, nil +} + +// GenerateAWSConfig initializes and returns an AWS configuration based on the provided inputs. +// It determines whether to perform single or multi-account validation based on the inputs. +func GenerateAWSConfig( + accessKeyID string, + secretAccessKey string, + roleNameInPrimaryAccount string, + crossAccountRoleARN string, + externalID string, + region string, +) (*aws.Config, error) { + var cfg aws.Config + var err error + + // Step 1: Set default region if not provided + if region == "" { + region = "us-east-2" + } + + // Step 2: Initialize the base credentials provider + var baseCredentials aws.CredentialsProvider = nil + + if accessKeyID != "" && secretAccessKey != "" { + // Use static credentials if provided + baseCredentials = credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, "") + } + + // Step 3: Load the initial AWS configuration + if baseCredentials != nil { + cfg, err = config.LoadDefaultConfig(context.TODO(), + config.WithCredentialsProvider(baseCredentials), + config.WithRegion(region), + ) + } else { + cfg, err = config.LoadDefaultConfig(context.TODO(), + config.WithRegion(region), + ) + } + if err != nil { + return nil, fmt.Errorf("unable to load SDK config: %v", err) + } + + // Step 4: Determine the type of validation based on provided inputs + isMultiAccount := false + if crossAccountRoleARN != "" || roleNameInPrimaryAccount != "" { + isMultiAccount = true + } + + if isMultiAccount { + // Assume Role in Primary Account if RoleNameInPrimaryAccount is provided + if roleNameInPrimaryAccount != "" { + primaryRoleARN := roleNameInPrimaryAccount // Expected to be full ARN + + // Create an STS client from the existing configuration + stsClient := sts.NewFromConfig(cfg) + + // Configure AssumeRole options + primaryAssumeRoleOptions := func(o *stscreds.AssumeRoleOptions) { + o.RoleSessionName = "primary-account-session" // Customize as needed + // Optional: o.DurationSeconds = 3600 + // Optional: o.MFAOptions = &stscreds.MFAOptions{SerialNumber: "YOUR_MFA_SERIAL", TokenCode: "123456"} + } + + // Create an AssumeRole provider for the primary account role + primaryRoleProvider := stscreds.NewAssumeRoleProvider(stsClient, primaryRoleARN, primaryAssumeRoleOptions) + + // Cache the credentials + primaryCredentials := aws.NewCredentialsCache(primaryRoleProvider) + + // Update the AWS configuration to use the assumed primary role credentials + cfg.Credentials = primaryCredentials + } + + // Assume Role in Cross Account if CrossAccountRoleARN is provided + if crossAccountRoleARN != "" { + // Create an STS client from the existing configuration + stsClient := sts.NewFromConfig(cfg) + + // Configure AssumeRole options + crossAccountAssumeRoleOptions := func(o *stscreds.AssumeRoleOptions) { + o.RoleSessionName = "cross-account-session" // Customize as needed + if externalID != "" { + o.ExternalID = aws.String(externalID) + } + // Optional: o.DurationSeconds = 3600 + // Optional: o.MFAOptions = &stscreds.MFAOptions{SerialNumber: "YOUR_MFA_SERIAL", TokenCode: "123456"} + } + + // Create an AssumeRole provider for the cross account role + crossAccountRoleProvider := stscreds.NewAssumeRoleProvider(stsClient, crossAccountRoleARN, crossAccountAssumeRoleOptions) + + // Cache the credentials + crossAccountCredentials := aws.NewCredentialsCache(crossAccountRoleProvider) + + // Update the AWS configuration to use the assumed cross account role credentials + cfg.Credentials = crossAccountCredentials + } + } + + return &cfg, nil +} + +// ValidateIntegrationHealth validates the integration health of the specified AWS account. +// It checks if the account is accessible and if the IAM principal has the required policies. +func ValidateIntegrationHealth(accountID string, creds AWSConfigInput) AccountResult { + var result AccountResult + result.AccountID = accountID + result.Details.RequiredPolicies = requiredPolicies + result.Details.CredentialType = "Single Account" // default, may change to "Multi-Account" + + // Create AWS Config using provided credentials + awsCfg, err := GenerateAWSConfig( + creds.AccessKeyID, + creds.SecretAccessKey, + creds.RoleNameInPrimaryAccount, + creds.CrossAccountRoleARN, + creds.ExternalID, + creds.Region, ) if err != nil { - return aws.Config{}, fmt.Errorf("failed to load configuration: %v", err) + result.Details.Error = fmt.Sprintf("Failed to generate AWS config: %v", err) + result.Details.IsAccessible = false + result.Details.HasPolicies = false + result.Healthy = false + return result + } + + // Initialize STS client + stsClient := sts.NewFromConfig(*awsCfg) + + // Determine if it's multi-account based on provided inputs + isMultiAccount := creds.CrossAccountRoleARN != "" || creds.RoleNameInPrimaryAccount != "" + + if isMultiAccount { + result.Details.CredentialType = "Multi-Account" + // Assume roles are already handled in GenerateAWSConfig + // Proceed to get caller identity + } + + // Get Caller Identity to check access + identityOutput, err := stsClient.GetCallerIdentity(context.TODO(), &sts.GetCallerIdentityInput{}) + if err != nil { + result.Details.IsAccessible = false + result.Details.HasPolicies = false + result.Details.Error = fmt.Sprintf("Failed to get caller identity: %v", err) + result.Healthy = false + return result + } + + // Verify if the account ID matches + if *identityOutput.Account != accountID { + result.Details.IsAccessible = false + result.Details.HasPolicies = false + result.Details.Error = fmt.Sprintf("Provided credentials do not match the account ID: %s", accountID) + result.Healthy = false + return result + } + + // Record the principal ARN + result.Details.IsAccessible = true + result.Details.IamPrincipal = *identityOutput.Arn + + // Check policies attached to the principal using utility functions + attachedPolicies, missingPolicies, err := GetAttachedPolicies(*awsCfg, result.Details.IamPrincipal, requiredPolicies) + if err != nil { + result.Details.HasPolicies = false + result.Details.Error = fmt.Sprintf("Error checking policies: %v", err) + result.Healthy = false + return result + } + + result.Details.AttachedPolicies = attachedPolicies + result.Details.MissingPolicies = missingPolicies + if len(missingPolicies) > 0 { + result.Details.HasPolicies = false + result.Details.Error = fmt.Sprintf("Missing policies: %v", missingPolicies) + } else { + result.Details.HasPolicies = true } - return cfg, nil + + result.Healthy = result.Details.IsAccessible && result.Details.HasPolicies + + return result +} + +// ParsePrincipalArn parses an AWS principal ARN and returns the entity type and entity name. +// This updated function handles assumed roles by extracting the actual role name. +func ParsePrincipalArn(principalArn string) (string, string, error) { + parts := strings.Split(principalArn, ":") + if len(parts) < 6 { + return "", "", fmt.Errorf("invalid ARN format") + } + + // parts[5] contains the resource part + resource := parts[5] + resourceParts := strings.SplitN(resource, "/", 2) + if len(resourceParts) != 2 { + return "", "", fmt.Errorf("invalid resource format in ARN") + } + + entityType := resourceParts[0] + entityName := resourceParts[1] + + if entityType == "assumed-role" { + entityType = "role" + // For assumed roles, entityName is "RoleName/SessionName" + // We only need the RoleName + roleParts := strings.SplitN(entityName, "/", 2) + entityName = roleParts[0] + } + + return entityType, entityName, nil } // GetAttachedPolicies retrieves the attached policies and identifies any missing required policies. +// It uses the ParsePrincipalArn function. func GetAttachedPolicies(cfg aws.Config, principalArn string, requiredPolicies []string) ([]string, []string, error) { var attachedPolicies []string var missingPolicies []string iamClient := iam.NewFromConfig(cfg) - entityType, entityName, err := parsePrincipalArn(principalArn) + entityType, entityName, err := ParsePrincipalArn(principalArn) if err != nil { return attachedPolicies, missingPolicies, fmt.Errorf("failed to parse principal ARN: %v", err) } @@ -136,171 +342,3 @@ func GetAttachedPolicies(cfg aws.Config, principalArn string, requiredPolicies [ return attachedPolicies, missingPolicies, nil } - -// parsePrincipalArn parses the ARN and returns the entity type and name. -func parsePrincipalArn(principalArn string) (entityType string, entityName string, err error) { - // Parse the ARN - parsedArn, err := awsarn.Parse(principalArn) - if err != nil { - return "", "", fmt.Errorf("failed to parse ARN: %v", err) - } - - switch parsedArn.Service { - case "iam": - // Resource format: {entityType}/{entityName} - resourceParts := strings.SplitN(parsedArn.Resource, "/", 2) - if len(resourceParts) < 2 { - return "", "", fmt.Errorf("invalid resource format in ARN: %s", principalArn) - } - - entityType = strings.ToLower(resourceParts[0]) - entityName = resourceParts[1] - case "sts": - // Resource format: assumed-role/{role-name}/{session-name} - resourceParts := strings.SplitN(parsedArn.Resource, "/", 3) - if len(resourceParts) < 2 { - return "", "", fmt.Errorf("invalid resource format in ARN: %s", principalArn) - } - - if strings.ToLower(resourceParts[0]) != "assumed-role" { - return "", "", fmt.Errorf("unsupported resource type in STS ARN: %s", resourceParts[0]) - } - - entityType = "role" - entityName = resourceParts[1] - default: - return "", "", fmt.Errorf("unsupported service in ARN: %s", parsedArn.Service) - } - - return entityType, entityName, nil -} - -// ValidateAccount attempts to access the specified account using the provided credentials and configurations. -// It returns an AccountResult indicating whether the account is accessible and has required policies. -func ValidateAccount(accountID string, cfg Config) AccountResult { - var result AccountResult - result.AccountID = accountID - result.Details.RequiredPolicies = requiredPolicies - result.Details.CredentialType = "Single Account" // default, may change to "Multi-Account" - - // Create AWS Config using provided credentials - credsProvider := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider( - cfg.AWSAccessKeyID, - cfg.AWSSecretAccessKey, - "", - )) - - awsCfg, err := GenerateAWSConfig(credsProvider) - if err != nil { - result.Details.Error = fmt.Sprintf("Failed to generate AWS config: %v", err) - result.Details.IsAccessible = false - result.Details.HasPolicies = false - result.Healthy = false - return result - } - - // Initialize STS client - stsClient := sts.NewFromConfig(awsCfg) - - // Check if we need to assume a role - var roleToAssume string - if cfg.CrossAccountRole != "" { - // Use CrossAccountRole to assume role in target account - roleToAssume = cfg.CrossAccountRole - result.Details.CredentialType = "Multi-Account" - } else if cfg.RoleToAssumeInMainAccount != "" { - // Use RoleToAssumeInMainAccount to assume role in target account - roleToAssume = cfg.RoleToAssumeInMainAccount - result.Details.CredentialType = "Multi-Account" - } - - if roleToAssume != "" { - // Attempt to assume the specified role in the target account - roleArn := fmt.Sprintf("arn:aws:iam::%s:role/%s", accountID, roleToAssume) - assumeRoleInput := &sts.AssumeRoleInput{ - RoleArn: aws.String(roleArn), - RoleSessionName: aws.String("AssumeRoleSession"), - } - - // Include ExternalID if provided - if cfg.ExternalID != "" { - assumeRoleInput.ExternalId = aws.String(cfg.ExternalID) - } - - // Attempt to assume the role - assumeRoleOutput, err := stsClient.AssumeRole(context.TODO(), assumeRoleInput) - if err != nil { - result.Details.IsAccessible = false - result.Details.HasPolicies = false - result.Details.Error = fmt.Sprintf("Failed to assume role %s in account %s: %v", roleToAssume, accountID, err) - result.Healthy = false - return result - } - - // Create new AWS Config with assumed role credentials - credsProvider = aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider( - *assumeRoleOutput.Credentials.AccessKeyId, - *assumeRoleOutput.Credentials.SecretAccessKey, - *assumeRoleOutput.Credentials.SessionToken, - )) - - awsCfg, err = GenerateAWSConfig(credsProvider) - if err != nil { - result.Details.IsAccessible = false - result.Details.HasPolicies = false - result.Details.Error = fmt.Sprintf("Failed to generate AWS config with assumed role: %v", err) - result.Healthy = false - return result - } - - // Record the assumed role ARN - result.Details.IsAccessible = true - result.Details.IamPrincipal = *assumeRoleOutput.AssumedRoleUser.Arn - } else { - // No role assumption, use provided credentials directly - - // Get Caller Identity to check access - identityOutput, err := stsClient.GetCallerIdentity(context.TODO(), &sts.GetCallerIdentityInput{}) - if err != nil { - result.Details.IsAccessible = false - result.Details.HasPolicies = false - result.Details.Error = fmt.Sprintf("Failed to get caller identity: %v", err) - result.Healthy = false - return result - } - - // Verify if the account ID matches - if *identityOutput.Account != accountID { - result.Details.IsAccessible = false - result.Details.HasPolicies = false - result.Details.Error = fmt.Sprintf("Provided credentials do not match the account ID: %s", accountID) - result.Healthy = false - return result - } - - // Record the principal ARN - result.Details.IsAccessible = true - result.Details.IamPrincipal = *identityOutput.Arn - } - - // Check policies attached to the principal - attachedPolicies, missingPolicies, err := GetAttachedPolicies(awsCfg, result.Details.IamPrincipal, requiredPolicies) - if err != nil { - result.Details.HasPolicies = false - result.Details.Error = fmt.Sprintf("Error checking policies: %v", err) - result.Healthy = false - return result - } - - result.Details.AttachedPolicies = attachedPolicies - if len(missingPolicies) > 0 { - result.Details.HasPolicies = false - result.Details.Error = fmt.Sprintf("Missing policies: %v", missingPolicies) - } else { - result.Details.HasPolicies = true - } - - result.Healthy = result.Details.IsAccessible && result.Details.HasPolicies - - return result -} diff --git a/services/integration/integration-type/azure-subscription/azure_subscription.go b/services/integration/integration-type/azure-subscription/azure_subscription.go index 45422ab7f..69ea0043b 100644 --- a/services/integration/integration-type/azure-subscription/azure_subscription.go +++ b/services/integration/integration-type/azure-subscription/azure_subscription.go @@ -23,19 +23,7 @@ func (i *AzureSubscriptionIntegration) GetDescriberConfiguration() interfaces.De } } -func (i *AzureSubscriptionIntegration) GetAnnotations(jsonData []byte) (map[string]string, error) { - annotations := make(map[string]string) - - return annotations, nil -} - -func (i *AzureSubscriptionIntegration) GetLabels(jsonData []byte) (map[string]string, error) { - annotations := make(map[string]string) - - return annotations, nil -} - -func (i *AzureSubscriptionIntegration) HealthCheck(jsonData []byte, providerId string, labels map[string]string) (bool, error) { +func (i *AzureSubscriptionIntegration) HealthCheck(jsonData []byte, providerId string, labels map[string]string, annotations map[string]string) (bool, error) { var credentials azureDescriberLocal.IntegrationCredentials err := json.Unmarshal(jsonData, &credentials) if err != nil { diff --git a/services/integration/integration-type/entra-id-directory/entra_id_directory.go b/services/integration/integration-type/entra-id-directory/entra_id_directory.go index b8e4a6923..be3d1ddd7 100644 --- a/services/integration/integration-type/entra-id-directory/entra_id_directory.go +++ b/services/integration/integration-type/entra-id-directory/entra_id_directory.go @@ -23,19 +23,7 @@ func (i *EntraIdDirectoryIntegration) GetDescriberConfiguration() interfaces.Des } } -func (i *EntraIdDirectoryIntegration) GetAnnotations(jsonData []byte) (map[string]string, error) { - annotations := make(map[string]string) - - return annotations, nil -} - -func (i *EntraIdDirectoryIntegration) GetLabels(jsonData []byte) (map[string]string, error) { - annotations := make(map[string]string) - - return annotations, nil -} - -func (i *EntraIdDirectoryIntegration) HealthCheck(jsonData []byte, providerId string, labels map[string]string) (bool, error) { +func (i *EntraIdDirectoryIntegration) HealthCheck(jsonData []byte, providerId string, labels map[string]string, annotations map[string]string) (bool, error) { var configs entraidDescriberLocal.IntegrationCredentials err := json.Unmarshal(jsonData, &configs) if err != nil { diff --git a/services/integration/integration-type/interfaces/integration.go b/services/integration/integration-type/interfaces/integration.go index 0e491118e..6a2ceae29 100644 --- a/services/integration/integration-type/interfaces/integration.go +++ b/services/integration/integration-type/interfaces/integration.go @@ -10,10 +10,8 @@ type DescriberConfiguration struct { type IntegrationType interface { GetDescriberConfiguration() DescriberConfiguration - GetAnnotations(jsonData []byte) (map[string]string, error) - GetLabels(jsonData []byte) (map[string]string, error) GetResourceTypesByLabels(map[string]string) ([]string, error) - HealthCheck(jsonData []byte, providerId string, labels map[string]string) (bool, error) + HealthCheck(jsonData []byte, providerId string, labels map[string]string, annotations map[string]string) (bool, error) DiscoverIntegrations(jsonData []byte) ([]models.Integration, error) GetResourceTypeFromTableName(tableName string) string }