diff --git a/.gitignore b/.gitignore index 662eb0334e..40022eb066 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/ +.vscode/ docs/*.1 docs/*.rst /moactl diff --git a/cmd/create/cluster/cmd.go b/cmd/create/cluster/cmd.go index 0dc412c924..535b3d549b 100644 --- a/cmd/create/cluster/cmd.go +++ b/cmd/create/cluster/cmd.go @@ -252,6 +252,25 @@ func run(cmd *cobra.Command, _ []string) { reporter.Errorf("Error getting region: %v", err) os.Exit(1) } + + // Create the AWS client: + client, err := aws.NewClient(). + Logger(logger). + Region(aws.DefaultRegion). + Build() + if err != nil { + reporter.Errorf("Error creating AWS client: %v", err) + os.Exit(1) + } + + // Validate AWS credentials for current user + reporter.Infof("Validating AWS credentials for CFUser...") + if err = client.ValidateCFUserCredentials(); err != nil { + reporter.Errorf("Error validating AWS credentials: %v", err) + os.Exit(1) + } + reporter.Infof("AWS credentials are valid!") + regionList, regionAZ, err := getRegionList(ocmClient, multiAZ) if err != nil { reporter.Errorf(fmt.Sprintf("%s", err)) diff --git a/pkg/aws/client.go b/pkg/aws/client.go index 5878a12249..81e3af422e 100644 --- a/pkg/aws/client.go +++ b/pkg/aws/client.go @@ -58,6 +58,7 @@ const ( type Client interface { GetRegion() string ValidateCredentials() (bool, error) + ValidateCFUserCredentials() error EnsureOsdCcsAdminUser(stackName string) (bool, error) DeleteOsdCcsAdminUser(stackName string) error GetAccessKeyFromStack(stackName string) (*AccessKey, error) @@ -240,6 +241,45 @@ func (c *awsClient) ValidateCredentials() (bool, error) { return true, nil } +// ValidateCFUserCredentials checks if CF-IAM credentials are valid. +// it gets stack's key and actual key and compares them +// to get the stack credentials: +// aws cloudformation describe-stack-resource \ +// --logical-resource-id osdCcsAdminAccessKeys --stack-name osdCcsAdminIAMUser +func (c *awsClient) ValidateCFUserCredentials() error { + name := AdminUserName + accessKeyInput := &iam.ListAccessKeysInput{ + UserName: &name, + } + accessKeyList, err := c.iamClient.ListAccessKeys(accessKeyInput) + if err != nil { + return err + } + + OsdCcsAdminStackNamePtr := OsdCcsAdminStackName + LogicalResourceIDPtr := "osdCcsAdminAccessKeys" + stackResourceInput := &cloudformation.DescribeStackResourceInput{ + StackName: &OsdCcsAdminStackNamePtr, + LogicalResourceId: &LogicalResourceIDPtr, + } + resources, err := c.cfClient.DescribeStackResource(stackResourceInput) + if err != nil { + return err + } + cfAccessKey := resources.StackResourceDetail.PhysicalResourceId + + for _, key := range accessKeyList.AccessKeyMetadata { + if *key.AccessKeyId == *cfAccessKey && *key.Status == "Active" { + return nil + } + } + + return fmt.Errorf( + "Invalid CloudFormation stack credentials: %s is not valid \n"+ + "you can recreate the CloudFormation stack with: \n"+ + "moactl init --delete-stack && moactl init \n", name) +} + // Ensure osdCcsAdmin IAM user is created func (c *awsClient) EnsureOsdCcsAdminUser(stackName string) (bool, error) { // Check already existing cloudformation stack status diff --git a/pkg/aws/client_test.go b/pkg/aws/client_test.go index b415e132c2..471933b1fa 100644 --- a/pkg/aws/client_test.go +++ b/pkg/aws/client_test.go @@ -3,6 +3,7 @@ package aws_test import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudformation" + "github.com/aws/aws-sdk-go/service/iam" "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -17,15 +18,17 @@ var _ = Describe("Client", func() { client aws.Client mockCtrl *gomock.Controller - mockCfAPI *mocks.MockCloudFormationAPI + mockCfAPI *mocks.MockCloudFormationAPI + mockIamAPI *mocks.MockIAMAPI ) BeforeEach(func() { mockCtrl = gomock.NewController(GinkgoT()) mockCfAPI = mocks.NewMockCloudFormationAPI(mockCtrl) + mockIamAPI = mocks.NewMockIAMAPI(mockCtrl) client = aws.New( logrus.New(), - mocks.NewMockIAMAPI(mockCtrl), + mockIamAPI, mocks.NewMockOrganizationsAPI(mockCtrl), mocks.NewMockSTSAPI(mockCtrl), mockCfAPI, @@ -38,6 +41,59 @@ var _ = Describe("Client", func() { mockCtrl.Finish() }) + Context("ValidateCFUserCredentials", func() { + var ( + CFsecretID = "sut" + IAMAccessKey = "sut" + oddIAMAccessKey = "longtestkey" + status = "Active" + ) + Context("when creds are OK and matches", func() { + BeforeEach(func() { + mockCfAPI.EXPECT().DescribeStackResource(gomock.Any()).Return(&cloudformation.DescribeStackResourceOutput{ + StackResourceDetail: &cloudformation.StackResourceDetail{ + PhysicalResourceId: &CFsecretID, + }, + }, nil) + + mockIamAPI.EXPECT().ListAccessKeys(gomock.Any()).Return(&iam.ListAccessKeysOutput{ + AccessKeyMetadata: []*iam.AccessKeyMetadata{ + { + AccessKeyId: &IAMAccessKey, + Status: &status, + }, + }, + }, nil) + }) + It("should finish successfully and return nil", func() { + err := client.ValidateCFUserCredentials() + Expect(err).To(BeNil()) + }) + }) + + Context("when the credentials don't match", func() { + BeforeEach(func() { + mockCfAPI.EXPECT().DescribeStackResource(gomock.Any()).Return(&cloudformation.DescribeStackResourceOutput{ + StackResourceDetail: &cloudformation.StackResourceDetail{ + PhysicalResourceId: &CFsecretID, + }, + }, nil) + + mockIamAPI.EXPECT().ListAccessKeys(gomock.Any()).Return(&iam.ListAccessKeysOutput{ + AccessKeyMetadata: []*iam.AccessKeyMetadata{ + { + AccessKeyId: &oddIAMAccessKey, + }, + }, + }, nil) + }) + It("should return err", func() { + err := client.ValidateCFUserCredentials() + Expect(err.Error()).Should(ContainSubstring("Invalid CloudFormation stack credentials")) + }) + }) + + }) Context("EnsureOsdCcsAdminUser", func() { var ( stackName string