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/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..4a3d12aaa 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() { @@ -476,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{}) }) }) }) @@ -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/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, 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..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()) @@ -193,6 +194,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/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/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/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", })) }) }) 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" }