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()`.
This commit is contained in:
Will Glynn 2016-02-06 18:38:40 -06:00
parent 1198444908
commit 13e01e1751
2 changed files with 27 additions and 19 deletions

View file

@ -15,29 +15,29 @@ type DNSProviderRoute53 struct {
} }
// NewDNSProviderRoute53 returns a DNSProviderRoute53 instance with a configured route53 client. // NewDNSProviderRoute53 returns a DNSProviderRoute53 instance with a configured route53 client.
// Authentication is either done using the passed credentials or - when empty - // Authentication is either done using the passed credentials or - when empty - falling back to
// using the environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. // 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) { func NewDNSProviderRoute53(awsAccessKey, awsSecretKey, awsRegionName string) (*DNSProviderRoute53, error) {
region, ok := aws.Regions[awsRegionName] region, ok := aws.Regions[awsRegionName]
if !ok { if !ok {
return nil, fmt.Errorf("Invalid AWS region name %s", awsRegionName) return nil, fmt.Errorf("Invalid AWS region name %s", awsRegionName)
} }
var auth aws.Auth // use aws.GetAuth, which tries really hard to find credentails:
// First try passed in credentials // - uses awsAccessKey and awsSecretKey, if provided
if awsAccessKey != "" && awsSecretKey != "" { // - uses AWS_PROFILE / AWS_CREDENTIAL_FILE, if provided
auth = aws.Auth{awsAccessKey, awsSecretKey, ""} // - 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 { } else {
// try getting credentials from environment client := route53.New(auth, region)
envAuth, err := aws.EnvAuth() return &DNSProviderRoute53{client: client}, nil
if err != nil {
return nil, fmt.Errorf("AWS credentials missing")
}
auth = envAuth
} }
client := route53.New(auth, region)
return &DNSProviderRoute53{client: client}, nil
} }
// Present creates a TXT record using the specified parameters // Present creates a TXT record using the specified parameters

View file

@ -11,9 +11,11 @@ import (
) )
var ( var (
route53Secret string route53Secret string
route53Key string route53Key string
testServer *testutil.HTTPServer awsCredentialFile string
homeDir string
testServer *testutil.HTTPServer
) )
var ChangeResourceRecordSetsAnswer = `<?xml version="1.0" encoding="UTF-8"?> var ChangeResourceRecordSetsAnswer = `<?xml version="1.0" encoding="UTF-8"?>
@ -69,6 +71,8 @@ var serverResponseMap = testutil.ResponseMap{
func init() { func init() {
route53Key = os.Getenv("AWS_ACCESS_KEY_ID") route53Key = os.Getenv("AWS_ACCESS_KEY_ID")
route53Secret = os.Getenv("AWS_SECRET_ACCESS_KEY") route53Secret = os.Getenv("AWS_SECRET_ACCESS_KEY")
awsCredentialFile = os.Getenv("AWS_CREDENTIAL_FILE")
homeDir = os.Getenv("HOME")
testServer = testutil.NewHTTPServer() testServer = testutil.NewHTTPServer()
testServer.Start() testServer.Start()
} }
@ -76,6 +80,8 @@ func init() {
func restoreRoute53Env() { func restoreRoute53Env() {
os.Setenv("AWS_ACCESS_KEY_ID", route53Key) os.Setenv("AWS_ACCESS_KEY_ID", route53Key)
os.Setenv("AWS_SECRET_ACCESS_KEY", route53Secret) os.Setenv("AWS_SECRET_ACCESS_KEY", route53Secret)
os.Setenv("AWS_CREDENTIAL_FILE", awsCredentialFile)
os.Setenv("HOME", homeDir)
} }
func makeRoute53TestServer() *testutil.HTTPServer { func makeRoute53TestServer() *testutil.HTTPServer {
@ -108,8 +114,10 @@ func TestNewDNSProviderRoute53ValidEnv(t *testing.T) {
func TestNewDNSProviderRoute53MissingAuthErr(t *testing.T) { func TestNewDNSProviderRoute53MissingAuthErr(t *testing.T) {
os.Setenv("AWS_ACCESS_KEY_ID", "") os.Setenv("AWS_ACCESS_KEY_ID", "")
os.Setenv("AWS_SECRET_ACCESS_KEY", "") 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") _, err := NewDNSProviderRoute53("", "", "us-east-1")
assert.EqualError(t, err, "AWS credentials missing") assert.EqualError(t, err, "No valid AWS authentication found")
restoreRoute53Env() restoreRoute53Env()
} }