From 710a3ac94c3dc0eaf680d417c87f37f92b4887f4 Mon Sep 17 00:00:00 2001 From: Markus Wolf Date: Wed, 5 May 2021 18:37:17 +0200 Subject: [PATCH] Add custom docker registry authentication (#665) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add custom docker registry authentication Uses DOCKER_USERNAME and DOCKER_PASSWORD as secrets provided into the act cli. Closes #527 Co-authored-by: Björn Brauer * Add test to check if pull authentication is filled in * Update debug message to be more descriptive Co-authored-by: Ryan (hackercat) Co-authored-by: Björn Brauer Co-authored-by: Ryan (hackercat) --- pkg/container/docker_pull.go | 38 ++++++++++++++++++++++++++++--- pkg/container/docker_pull_test.go | 19 +++++++++++++++- pkg/container/docker_run.go | 4 ++++ pkg/runner/run_context.go | 2 ++ pkg/runner/step_context.go | 2 ++ 5 files changed, 61 insertions(+), 4 deletions(-) diff --git a/pkg/container/docker_pull.go b/pkg/container/docker_pull.go index 9ae49abf..55545a80 100644 --- a/pkg/container/docker_pull.go +++ b/pkg/container/docker_pull.go @@ -2,6 +2,8 @@ package container import ( "context" + "encoding/base64" + "encoding/json" "fmt" "strings" @@ -17,6 +19,8 @@ type NewDockerPullExecutorInput struct { Image string ForcePull bool Platform string + Username string + Password string } // NewDockerPullExecutor function to create a run executor for the container @@ -54,9 +58,13 @@ func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor { return err } - reader, err := cli.ImagePull(ctx, imageRef, types.ImagePullOptions{ - Platform: input.Platform, - }) + imagePullOptions, err := getImagePullOptions(ctx, input) + if err != nil { + return err + } + + reader, err := cli.ImagePull(ctx, imageRef, imagePullOptions) + _ = logDockerResponse(logger, reader, err != nil) if err != nil { return err @@ -65,6 +73,30 @@ func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor { } } +func getImagePullOptions(ctx context.Context, input NewDockerPullExecutorInput) (types.ImagePullOptions, error) { + imagePullOptions := types.ImagePullOptions{ + Platform: input.Platform, + } + if input.Username != "" && input.Password != "" { + logger := common.Logger(ctx) + logger.Debugf("using authentication for docker pull") + + authConfig := types.AuthConfig{ + Username: input.Username, + Password: input.Password, + } + + encodedJSON, err := json.Marshal(authConfig) + if err != nil { + return imagePullOptions, err + } + + imagePullOptions.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON) + } + + return imagePullOptions, nil +} + func cleanImage(image string) string { imageParts := len(strings.Split(image, "/")) if imageParts == 1 { diff --git a/pkg/container/docker_pull_test.go b/pkg/container/docker_pull_test.go index b8bffb85..a7494dea 100644 --- a/pkg/container/docker_pull_test.go +++ b/pkg/container/docker_pull_test.go @@ -1,10 +1,11 @@ package container import ( + "context" "testing" log "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" + "gotest.tools/v3/assert" ) func init() { @@ -27,3 +28,19 @@ func TestCleanImage(t *testing.T) { assert.Equal(t, table.imageOut, imageOut) } } + +func TestGetImagePullOptions(t *testing.T) { + ctx := context.Background() + + options, err := getImagePullOptions(ctx, NewDockerPullExecutorInput{}) + assert.NilError(t, err, "Failed to create ImagePullOptions") + assert.Equal(t, options.RegistryAuth, "", "RegistryAuth should be empty if no username or password is set") + + options, err = getImagePullOptions(ctx, NewDockerPullExecutorInput{ + Image: "", + Username: "username", + Password: "password", + }) + assert.NilError(t, err, "Failed to create ImagePullOptions") + assert.Equal(t, options.RegistryAuth, "eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwicGFzc3dvcmQiOiJwYXNzd29yZCJ9", "Username and Password should be provided") +} diff --git a/pkg/container/docker_run.go b/pkg/container/docker_run.go index 371d8b66..1050ba07 100644 --- a/pkg/container/docker_run.go +++ b/pkg/container/docker_run.go @@ -37,6 +37,8 @@ import ( // NewContainerInput the input for the New function type NewContainerInput struct { Image string + Username string + Password string Entrypoint []string Cmd []string WorkingDir string @@ -126,6 +128,8 @@ func (cr *containerReference) Pull(forcePull bool) common.Executor { Image: cr.input.Image, ForcePull: forcePull, Platform: cr.input.Platform, + Username: cr.input.Username, + Password: cr.input.Password, }) } func (cr *containerReference) Copy(destPath string, files ...*FileEntry) common.Executor { diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go index 03f4364b..e3d9648f 100755 --- a/pkg/runner/run_context.go +++ b/pkg/runner/run_context.go @@ -118,6 +118,8 @@ func (rc *RunContext) startJobContainer() common.Executor { Entrypoint: []string{"/usr/bin/tail", "-f", "/dev/null"}, WorkingDir: rc.Config.ContainerWorkdir(), Image: image, + Username: rc.Config.Secrets["DOCKER_USERNAME"], + Password: rc.Config.Secrets["DOCKER_PASSWORD"], Name: name, Env: envList, Mounts: mounts, diff --git a/pkg/runner/step_context.go b/pkg/runner/step_context.go index 1a1a81b1..fad38e47 100755 --- a/pkg/runner/step_context.go +++ b/pkg/runner/step_context.go @@ -247,6 +247,8 @@ func (sc *StepContext) newStepContainer(ctx context.Context, image string, cmd [ Entrypoint: entrypoint, WorkingDir: rc.Config.ContainerWorkdir(), Image: image, + Username: rc.Config.Secrets["DOCKER_USERNAME"], + Password: rc.Config.Secrets["DOCKER_PASSWORD"], Name: createContainerName(rc.jobContainerName(), step.ID), Env: envList, Mounts: mounts,