From 0800de767341b9c3a29db6998bc98b15c7c65820 Mon Sep 17 00:00:00 2001 From: Liam Stanley Date: Wed, 29 Jun 2022 18:04:42 -0400 Subject: [PATCH 1/2] feat: add support for dynamic credentials during get/put Signed-off-by: Liam Stanley --- README.md | 34 ++++++++++++++++++---------------- commands/in.go | 4 ++++ commands/out.go | 4 ++++ types.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 591e942..c85a833 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,6 @@ differences: too many ways to build and publish Docker images. It will be easier to support many smaller resources + tasks rather than one huge interface. - ## Source Configuration @@ -158,7 +157,7 @@ differences:
  • - host (Required): + host (Required): A hostname pointing to a Docker registry mirror service. Note that this is only used if no registry hostname prefix is specified in the repository key. If the repository contains a @@ -167,7 +166,7 @@ differences: registry in the repository key is used.
  • - username and password (Optional): + username and password (Optional): A username and password to use when authenticating to the mirror.
@@ -178,30 +177,30 @@ differences:
  • - server (Optional): + server (Optional): URL for the notary server. (equal to DOCKER_CONTENT_TRUST_SERVER)
  • - repository_key_id (Required): + repository_key_id (Required): Target key's ID used to sign the trusted collection, could be retrieved by notary key list
  • - repository_key (Required): + repository_key (Required): Target key used to sign the trusted collection.
  • - repository_passphrase (Required): + repository_passphrase (Required): The passphrase of the signing/target key. (equal to DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE)
  • - tls_key (Optional): + tls_key (Optional): TLS key for the notary server.
  • - tls_cert (Optional): + tls_cert (Optional): TLS certificate for the notary server.
  • @@ -301,7 +300,7 @@ Each unique digest will be returned only once, with the most specific version tag available. This is to handle "alias" tags like `1`, `1.2` pointing to `1.2.3`. -Note: the initial `check` call will return *all valid versions*, which is +Note: the initial `check` call will return _all valid versions_, which is unlike most resources which only return the latest version. This is an intentional choice which will become the normal behavior for resources in the future (per concourse/rfcs#38). @@ -341,7 +340,7 @@ With a `variant` value specified, only semver tags with the matching variant will be detected. With `variant` omitted, tags which include a variant are ignored. -Note: some image tags actually include *mutliple* variants, e.g. +Note: some image tags actually include _mutliple_ variants, e.g. `1.2.3-php7.3-apache`. With a variant of only `apache` configured, these tags will be skipped to avoid accidentally using multiple variants. In order to use these tags, you must specify the full variant combination, e.g. @@ -385,6 +384,11 @@ this reason, the resource will only consider prerelease data starting with `alpha`, `beta`, or `rc` as a proper prerelease, treating anything else as a variant. +### Usage of dynamically generated credentials for `get` and `put` steps + +Both the `get` and `put` steps allow you to override `username`/`password` as well +as `aws_*` fields. This is primarily beneficial when you are unable to generate +persistent credentials, and must use on-demand generated credentials. ### `get` Step (`in` script): fetch an image @@ -447,7 +451,6 @@ In this format, the resource will produce the following files: * `./image.tar`: the OCI image tarball, suitable for passing to `docker load`. - ### `put` Step (`out` script): push and tag an image Pushes an image to the registry as the specified tags. @@ -529,8 +532,7 @@ Anonymous resources can specify [a version](https://concourse-ci.org/tasks.html#schema.anonymous_resource.version), which is the image digest. For example: - -``` +```yaml image_resource: type: docker-image source: @@ -546,8 +548,8 @@ going to be re-used. ### Prerequisites -* golang is *required* - version 1.11.x or above is required for go mod to work -* docker is *required* - version 17.06.x is tested; earlier versions may also +* golang is _required_ - version 1.11.x or above is required for go mod to work +* docker is _required_ - version 17.06.x is tested; earlier versions may also work. * go mod is used for dependency management of the golang packages. diff --git a/commands/in.go b/commands/in.go index 3fc63d7..0d5b2f6 100644 --- a/commands/in.go +++ b/commands/in.go @@ -65,6 +65,10 @@ func (i *In) Execute() error { dest := i.args[1] + // If credentials were defined in params, override the source configuration. + req.Source.BasicCredentials.Inherit(req.Params.BasicCredentials) + req.Source.AwsCredentials.Inherit(req.Params.AwsCredentials) + if req.Source.AwsAccessKeyId != "" && req.Source.AwsSecretAccessKey != "" && 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 04ce108..c224482 100644 --- a/commands/out.go +++ b/commands/out.go @@ -63,6 +63,10 @@ func (o *Out) Execute() error { src := o.args[1] + // If credentials were defined in params, override the source configuration. + req.Source.BasicCredentials.Inherit(req.Params.BasicCredentials) + req.Source.AwsCredentials.Inherit(req.Params.AwsCredentials) + if req.Source.AwsAccessKeyId != "" && req.Source.AwsSecretAccessKey != "" && req.Source.AwsRegion != "" { if !req.Source.AuthenticateToECR() { return fmt.Errorf("cannot authenticate with ECR") diff --git a/types.go b/types.go index eaac9e2..3bcfa22 100644 --- a/types.go +++ b/types.go @@ -64,11 +64,44 @@ type AwsCredentials struct { AwsRoleArns []string `json:"aws_role_arns,omitempty"` } +func (c *AwsCredentials) Inherit(parent AwsCredentials) { + if c.AwsAccessKeyId == "" { + c.AwsAccessKeyId = parent.AwsAccessKeyId + } + if c.AwsSecretAccessKey == "" { + c.AwsSecretAccessKey = parent.AwsSecretAccessKey + } + if c.AwsSessionToken == "" { + c.AwsSessionToken = parent.AwsSessionToken + } + if c.AwsRegion == "" { + c.AwsRegion = parent.AwsRegion + } + if c.AWSECRRegistryId == "" { + c.AWSECRRegistryId = parent.AWSECRRegistryId + } + if c.AwsRoleArn == "" { + c.AwsRoleArn = parent.AwsRoleArn + } + if len(c.AwsRoleArns) == 0 { + c.AwsRoleArns = parent.AwsRoleArns + } +} + type BasicCredentials struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` } +func (c *BasicCredentials) Inherit(parent BasicCredentials) { + if c.Username == "" { + c.Username = parent.Username + } + if c.Password == "" { + c.Password = parent.Password + } +} + type RegistryMirror struct { Host string `json:"host,omitempty"` @@ -399,6 +432,11 @@ type MetadataField struct { } type GetParams struct { + // Allow overriding credentials during put events, primarily beneficial for + // using short-lived credentials for cloud environments. + BasicCredentials + AwsCredentials + RawFormat string `json:"format"` SkipDownload bool `json:"skip_download"` } @@ -412,6 +450,11 @@ func (p GetParams) Format() string { } type PutParams struct { + // Allow overriding credentials during put events, primarily beneficial for + // using short-lived credentials for cloud environments. + BasicCredentials + AwsCredentials + // Path to an OCI image tarball to push. Image string `json:"image"` From 060bb2e5a3b8b9ebf46acb5da66f5e20e7981b2a Mon Sep 17 00:00:00 2001 From: Liam Stanley Date: Thu, 30 Jun 2022 20:39:05 -0400 Subject: [PATCH 2/2] feat: dynamic credential generation example Signed-off-by: Liam Stanley --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/README.md b/README.md index c85a833..e348cad 100644 --- a/README.md +++ b/README.md @@ -390,6 +390,51 @@ Both the `get` and `put` steps allow you to override `username`/`password` as we as `aws_*` fields. This is primarily beneficial when you are unable to generate persistent credentials, and must use on-demand generated credentials. +An example of what this may look like is shown below: + +```yaml +resources: + - name: src + type: git + source: {} + - name: registry + type: registry-image + check_every: never + source: + repository: ((aws-registry)) + tag: latest + aws_region: ((aws-region)) + +jobs: + - name: push-to-ecr + plan: + - get: src + - get: repo-task-aws-deploy + params: { depth: 1 } + - task: build-image + # build your image here. + - task: get-credentials # write your credentials to credentials/example.json + config: + platform: linux + inputs: [{ name: src }] + outputs: [{ name: credentials }] + params: {} + run: { path: src/some-credential-script.sh } + - load_var: creds + file: credentials/example.json + - put: registry + params: + image: image/image.tar + aws_access_key_id: ((.:creds.aws_access_key_id)) + aws_secret_access_key: ((.:creds.aws_secret_access_key)) + aws_session_token: ((.:creds.aws_session_token)) + get_params: + skip_download: true + aws_access_key_id: ((.:creds.aws_access_key_id)) + aws_secret_access_key: ((.:creds.aws_secret_access_key)) + aws_session_token: ((.:creds.aws_session_token)) +``` + ### `get` Step (`in` script): fetch an image Fetches an image at the exact digest specified by the version.