From 712f68f91ff8d988894f8378948eb5b1eaa8f0cb Mon Sep 17 00:00:00 2001 From: Ohad Aharoni Date: Fri, 12 Apr 2024 15:06:07 -0400 Subject: [PATCH] OCM-6312 | feat: migrate openshift-rosa-cli v1 code to v2 --- go.mod | 11 +- go.sum | 12 ++ pkg/aws/aws_client/client.go | 2 + pkg/aws/aws_client/cloudwatch_logs.go | 38 +++++ pkg/aws/aws_client/instance.go | 49 ++++++ pkg/aws/aws_client/role.go | 215 +++++++++++++++++++++++++- pkg/aws/aws_client/route53.go | 2 +- 7 files changed, 318 insertions(+), 11 deletions(-) create mode 100644 pkg/aws/aws_client/cloudwatch_logs.go diff --git a/go.mod b/go.mod index c8be96e..62e4588 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/apparentlymart/go-cidr v1.1.0 - github.com/aws/aws-sdk-go-v2 v1.26.0 + github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2/config v1.27.9 github.com/aws/aws-sdk-go-v2/credentials v1.17.9 github.com/aws/aws-sdk-go-v2/service/cloudformation v1.48.0 @@ -24,17 +24,20 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 ) +require github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect + require ( - github.com/aws/smithy-go v1.20.1 + github.com/aws/smithy-go v1.20.2 github.com/kr/pretty v0.1.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) require ( github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.35.1 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 // indirect diff --git a/go.sum b/go.sum index ef7b139..72e2d7b 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,10 @@ github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4t github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/aws/aws-sdk-go-v2 v1.26.0 h1:/Ce4OCiM3EkpW7Y+xUnfAFpchU78K7/Ug01sZni9PgA= github.com/aws/aws-sdk-go-v2 v1.26.0/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= github.com/aws/aws-sdk-go-v2/config v1.27.9 h1:gRx/NwpNEFSk+yQlgmk1bmxxvQ5TyJ76CWXs9XScTqg= github.com/aws/aws-sdk-go-v2/config v1.27.9/go.mod h1:dK1FQfpwpql83kbD873E9vz4FyAxuJtR22wzoXn3qq0= github.com/aws/aws-sdk-go-v2/credentials v1.17.9 h1:N8s0/7yW+h8qR8WaRlPQeJ6czVMNQVNtNdUqf6cItao= @@ -10,12 +14,18 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 h1:af5YzcLf80tv4Em4jWVD75l github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0/go.mod h1:nQ3how7DMnFMWiU1SpECohgC82fpn4cKZ875NDMmwtA= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 h1:0ScVK/4qZ8CIW0k8jOeFVsyS/sAiXpYxRBLolMkuLQM= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4/go.mod h1:84KyjNZdHC6QZW08nfHI6yZgPd+qRgaWcYsyLUo3QY8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 h1:sHmMWWX5E7guWEFQ9SVo6A3S4xpPrWnd77a6y4WM6PU= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4/go.mod h1:WjpDrhWisWOIoS9n3nk67A3Ll1vfULJ9Kq6h29HTD48= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/service/cloudformation v1.48.0 h1:uMlYsoHdd2Gr9sDGq2ieUR5jVu7F5AqPYz6UBJmdRhY= github.com/aws/aws-sdk-go-v2/service/cloudformation v1.48.0/go.mod h1:G2qcp9xrwch6TH9AlzWoYbV9QScyZhLCoMCQ1+BD404= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.35.1 h1:suWu59CRsDNhw2YXPpa6drYEetIUUIMUhkzHmucbCf8= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.35.1/go.mod h1:tZiRxrv5yBRgZ9Z4OOOxwscAZRFk5DgYhEcjX1QpvgI= github.com/aws/aws-sdk-go-v2/service/ec2 v1.152.0 h1:ltCQObuImVYmIrMX65ikB9W83MEun3Ry2Sk11ecZ8Xw= github.com/aws/aws-sdk-go-v2/service/ec2 v1.152.0/go.mod h1:TeZ9dVQzGaLG+SBIgdLIDbJ6WmfFvksLeG3EHGnNfZM= github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.24.3 h1:pjgSJEvgJzv+e0frrqspeYdHz2JSW1KAGMXRe1FuQ1M= @@ -38,6 +48,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 h1:J/PpTf/hllOjx8Xu9DMflff3Fajf github.com/aws/aws-sdk-go-v2/service/sts v1.28.5/go.mod h1:0ih0Z83YDH/QeQ6Ori2yGE2XvWYv/Xm+cZc01LC6oK0= github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= diff --git a/pkg/aws/aws_client/client.go b/pkg/aws/aws_client/client.go index 0525ec0..fd5f02c 100644 --- a/pkg/aws/aws_client/client.go +++ b/pkg/aws/aws_client/client.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/cloudformation" + "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/iam" "github.com/aws/aws-sdk-go-v2/service/kms" @@ -30,6 +31,7 @@ type AWSClient struct { ClientContext context.Context AccountID string KmsClient *kms.Client + CloudWatchLogsClient *cloudwatchlogs.Client } func CreateAWSClient(profileName string, region string) (*AWSClient, error) { diff --git a/pkg/aws/aws_client/cloudwatch_logs.go b/pkg/aws/aws_client/cloudwatch_logs.go new file mode 100644 index 0000000..1d3b55a --- /dev/null +++ b/pkg/aws/aws_client/cloudwatch_logs.go @@ -0,0 +1,38 @@ +package aws_client + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" + "github.com/openshift-online/ocm-common/pkg/log" +) + +func (client *AWSClient) DescribeLogGroupsByName(logGroupName string) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { + output, err := client.CloudWatchLogsClient.DescribeLogGroups(context.TODO(), &cloudwatchlogs.DescribeLogGroupsInput{ + LogGroupNamePrefix: &logGroupName, + }) + if err != nil { + log.LogError("Got error describe log group:%s ", err) + } + return output, err +} + +func (client *AWSClient) DescribeLogStreamByName(logGroupName string) (*cloudwatchlogs.DescribeLogStreamsOutput, error) { + output, err := client.CloudWatchLogsClient.DescribeLogStreams(context.TODO(), &cloudwatchlogs.DescribeLogStreamsInput{ + LogGroupName: &logGroupName, + }) + if err != nil { + log.LogError("Got error describe log stream: %s", err) + } + return output, err +} + +func (client *AWSClient) DeleteLogGroupByName(logGroupName string) (*cloudwatchlogs.DeleteLogGroupOutput, error) { + output, err := client.CloudWatchLogsClient.DeleteLogGroup(context.TODO(), &cloudwatchlogs.DeleteLogGroupInput{ + LogGroupName: &logGroupName, + }) + if err != nil { + log.LogError("Got error delete log group: %s", err) + } + return output, err +} diff --git a/pkg/aws/aws_client/instance.go b/pkg/aws/aws_client/instance.go index 18f6c6d..25575e9 100644 --- a/pkg/aws/aws_client/instance.go +++ b/pkg/aws/aws_client/instance.go @@ -254,3 +254,52 @@ func (client *AWSClient) GetTagsOfInstanceProfile(instanceProfileName string) ([ tags := resp.Tags return tags, err } + +func GetInstanceName(instance *types.Instance) string { + tags := instance.Tags + for _, tag := range tags { + if *tag.Key == "Name" { + return *tag.Value + } + } + return "" +} + +// GetInstancesByInfraID will return the instances with tag tag:kubernetes.io/cluster/ +func (client *AWSClient) GetInstancesByInfraID(infraID string) ([]types.Instance, error) { + filter := types.Filter{ + Name: aws.String("tag:kubernetes.io/cluster/" + infraID), + Values: []string{ + "owned", + }, + } + output, err := client.Ec2Client.DescribeInstances(context.TODO(), &ec2.DescribeInstancesInput{ + Filters: []types.Filter{ + filter, + }, + MaxResults: aws.Int32(100), + }) + if err != nil { + return nil, err + } + var instances []types.Instance + for _, reservation := range output.Reservations { + instances = append(instances, reservation.Instances...) + } + return instances, err +} + +func (client *AWSClient) ListAvaliableRegionsFromAWS() ([]types.Region, error) { + optInStatus := "opt-in-status" + optInNotRequired := "opt-in-not-required" + optIn := "opted-in" + filter := types.Filter{Name: &optInStatus, Values: []string{optInNotRequired, optIn}} + + output, err := client.Ec2Client.DescribeRegions(context.TODO(), &ec2.DescribeRegionsInput{ + Filters: []types.Filter{ + filter, + }, + }) + + return output.Regions, err +} diff --git a/pkg/aws/aws_client/role.go b/pkg/aws/aws_client/role.go index d486302..c7c00d8 100644 --- a/pkg/aws/aws_client/role.go +++ b/pkg/aws/aws_client/role.go @@ -2,8 +2,11 @@ package aws_client import ( "context" + "encoding/json" "fmt" + "time" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/iam" "github.com/aws/aws-sdk-go-v2/service/iam/types" ) @@ -25,11 +28,18 @@ func (client *AWSClient) CreateRole(roleName string, input := &iam.CreateRoleInput{ RoleName: &roleName, AssumeRolePolicyDocument: &assumeRolePolicyDocument, - Path: &path, - PermissionsBoundary: &permissionBoundry, - Tags: roleTags, Description: &description, } + if path != "" { + input.Path = &path + } + if permissionBoundry != "" { + input.PermissionsBoundary = &permissionBoundry + } + if len(tags) != 0 { + input.Tags = roleTags + } + resp, err := client.IamClient.CreateRole(context.TODO(), input) if err != nil { return *resp.Role, err @@ -63,9 +73,6 @@ func (client *AWSClient) DeleteRoleAndPolicy(roleName string, managedPolicy bool return err } - if err != nil { - return err - } fmt.Println(output.AttachedPolicies) for _, policy := range output.AttachedPolicies { err = client.DetachIAMPolicy(roleName, *policy.PolicyArn) @@ -89,3 +96,199 @@ func (client *AWSClient) ListRoles() ([]types.Role, error) { out, err := client.IamClient.ListRoles(context.TODO(), input) return out.Roles, err } + +func (client *AWSClient) IsPolicyAttachedToRole(roleName string, policyArn string) (bool, error) { + policies, err := client.ListAttachedRolePolicies(roleName) + if err != nil { + return false, err + } + for _, policy := range policies { + if aws.ToString(policy.PolicyArn) == policyArn { + return true, nil + } + } + return false, nil +} + +func (client *AWSClient) ListAttachedRolePolicies(roleName string) ([]types.AttachedPolicy, error) { + policies := []types.AttachedPolicy{} + policyLister := iam.ListAttachedRolePoliciesInput{ + RoleName: &roleName, + } + policyOut, err := client.IamClient.ListAttachedRolePolicies(context.TODO(), &policyLister) + if err != nil { + return policies, err + } + return policyOut.AttachedPolicies, nil +} + +func (client *AWSClient) DetachRolePolicies(roleName string) error { + policies, err := client.ListAttachedRolePolicies(roleName) + if err != nil { + return err + } + for _, policy := range policies { + policyDetacher := iam.DetachRolePolicyInput{ + PolicyArn: policy.PolicyArn, + RoleName: &roleName, + } + _, err := client.IamClient.DetachRolePolicy(context.TODO(), &policyDetacher) + if err != nil { + return err + } + } + return nil +} + +func (client *AWSClient) DeleteRoleInstanceProfiles(roleName string) error { + inProfileLister := iam.ListInstanceProfilesForRoleInput{ + RoleName: &roleName, + } + out, err := client.IamClient.ListInstanceProfilesForRole(context.TODO(), &inProfileLister) + if err != nil { + return err + } + for _, inProfile := range out.InstanceProfiles { + profileDeleter := iam.RemoveRoleFromInstanceProfileInput{ + InstanceProfileName: inProfile.InstanceProfileName, + RoleName: &roleName, + } + _, err = client.IamClient.RemoveRoleFromInstanceProfile(context.TODO(), &profileDeleter) + if err != nil { + return err + } + } + + return nil +} + +func (client *AWSClient) CreateIAMRole(roleName string, ProdENVTrustedRole string, StageENVTrustedRole string, StageIssuerTrustedRole string, + externalID ...string) (types.Role, error) { + statement := map[string]interface{}{ + "Effect": "Allow", + "Principal": map[string]interface{}{ + "Service": "ec2.amazonaws.com", + "AWS": []string{ + ProdENVTrustedRole, + StageENVTrustedRole, + StageIssuerTrustedRole, + }, + }, + "Action": "sts:AssumeRole", + } + + if len(externalID) == 1 { + statement["Condition"] = map[string]map[string]string{ + "StringEquals": { + "sts:ExternalId": "aaaa", + }, + } + } + + assumeRolePolicyDocument, err := completeRolePolicyDocument(statement) + if err != nil { + fmt.Println("Failed to convert Role Policy Document into JSON: ", err) + return types.Role{}, err + } + + return client.CreateRole(roleName, string(assumeRolePolicyDocument), "", make(map[string]string), "/") +} + +func (client *AWSClient) CreateRegularRole(roleName string) (types.Role, error) { + + statement := map[string]interface{}{ + "Effect": "Allow", + "Principal": map[string]interface{}{ + "Service": "ec2.amazonaws.com", + }, + "Action": "sts:AssumeRole", + } + + assumeRolePolicyDocument, err := completeRolePolicyDocument(statement) + if err != nil { + fmt.Println("Failed to convert Role Policy Document into JSON: ", err) + return types.Role{}, err + } + return client.CreateRole(roleName, assumeRolePolicyDocument, "", make(map[string]string), "/") +} + +func (client *AWSClient) CreateRoleForAuditLogForward(roleName, awsAccountID string, oidcEndpointURL string) (types.Role, error) { + statement := map[string]interface{}{ + "Effect": "Allow", + "Principal": map[string]interface{}{ + "Federated": fmt.Sprintf("arn:aws:iam::%s:oidc-provider/%s", awsAccountID, oidcEndpointURL), + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": map[string]interface{}{ + "StringEquals": map[string]interface{}{ + fmt.Sprintf("%s:sub", oidcEndpointURL): "system:serviceaccount:openshift-config-managed:cloudwatch-audit-exporter", + }, + }, + } + + assumeRolePolicyDocument, err := completeRolePolicyDocument(statement) + if err != nil { + fmt.Println("Failed to convert Role Policy Document into JSON: ", err) + return types.Role{}, err + } + + return client.CreateRole(roleName, string(assumeRolePolicyDocument), "", make(map[string]string), "/") +} + +func (client *AWSClient) CreatePolicy(policyName string, statements ...map[string]interface{}) (string, error) { + timeCreation := time.Now().Local().String() + description := fmt.Sprintf("Created by OCM QE at %s", timeCreation) + document := map[string]interface{}{ + "Version": "2012-10-17", + "Statement": []map[string]interface{}{}, + } + if len(statements) != 0 { + for _, statement := range statements { + document["Statement"] = append(document["Statement"].([]map[string]interface{}), statement) + } + } + documentBytes, err := json.Marshal(document) + if err != nil { + err = fmt.Errorf("error to unmarshal the statement to string: %v", err) + return "", err + } + documentStr := string(documentBytes) + policyCreator := iam.CreatePolicyInput{ + PolicyDocument: &documentStr, + PolicyName: &policyName, + Description: &description, + } + outRes, err := client.IamClient.CreatePolicy(context.TODO(), &policyCreator) + if err != nil { + return "", err + } + policyArn := *outRes.Policy.Arn + return policyArn, err +} + +func (client *AWSClient) CreatePolicyForAuditLogForward(policyName string) (string, error) { + + statement := map[string]interface{}{ + "Effect": "Allow", + "Resource": "arn:aws:logs:*:*:*", + "Action": []string{ + "logs:PutLogEvents", + "logs:CreateLogGroup", + "logs:PutRetentionPolicy", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + }, + } + return client.CreatePolicy(policyName, statement) +} + +func completeRolePolicyDocument(statement map[string]interface{}) (string, error) { + rolePolicyDocument := map[string]interface{}{ + "Version": "2012-10-17", + "Statement": statement, + } + + assumeRolePolicyDocument, err := json.Marshal(rolePolicyDocument) + return string(assumeRolePolicyDocument), err +} diff --git a/pkg/aws/aws_client/route53.go b/pkg/aws/aws_client/route53.go index 20d0dfc..4829d9d 100644 --- a/pkg/aws/aws_client/route53.go +++ b/pkg/aws/aws_client/route53.go @@ -25,7 +25,7 @@ func (awsClient AWSClient) CreateHostedZone(hostedZoneName string, vpcID string, if err != nil { log.LogError("Create hosted zone failed for vpc %s with name %s: %s", vpcID, hostedZoneName, err.Error()) } else { - log.LogError("Create hosted zone succeed for vpc %s with name %s: %s", vpcID, hostedZoneName, err.Error()) + log.LogError("Create hosted zone succeed for vpc %s with name %s", vpcID, hostedZoneName) } return resp, err }