Skip to content

Commit

Permalink
Make STS auth optional and revert back to account access keys when no…
Browse files Browse the repository at this point in the history
…t enabled.
  • Loading branch information
mhlias committed Jul 25, 2016
1 parent 8670045 commit 9c59513
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 36 deletions.
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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


Expand Down Expand Up @@ -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`.





74 changes: 49 additions & 25 deletions aws_helper/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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),
Expand All @@ -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

}

Expand Down
12 changes: 10 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
}

Expand Down
3 changes: 2 additions & 1 deletion test-fixtures/project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
test-prd: 1002

0 comments on commit 9c59513

Please sign in to comment.