From 609d203bedba724d3574697c3e1100b4b68b997e Mon Sep 17 00:00:00 2001 From: Colin Hoglund Date: Thu, 16 Sep 2021 00:42:03 -0400 Subject: [PATCH] support dockerhub credentials when pulling with kaniko-ecr (#27) --- cmd/kaniko-ecr/main.go | 80 ++++++++++++++++++++++++++----------- cmd/kaniko-ecr/main_test.go | 31 ++++++++++++++ pkg/docker/config.go | 34 ++++++++++++++++ pkg/docker/config_test.go | 25 ++++++++++++ pkg/docker/constants.go | 7 ++++ 5 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 cmd/kaniko-ecr/main_test.go create mode 100644 pkg/docker/config.go create mode 100644 pkg/docker/config_test.go create mode 100644 pkg/docker/constants.go diff --git a/cmd/kaniko-ecr/main.go b/cmd/kaniko-ecr/main.go index 4e747d4..a838715 100644 --- a/cmd/kaniko-ecr/main.go +++ b/cmd/kaniko-ecr/main.go @@ -2,18 +2,20 @@ package main import ( "context" + "encoding/json" "fmt" - "github.com/aws/aws-sdk-go-v2/aws" "io/ioutil" "os" "strings" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/ecr" "github.com/aws/aws-sdk-go-v2/service/ecrpublic" "github.com/aws/smithy-go" kaniko "github.com/drone/drone-kaniko" "github.com/drone/drone-kaniko/cmd/artifact" + "github.com/drone/drone-kaniko/pkg/docker" "github.com/joho/godotenv" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -53,6 +55,16 @@ func main() { Value: "Dockerfile", EnvVar: "PLUGIN_DOCKERFILE", }, + cli.StringFlag{ + Name: "docker-username", + Usage: "docker username", + EnvVar: "PLUGIN_USERNAME,DOCKER_USERNAME", + }, + cli.StringFlag{ + Name: "docker-password", + Usage: "docker password", + EnvVar: "PLUGIN_PASSWORD,DOCKER_PASSWORD", + }, cli.StringFlag{ Name: "context", Usage: "build context", @@ -168,14 +180,27 @@ func run(c *cli.Context) error { repo := c.String("repo") registry := c.String("registry") region := c.String("region") - accessKey := c.String("access-key") noPush := c.Bool("no-push") - // only setup auth when pushing or credentials are defined - if !noPush || accessKey != "" { - if err := setupECRAuth(accessKey, c.String("secret-key"), registry); err != nil { - return err - } + dockerConfig, err := createDockerConfig( + c.String("docker-username"), + c.String("docker-password"), + c.String("access-key"), + c.String("secret-key"), + registry, + noPush, + ) + if err != nil { + return err + } + + jsonBytes, err := json.Marshal(dockerConfig) + if err != nil { + return err + } + + if err := ioutil.WriteFile(dockerConfigPath, jsonBytes, 0644); err != nil { + return err } // only create repository when pushing and create-repository is true @@ -233,30 +258,37 @@ func run(c *cli.Context) error { return plugin.Exec() } -func setupECRAuth(accessKey, secretKey, registry string) error { - if registry == "" { - return fmt.Errorf("registry must be specified") +func createDockerConfig(dockerUsername, dockerPassword, accessKey, secretKey, registry string, noPush bool) (*docker.Config, error) { + dockerConfig := docker.NewConfig() + + if dockerUsername != "" { + dockerConfig.SetAuth(docker.RegistryV1, dockerUsername, dockerPassword) } - // If IAM role is used, access key & secret key are not required - if accessKey != "" && secretKey != "" { - err := os.Setenv(accessKeyEnv, accessKey) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("failed to set %s environment variable", accessKeyEnv)) + // only setup auth when pushing or credentials are defined + if !noPush || accessKey != "" { + if registry == "" { + return nil, fmt.Errorf("registry must be specified") } - err = os.Setenv(secretKeyEnv, secretKey) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("failed to set %s environment variable", secretKeyEnv)) + // If IAM role is used, access key & secret key are not required + if accessKey != "" && secretKey != "" { + err := os.Setenv(accessKeyEnv, accessKey) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("failed to set %s environment variable", accessKeyEnv)) + } + + err = os.Setenv(secretKeyEnv, secretKey) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("failed to set %s environment variable", secretKeyEnv)) + } } + + dockerConfig.SetCredHelper(ecrPublicDomain, "ecr-login") + dockerConfig.SetCredHelper(registry, "ecr-login") } - jsonBytes := []byte(fmt.Sprintf(`{"credStore": "ecr-login", "credHelpers": {"%s": "ecr-login", "%s": "ecr-login"}}`, ecrPublicDomain, registry)) - err := ioutil.WriteFile(dockerConfigPath, jsonBytes, 0644) - if err != nil { - return errors.Wrap(err, "failed to create docker config file") - } - return nil + return dockerConfig, nil } func createRepository(region, repo, registry string) error { diff --git a/cmd/kaniko-ecr/main_test.go b/cmd/kaniko-ecr/main_test.go new file mode 100644 index 0000000..dc9f319 --- /dev/null +++ b/cmd/kaniko-ecr/main_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "reflect" + "testing" + + "github.com/drone/drone-kaniko/pkg/docker" +) + +func TestCreateDockerConfig(t *testing.T) { + got, err := createDockerConfig( + "docker-username", + "docker-password", + "access-key", + "secret-key", + "ecr-registry", + false, + ) + if err != nil { + t.Error("failed to create docker config") + } + + want := docker.NewConfig() + want.SetAuth(docker.RegistryV1, "docker-username", "docker-password") + want.SetCredHelper(docker.RegistryECRPublic, "ecr-login") + want.SetCredHelper("ecr-registry", "ecr-login") + + if !reflect.DeepEqual(want, got) { + t.Errorf("not equal:\n want: %#v\n got: %#v", want, got) + } +} diff --git a/pkg/docker/config.go b/pkg/docker/config.go new file mode 100644 index 0000000..6873a5b --- /dev/null +++ b/pkg/docker/config.go @@ -0,0 +1,34 @@ +package docker + +import ( + "encoding/base64" + "fmt" +) + +type ( + Auth struct { + Auth string `json:"auth"` + } + + Config struct { + Auths map[string]Auth `json:"auths"` + CredHelpers map[string]string `json:"credHelpers"` + } +) + +func NewConfig() *Config { + return &Config{ + Auths: map[string]Auth{}, + CredHelpers: map[string]string{}, + } +} + +func (c *Config) SetAuth(registry, username, password string) { + authBytes := []byte(fmt.Sprintf("%s:%s", username, password)) + encodedString := base64.StdEncoding.EncodeToString(authBytes) + c.Auths[registry] = Auth{Auth: encodedString} +} + +func (c *Config) SetCredHelper(registry, helper string) { + c.CredHelpers[registry] = helper +} diff --git a/pkg/docker/config_test.go b/pkg/docker/config_test.go new file mode 100644 index 0000000..a6e1323 --- /dev/null +++ b/pkg/docker/config_test.go @@ -0,0 +1,25 @@ +package docker + +import ( + "encoding/json" + "testing" +) + +func TestConfig(t *testing.T) { + c := NewConfig() + + c.SetAuth(RegistryV1, "test", "password") + c.SetCredHelper(RegistryECRPublic, "ecr-login") + + bytes, err := json.Marshal(c) + if err != nil { + t.Error("json marshal failed") + } + + want := `{"auths":{"https://index.docker.io/v1/":{"auth":"dGVzdDpwYXNzd29yZA=="}},"credHelpers":{"public.ecr.aws":"ecr-login"}}` + got := string(bytes) + + if want != got { + t.Errorf("unexpected json output:\n want: %s\n got: %s", want, got) + } +} diff --git a/pkg/docker/constants.go b/pkg/docker/constants.go new file mode 100644 index 0000000..e467ead --- /dev/null +++ b/pkg/docker/constants.go @@ -0,0 +1,7 @@ +package docker + +const ( + RegistryV1 string = "https://index.docker.io/v1/" + RegistryV2 string = "https://index.docker.io/v2/" + RegistryECRPublic string = "public.ecr.aws" +)