From 6f6926e61cdddf3a759b1d392b3f65d3be8c969c Mon Sep 17 00:00:00 2001 From: nxtcoder17 Date: Fri, 9 Feb 2024 02:03:17 +0530 Subject: [PATCH] feat(apps/infra): Check AWS Access, now returns true, only when the given cloudformation is completed --- .../infra/cloud-provider-secrets.graphql.yml | 2 +- .../domain/provider-secrets-validator.go | 145 ------------------ .../infra/internal/domain/provider-secrets.go | 49 +++++- 3 files changed, 43 insertions(+), 153 deletions(-) delete mode 100644 apps/infra/internal/domain/provider-secrets-validator.go diff --git a/.tools/nvim/__http__/infra/cloud-provider-secrets.graphql.yml b/.tools/nvim/__http__/infra/cloud-provider-secrets.graphql.yml index e17e1bd0c..beb72517f 100644 --- a/.tools/nvim/__http__/infra/cloud-provider-secrets.graphql.yml +++ b/.tools/nvim/__http__/infra/cloud-provider-secrets.graphql.yml @@ -4,7 +4,7 @@ global: clusterName: sample-cluster providerNamespace: kl-account-kloudlite-dev - providerName: aws-creds + providerName: aws-creds2 --- diff --git a/apps/infra/internal/domain/provider-secrets-validator.go b/apps/infra/internal/domain/provider-secrets-validator.go deleted file mode 100644 index 9a0ebe143..000000000 --- a/apps/infra/internal/domain/provider-secrets-validator.go +++ /dev/null @@ -1,145 +0,0 @@ -package domain - -import ( - "encoding/json" - "github.com/kloudlite/api/pkg/errors" - "net/url" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/iam" -) - -func (d *domain) ValidateProviderSecret(providerName string, accessKeyId, secretAccessKey string) error { - switch providerName { - case "aws": - { - return validateAwsKeys(accessKeyId, secretAccessKey) - } - default: - { - return errors.Newf("provider %s is not supported", providerName) - } - } -} - -func validateAwsKeys(accessKeyID, secretAccessKey string) error { - requiredActions := []string{"iam:*", "s3:*", "ec2:*"} - - sess, err := session.NewSession(&aws.Config{ - Region: aws.String("us-west-2"), - Credentials: credentials.NewStaticCredentials(accessKeyID, secretAccessKey, ""), - }) - - if err != nil { - return errors.Newf("Error creating session: %s", err) - } - - svc := iam.New(sess) - userOutput, err := svc.GetUser(nil) - - if err != nil { - return errors.Newf("Failed to get user: %s", err) - } - - // Getting the policies attached to the user - listUserPoliciesInput := &iam.ListAttachedUserPoliciesInput{ - UserName: userOutput.User.UserName, - } - - policies, err := svc.ListAttachedUserPolicies(listUserPoliciesInput) - - if err != nil { - return errors.Newf("Failed to list user policies: %s", err) - } - - for _, attachedPolicy := range policies.AttachedPolicies { - policyOutput, err := svc.GetPolicy(&iam.GetPolicyInput{ - PolicyArn: attachedPolicy.PolicyArn, - }) - - if err != nil { - // fmt.Println("Failed to get policy:", err) - continue - } - - policyVersionOutput, err := svc.GetPolicyVersion(&iam.GetPolicyVersionInput{ - PolicyArn: policyOutput.Policy.Arn, - VersionId: policyOutput.Policy.DefaultVersionId, - }) - - if err != nil { - // fmt.Println("Failed to get policy version:", err) - continue - } - - s, err := url.QueryUnescape(*policyVersionOutput.PolicyVersion.Document) - if err != nil { - // fmt.Println("Failed to get policy version:", err) - continue - } - - b, err := validatePolicyJson(s, requiredActions) - if err != nil { - // fmt.Println("Permission Check Error:", err) - continue - } - - if b { - return nil - } - } - return errors.Newf("coudn't find the required permissions ( full access to [S3,EC2,IAM] on all resources )") - -} - -func validatePolicyJson(policyJSON string, actions []string) (bool, error) { - type Statement struct { - Action []string `json:"Action"` - Resource string `json:"Resource"` - } - - type Policy struct { - Statement []Statement `json:"Statement"` - } - - var policy Policy - if err := json.Unmarshal([]byte(policyJSON), &policy); err != nil { - return false, errors.Newf("Failed to unmarshal policy document: %v", err) - } - - for _, s := range policy.Statement { - if s.Resource != "*" { - continue - } - found := false - for _, v := range actions { - f := false - for _, v2 := range s.Action { - if v == v2 { - f = true - } - } - if !f { - found = false - break - } - found = true - } - if found { - return true, nil - } - } - - return false, errors.Newf("permissions not matched") -} - -func contains(slice []string, value string) bool { - for _, v := range slice { - if v == value { - return true - } - } - return false -} diff --git a/apps/infra/internal/domain/provider-secrets.go b/apps/infra/internal/domain/provider-secrets.go index 57a9b4118..d7b732e9f 100644 --- a/apps/infra/internal/domain/provider-secrets.go +++ b/apps/infra/internal/domain/provider-secrets.go @@ -4,13 +4,14 @@ import ( "bytes" "context" "fmt" + "strings" + "time" + fc "github.com/kloudlite/api/apps/infra/internal/entities/field-constants" "github.com/kloudlite/api/common/fields" "github.com/kloudlite/api/pkg/errors" "github.com/kloudlite/operator/pkg/constants" corev1 "k8s.io/api/core/v1" - "strings" - "time" iamT "github.com/kloudlite/api/apps/iam/types" "github.com/kloudlite/api/common" @@ -22,7 +23,9 @@ import ( "github.com/kloudlite/api/pkg/repos" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/sts" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -49,13 +52,15 @@ func generateAWSCloudformationTemplateUrl(args entities.AWSSecretCredentials, ev return result.String(), nil } -func (d *domain) validateAWSAssumeRole(_ context.Context, paramExternalId string, roleARN string) error { +func (d *domain) validateAWSAssumeRole(_ context.Context, paramExternalId string, roleARN string, instanceProfileName string, cfStackName string) error { sess, err := session.NewSession() + sess.Config.Region = aws.String("ap-south-1") if err != nil { d.logger.Errorf(err, "while creating new session") return errors.NewE(err) } + // 1. validating IAM Assume Role svc := sts.New(sess) resp, err := svc.AssumeRole(&sts.AssumeRoleInput{ @@ -68,8 +73,39 @@ func (d *domain) validateAWSAssumeRole(_ context.Context, paramExternalId string return errors.NewE(err) } - if resp.AssumedRoleUser.Arn != nil { - return nil + if resp.AssumedRoleUser == nil || resp.AssumedRoleUser.Arn == nil { + return errors.Newf("AWS assume role (%s) not found", roleARN) + } + + nsess, err := session.NewSession(&aws.Config{ + Region: aws.String("ap-south-1"), + Credentials: credentials.NewStaticCredentials(*resp.Credentials.AccessKeyId, *resp.Credentials.SecretAccessKey, *resp.Credentials.SessionToken), + }) + if err != nil { + return errors.NewE(err) + } + + cf := cloudformation.New(nsess) + dso, err := cf.DescribeStacks(&cloudformation.DescribeStacksInput{ + StackName: &cfStackName, + }) + if err != nil { + return errors.NewE(err) + } + + stackFound := false + + for i := range dso.Stacks { + if dso.Stacks[i] != nil && *dso.Stacks[i].StackName == cfStackName { + stackFound = true + if *dso.Stacks[i].StackStatus != cloudformation.StackStatusCreateComplete { + return errors.Newf("cloudformation stack (%s) is not completed, yet", cfStackName) + } + } + } + + if !stackFound { + return errors.Newf("waiting for cloudformation stack to be created") } return nil @@ -94,7 +130,7 @@ func (d *domain) ValidateProviderSecretAWSAccess(ctx InfraContext, name string) return nil, errors.NewE(err) } - if err := d.validateAWSAssumeRole(ctx, psecret.AWS.CfParamExternalID, psecret.AWS.GetAssumeRoleRoleARN()); err != nil { + if err := d.validateAWSAssumeRole(ctx, psecret.AWS.CfParamExternalID, psecret.AWS.GetAssumeRoleRoleARN(), psecret.AWS.CfParamInstanceProfileName, psecret.AWS.CfParamStackName); err != nil { installationURL, err := generateAWSCloudformationTemplateUrl(*psecret.AWS, d.env) if err != nil { return nil, errors.NewE(err) @@ -246,7 +282,6 @@ func (d *domain) UpdateProviderSecret(ctx InfraContext, providerSecretIn entitie }, patchForUpdate, ) - if err != nil { return nil, errors.NewE(err) }