mirror of https://github.com/docker/docs.git
Merge pull request #2839 from jeanlaurent/aws-credentials
Use ~/.aws/credentials file to login to ec2
This commit is contained in:
commit
438f91c6ba
|
|
@ -10,21 +10,44 @@ parent="smn_machine_drivers"
|
||||||
|
|
||||||
# Amazon Web Services
|
# Amazon Web Services
|
||||||
|
|
||||||
Create machines on [Amazon Web Services](http://aws.amazon.com). To create machines on [Amazon Web Services](http://aws.amazon.com), you must supply two required parameters:
|
Create machines on [Amazon Web Services](http://aws.amazon.com).
|
||||||
|
|
||||||
- Access Key ID
|
To create machines on [Amazon Web Services](http://aws.amazon.com), you must supply two parameters: the AWS Access Key ID and the AWS Secret Access Key.
|
||||||
- Secret Access Key
|
|
||||||
|
|
||||||
Obtain your IDs and Keys from AWS.
|
|
||||||
|
|
||||||
To create the machine instance, specify `--driver amazonec2` and the three required parameters.
|
## Configuring credentials
|
||||||
|
|
||||||
|
Before using the amazonec2 driver, ensure that you've configured credentials.
|
||||||
|
|
||||||
|
### AWS credential file
|
||||||
|
One way to configure credentials is to use the standard credential file for Amazon AWS `~/.aws/credentials` file, which might look like:
|
||||||
|
|
||||||
|
[default]
|
||||||
|
aws_access_key_id = AKID1234567890
|
||||||
|
aws_secret_access_key = MY-SECRET-KEY
|
||||||
|
|
||||||
|
You can learn more about the credentials file from this [blog post](http://blogs.aws.amazon.com/security/post/Tx3D6U6WSFGOK2H/A-New-and-Standardized-Way-to-Manage-Credentials-in-the-AWS-SDKs).
|
||||||
|
|
||||||
|
This is the simplest case, you can then create a new machine with:
|
||||||
|
|
||||||
|
$ docker-machine create --driver amazonec2 aws01
|
||||||
|
|
||||||
|
### Command line flags
|
||||||
|
Alternatively, you can use the flags `--amazonec2-access-key` and `--amazonec2-secret-key` on the command line:
|
||||||
|
|
||||||
$ docker-machine create --driver amazonec2 --amazonec2-access-key AKI******* --amazonec2-secret-key 8T93C******* aws01
|
$ docker-machine create --driver amazonec2 --amazonec2-access-key AKI******* --amazonec2-secret-key 8T93C******* aws01
|
||||||
|
|
||||||
|
### Environment variables
|
||||||
|
You can use environment variables:
|
||||||
|
|
||||||
|
$ export AWS_ACCESS_KEY_ID=AKID1234567890
|
||||||
|
$ export AWS_SECRET_ACCESS_KEY=MY-SECRET-KEY
|
||||||
|
$ docker-machine create --driver amazonec2 aws01
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
- `--amazonec2-access-key`: **required** Your access key id for the Amazon Web Services API.
|
- `--amazonec2-access-key`: Your access key id for the Amazon Web Services API.
|
||||||
- `--amazonec2-secret-key`: **required** Your secret access key for the Amazon Web Services API.
|
- `--amazonec2-secret-key`: Your secret access key for the Amazon Web Services API.
|
||||||
- `--amazonec2-session-token`: Your session token for the Amazon Web Services API.
|
- `--amazonec2-session-token`: Your session token for the Amazon Web Services API.
|
||||||
- `--amazonec2-ami`: The AMI ID of the instance to use.
|
- `--amazonec2-ami`: The AMI ID of the instance to use.
|
||||||
- `--amazonec2-region`: The region to use when launching the instance.
|
- `--amazonec2-region`: The region to use when launching the instance.
|
||||||
|
|
@ -66,8 +89,8 @@ Environment variables and default values:
|
||||||
|
|
||||||
| CLI option | Environment variable | Default |
|
| CLI option | Environment variable | Default |
|
||||||
| ---------------------------------------- | ----------------------- | ---------------- |
|
| ---------------------------------------- | ----------------------- | ---------------- |
|
||||||
| **`--amazonec2-access-key`** | `AWS_ACCESS_KEY_ID` | - |
|
| `--amazonec2-access-key` | `AWS_ACCESS_KEY_ID` | - |
|
||||||
| **`--amazonec2-secret-key`** | `AWS_SECRET_ACCESS_KEY` | - |
|
| `--amazonec2-secret-key` | `AWS_SECRET_ACCESS_KEY` | - |
|
||||||
| `--amazonec2-session-token` | `AWS_SESSION_TOKEN` | - |
|
| `--amazonec2-session-token` | `AWS_SESSION_TOKEN` | - |
|
||||||
| `--amazonec2-ami` | `AWS_AMI` | `ami-5f709f34` |
|
| `--amazonec2-ami` | `AWS_AMI` | `ami-5f709f34` |
|
||||||
| `--amazonec2-region` | `AWS_DEFAULT_REGION` | `us-east-1` |
|
| `--amazonec2-region` | `AWS_DEFAULT_REGION` | `us-east-1` |
|
||||||
|
|
@ -90,8 +113,7 @@ Environment variables and default values:
|
||||||
| `--amazonec2-use-ebs-optimized-instance` | - | `false` |
|
| `--amazonec2-use-ebs-optimized-instance` | - | `false` |
|
||||||
|
|
||||||
## Security Group
|
## Security Group
|
||||||
|
Note that a security group will be created and associated to the host. This security group will have the following ports opened inbound:
|
||||||
Note that a security group will be created and associated to the host. This security group will have the following ports opened inbound :
|
|
||||||
|
|
||||||
- ssh (22/tcp)
|
- ssh (22/tcp)
|
||||||
- docker (2376/tcp)
|
- docker (2376/tcp)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import (
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/docker/machine/libmachine/drivers"
|
"github.com/docker/machine/libmachine/drivers"
|
||||||
|
|
@ -49,14 +48,15 @@ const (
|
||||||
var (
|
var (
|
||||||
dockerPort = 2376
|
dockerPort = 2376
|
||||||
swarmPort = 3376
|
swarmPort = 3376
|
||||||
errorMissingAccessKeyOption = errors.New("amazonec2 driver requires the --amazonec2-access-key option")
|
errorMissingAccessKeyOption = errors.New("amazonec2 driver requires the --amazonec2-access-key option or proper credentials in ~/.aws/credentials")
|
||||||
errorMissingSecretKeyOption = errors.New("amazonec2 driver requires the --amazonec2-secret-key option")
|
errorMissingSecretKeyOption = errors.New("amazonec2 driver requires the --amazonec2-secret-key option or proper credentials in ~/.aws/credentials")
|
||||||
errorNoVPCIdFound = errors.New("amazonec2 driver requires either the --amazonec2-subnet-id or --amazonec2-vpc-id option or an AWS Account with a default vpc-id")
|
errorNoVPCIdFound = errors.New("amazonec2 driver requires either the --amazonec2-subnet-id or --amazonec2-vpc-id option or an AWS Account with a default vpc-id")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Driver struct {
|
type Driver struct {
|
||||||
*drivers.BaseDriver
|
*drivers.BaseDriver
|
||||||
clientFactory func() Ec2Client
|
clientFactory func() Ec2Client
|
||||||
|
awsCredentials awsCredentials
|
||||||
Id string
|
Id string
|
||||||
AccessKey string
|
AccessKey string
|
||||||
SecretKey string
|
SecretKey string
|
||||||
|
|
@ -226,6 +226,7 @@ func NewDriver(hostName, storePath string) *Driver {
|
||||||
MachineName: hostName,
|
MachineName: hostName,
|
||||||
StorePath: storePath,
|
StorePath: storePath,
|
||||||
},
|
},
|
||||||
|
awsCredentials: &defaultAWSCredentials{},
|
||||||
}
|
}
|
||||||
|
|
||||||
driver.clientFactory = driver.buildClient
|
driver.clientFactory = driver.buildClient
|
||||||
|
|
@ -237,7 +238,7 @@ func (d *Driver) buildClient() Ec2Client {
|
||||||
config := aws.NewConfig()
|
config := aws.NewConfig()
|
||||||
alogger := AwsLogger()
|
alogger := AwsLogger()
|
||||||
config = config.WithRegion(d.Region)
|
config = config.WithRegion(d.Region)
|
||||||
config = config.WithCredentials(credentials.NewStaticCredentials(d.AccessKey, d.SecretKey, d.SessionToken))
|
config = config.WithCredentials(d.awsCredentials.NewStaticCredentials(d.AccessKey, d.SecretKey, d.SessionToken))
|
||||||
config = config.WithLogger(alogger)
|
config = config.WithLogger(alogger)
|
||||||
config = config.WithLogLevel(aws.LogDebugWithHTTPBody)
|
config = config.WithLogLevel(aws.LogDebugWithHTTPBody)
|
||||||
return ec2.New(session.New(config))
|
return ec2.New(session.New(config))
|
||||||
|
|
@ -284,6 +285,18 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
|
||||||
d.UseEbsOptimizedInstance = flags.Bool("amazonec2-use-ebs-optimized-instance")
|
d.UseEbsOptimizedInstance = flags.Bool("amazonec2-use-ebs-optimized-instance")
|
||||||
d.SetSwarmConfigFromFlags(flags)
|
d.SetSwarmConfigFromFlags(flags)
|
||||||
|
|
||||||
|
if d.AccessKey == "" && d.SecretKey == "" {
|
||||||
|
credentials, err := d.awsCredentials.NewSharedCredentials("", "").Get()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Could not load credentials from ~/.aws/credentials")
|
||||||
|
} else {
|
||||||
|
log.Debug("Successfully loaded credentials from ~/.aws/credentials")
|
||||||
|
d.AccessKey = credentials.AccessKeyID
|
||||||
|
d.SecretKey = credentials.SecretAccessKey
|
||||||
|
d.SessionToken = credentials.SessionToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if d.AccessKey == "" {
|
if d.AccessKey == "" {
|
||||||
return errorMissingAccessKeyOption
|
return errorMissingAccessKeyOption
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -240,34 +240,10 @@ func TestSetConfigFromFlags(t *testing.T) {
|
||||||
assert.Empty(t, checkFlags.InvalidFlags)
|
assert.Empty(t, checkFlags.InvalidFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeEC2WithDescribe struct {
|
|
||||||
*ec2.EC2
|
|
||||||
output *ec2.DescribeAccountAttributesOutput
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeEC2WithDescribe) DescribeAccountAttributes(input *ec2.DescribeAccountAttributesInput) (*ec2.DescribeAccountAttributesOutput, error) {
|
|
||||||
return f.output, f.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindDefaultVPC(t *testing.T) {
|
func TestFindDefaultVPC(t *testing.T) {
|
||||||
defaultVpc := "default-vpc"
|
|
||||||
vpcName := "vpc-9999"
|
|
||||||
|
|
||||||
driver := NewDriver("machineFoo", "path")
|
driver := NewDriver("machineFoo", "path")
|
||||||
driver.clientFactory = func() Ec2Client {
|
driver.clientFactory = func() Ec2Client {
|
||||||
return &fakeEC2WithDescribe{
|
return &fakeEC2WithLogin{}
|
||||||
output: &ec2.DescribeAccountAttributesOutput{
|
|
||||||
AccountAttributes: []*ec2.AccountAttribute{
|
|
||||||
{
|
|
||||||
AttributeName: &defaultVpc,
|
|
||||||
AttributeValues: []*ec2.AccountAttributeValue{
|
|
||||||
{AttributeValue: &vpcName},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vpc, err := driver.getDefaultVPCId()
|
vpc, err := driver.getDefaultVPCId()
|
||||||
|
|
@ -305,3 +281,97 @@ func TestDescribeAccountAttributeFails(t *testing.T) {
|
||||||
assert.EqualError(t, err, "Not Found")
|
assert.EqualError(t, err, "Not Found")
|
||||||
assert.Empty(t, vpc)
|
assert.Empty(t, vpc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccessKeyIsMandatory(t *testing.T) {
|
||||||
|
driver := NewDriver("machineFoo", "path")
|
||||||
|
driver.clientFactory = func() Ec2Client { return &fakeEC2{} }
|
||||||
|
driver.awsCredentials = &cliCredentials{}
|
||||||
|
options := &commandstest.FakeFlagger{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
"amazonec2-region": "us-east-1",
|
||||||
|
"amazonec2-zone": "e",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := driver.SetConfigFromFlags(options)
|
||||||
|
|
||||||
|
assert.Equal(t, err, errorMissingAccessKeyOption)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessKeyIsMandatoryEvenIfSecretKeyIsPassed(t *testing.T) {
|
||||||
|
driver := NewDriver("machineFoo", "path")
|
||||||
|
driver.clientFactory = func() Ec2Client { return &fakeEC2{} }
|
||||||
|
driver.awsCredentials = &cliCredentials{}
|
||||||
|
options := &commandstest.FakeFlagger{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
"amazonec2-secret-key": "123",
|
||||||
|
"amazonec2-region": "us-east-1",
|
||||||
|
"amazonec2-zone": "e",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := driver.SetConfigFromFlags(options)
|
||||||
|
|
||||||
|
assert.Equal(t, err, errorMissingAccessKeyOption)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecretKeyIsMandatory(t *testing.T) {
|
||||||
|
driver := NewDriver("machineFoo", "path")
|
||||||
|
driver.clientFactory = func() Ec2Client { return &fakeEC2{} }
|
||||||
|
driver.awsCredentials = &cliCredentials{}
|
||||||
|
options := &commandstest.FakeFlagger{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
"amazonec2-access-key": "foobar",
|
||||||
|
"amazonec2-region": "us-east-1",
|
||||||
|
"amazonec2-zone": "e",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := driver.SetConfigFromFlags(options)
|
||||||
|
|
||||||
|
assert.Equal(t, err, errorMissingSecretKeyOption)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadingFromCredentialsWorked(t *testing.T) {
|
||||||
|
driver := NewDriver("machineFoo", "path")
|
||||||
|
driver.clientFactory = func() Ec2Client { return &fakeEC2WithLogin{} }
|
||||||
|
driver.awsCredentials = &fileCredentials{}
|
||||||
|
options := &commandstest.FakeFlagger{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
"amazonec2-region": "us-east-1",
|
||||||
|
"amazonec2-zone": "e",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := driver.SetConfigFromFlags(options)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "access", driver.AccessKey)
|
||||||
|
assert.Equal(t, "secret", driver.SecretKey)
|
||||||
|
assert.Equal(t, "token", driver.SessionToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassingBothCLIArgWorked(t *testing.T) {
|
||||||
|
driver := NewDriver("machineFoo", "path")
|
||||||
|
driver.clientFactory = func() Ec2Client { return &fakeEC2WithLogin{} }
|
||||||
|
driver.awsCredentials = &cliCredentials{}
|
||||||
|
options := &commandstest.FakeFlagger{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
"amazonec2-access-key": "foobar",
|
||||||
|
"amazonec2-secret-key": "123",
|
||||||
|
"amazonec2-region": "us-east-1",
|
||||||
|
"amazonec2-zone": "e",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := driver.SetConfigFromFlags(options)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "foobar", driver.AccessKey)
|
||||||
|
assert.Equal(t, "123", driver.SecretKey)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package amazonec2
|
||||||
|
|
||||||
|
import "github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
|
||||||
|
type awsCredentials interface {
|
||||||
|
NewStaticCredentials(id, secret, token string) *credentials.Credentials
|
||||||
|
|
||||||
|
NewSharedCredentials(filename, profile string) *credentials.Credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultAWSCredentials struct{}
|
||||||
|
|
||||||
|
func (c *defaultAWSCredentials) NewStaticCredentials(id, secret, token string) *credentials.Credentials {
|
||||||
|
return credentials.NewStaticCredentials(id, secret, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *defaultAWSCredentials) NewSharedCredentials(filename, profile string) *credentials.Credentials {
|
||||||
|
return credentials.NewSharedCredentials(filename, profile)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
package amazonec2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeEC2 struct {
|
||||||
|
*ec2.EC2
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorProvider struct{}
|
||||||
|
|
||||||
|
func (p *errorProvider) Retrieve() (credentials.Value, error) {
|
||||||
|
return credentials.Value{}, errors.New("bad credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *errorProvider) IsExpired() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type okProvider struct {
|
||||||
|
accessKeyID string
|
||||||
|
secretAccessKey string
|
||||||
|
sessionToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *okProvider) Retrieve() (credentials.Value, error) {
|
||||||
|
return credentials.Value{
|
||||||
|
AccessKeyID: p.accessKeyID,
|
||||||
|
SecretAccessKey: p.secretAccessKey,
|
||||||
|
SessionToken: p.sessionToken,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *okProvider) IsExpired() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type cliCredentials struct{}
|
||||||
|
|
||||||
|
func (c *cliCredentials) NewStaticCredentials(id, secret, token string) *credentials.Credentials {
|
||||||
|
return credentials.NewCredentials(&okProvider{id, secret, token})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cliCredentials) NewSharedCredentials(filename, profile string) *credentials.Credentials {
|
||||||
|
return credentials.NewCredentials(&errorProvider{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileCredentials struct{}
|
||||||
|
|
||||||
|
func (c *fileCredentials) NewStaticCredentials(id, secret, token string) *credentials.Credentials {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fileCredentials) NewSharedCredentials(filename, profile string) *credentials.Credentials {
|
||||||
|
return credentials.NewCredentials(&okProvider{"access", "secret", "token"})
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeEC2WithDescribe struct {
|
||||||
|
*fakeEC2
|
||||||
|
output *ec2.DescribeAccountAttributesOutput
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeEC2WithDescribe) DescribeAccountAttributes(input *ec2.DescribeAccountAttributesInput) (*ec2.DescribeAccountAttributesOutput, error) {
|
||||||
|
return f.output, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeEC2WithLogin struct {
|
||||||
|
*fakeEC2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeEC2WithLogin) DescribeAccountAttributes(input *ec2.DescribeAccountAttributesInput) (*ec2.DescribeAccountAttributesOutput, error) {
|
||||||
|
defaultVpc := "default-vpc"
|
||||||
|
vpcName := "vpc-9999"
|
||||||
|
|
||||||
|
return &ec2.DescribeAccountAttributesOutput{
|
||||||
|
AccountAttributes: []*ec2.AccountAttribute{
|
||||||
|
{
|
||||||
|
AttributeName: &defaultVpc,
|
||||||
|
AttributeValues: []*ec2.AccountAttributeValue{
|
||||||
|
{AttributeValue: &vpcName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
load ${BASE_TEST_DIR}/helpers.bash
|
||||||
|
|
||||||
|
only_if_env DRIVER amazonec2
|
||||||
|
|
||||||
|
use_disposable_machine
|
||||||
|
|
||||||
|
require_env AWS_ACCESS_KEY_ID
|
||||||
|
require_env AWS_SECRET_ACCESS_KEY
|
||||||
|
|
||||||
|
@test "$DRIVER: Should Create a default host" {
|
||||||
|
run machine create -d amazonec2 $NAME
|
||||||
|
echo ${output}
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue