diff --git a/builder/amazon/common/access_config.go b/builder/amazon/common/access_config.go index dccde08d4..a4aed9bd5 100644 --- a/builder/amazon/common/access_config.go +++ b/builder/amazon/common/access_config.go @@ -10,41 +10,73 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" + "github.com/aws/aws-sdk-go/aws/credentials/stscreds" + "github.com/aws/aws-sdk-go/aws/session" "github.com/mitchellh/packer/template/interpolate" ) // AccessConfig is for common configuration related to AWS access type AccessConfig struct { - AccessKey string `mapstructure:"access_key"` - SecretKey string `mapstructure:"secret_key"` - RawRegion string `mapstructure:"region"` - Token string `mapstructure:"token"` + AccessKey string `mapstructure:"access_key"` + SecretKey string `mapstructure:"secret_key"` + RawRegion string `mapstructure:"region"` + Token string `mapstructure:"token"` + ProfileName string `mapstructure:"profile"` } // Config returns a valid aws.Config object for access to AWS services, or // an error if the authentication and region couldn't be resolved func (c *AccessConfig) Config() (*aws.Config, error) { - creds := credentials.NewChainCredentials([]credentials.Provider{ - &credentials.StaticProvider{Value: credentials.Value{ - AccessKeyID: c.AccessKey, - SecretAccessKey: c.SecretKey, - SessionToken: c.Token, - }}, - &credentials.EnvProvider{}, - &credentials.SharedCredentialsProvider{Filename: "", Profile: ""}, - &ec2rolecreds.EC2RoleProvider{}, - }) + var creds *credentials.Credentials + + profile := &CLIConfig{} + err := profile.Prepare(c.ProfileName) + if err != nil { + return nil, err + } region, err := c.Region() if err != nil { return nil, err } - return &aws.Config{ - Region: aws.String(region), - Credentials: creds, - MaxRetries: aws.Int(11), - }, nil + config := &aws.Config{ + Region: aws.String(region), + MaxRetries: aws.Int(11), + } + + if c.ProfileName != "" { + creds = c.assumeRoleCreds(config, profile) + } else { + creds = credentials.NewChainCredentials([]credentials.Provider{ + &credentials.StaticProvider{Value: credentials.Value{ + AccessKeyID: c.AccessKey, + SecretAccessKey: c.SecretKey, + SessionToken: c.Token, + }}, + &credentials.EnvProvider{}, + &credentials.SharedCredentialsProvider{Filename: "", Profile: ""}, + &ec2rolecreds.EC2RoleProvider{}, + }) + } + return config.WithCredentials(creds), nil +} + +func (c *AccessConfig) assumeRoleCreds(conf *aws.Config, profile *CLIConfig) (*credentials.Credentials) { + src_creds := credentials.NewStaticCredentials( + profile.Source.AccessKeyID, + profile.Source.SecretAccessKey, + profile.Source.SessionToken, + ) + role_cfg := aws.NewConfig().WithCredentials(src_creds) + role_cfg.MergeIn(conf) + sess := session.New(role_cfg) + return stscreds.NewCredentials(sess, *profile.AssumeRoleInput.RoleArn, func(p *stscreds.AssumeRoleProvider) { + p.RoleSessionName = *profile.AssumeRoleInput.RoleSessionName + if extId := *profile.AssumeRoleInput.ExternalId; extId != "" { + p.ExternalID = &extId + } + }) } // Region returns the aws.Region object for access to AWS services, requesting diff --git a/builder/amazon/common/cli_config.go b/builder/amazon/common/cli_config.go new file mode 100644 index 000000000..2b1b2c014 --- /dev/null +++ b/builder/amazon/common/cli_config.go @@ -0,0 +1,95 @@ +package common + +import ( + "fmt" + "os" + "path" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/service/sts" + "github.com/go-ini/ini" +) + +type CLIConfig struct { + SourceProfile string + + Source credentials.Value + AssumeRoleInput sts.AssumeRoleInput +} + +// Sets params in the struct based on the file section +func (c *CLIConfig) Prepare(name string) (error) { + cfg, err := c.config() + + cfg_profile_name := fmt.Sprintf("profile %s", name) + profile_cfg, err := cfg.GetSection(cfg_profile_name) + if err != nil { + return err + } + + c.SourceProfile = profile_cfg.Key("source_profile").Value(); + if c.SourceProfile == "" { + c.SourceProfile = name + } + + c.AssumeRoleInput.RoleArn = aws.String(profile_cfg.Key("role_arn").Value()) + + host, err := os.Hostname() + if err != nil { + return err + } + + sessName := fmt.Sprintf("packer-%s", host) + c.AssumeRoleInput.RoleSessionName = &sessName + c.AssumeRoleInput.SerialNumber = aws.String(profile_cfg.Key("mfa_serial").Value()) + if extId := aws.String(profile_cfg.Key("external_id").Value()); extId != nil { + c.AssumeRoleInput.ExternalId = extId + } + + creds, err := c.credentials() + cred_cfg, err := creds.GetSection(c.SourceProfile) + if err != nil { + return err + } + + if len(c.SourceProfile) != 0 { + c.Source.AccessKeyID = cred_cfg.Key("aws_access_key_id").Value() + c.Source.SecretAccessKey = cred_cfg.Key("aws_secret_access_key").Value() + c.Source.SessionToken = cred_cfg.Key("aws_session_token").Value() + } + return nil +} + +func (c *CLIConfig) config() (*ini.File, error) { + config_path := os.Getenv("AWS_CONFIG_FILE") + if config_path == "" { + config_path = path.Join(os.Getenv("HOME"), ".aws", "config") + } + ini, err := c.readFile(config_path) + if err != nil { + return nil, err + } + + return ini, nil +} + +func (c *CLIConfig) credentials() (*ini.File, error) { + cred_path := os.Getenv("AWS_SHARED_CREDENTIALS_FILE") + if cred_path == "" { + cred_path = path.Join(os.Getenv("HOME"), ".aws", "credentials") + } + ini, err := c.readFile(cred_path) + if err != nil { + return nil, err + } + return ini, nil +} + +func (c *CLIConfig) readFile(path string) (*ini.File, error) { + cfg, err := ini.Load(path) + if err != nil { + return nil, err + } + return cfg, nil +} \ No newline at end of file