From 397083c467401f392585df8947b05c551d4515fc Mon Sep 17 00:00:00 2001 From: Frederic Francois Date: Tue, 6 Jul 2021 23:06:42 +0100 Subject: [PATCH] support EC2 AWS credentials and IAM roles chain In order to avoid passing explicitly the AWS credentials, the resource has been modified to support retrieval of AWS credentials via EC2 metadata. In addition, IAM roles chain is now supported to provide the flexibility required for GOV.UK scenario where the ec2 instances have the credentials of the Concourse worker role which needs to be used to assume the Concourse deployer role. The deployer role is then used to assume a role in the GOV.UK production ECR to pull images. To build and push image, e.g. procedure: ```sh docker build -t registry-image-resource --target tests -f dockerfiles/alpine/Dockerfile . docker tag registry-image-resource:latest fredericfran/registry-image:0.0.2 docker push fredericfran/registry-image:0.0.2 ``` Signed-off-by: Frederic Francois --- README.md | 14 +++++++++++--- commands/check.go | 3 ++- commands/in.go | 3 ++- commands/out.go | 3 ++- types.go | 46 +++++++++++++++++++++++++++++++++++++--------- 5 files changed, 54 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 73661d0..612aec1 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,16 @@ differences: * `aws_region`: *Optional. Default `""`.* The region to use for accessing ECR. This is required if you are using ECR. This region will help determine the full repository URL you are accessing (e.g., `012345678910.dkr.ecr.us-east-1.amazonaws.com`) +* `aws_ec2_credentials`: *Optional. Default `false`.* If set, allows the retrieval of + AWS credentials from the EC2 metadata. + * `aws_role_arn`: *Optional. Default `""`.* If set, then this role will be - assumed before authenticating to ECR. + assumed before authenticating to ECR. It is overridden by `aws_role_arns` if + latter is also specified. This is kept for backward compatibility. + +* `aws_role_arns`: *Optional. Default `""`.* A comma-delimited list of AWS IAM roles. + If set, these roles will be assumed in the specified order before authenticating to ECR. + It overrides `aws_role_arn`. * `debug`: *Optional. Default `false`.* If set, progress bars will be disabled and debugging output will be printed instead. @@ -110,7 +118,7 @@ differences: This is used to validate the certificate of the docker registry when the registry's certificate is signed by a custom authority (or itself). -### Signing with Docker Hub +### Signing with Docker Hub Configure Docker Content Trust for use with the [Docker Hub](https:/hub.docker.io) and Notary service by specifying the above source parameters as follows: @@ -265,7 +273,7 @@ Fetches an image at the exact digest specified by the version. The resource will produce the following files: -* `./repository`: A file containing the image's full repository name, e.g. `concourse/concourse`. +* `./repository`: A file containing the image's full repository name, e.g. `concourse/concourse`. For ECR images, this will include the registry the image was pulled from. * `./tag`: A file containing the tag from the version. * `./digest`: A file containing the digest from the version, e.g. `sha256:...`. diff --git a/commands/check.go b/commands/check.go index 646ffba..d155af5 100644 --- a/commands/check.go +++ b/commands/check.go @@ -49,7 +49,8 @@ func (c *Check) Execute() error { return fmt.Errorf("invalid payload: %s", err) } - if req.Source.AwsAccessKeyId != "" && req.Source.AwsSecretAccessKey != "" && req.Source.AwsRegion != "" { + if ((req.Source.AwsAccessKeyId != "" && req.Source.AwsSecretAccessKey != "") || + (req.Source.AwsEC2Credentials)) && req.Source.AwsRegion != "" { if !req.Source.AuthenticateToECR() { return fmt.Errorf("cannot authenticate with ECR") } diff --git a/commands/in.go b/commands/in.go index cbffa4e..4ded5d0 100644 --- a/commands/in.go +++ b/commands/in.go @@ -65,7 +65,8 @@ func (i *In) Execute() error { dest := i.args[1] - if req.Source.AwsAccessKeyId != "" && req.Source.AwsSecretAccessKey != "" && req.Source.AwsRegion != "" { + if ((req.Source.AwsAccessKeyId != "" && req.Source.AwsSecretAccessKey != "") || + (req.Source.AwsEC2Credentials)) && req.Source.AwsRegion != "" { if !req.Source.AuthenticateToECR() { return fmt.Errorf("cannot authenticate with ECR") } diff --git a/commands/out.go b/commands/out.go index 03a8fa1..28a5e3d 100644 --- a/commands/out.go +++ b/commands/out.go @@ -62,7 +62,8 @@ func (o *Out) Execute() error { src := o.args[1] - if req.Source.AwsAccessKeyId != "" && req.Source.AwsSecretAccessKey != "" && req.Source.AwsRegion != "" { + if ((req.Source.AwsAccessKeyId != "" && req.Source.AwsSecretAccessKey != "") || + (req.Source.AwsEC2Credentials)) && req.Source.AwsRegion != "" { if !req.Source.AuthenticateToECR() { return fmt.Errorf("cannot authenticate with ECR") } diff --git a/types.go b/types.go index e9aade4..841c2df 100644 --- a/types.go +++ b/types.go @@ -15,7 +15,9 @@ import ( "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/credentials/stscreds" + "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ecr" "github.com/aws/aws-sdk-go/service/ecr/ecriface" @@ -55,12 +57,14 @@ type OutResponse struct { } type AwsCredentials struct { + AwsEC2Credentials bool `json:"aws_ec2_credentials,omitempty"` AwsAccessKeyId string `json:"aws_access_key_id,omitempty"` AwsSecretAccessKey string `json:"aws_secret_access_key,omitempty"` AwsSessionToken string `json:"aws_session_token,omitempty"` AwsRegion string `json:"aws_region,omitempty"` AWSECRRegistryId string `json:"aws_ecr_registry_id,omitempty"` AwsRoleArn string `json:"aws_role_arn,omitempty"` + AwsRoleArns string `json:"aws_role_arns,omitempty"` } type BasicCredentials struct { @@ -278,19 +282,43 @@ func (source *Source) Metadata() []MetadataField { func (source *Source) AuthenticateToECR() bool { logrus.Warnln("ECR integration is experimental and untested") - mySession := session.Must(session.NewSession(&aws.Config{ - Region: aws.String(source.AwsRegion), - Credentials: credentials.NewStaticCredentials(source.AwsAccessKeyId, source.AwsSecretAccessKey, source.AwsSessionToken), - })) - var config aws.Config + var mySession *session.Session - // If a role arn has been supplied, then assume role and get a new session - if source.AwsRoleArn != "" { - config = aws.Config{Credentials: stscreds.NewCredentials(mySession, source.AwsRoleArn)} + if source.AwsEC2Credentials { + mySession = session.Must(session.NewSession(&aws.Config{ + Region: aws.String(source.AwsRegion), + Credentials: credentials.NewCredentials(&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(session.New())}), + })) + + } else { + mySession = session.Must(session.NewSession(&aws.Config{ + Region: aws.String(source.AwsRegion), + Credentials: credentials.NewStaticCredentials(source.AwsAccessKeyId, source.AwsSecretAccessKey, source.AwsSessionToken), + })) + } + + // If aws role arns chain has been supplied, then assume roles in turn and get a new session + if source.AwsRoleArns != "" { + logrus.Warnln("Using `aws_role_arns` rather than `aws_role_arn`, see documentation") + awsRoleArnsList := strings.Split(source.AwsRoleArns, ",") + + for _, roleArn := range awsRoleArnsList { + logrus.Debugf("assuming new role: %s", roleArn) + mySession = session.Must(session.NewSession(&aws.Config{ + Region: aws.String(source.AwsRegion), + Credentials: stscreds.NewCredentials(mySession, roleArn), + })) + } + } else if source.AwsRoleArn != "" { //Assume one aws role only, kept for backward compatibility. + logrus.Debugf("assuming new role: %s", source.AwsRoleArn) + mySession = session.Must(session.NewSession(&aws.Config{ + Region: aws.String(source.AwsRegion), + Credentials: stscreds.NewCredentials(mySession, source.AwsRoleArn), + })) } - client := ecr.New(mySession, &config) + client := ecr.New(mySession) result, err := source.GetECRAuthorizationToken(client) if err != nil { logrus.Errorf("failed to authenticate to ECR: %s", err)