Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple credential providers via aws-sdk-go #1049

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 68 additions & 10 deletions builtin/providers/aws/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package aws
import (
"fmt"
"log"

"github.com/hashicorp/terraform/helper/multierror"
"os"
"time"

"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/autoscaling"
Expand All @@ -14,13 +14,18 @@ import (
"github.com/awslabs/aws-sdk-go/service/rds"
"github.com/awslabs/aws-sdk-go/service/route53"
"github.com/awslabs/aws-sdk-go/service/s3"

"github.com/hashicorp/terraform/helper/multierror"
)

type Config struct {
AccessKey string
SecretKey string
Token string
Region string
AccessKey string
SecretKey string
Token string
CredentialsFilePath string
CredentialsFileProfile string
Region string
Provider aws.CredentialsProvider
}

type AWSClient struct {
Expand All @@ -34,6 +39,61 @@ type AWSClient struct {
iamconn *iam.IAM
}

func (c *Config) loadAndValidate(providerCode string) (interface{}, error) {
c.tryLoadingDeprecatedEnvVars()
credsProvider, err := c.getCredsProvider(providerCode)
if err != nil {
return nil, err
}

if _, err := credsProvider.Credentials(); err != nil {
return nil, err
}

c.Provider = credsProvider

return c.Client()
}

func (c *Config) tryLoadingDeprecatedEnvVars() {
// Backward compatibility
if c.Token == "" {
c.Token = os.Getenv("AWS_SECURITY_TOKEN")
}
if c.CredentialsFilePath == "" {
c.CredentialsFilePath = os.Getenv("AWS_CREDENTIAL_FILE")
}
if c.CredentialsFileProfile == "" {
c.CredentialsFileProfile = os.Getenv("AWS_PROFILE")
}
}

func (c *Config) getCredsProvider(providerCode string) (aws.CredentialsProvider, error) {
switch providerCode {
case "static":
log.Println("[INFO] Loading static credentials")
return aws.Creds(c.AccessKey, c.SecretKey, c.Token), nil
case "iam":
log.Println("[INFO] Loading credentials via IAM")
return aws.IAMCreds(), nil
case "env":
log.Println("[INFO] Loading credentials from ENV variables")
return aws.EnvCreds()
case "file":
log.Printf("[INFO] Loading credentials from config file at %s",
c.CredentialsFilePath)
// TODO: Could be a variable but there's no standardized name for it
// More importantly, what is really the point of this variable??
expiry := 10 * time.Minute

return aws.ProfileCreds(
c.CredentialsFilePath, c.CredentialsFileProfile, expiry)
}

log.Println("[INFO] Loading credentials automagically via AWS library")
return aws.DetectCreds(c.AccessKey, c.SecretKey, c.Token), nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switch statement would probably be saner here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, fixed.


// Client configures and returns a fully initailized AWSClient
func (c *Config) Client() (interface{}, error) {
var client AWSClient
Expand All @@ -54,9 +114,8 @@ func (c *Config) Client() (interface{}, error) {
client.region = c.Region

log.Println("[INFO] Building AWS auth structure")
creds := aws.DetectCreds(c.AccessKey, c.SecretKey, c.Token)
awsConfig := &aws.Config{
Credentials: creds,
Credentials: c.Provider,
Region: c.Region,
}

Expand All @@ -82,10 +141,9 @@ func (c *Config) Client() (interface{}, error) {
// See http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
log.Println("[INFO] Initializing Route 53 connection")
client.r53conn = route53.New(&aws.Config{
Credentials: creds,
Credentials: c.Provider,
Region: "us-east-1",
})

}

if len(errs) > 0 {
Expand Down
73 changes: 51 additions & 22 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,47 @@ func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"access_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"AWS_ACCESS_KEY",
"AWS_ACCESS_KEY_ID",
}, nil),
Type: schema.TypeString,
Optional: true,
Description: descriptions["access_key"],
},

"secret_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"AWS_SECRET_KEY",
"AWS_SECRET_ACCESS_KEY",
}, nil),
Type: schema.TypeString,
Optional: true,
Description: descriptions["secret_key"],
},

"security_token": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: descriptions["security_token"],
},

"credentials_provider": &schema.Schema{
Type: schema.TypeString,
Optional: true,
InputDefault: "detect",
Description: descriptions["credentials_provider"],
},

"credentials_file_path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
InputDefault: "",
Description: descriptions["credentials_file_path"],
},

"credentials_file_profile": &schema.Schema{
Type: schema.TypeString,
Optional: true,
InputDefault: "",
Description: descriptions["credentials_file_profile"],
},

"region": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"AWS_REGION",
"AWS_DEFAULT_REGION",
}, nil),
Type: schema.TypeString,
Optional: true,
Description: descriptions["region"],
InputDefault: "us-east-1",
},
Expand Down Expand Up @@ -87,15 +102,29 @@ func init() {

"secret_key": "The secret key for API operations. You can retrieve this\n" +
"from the 'Security & Credentials' section of the AWS console.",

"security_token": "The temporary token from AWS STS service (if applicable)",

"credentials_provider": "How to load credentials (static | iam | env | file)\n" +
"Defaults to detect",

"credentials_file_path": "Path to a file with credentials. Default\n" +
"is ~/.aws/credentials. Implies credentials_provider=file",

"credentials_file_profile": "Profile name in a credentials file." +
"Default is 'default'. Implies credentials_provider=file",
}
}

func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := Config{
AccessKey: d.Get("access_key").(string),
SecretKey: d.Get("secret_key").(string),
Region: d.Get("region").(string),
AccessKey: d.Get("access_key").(string),
SecretKey: d.Get("secret_key").(string),
Token: d.Get("security_token").(string),
CredentialsFilePath: d.Get("credentials_file_path").(string),
CredentialsFileProfile: d.Get("credentials_file_profile").(string),
Region: d.Get("region").(string),
}

return config.Client()
return config.loadAndValidate(d.Get("credentials_provider").(string))
}