From 13e01e1751d5dcff1c0f26b3dcf5d37a8d843779 Mon Sep 17 00:00:00 2001 From: Will Glynn Date: Sat, 6 Feb 2016 18:38:40 -0600 Subject: [PATCH] Add support for additional AWS authentication sources AWS client tools commonly support passing credentials via `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY`, but supporting only this is insufficient. For example, access key IDs provided by STS require passing in `AWS_SECURITY_TOKEN` as a third value, and EC2 instances are often provided dynamic credentials at runtime via the EC2 metadata service. This changeset makes `lego` attempt to find credentials in the same way that the `aws` CLI tool attempts to find credentials. The result is even less auth code than before because `goamz` provides all this with `aws.GetAuth()`. --- acme/dns_challenge_route53.go | 30 +++++++++++++++--------------- acme/dns_challenge_route53_test.go | 16 ++++++++++++---- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/acme/dns_challenge_route53.go b/acme/dns_challenge_route53.go index 28ed0259..9ee77e35 100644 --- a/acme/dns_challenge_route53.go +++ b/acme/dns_challenge_route53.go @@ -15,29 +15,29 @@ type DNSProviderRoute53 struct { } // NewDNSProviderRoute53 returns a DNSProviderRoute53 instance with a configured route53 client. -// Authentication is either done using the passed credentials or - when empty - -// using the environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. +// Authentication is either done using the passed credentials or - when empty - falling back to +// the customary AWS credential mechanisms, including the file refernced by $AWS_CREDENTIAL_FILE +// (defaulting to $HOME/.aws/credentials) optionally scoped to $AWS_PROFILE, credentials +// supplied by the environment variables AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY [ + AWS_SECURITY_TOKEN ], +// and finally credentials available via the EC2 instance metadata service. func NewDNSProviderRoute53(awsAccessKey, awsSecretKey, awsRegionName string) (*DNSProviderRoute53, error) { region, ok := aws.Regions[awsRegionName] if !ok { return nil, fmt.Errorf("Invalid AWS region name %s", awsRegionName) } - var auth aws.Auth - // First try passed in credentials - if awsAccessKey != "" && awsSecretKey != "" { - auth = aws.Auth{awsAccessKey, awsSecretKey, ""} + // use aws.GetAuth, which tries really hard to find credentails: + // - uses awsAccessKey and awsSecretKey, if provided + // - uses AWS_PROFILE / AWS_CREDENTIAL_FILE, if provided + // - uses AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY and optionally AWS_SECURITY_TOKEN, if provided + // - uses EC2 instance metadata credentials (http://169.254.169.254/latest/meta-data/…), if available + // ...and otherwise returns an error + if auth, err := aws.GetAuth(awsAccessKey, awsSecretKey); err != nil { + return nil, err } else { - // try getting credentials from environment - envAuth, err := aws.EnvAuth() - if err != nil { - return nil, fmt.Errorf("AWS credentials missing") - } - auth = envAuth + client := route53.New(auth, region) + return &DNSProviderRoute53{client: client}, nil } - - client := route53.New(auth, region) - return &DNSProviderRoute53{client: client}, nil } // Present creates a TXT record using the specified parameters diff --git a/acme/dns_challenge_route53_test.go b/acme/dns_challenge_route53_test.go index eb551d03..20eb008b 100644 --- a/acme/dns_challenge_route53_test.go +++ b/acme/dns_challenge_route53_test.go @@ -11,9 +11,11 @@ import ( ) var ( - route53Secret string - route53Key string - testServer *testutil.HTTPServer + route53Secret string + route53Key string + awsCredentialFile string + homeDir string + testServer *testutil.HTTPServer ) var ChangeResourceRecordSetsAnswer = ` @@ -69,6 +71,8 @@ var serverResponseMap = testutil.ResponseMap{ func init() { route53Key = os.Getenv("AWS_ACCESS_KEY_ID") route53Secret = os.Getenv("AWS_SECRET_ACCESS_KEY") + awsCredentialFile = os.Getenv("AWS_CREDENTIAL_FILE") + homeDir = os.Getenv("HOME") testServer = testutil.NewHTTPServer() testServer.Start() } @@ -76,6 +80,8 @@ func init() { func restoreRoute53Env() { os.Setenv("AWS_ACCESS_KEY_ID", route53Key) os.Setenv("AWS_SECRET_ACCESS_KEY", route53Secret) + os.Setenv("AWS_CREDENTIAL_FILE", awsCredentialFile) + os.Setenv("HOME", homeDir) } func makeRoute53TestServer() *testutil.HTTPServer { @@ -108,8 +114,10 @@ func TestNewDNSProviderRoute53ValidEnv(t *testing.T) { func TestNewDNSProviderRoute53MissingAuthErr(t *testing.T) { os.Setenv("AWS_ACCESS_KEY_ID", "") os.Setenv("AWS_SECRET_ACCESS_KEY", "") + os.Setenv("AWS_CREDENTIAL_FILE", "") // in case test machine has this variable set + os.Setenv("HOME", "/") // in case test machine has ~/.aws/credentials _, err := NewDNSProviderRoute53("", "", "us-east-1") - assert.EqualError(t, err, "AWS credentials missing") + assert.EqualError(t, err, "No valid AWS authentication found") restoreRoute53Env() }