From 9c59513132bb96b8c8c2a46b798cc8bbfcb23c13 Mon Sep 17 00:00:00 2001 From: Ilias Bertsimas Date: Mon, 25 Jul 2016 14:52:02 +0100 Subject: [PATCH] Make STS auth optional and revert back to account access keys when not enabled. --- README.md | 11 +++--- aws_helper/conn.go | 74 +++++++++++++++++++++++++------------- main.go | 12 +++++-- main_test.go | 2 +- test-fixtures/project.yaml | 3 +- 5 files changed, 66 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 49c28fb..2599437 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,14 @@ This tool wraps terraform execution forcing a specific structure while providing - Creates an S3 bucket in the current account and enables versioning. - Configures remote terraform state on the created S3 bucket. - Provides management of remote git/github terraform modules using the Terrafile concept. - - Provides plan and apply functionality and keeps the local & remote states in sync. + - Provides plan and apply functionality and keeps the local & remote states in sync. ### Setup Requirements This tool will ask you for input on the first run to configure itself for your user. Configuration input required: - + - Directory levels from root directory of your project: If your project root directory is `/home/user/projects/my-project` and your terraform templates reside in `/home/user/projects/my-project/terraform/my-account-dev/staging/` then you need to set it to `3`. - Name of your project config yaml file: This file will reside on the root directory of your project and needs to be `%name.yaml` which `%name` you specify in this stage. - Directory name of your terraform modules: This will be always created a level down of the root of your project and will be used by the Terrafile concept to store your project's modules. Will also be the modules source in your terraform templates. @@ -34,6 +34,7 @@ From the files mentioned above here are some examples of what their contents nee project: name_of_your_project region: eu-west-1 roam-role: roam-role +use-sts: true accounts-mapping: project-dev: 100000000001 project-prd: 100000000002 @@ -42,6 +43,7 @@ accounts-mapping: - `project` should match the name of your AWS accounts without -dev/prd - `region` is the AWS region your project will be deployed into - `roam-role` is the AWS IAM role that you can assume in the project's AWS accounts *1 +- `use-sts` is a boolean value that enables or disables STS authentication. If not enabled a profile name matching project-dev/prd is expected to be found in your AWS shared credentials file with access and secret keys. - `accounts-mapping` is a hash mapping your account-dev/prd used in the project to their AWS account IDS which is needed to assume roles and get STS tokens @@ -109,8 +111,3 @@ Credits and thanks to the following people and old colleagues from ITV: - [Efstathios Xagoraris] (https://github.com/xiii) for coming up with `Terrafile` which tholos implements. - [Ben Snape] (https://github.com/bsnape) for the `domed-city` implementation which tholos is inspired from. - [Stefan Coccora] (https://github.com/stefancocora) & [Andrew Stangl] (https://github.com/madandroid) for their original concept and implementation that evolved to `domed-city`. - - - - - diff --git a/aws_helper/conn.go b/aws_helper/conn.go index 5f6d661..2d0f408 100644 --- a/aws_helper/conn.go +++ b/aws_helper/conn.go @@ -24,6 +24,7 @@ type Config struct { Role string Account_id string Use_mfa bool + Use_sts bool Mfa_device_id string Mfa_token string } @@ -40,6 +41,8 @@ func (c *Config) Connect() interface{} { screds := &credentials.SharedCredentialsProvider{Profile: c.Profile} + log.Printf("[INFO] Using aws shared credentials profile: %s\n", c.Profile) + awsConfig := &aws.Config{ Credentials: credentials.NewCredentials(screds), Region: aws.String(c.Region), @@ -48,44 +51,65 @@ func (c *Config) Connect() interface{} { sess := session.New(awsConfig) - log.Println("[INFO] Initializing STS Connection") - client.stsconn = sts.New(sess) + if c.Use_sts { + + log.Println("[INFO] Initializing STS Connection") + client.stsconn = sts.New(sess) + + params := &sts.AssumeRoleInput{} + + if c.Use_mfa { - params := &sts.AssumeRoleInput{} + params = &sts.AssumeRoleInput{ + RoleArn: aws.String(fmt.Sprintf("arn:aws:iam::%s:role/%s", c.Account_id, c.Role)), + RoleSessionName: aws.String(fmt.Sprintf("%s-%s", c.Account_id, c.Role)), + DurationSeconds: aws.Int64(3600), + SerialNumber: aws.String(c.Mfa_device_id), + TokenCode: aws.String(c.Mfa_token), + } - if c.Use_mfa { + } else { + + params = &sts.AssumeRoleInput{ + RoleArn: aws.String(fmt.Sprintf("arn:aws:iam::%s:role/%s", c.Account_id, c.Role)), + RoleSessionName: aws.String(fmt.Sprintf("%s-%s", c.Account_id, c.Role)), + DurationSeconds: aws.Int64(3600), + } - params = &sts.AssumeRoleInput{ - RoleArn: aws.String(fmt.Sprintf("arn:aws:iam::%s:role/%s", c.Account_id, c.Role)), - RoleSessionName: aws.String(fmt.Sprintf("%s-%s", c.Account_id, c.Role)), - DurationSeconds: aws.Int64(3600), - SerialNumber: aws.String(c.Mfa_device_id), - TokenCode: aws.String(c.Mfa_token), } - } else { + sts_resp, sts_err := client.stsconn.AssumeRole(params) - params = &sts.AssumeRoleInput{ - RoleArn: aws.String(fmt.Sprintf("arn:aws:iam::%s:role/%s", c.Account_id, c.Role)), - RoleSessionName: aws.String(fmt.Sprintf("%s-%s", c.Account_id, c.Role)), - DurationSeconds: aws.Int64(3600), + if sts_err != nil { + log.Fatalf("Unable to assume role: %v", sts_err.Error()) } - } + os.Setenv("AWS_ACCESS_KEY_ID", *sts_resp.Credentials.AccessKeyId) + os.Setenv("AWS_SECRET_ACCESS_KEY", *sts_resp.Credentials.SecretAccessKey) + os.Setenv("AWS_SECURITY_TOKEN", *sts_resp.Credentials.SessionToken) + os.Setenv("AWS_SESSION_TOKEN", *sts_resp.Credentials.SessionToken) + os.Setenv("AWS_DEFAULT_REGION", c.Region) - sts_resp, sts_err := client.stsconn.AssumeRole(params) + return c.assumeConnect(sts_resp) - if sts_err != nil { - log.Fatalf("Unable to assume role: %v", sts_err.Error()) + } else { + + profile_creds := credentials.Value{} + var profile_err error + + if profile_creds, profile_err = screds.Retrieve(); profile_err != nil { + log.Fatalf("[ERROR] Failed to get aws credentials for profile: %s with error: %s\n", c.Profile, profile_err.Error()) + } + + os.Setenv("AWS_ACCESS_KEY_ID", profile_creds.AccessKeyID) + os.Setenv("AWS_SECRET_ACCESS_KEY", profile_creds.SecretAccessKey) + os.Setenv("AWS_DEFAULT_REGION", c.Region) } - os.Setenv("AWS_ACCESS_KEY_ID", *sts_resp.Credentials.AccessKeyId) - os.Setenv("AWS_SECRET_ACCESS_KEY", *sts_resp.Credentials.SecretAccessKey) - os.Setenv("AWS_SECURITY_TOKEN", *sts_resp.Credentials.SessionToken) - os.Setenv("AWS_SESSION_TOKEN", *sts_resp.Credentials.SessionToken) - os.Setenv("AWS_DEFAULT_REGION", c.Region) + log.Println("[INFO] Initializing S3 Connection") + client.S3conn = s3.New(sess) - return c.assumeConnect(sts_resp) + return &client } diff --git a/main.go b/main.go index 388f8c1..f918ade 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ type conf struct { Region string Roam_role string `yaml:"roam-role"` Accounts_mapping map[string]string `yaml:"accounts-mapping"` + Use_sts bool `yaml:"use-sts"` environment string account string } @@ -114,12 +115,19 @@ func main() { if !*modulesPtr { + profile := project_config.account + + if project_config.Use_sts { + profile = tholos_conf.Root_profile + } + awsconf := &aws_helper.Config{ Region: project_config.Region, - Profile: tholos_conf.Root_profile, + Profile: profile, Role: project_config.Roam_role, Account_id: project_config.Accounts_mapping[project_config.account], Use_mfa: use_mfa, + Use_sts: project_config.Use_sts, Mfa_device_id: mfa_device_id, Mfa_token: mfa_token, } @@ -172,7 +180,7 @@ func load_config(project_config_file string) *conf { yamlConf, file_err := ioutil.ReadFile(configFile) if file_err != nil { - log.Fatalf("[ERROR] File does not exist or not accessible: ", file_err) + log.Fatalln("[ERROR] File does not exist or not accessible: ", file_err) } yaml_err := yaml.Unmarshal(yamlConf, &project_config) diff --git a/main_test.go b/main_test.go index 3013812..5fe0b34 100644 --- a/main_test.go +++ b/main_test.go @@ -11,7 +11,7 @@ func TestProjectConfig(t *testing.T) { project_config := load_config(fmt.Sprintf("%s/project.yaml", fixtures_dir)) - if project_config.Project != "test" || project_config.Region != "eu-west-1" || project_config.Roam_role != "roam-role" || len(project_config.Accounts_mapping[fmt.Sprintf("%s-dev", project_config.Project)]) <= 0 || len(project_config.Accounts_mapping[fmt.Sprintf("%s-prd", project_config.Project)]) <= 0 { + if project_config.Project != "test" || project_config.Region != "eu-west-1" || !project_config.Use_sts || project_config.Roam_role != "roam-role" || len(project_config.Accounts_mapping[fmt.Sprintf("%s-dev", project_config.Project)]) <= 0 || len(project_config.Accounts_mapping[fmt.Sprintf("%s-prd", project_config.Project)]) <= 0 { t.Fatal("Project configuration parameters in fixtures don't match expected values when parsed.") } diff --git a/test-fixtures/project.yaml b/test-fixtures/project.yaml index 8f56474..ccede5a 100644 --- a/test-fixtures/project.yaml +++ b/test-fixtures/project.yaml @@ -3,6 +3,7 @@ project: test region: eu-west-1 roam-role: roam-role +use-sts: true accounts-mapping: test-dev: 1001 - test-prd: 1002 \ No newline at end of file + test-prd: 1002