From 462a81c153e573b528654f0c0dff47f8cd81adbf Mon Sep 17 00:00:00 2001 From: Daniel Felipe Ochoa Date: Fri, 5 May 2023 18:42:33 -0500 Subject: [PATCH 1/4] Add assume role support to BBL Initial commit. Includes: BBL assuming the role while terraforming and the iaas interactions between bbl and aws. Missing: using the ops file found in bosh-deployment: `aws/cpi-assume-role-credentials.yml` to make the AWS CPI use the role. Unit tests/Integration tests. --- aws/client.go | 16 +++++++++++----- config/global_flags.go | 1 + config/load_state_test.go | 1 + config/merger.go | 1 + storage/aws.go | 1 + terraform/aws/input_generator.go | 1 + terraform/aws/templates/base.tf | 8 ++++++++ 7 files changed, 24 insertions(+), 5 deletions(-) diff --git a/aws/client.go b/aws/client.go index dafc2dd67..011467dce 100644 --- a/aws/client.go +++ b/aws/client.go @@ -8,6 +8,7 @@ import ( awslib "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/credentials/stscreds" "github.com/aws/aws-sdk-go/aws/session" awsec2 "github.com/aws/aws-sdk-go/service/ec2" awsroute53 "github.com/aws/aws-sdk-go/service/route53" @@ -43,14 +44,19 @@ type Client struct { } func NewClient(creds storage.AWS, logger logger) Client { - config := &awslib.Config{ - Credentials: credentials.NewStaticCredentials(creds.AccessKeyID, creds.SecretAccessKey, ""), - Region: awslib.String(creds.Region), + config := awslib.NewConfig(). + WithCredentials(credentials.NewStaticCredentials(creds.AccessKeyID, creds.SecretAccessKey, "")). + WithRegion(creds.Region) + awsSession := session.Must(session.NewSession(config)) + + if creds.AssumeRoleArn != "" { + stsCredentials := stscreds.NewCredentials(awsSession, creds.AssumeRoleArn) + awsSession = session.Must(session.NewSession(awslib.NewConfig().WithCredentials(stsCredentials).WithRegion(creds.Region))) } return Client{ - ec2Client: awsec2.New(session.Must(session.NewSession(config))), - route53Client: awsroute53.New(session.Must(session.NewSession(config))), + ec2Client: awsec2.New(awsSession), + route53Client: awsroute53.New(awsSession), logger: logger, } } diff --git a/config/global_flags.go b/config/global_flags.go index 90385b34a..eca6525a4 100644 --- a/config/global_flags.go +++ b/config/global_flags.go @@ -13,6 +13,7 @@ type GlobalFlags struct { AWSAccessKeyID string `long:"aws-access-key-id" env:"BBL_AWS_ACCESS_KEY_ID"` AWSSecretAccessKey string `long:"aws-secret-access-key" env:"BBL_AWS_SECRET_ACCESS_KEY"` AWSRegion string `long:"aws-region" env:"BBL_AWS_REGION"` + AWSAssumeRole string `long:"aws-assume-role" env:"BBL_AWS_ASSUME_ROLE"` AzureClientID string `long:"azure-client-id" env:"BBL_AZURE_CLIENT_ID"` AzureClientSecret string `long:"azure-client-secret" env:"BBL_AZURE_CLIENT_SECRET"` diff --git a/config/load_state_test.go b/config/load_state_test.go index e03a4e510..4e561bbc6 100644 --- a/config/load_state_test.go +++ b/config/load_state_test.go @@ -193,6 +193,7 @@ var _ = Describe("LoadState", func() { Expect(flags.EnvID).To(Equal("some-name")) Expect(flags.AWSAccessKeyID).To(Equal("some-aws-access-key")) Expect(flags.AWSSecretAccessKey).To(Equal("some-aws-secret-access-key")) + Expect(flags.AWSAssumeRole).To(Equal("some-aws-assume-role")) }) }) }) diff --git a/config/merger.go b/config/merger.go index b836585db..485599c11 100644 --- a/config/merger.go +++ b/config/merger.go @@ -116,6 +116,7 @@ func (m Merger) updateVSphereState(globalFlags GlobalFlags, state storage.State) func (m Merger) updateAWSState(globalFlags GlobalFlags, state storage.State) (storage.State, error) { copyFlagToState(globalFlags.AWSAccessKeyID, &state.AWS.AccessKeyID) copyFlagToState(globalFlags.AWSSecretAccessKey, &state.AWS.SecretAccessKey) + copyFlagToState(globalFlags.AWSAssumeRole, &state.AWS.AssumeRoleArn) if globalFlags.AWSRegion != "" { if state.AWS.Region != "" && globalFlags.AWSRegion != state.AWS.Region { diff --git a/storage/aws.go b/storage/aws.go index 69071c719..50bb2b6c0 100644 --- a/storage/aws.go +++ b/storage/aws.go @@ -3,5 +3,6 @@ package storage type AWS struct { AccessKeyID string `json:"-"` SecretAccessKey string `json:"-"` + AssumeRoleArn string `json:"assumeRole,omitempty"` Region string `json:"region,omitempty"` } diff --git a/terraform/aws/input_generator.go b/terraform/aws/input_generator.go index 50ecc8806..db9a30b1e 100644 --- a/terraform/aws/input_generator.go +++ b/terraform/aws/input_generator.go @@ -66,5 +66,6 @@ func (i InputGenerator) Credentials(state storage.State) map[string]string { return map[string]string{ "access_key": state.AWS.AccessKeyID, "secret_key": state.AWS.SecretAccessKey, + "role_arn": state.AWS.AssumeRoleArn, } } diff --git a/terraform/aws/templates/base.tf b/terraform/aws/templates/base.tf index ce5e1ffd3..64f193942 100644 --- a/terraform/aws/templates/base.tf +++ b/terraform/aws/templates/base.tf @@ -15,6 +15,9 @@ provider "aws" { access_key = "${var.access_key}" secret_key = "${var.secret_key}" region = "${var.region}" + assume_role { + role_arn = "${var.role_arn}" + } } variable "access_key" { @@ -29,6 +32,11 @@ variable "region" { type = string } +variable "role_arn" { + type = string + default = "" +} + variable "bosh_inbound_cidr" { default = "0.0.0.0/0" } From 1cb006272e93e9a0598466c11f92259684720b94 Mon Sep 17 00:00:00 2001 From: Brian Upton Date: Tue, 9 May 2023 15:11:24 -0700 Subject: [PATCH 2/4] Pass AWS assume role ops files and variables If the AWS assume role ARN is set, the scripts to create both the BOSH Director and jumpbox need to pass additional ops files and variables. [#184999423] Add AssumeRole support to bbl --- bosh/executor.go | 31 ++++- bosh/executor_test.go | 168 +++++++++++++++++++------- bosh/manager.go | 8 +- bosh/manager_test.go | 18 +-- config/load_state_test.go | 1 + fakes/bosh_executor.go | 32 ++--- terraform/aws/input_generator_test.go | 4 +- 7 files changed, 186 insertions(+), 76 deletions(-) diff --git a/bosh/executor.go b/bosh/executor.go index d4b970ce5..5ca9d019e 100644 --- a/bosh/executor.go +++ b/bosh/executor.go @@ -101,6 +101,10 @@ func (e Executor) getSetupFiles(sourcePath, destPath string) []setupFile { } func (e Executor) PlanJumpbox(input DirInput, deploymentDir, iaas string) error { + return e.PlanJumpboxWithState(input, deploymentDir, iaas, storage.State{}) +} + +func (e Executor) PlanJumpboxWithState(input DirInput, deploymentDir, iaas string, state storage.State) error { setupFiles := e.getSetupFiles(jumpboxDeploymentRepo, deploymentDir) for _, f := range setupFiles { @@ -128,6 +132,12 @@ func (e Executor) PlanJumpbox(input DirInput, deploymentDir, iaas string) error } } + if iaas == "aws" { + if state.AWS.AssumeRoleArn != "" { + sharedArgs = append(sharedArgs, "-o", filepath.Join(deploymentDir, "aws", "cpi-assume-role-credentials.yml")) + } + } + jumpboxState := filepath.Join(input.VarsDir, "jumpbox-state.json") boshArgs := append([]string{filepath.Join(deploymentDir, "jumpbox.yml"), "--state", jumpboxState}, sharedArgs...) @@ -138,6 +148,11 @@ func (e Executor) PlanJumpbox(input DirInput, deploymentDir, iaas string) error "-v", `access_key_id="${BBL_AWS_ACCESS_KEY_ID}"`, "-v", `secret_access_key="${BBL_AWS_SECRET_ACCESS_KEY}"`, ) + if state.AWS.AssumeRoleArn != "" { + boshArgs = append(boshArgs, + "-v", `role_arn="${BBL_AWS_ASSUME_ROLE}"`, + ) + } case "azure": boshArgs = append(boshArgs, "-v", `subscription_id="${BBL_AZURE_SUBSCRIPTION_ID}"`, @@ -210,7 +225,7 @@ func (e Executor) getDirectorSetupFiles(stateDir, deploymentDir, iaas string) [] return files } -func (e Executor) getDirectorOpsFiles(stateDir, deploymentDir, iaas string) []string { +func (e Executor) getDirectorOpsFiles(stateDir, deploymentDir, iaas string, state storage.State) []string { files := []string{ filepath.Join(deploymentDir, iaas, "cpi.yml"), filepath.Join(deploymentDir, "jumpbox-user.yml"), @@ -223,6 +238,9 @@ func (e Executor) getDirectorOpsFiles(stateDir, deploymentDir, iaas string) []st files = append(files, filepath.Join(stateDir, "bbl-ops-files", iaas, "bosh-director-ephemeral-ip-ops.yml")) files = append(files, filepath.Join(deploymentDir, iaas, "iam-instance-profile.yml")) files = append(files, filepath.Join(deploymentDir, iaas, "encrypted-disk.yml")) + if state.AWS.AssumeRoleArn != "" { + files = append(files, filepath.Join(deploymentDir, iaas, "cpi-assume-role-credentials.yml")) + } } else if iaas == "vsphere" { files = append(files, filepath.Join(deploymentDir, "vsphere", "resource-pool.yml")) } @@ -230,6 +248,10 @@ func (e Executor) getDirectorOpsFiles(stateDir, deploymentDir, iaas string) []st } func (e Executor) PlanDirector(input DirInput, deploymentDir, iaas string) error { + return e.PlanDirectorWithState(input, deploymentDir, iaas, storage.State{}) +} + +func (e Executor) PlanDirectorWithState(input DirInput, deploymentDir, iaas string, state storage.State) error { setupFiles := e.getDirectorSetupFiles(input.StateDir, deploymentDir, iaas) for _, f := range setupFiles { @@ -246,7 +268,7 @@ func (e Executor) PlanDirector(input DirInput, deploymentDir, iaas string) error "--vars-file", filepath.Join(input.VarsDir, "director-vars-file.yml"), } - for _, f := range e.getDirectorOpsFiles(input.StateDir, deploymentDir, iaas) { + for _, f := range e.getDirectorOpsFiles(input.StateDir, deploymentDir, iaas, state) { sharedArgs = append(sharedArgs, "-o", f) } @@ -260,6 +282,11 @@ func (e Executor) PlanDirector(input DirInput, deploymentDir, iaas string) error "-v", `access_key_id="${BBL_AWS_ACCESS_KEY_ID}"`, "-v", `secret_access_key="${BBL_AWS_SECRET_ACCESS_KEY}"`, ) + if state.AWS.AssumeRoleArn != "" { + boshArgs = append(boshArgs, + "-v", `role_arn="${BBL_AWS_ASSUME_ROLE}"`, + ) + } case "azure": boshArgs = append(boshArgs, "-v", `subscription_id="${BBL_AZURE_SUBSCRIPTION_ID}"`, diff --git a/bosh/executor_test.go b/bosh/executor_test.go index d5e43138d..950458d0a 100644 --- a/bosh/executor_test.go +++ b/bosh/executor_test.go @@ -75,55 +75,102 @@ var _ = Describe("Executor", func() { }) Describe("PlanJumpbox", func() { - It("writes bosh-deployment assets to the deployment dir", func() { - err := executor.PlanJumpbox(dirInput, deploymentDir, "aws") - Expect(err).NotTo(HaveOccurred()) + Context("on aws", func() { + It("writes bosh-deployment assets to the deployment dir", func() { + err := executor.PlanJumpbox(dirInput, deploymentDir, "aws") + Expect(err).NotTo(HaveOccurred()) - By("writing bosh-deployment assets to the deployment dir", func() { - simplePath := filepath.Join(deploymentDir, "no-external-ip.yml") + By("writing bosh-deployment assets to the deployment dir", func() { + simplePath := filepath.Join(deploymentDir, "no-external-ip.yml") - contents, err := fs.ReadFile(simplePath) - Expect(err).NotTo(HaveOccurred()) - Expect(string(contents)).To(Equal("no-ip")) + contents, err := fs.ReadFile(simplePath) + Expect(err).NotTo(HaveOccurred()) + Expect(string(contents)).To(Equal("no-ip")) - nestedPath := filepath.Join(deploymentDir, "aws", "cpi.yml") + nestedPath := filepath.Join(deploymentDir, "aws", "cpi.yml") - contents, err = fs.ReadFile(nestedPath) - Expect(err).NotTo(HaveOccurred()) - Expect(string(contents)).To(Equal("aws-cpi")) + contents, err = fs.ReadFile(nestedPath) + Expect(err).NotTo(HaveOccurred()) + Expect(string(contents)).To(Equal("aws-cpi")) + }) + + By("writing create-env and delete-env scripts", func() { + expectedArgs := []string{ + fmt.Sprintf("%s/jumpbox.yml", relativeDeploymentDir), + "--state", fmt.Sprintf("%s/jumpbox-state.json", relativeVarsDir), + "--vars-store", fmt.Sprintf("%s/jumpbox-vars-store.yml", relativeVarsDir), + "--vars-file", fmt.Sprintf("%s/jumpbox-vars-file.yml", relativeVarsDir), + "-o", fmt.Sprintf("%s/aws/cpi.yml", relativeDeploymentDir), + "-v", `access_key_id="${BBL_AWS_ACCESS_KEY_ID}"`, + "-v", `secret_access_key="${BBL_AWS_SECRET_ACCESS_KEY}"`, + } + + expectedScript := formatScript("create-env", stateDir, expectedArgs) + scriptPath := fmt.Sprintf("%s/create-jumpbox.sh", stateDir) + shellScript, err := fs.ReadFile(scriptPath) + Expect(err).NotTo(HaveOccurred()) + + fileinfo, err := fs.Stat(scriptPath) + Expect(err).NotTo(HaveOccurred()) + Expect(fileinfo.Mode().String()).To(Equal("-rwxr-x---")) + Expect(string(shellScript)).To(Equal(expectedScript)) + + expectedScript = formatScript("delete-env", stateDir, expectedArgs) + scriptPath = fmt.Sprintf("%s/delete-jumpbox.sh", stateDir) + shellScript, err = fs.ReadFile(scriptPath) + Expect(err).NotTo(HaveOccurred()) + + fileinfo, err = fs.Stat(scriptPath) + Expect(err).NotTo(HaveOccurred()) + Expect(fileinfo.Mode().String()).To(Equal("-rwxr-x---")) + Expect(err).NotTo(HaveOccurred()) + Expect(string(shellScript)).To(Equal(expectedScript)) + }) }) - By("writing create-env and delete-env scripts", func() { - expectedArgs := []string{ - fmt.Sprintf("%s/jumpbox.yml", relativeDeploymentDir), - "--state", fmt.Sprintf("%s/jumpbox-state.json", relativeVarsDir), - "--vars-store", fmt.Sprintf("%s/jumpbox-vars-store.yml", relativeVarsDir), - "--vars-file", fmt.Sprintf("%s/jumpbox-vars-file.yml", relativeVarsDir), - "-o", fmt.Sprintf("%s/aws/cpi.yml", relativeDeploymentDir), - "-v", `access_key_id="${BBL_AWS_ACCESS_KEY_ID}"`, - "-v", `secret_access_key="${BBL_AWS_SECRET_ACCESS_KEY}"`, - } + Context("when assume role is set", func() { + It("writes create-env and delete-env scripts with the assume role ops files and variables", func() { + state := storage.State{ + AWS: storage.AWS{ + AssumeRoleArn: "some-aws-assume-role", + }, + } + err := executor.PlanJumpboxWithState(dirInput, deploymentDir, "aws", state) + Expect(err).NotTo(HaveOccurred()) - expectedScript := formatScript("create-env", stateDir, expectedArgs) - scriptPath := fmt.Sprintf("%s/create-jumpbox.sh", stateDir) - shellScript, err := fs.ReadFile(scriptPath) - Expect(err).NotTo(HaveOccurred()) + expectedArgs := []string{ + fmt.Sprintf("%s/jumpbox.yml", relativeDeploymentDir), + "--state", fmt.Sprintf("%s/jumpbox-state.json", relativeVarsDir), + "--vars-store", fmt.Sprintf("%s/jumpbox-vars-store.yml", relativeVarsDir), + "--vars-file", fmt.Sprintf("%s/jumpbox-vars-file.yml", relativeVarsDir), + "-o", fmt.Sprintf("%s/aws/cpi.yml", relativeDeploymentDir), + "-o", fmt.Sprintf("%s/aws/cpi-assume-role-credentials.yml", relativeDeploymentDir), + "-v", `access_key_id="${BBL_AWS_ACCESS_KEY_ID}"`, + "-v", `secret_access_key="${BBL_AWS_SECRET_ACCESS_KEY}"`, + "-v", `role_arn="${BBL_AWS_ASSUME_ROLE}"`, + } - fileinfo, err := fs.Stat(scriptPath) - Expect(err).NotTo(HaveOccurred()) - Expect(fileinfo.Mode().String()).To(Equal("-rwxr-x---")) - Expect(string(shellScript)).To(Equal(expectedScript)) + expectedScript := formatScript("create-env", stateDir, expectedArgs) + scriptPath := fmt.Sprintf("%s/create-jumpbox.sh", stateDir) + shellScript, err := fs.ReadFile(scriptPath) + Expect(err).NotTo(HaveOccurred()) - expectedScript = formatScript("delete-env", stateDir, expectedArgs) - scriptPath = fmt.Sprintf("%s/delete-jumpbox.sh", stateDir) - shellScript, err = fs.ReadFile(scriptPath) - Expect(err).NotTo(HaveOccurred()) + fileinfo, err := fs.Stat(scriptPath) + Expect(err).NotTo(HaveOccurred()) + Expect(fileinfo.Mode().String()).To(Equal("-rwxr-x---")) + Expect(string(shellScript)).To(Equal(expectedScript)) - fileinfo, err = fs.Stat(scriptPath) - Expect(err).NotTo(HaveOccurred()) - Expect(fileinfo.Mode().String()).To(Equal("-rwxr-x---")) - Expect(err).NotTo(HaveOccurred()) - Expect(string(shellScript)).To(Equal(expectedScript)) + expectedScript = formatScript("delete-env", stateDir, expectedArgs) + scriptPath = fmt.Sprintf("%s/delete-jumpbox.sh", stateDir) + shellScript, err = fs.ReadFile(scriptPath) + Expect(err).NotTo(HaveOccurred()) + + fileinfo, err = fs.Stat(scriptPath) + Expect(err).NotTo(HaveOccurred()) + Expect(fileinfo.Mode().String()).To(Equal("-rwxr-x---")) + Expect(err).NotTo(HaveOccurred()) + Expect(string(shellScript)).To(Equal(expectedScript)) + }) }) }) @@ -347,7 +394,7 @@ var _ = Describe("Executor", func() { "-v", `secret_access_key="${BBL_AWS_SECRET_ACCESS_KEY}"`, } - behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "aws", stateDir) + behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "aws", stateDir, storage.State{}) }) It("writes aws-specific ops files", func() { @@ -364,6 +411,35 @@ var _ = Describe("Executor", func() { value: true `)) }) + + Context("when assume role is set", func() { + It("writes create-director.sh and delete-director.sh including the assume role ops files and variables", func() { + expectedArgs := []string{ + filepath.Join(relativeDeploymentDir, "bosh.yml"), + "--state", filepath.Join(relativeVarsDir, "bosh-state.json"), + "--vars-store", filepath.Join(relativeVarsDir, "director-vars-store.yml"), + "--vars-file", filepath.Join(relativeVarsDir, "director-vars-file.yml"), + "-o", filepath.Join(relativeDeploymentDir, "aws", "cpi.yml"), + "-o", filepath.Join(relativeDeploymentDir, "jumpbox-user.yml"), + "-o", filepath.Join(relativeDeploymentDir, "uaa.yml"), + "-o", filepath.Join(relativeDeploymentDir, "credhub.yml"), + "-o", filepath.Join(relativeStateDir, "bbl-ops-files", "aws", "bosh-director-ephemeral-ip-ops.yml"), + "-o", filepath.Join(relativeDeploymentDir, "aws", "iam-instance-profile.yml"), + "-o", filepath.Join(relativeDeploymentDir, "aws", "encrypted-disk.yml"), + "-o", filepath.Join(relativeDeploymentDir, "aws", "cpi-assume-role-credentials.yml"), + "-v", `access_key_id="${BBL_AWS_ACCESS_KEY_ID}"`, + "-v", `secret_access_key="${BBL_AWS_SECRET_ACCESS_KEY}"`, + "-v", `role_arn="${BBL_AWS_ASSUME_ROLE}"`, + } + + state := storage.State{ + AWS: storage.AWS{ + AssumeRoleArn: "some-aws-assume-role", + }, + } + behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "aws", stateDir, state) + }) + }) }) Context("gcp", func() { @@ -383,7 +459,7 @@ var _ = Describe("Executor", func() { "-v", `zone="${BBL_GCP_ZONE}"`, } - behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "gcp", stateDir) + behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "gcp", stateDir, storage.State{}) }) It("writes gcp-specific ops files", func() { @@ -419,7 +495,7 @@ var _ = Describe("Executor", func() { "-v", `tenant_id="${BBL_AZURE_TENANT_ID}"`, } - behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "azure", stateDir) + behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "azure", stateDir, storage.State{}) }) }) @@ -439,7 +515,7 @@ var _ = Describe("Executor", func() { "-v", `vcenter_password="${BBL_VSPHERE_VCENTER_PASSWORD}"`, } - behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "vsphere", stateDir) + behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "vsphere", stateDir, storage.State{}) }) }) @@ -458,7 +534,7 @@ var _ = Describe("Executor", func() { "-v", `openstack_password="${BBL_OPENSTACK_PASSWORD}"`, } - behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "openstack", stateDir) + behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "openstack", stateDir, storage.State{}) }) }) Context("cloudstack", func() { @@ -1028,13 +1104,13 @@ type behavesLikePlanFs interface { fileio.Stater } -func behavesLikePlan(expectedArgs []string, cli *fakes.BOSHCLI, fs behavesLikePlanFs, executor bosh.Executor, input bosh.DirInput, deploymentDir, iaas, stateDir string) { +func behavesLikePlan(expectedArgs []string, cli *fakes.BOSHCLI, fs behavesLikePlanFs, executor bosh.Executor, input bosh.DirInput, deploymentDir, iaas, stateDir string, state storage.State) { cli.RunStub = func(stdout io.Writer, workingDirectory string, args []string) error { stdout.Write([]byte("some-manifest")) //nolint:errcheck return nil } - err := executor.PlanDirector(input, deploymentDir, iaas) + err := executor.PlanDirectorWithState(input, deploymentDir, iaas, state) Expect(err).NotTo(HaveOccurred()) Expect(cli.RunCallCount()).To(Equal(0)) diff --git a/bosh/manager.go b/bosh/manager.go index d576a2f6b..2ec0e2e79 100644 --- a/bosh/manager.go +++ b/bosh/manager.go @@ -40,8 +40,8 @@ type directorVars struct { } type executor interface { - PlanDirector(DirInput, string, string) error - PlanJumpbox(DirInput, string, string) error + PlanDirectorWithState(DirInput, string, string, storage.State) error + PlanJumpboxWithState(DirInput, string, string, storage.State) error CreateEnv(DirInput, storage.State) (string, error) DeleteEnv(DirInput, storage.State) error WriteDeploymentVars(DirInput, string) error @@ -107,7 +107,7 @@ func (m *Manager) InitializeJumpbox(state storage.State) error { VarsDir: varsDir, } - err = m.executor.PlanJumpbox(iaasInputs, deploymentDir, state.IAAS) + err = m.executor.PlanJumpboxWithState(iaasInputs, deploymentDir, state.IAAS, state) if err != nil { return fmt.Errorf("Jumpbox interpolate: %s", err) } @@ -186,7 +186,7 @@ func (m *Manager) InitializeDirector(state storage.State) error { VarsDir: varsDir, } - err = m.executor.PlanDirector(iaasInputs, directorDeploymentDir, state.IAAS) + err = m.executor.PlanDirectorWithState(iaasInputs, directorDeploymentDir, state.IAAS, state) if err != nil { return err } diff --git a/bosh/manager_test.go b/bosh/manager_test.go index 7bcd55872..e1eba318c 100644 --- a/bosh/manager_test.go +++ b/bosh/manager_test.go @@ -102,17 +102,17 @@ director_ssl: It("Calls PlanDirector", func() { err := boshManager.InitializeDirector(state) Expect(err).NotTo(HaveOccurred()) - Expect(boshExecutor.PlanDirectorCall.Receives.DirInput.VarsDir).To(Equal("some-bbl-vars-dir")) - Expect(boshExecutor.PlanDirectorCall.Receives.DirInput.StateDir).To(Equal("some-state-dir")) - Expect(boshExecutor.PlanDirectorCall.Receives.DeploymentDir).To(Equal("some-director-deployment-dir")) - Expect(boshExecutor.PlanJumpboxCall.CallCount).To(Equal(0)) + Expect(boshExecutor.PlanDirectorWithStateCall.Receives.DirInput.VarsDir).To(Equal("some-bbl-vars-dir")) + Expect(boshExecutor.PlanDirectorWithStateCall.Receives.DirInput.StateDir).To(Equal("some-state-dir")) + Expect(boshExecutor.PlanDirectorWithStateCall.Receives.DeploymentDir).To(Equal("some-director-deployment-dir")) + Expect(boshExecutor.PlanJumpboxWithStateCall.CallCount).To(Equal(0)) Expect(boshExecutor.CreateEnvCall.CallCount).To(Equal(0)) }) Context("when create env args fails", func() { BeforeEach(func() { - boshExecutor.PlanDirectorCall.Returns.Error = errors.New("failed to interpolate") + boshExecutor.PlanDirectorWithStateCall.Returns.Error = errors.New("failed to interpolate") }) It("returns an error", func() { @@ -238,13 +238,13 @@ director_ssl: }) Describe("InitializeJumpbox", func() { - It("calls PlanJumpboxCall appropriately", func() { + It("calls PlanJumpboxWithStateCall appropriately", func() { err := boshManager.InitializeJumpbox(state) Expect(err).NotTo(HaveOccurred()) - Expect(boshExecutor.PlanJumpboxCall.Receives.DeploymentDir).To(Equal("some-jumpbox-deployment-dir")) - Expect(boshExecutor.PlanJumpboxCall.Receives.DirInput.VarsDir).To(Equal("some-bbl-vars-dir")) - Expect(boshExecutor.PlanJumpboxCall.Receives.DirInput.StateDir).To(Equal("some-state-dir")) + Expect(boshExecutor.PlanJumpboxWithStateCall.Receives.DeploymentDir).To(Equal("some-jumpbox-deployment-dir")) + Expect(boshExecutor.PlanJumpboxWithStateCall.Receives.DirInput.VarsDir).To(Equal("some-bbl-vars-dir")) + Expect(boshExecutor.PlanJumpboxWithStateCall.Receives.DirInput.StateDir).To(Equal("some-state-dir")) }) Context("when an error occurs", func() { diff --git a/config/load_state_test.go b/config/load_state_test.go index 4e561bbc6..b330c3f87 100644 --- a/config/load_state_test.go +++ b/config/load_state_test.go @@ -184,6 +184,7 @@ var _ = Describe("LoadState", func() { "--name", "some-name", "--aws-access-key-id", "some-aws-access-key", "--aws-secret-access-key", "some-aws-secret-access-key", + "--aws-assume-role", "some-aws-assume-role", })) Expect(err).NotTo(HaveOccurred()) diff --git a/fakes/bosh_executor.go b/fakes/bosh_executor.go index d587643bc..38c02eb05 100644 --- a/fakes/bosh_executor.go +++ b/fakes/bosh_executor.go @@ -29,24 +29,26 @@ type BOSHExecutor struct { } } - PlanJumpboxCall struct { + PlanJumpboxWithStateCall struct { CallCount int Receives struct { DirInput bosh.DirInput DeploymentDir string Iaas string + State storage.State } Returns struct { Error error } } - PlanDirectorCall struct { + PlanDirectorWithStateCall struct { CallCount int Receives struct { DirInput bosh.DirInput DeploymentDir string Iaas string + State storage.State } Returns struct { Error error @@ -104,22 +106,24 @@ func (e *BOSHExecutor) DeleteEnv(input bosh.DirInput, state storage.State) error return e.DeleteEnvCall.Returns.Error } -func (e *BOSHExecutor) PlanJumpbox(input bosh.DirInput, deploymentDir, iaas string) error { - e.PlanJumpboxCall.CallCount++ - e.PlanJumpboxCall.Receives.DirInput = input - e.PlanJumpboxCall.Receives.DeploymentDir = deploymentDir - e.PlanJumpboxCall.Receives.Iaas = iaas +func (e *BOSHExecutor) PlanJumpboxWithState(input bosh.DirInput, deploymentDir, iaas string, state storage.State) error { + e.PlanJumpboxWithStateCall.CallCount++ + e.PlanJumpboxWithStateCall.Receives.DirInput = input + e.PlanJumpboxWithStateCall.Receives.DeploymentDir = deploymentDir + e.PlanJumpboxWithStateCall.Receives.Iaas = iaas + e.PlanJumpboxWithStateCall.Receives.State = state - return e.PlanJumpboxCall.Returns.Error + return e.PlanJumpboxWithStateCall.Returns.Error } -func (e *BOSHExecutor) PlanDirector(input bosh.DirInput, deploymentDir, iaas string) error { - e.PlanDirectorCall.CallCount++ - e.PlanDirectorCall.Receives.DirInput = input - e.PlanDirectorCall.Receives.DeploymentDir = deploymentDir - e.PlanDirectorCall.Receives.Iaas = iaas +func (e *BOSHExecutor) PlanDirectorWithState(input bosh.DirInput, deploymentDir, iaas string, state storage.State) error { + e.PlanDirectorWithStateCall.CallCount++ + e.PlanDirectorWithStateCall.Receives.DirInput = input + e.PlanDirectorWithStateCall.Receives.DeploymentDir = deploymentDir + e.PlanDirectorWithStateCall.Receives.Iaas = iaas + e.PlanDirectorWithStateCall.Receives.State = state - return e.PlanDirectorCall.Returns.Error + return e.PlanDirectorWithStateCall.Returns.Error } func (e *BOSHExecutor) Path() string { diff --git a/terraform/aws/input_generator_test.go b/terraform/aws/input_generator_test.go index 6a9d2cec5..dcdc459eb 100644 --- a/terraform/aws/input_generator_test.go +++ b/terraform/aws/input_generator_test.go @@ -146,11 +146,12 @@ var _ = Describe("InputGenerator", func() { }) Describe("Credentials", func() { - It("returns the access key and secret key", func() { + It("returns the access key, secret key, and role arn", func() { state := storage.State{ AWS: storage.AWS{ AccessKeyID: "some-access-key-id", SecretAccessKey: "some-secret-access-key", + AssumeRoleArn: "some-assume-role-arn", Region: "some-region", }, } @@ -160,6 +161,7 @@ var _ = Describe("InputGenerator", func() { Expect(credentials).To(Equal(map[string]string{ "access_key": "some-access-key-id", "secret_key": "some-secret-access-key", + "role_arn": "some-assume-role-arn", })) }) }) From 7e724935d61be623ba3b565faa3fcf3e81292ce7 Mon Sep 17 00:00:00 2001 From: Brian Upton Date: Tue, 9 May 2023 16:49:32 -0700 Subject: [PATCH 3/4] Add error when using assume role + state bucket [#184999423] Add AssumeRole support to bbl --- config/downloader.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/downloader.go b/config/downloader.go index 136bd0a7a..53783313d 100644 --- a/config/downloader.go +++ b/config/downloader.go @@ -1,6 +1,8 @@ package config import ( + "errors" + "github.com/cloudfoundry/bosh-bootloader/backends" ) @@ -29,6 +31,9 @@ func (d Downloader) DownloadAndPrepareState(flags GlobalFlags) error { AWSSecretAccessKey: flags.AWSSecretAccessKey, } + if flags.AWSAssumeRole != "" { + return errors.New("Assume role not supported when using an AWS state bucket") + } case "gcp": config = backends.Config{ Dest: flags.StateDir, From 7b1477311e572e23c45d42f2ffd594e7550b4a15 Mon Sep 17 00:00:00 2001 From: Ed King Date: Mon, 3 Jun 2024 15:23:10 +0100 Subject: [PATCH 4/4] Fix linter/test --- bosh/executor_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bosh/executor_test.go b/bosh/executor_test.go index 950458d0a..4a3d12aaa 100644 --- a/bosh/executor_test.go +++ b/bosh/executor_test.go @@ -552,7 +552,7 @@ var _ = Describe("Executor", func() { "-v", `cloudstack_secret_access_key="${BBL_CLOUDSTACK_SECRET_ACCESS_KEY}"`, } - behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "cloudstack", stateDir) + behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "cloudstack", stateDir, storage.State{}) }) }) })