From b2d9782a9e23745059bbb8c2f420f05ae6e4e73f Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Sat, 3 Sep 2016 16:45:52 +0200 Subject: [PATCH] Improved support for Amazon EC2 Container Registry - ECR This adds support for authenticating towards ECR in the docker builder and docker-push post-processor using them same mechanisms as in the amazon builders. I.g. access key/secret key, credentials on file, environment variables, sts tokens or IAM instance roles. --- builder/docker/config.go | 16 ++-- builder/docker/ecr_login.go | 88 +++++++++++++++++++ builder/docker/step_pull.go | 17 +++- post-processor/docker-push/post-processor.go | 29 ++++-- website/source/docs/builders/docker.html.md | 36 +++++--- .../docs/post-processors/docker-push.html.md | 26 +++++- 6 files changed, 185 insertions(+), 27 deletions(-) create mode 100644 builder/docker/ecr_login.go diff --git a/builder/docker/config.go b/builder/docker/config.go index 159c3a138..236a3060d 100644 --- a/builder/docker/config.go +++ b/builder/docker/config.go @@ -35,11 +35,13 @@ type Config struct { // This is used to login to dockerhub to pull a private base container. For // pushing to dockerhub, see the docker post-processors - Login bool - LoginEmail string `mapstructure:"login_email"` - LoginPassword string `mapstructure:"login_password"` - LoginServer string `mapstructure:"login_server"` - LoginUsername string `mapstructure:"login_username"` + Login bool + LoginEmail string `mapstructure:"login_email"` + LoginPassword string `mapstructure:"login_password"` + LoginServer string `mapstructure:"login_server"` + LoginUsername string `mapstructure:"login_username"` + EcrLogin bool `mapstructure:"ecr_login"` + AwsAccessConfig `mapstructure:",squash"` ctx interpolate.Context } @@ -107,6 +109,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } } + if c.EcrLogin && c.LoginServer == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("ECR login requires login server to be provided.")) + } + if errs != nil && len(errs.Errors) > 0 { return nil, nil, errs } diff --git a/builder/docker/ecr_login.go b/builder/docker/ecr_login.go new file mode 100644 index 000000000..6b0b5c1a3 --- /dev/null +++ b/builder/docker/ecr_login.go @@ -0,0 +1,88 @@ +package docker + +import ( + "encoding/base64" + "fmt" + "log" + "regexp" + "strings" + + "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/ec2metadata" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ecr" +) + +type AwsAccessConfig struct { + AccessKey string `mapstructure:"aws_access_key"` + SecretKey string `mapstructure:"aws_secret_key"` + Token string `mapstructure:"aws_token"` +} + +// 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 *AwsAccessConfig) config(region string) (*aws.Config, error) { + var creds *credentials.Credentials + + config := aws.NewConfig().WithRegion(region).WithMaxRetries(11) + sess := session.New(config) + 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{ + Client: ec2metadata.New(sess), + }, + }) + return config.WithCredentials(creds), nil +} + +// Get a login token for Amazon AWS ECR. Returns username and password +// or an error. +func (c *AwsAccessConfig) EcrGetLogin(ecrUrl string) (string, string, error) { + + exp := regexp.MustCompile("(?:http://|https://|)([0-9]*)\\.dkr\\.ecr\\.(.*)\\.amazonaws\\.com.*") + splitUrl := exp.FindStringSubmatch(ecrUrl) + accountId := splitUrl[1] + region := splitUrl[2] + + log.Println(fmt.Sprintf("Getting ECR token for account: %s in %s..", accountId, region)) + + awsConfig, err := c.config(region) + if err != nil { + return "", "", err + } + + session, err := session.NewSession(awsConfig) + if err != nil { + return "", "", fmt.Errorf("failed to create session: %s", err) + } + + service := ecr.New(session) + + params := &ecr.GetAuthorizationTokenInput{ + RegistryIds: []*string{ + aws.String(accountId), + }, + } + resp, err := service.GetAuthorizationToken(params) + if err != nil { + return "", "", fmt.Errorf(err.Error()) + } + + auth, err := base64.StdEncoding.DecodeString(*resp.AuthorizationData[0].AuthorizationToken) + if err != nil { + return "", "", fmt.Errorf("Error decoding ECR AuthorizationToken: %s", err) + } + + authParts := strings.SplitN(string(auth), ":", 2) + log.Printf("Successfully got login for ECR: %s", ecrUrl) + + return authParts[0], authParts[1], nil +} diff --git a/builder/docker/step_pull.go b/builder/docker/step_pull.go index 346334e1e..3f5b1b62a 100644 --- a/builder/docker/step_pull.go +++ b/builder/docker/step_pull.go @@ -21,7 +21,22 @@ func (s *StepPull) Run(state multistep.StateBag) multistep.StepAction { ui.Say(fmt.Sprintf("Pulling Docker image: %s", config.Image)) - if config.Login { + if config.EcrLogin { + ui.Message("Fetching ECR credentials...") + + username, password, err := config.EcrGetLogin(config.LoginServer) + if err != nil { + err := fmt.Errorf("Error fetching ECR credentials: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + config.LoginUsername = username + config.LoginPassword = password + } + + if config.Login || config.EcrLogin { ui.Message("Logging in...") err := driver.Login( config.LoginServer, diff --git a/post-processor/docker-push/post-processor.go b/post-processor/docker-push/post-processor.go index ca8e1a84e..17a0f617c 100644 --- a/post-processor/docker-push/post-processor.go +++ b/post-processor/docker-push/post-processor.go @@ -15,11 +15,13 @@ import ( type Config struct { common.PackerConfig `mapstructure:",squash"` - Login bool - LoginEmail string `mapstructure:"login_email"` - LoginUsername string `mapstructure:"login_username"` - LoginPassword string `mapstructure:"login_password"` - LoginServer string `mapstructure:"login_server"` + Login bool + LoginEmail string `mapstructure:"login_email"` + LoginUsername string `mapstructure:"login_username"` + LoginPassword string `mapstructure:"login_password"` + LoginServer string `mapstructure:"login_server"` + EcrLogin bool `mapstructure:"ecr_login"` + docker.AwsAccessConfig `mapstructure:",squash"` ctx interpolate.Context } @@ -42,6 +44,9 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { return err } + if p.config.EcrLogin && p.config.LoginServer == "" { + return fmt.Errorf("ECR login requires login server to be provided.") + } return nil } @@ -60,7 +65,19 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac driver = &docker.DockerDriver{Ctx: &p.config.ctx, Ui: ui} } - if p.config.Login { + if p.config.EcrLogin { + ui.Message("Fetching ECR credentials...") + + username, password, err := p.config.EcrGetLogin(p.config.LoginServer) + if err != nil { + return nil, false, err + } + + p.config.LoginUsername = username + p.config.LoginPassword = password + } + + if p.config.Login || p.config.EcrLogin { ui.Message("Logging in...") err := driver.Login( p.config.LoginServer, diff --git a/website/source/docs/builders/docker.html.md b/website/source/docs/builders/docker.html.md index d9832bb1e..7d2a4f21e 100644 --- a/website/source/docs/builders/docker.html.md +++ b/website/source/docs/builders/docker.html.md @@ -86,9 +86,27 @@ You must specify (only) one of `commit`, `discard`, or `export_path`. ### Optional: +- `aws_access_key` (string) - The AWS access key used to communicate with AWS. + [Learn how to set this.](/docs/builders/amazon.html#specifying-amazon-credentials) + +- `aws_secret_key` (string) - The AWS secret key used to communicate with AWS. + [Learn how to set this.](/docs/builders/amazon.html#specifying-amazon-credentials) + +- `aws_token` (string) - The AWS access token to use. This is different from the + access key and secret key. If you're not sure what this is, then you + probably don't need it. This will also be read from the `AWS_SESSION_TOKEN` + environmental variable. + +- `ecr_login` (boolean) - Defaults to false. If true, the builder will login in + order to pull the image from + [Amazon EC2 Container Registry (ECR)](https://aws.amazon.com/ecr/). + The builder only logs in for the duration of the pull. If true + `login_server` is required and `login`, `login_username`, and + `login_password` will be ignored. + - `login` (boolean) - Defaults to false. If true, the builder will login in order to pull the image. The builder only logs in for the duration of - the pull. It always logs out afterwards. + the pull. It always logs out afterwards. For log into ECR see `ecr_login`. - `login_email` (string) - The email to use to authenticate to login. @@ -237,10 +255,9 @@ shown below: }, { "type": "docker-push", - "login": true, - "login_email": "none", - "login_username": "AWS", - "login_password": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "ecr_login": true, + "aws_access_key": "YOUR KEY HERE", + "aws_secret_key": "YOUR SECRET KEY HERE", "login_server": "https://12345.dkr.ecr.us-east-1.amazonaws.com/" } ] @@ -248,10 +265,7 @@ shown below: } ``` -See the -[AWS documentation](https://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html) -for steps to obtain Amazon ECR registry credentials. - +[Learn how to set Amazon AWS credentials.](/docs/builders/amazon.html#specifying-amazon-credentials) ## Dockerfiles @@ -263,8 +277,8 @@ Instead, you can just provide shell scripts, Chef recipes, Puppet manifests, etc. to provision your Docker container just like you would a regular virtualized or dedicated machine. -While Docker has many features, Packer views Docker simply as an LXC container -runner. To that end, Packer is able to repeatably build these LXC containers +While Docker has many features, Packer views Docker simply as an container +runner. To that end, Packer is able to repeatably build these containers using portable provisioning scripts. Dockerfiles have some additional features that Packer doesn't support which are diff --git a/website/source/docs/post-processors/docker-push.html.md b/website/source/docs/post-processors/docker-push.html.md index 3a5247320..69ac93b43 100644 --- a/website/source/docs/post-processors/docker-push.html.md +++ b/website/source/docs/post-processors/docker-push.html.md @@ -18,8 +18,26 @@ pushes it to a Docker registry. This post-processor has only optional configuration: +- `aws_access_key` (string) - The AWS access key used to communicate with AWS. + [Learn how to set this.](/docs/builders/amazon.html#specifying-amazon-credentials) + +- `aws_secret_key` (string) - The AWS secret key used to communicate with AWS. + [Learn how to set this.](/docs/builders/amazon.html#specifying-amazon-credentials) + +- `aws_token` (string) - The AWS access token to use. This is different from the + access key and secret key. If you're not sure what this is, then you + probably don't need it. This will also be read from the `AWS_SESSION_TOKEN` + environmental variable. + +- `ecr_login` (boolean) - Defaults to false. If true, the post-processor + will login in order to push the image to + [Amazon EC2 Container Registry (ECR)](https://aws.amazon.com/ecr/). + The post-processor only logs in for the duration of the push. If true + `login_server` is required and `login`, `login_username`, and + `login_password` will be ignored. + - `login` (boolean) - Defaults to false. If true, the post-processor will - login prior to pushing. + login prior to pushing. For log into ECR see `ecr_login`. - `login_email` (string) - The email to use to authenticate to login. @@ -29,9 +47,9 @@ This post-processor has only optional configuration: - `login_server` (string) - The server address to login to. -Note: When using _Docker Hub_ or _Quay_ registry servers, `login` must to be -set to `true` and `login_email`, `login_username`, **and** `login_password` -must to be set to your registry credentials. When using Docker Hub, +Note: When using _Docker Hub_ or _Quay_ registry servers, `login` must to be +set to `true` and `login_email`, `login_username`, **and** `login_password` +must to be set to your registry credentials. When using Docker Hub, `login_server` can be omitted. -> **Note:** If you login using the credentials above, the post-processor