From 6519b666fd181601cf6e01ba55b1fac0d43e66cc Mon Sep 17 00:00:00 2001 From: Christian Kotzbauer Date: Fri, 4 Feb 2022 13:15:12 +0100 Subject: [PATCH] fix: change legacy-support for .dockercfg Signed-off-by: Christian Kotzbauer --- .github/workflows/test-registries.yml | 10 ++- internal/registry/configfile.go | 94 ++++++++++++++++++++++++ internal/registry/registry.go | 2 +- internal/registry/registry_suite_test.go | 4 +- 4 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 internal/registry/configfile.go diff --git a/.github/workflows/test-registries.yml b/.github/workflows/test-registries.yml index f8c541ba..c284e2a9 100644 --- a/.github/workflows/test-registries.yml +++ b/.github/workflows/test-registries.yml @@ -94,19 +94,21 @@ jobs: --docker-email="${{ secrets.TEST_EMAIL }}" \ -o json --dry-run=client | jq -r '.data.".dockerconfigjson"' > auth/hub.yaml - - name: Prepare legacy Hub secrets + - name: Prepare legacy GHCR secrets shell: bash + env: + TEST_GCR_PASSWORD: ${{ secrets.TEST_GCR_PASSWORD }} run: | cat << EOF > .dockercfg { - "https://index.docker.io/v1/": { "username": "${{ secrets.TEST_HUB_USERNAME }}", "password": "${{ secrets.TEST_HUB_PASSWORD }}" } + "ghcr.io": { "username": "${{ secrets.TEST_GHCR_USERNAME }}", "password": "${{ secrets.TEST_GHCR_PASSWORD }}" } } EOF - kubectl create secret generic hub-secret \ + kubectl create secret generic ghcr-secret \ --from-file=.dockercfg \ --type=kubernetes.io/dockercfg \ - -o json --dry-run=client | jq -r '.data.".dockercfg"' > auth/legacy-hub.yaml + -o json --dry-run=client | jq -r '.data.".dockercfg"' > auth/legacy-ghcr.yaml - name: Execute Registry-Tests run: make test-registries diff --git a/internal/registry/configfile.go b/internal/registry/configfile.go new file mode 100644 index 00000000..4a8421e2 --- /dev/null +++ b/internal/registry/configfile.go @@ -0,0 +1,94 @@ +package registry + +import ( + "encoding/base64" + "encoding/json" + "io" + "io/ioutil" + "strings" + + "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/config/types" + "github.com/pkg/errors" +) + +// This is ported from https://github.com/docker/cli/blob/v20.10.12/cli/config/config.go +// The only changes to the original source are the fact, that the "auth" field is not decoded +// when "username" or "password" are not blank to avoid overwrites. + +const ( + // This constant is only used for really old config files when the + // URL wasn't saved as part of the config file and it was just + // assumed to be this value. + defaultIndexServer = "https://index.docker.io/v1/" +) + +// LegacyLoadFromReader reads the non-nested configuration data given and sets up the +// auth config information with given directory and populates the receiver object +func LegacyLoadFromReader(configData io.Reader, configFile *configfile.ConfigFile) error { + b, err := ioutil.ReadAll(configData) + if err != nil { + return err + } + + if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil { + arr := strings.Split(string(b), "\n") + if len(arr) < 2 { + return errors.Errorf("The Auth config file is empty") + } + authConfig := types.AuthConfig{} + origAuth := strings.Split(arr[0], " = ") + if len(origAuth) != 2 { + return errors.Errorf("Invalid Auth config file") + } + + // Only decode the "auth" field when "username" and "password" are blank. + if len(authConfig.Username) == 0 && len(authConfig.Password) == 0 { + authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) + if err != nil { + return err + } + } + + authConfig.ServerAddress = defaultIndexServer + configFile.AuthConfigs[defaultIndexServer] = authConfig + } else { + for k, authConfig := range configFile.AuthConfigs { + // Only decode the "auth" field when "username" and "password" are blank. + if len(authConfig.Username) == 0 && len(authConfig.Password) == 0 { + authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) + if err != nil { + return err + } + } + authConfig.Auth = "" + authConfig.ServerAddress = k + configFile.AuthConfigs[k] = authConfig + } + } + return nil +} + +// decodeAuth decodes a base64 encoded string and returns username and password +func decodeAuth(authStr string) (string, string, error) { + if authStr == "" { + return "", "", nil + } + + decLen := base64.StdEncoding.DecodedLen(len(authStr)) + decoded := make([]byte, decLen) + authByte := []byte(authStr) + n, err := base64.StdEncoding.Decode(decoded, authByte) + if err != nil { + return "", "", err + } + if n > decLen { + return "", "", errors.Errorf("Something went wrong decoding auth config") + } + arr := strings.SplitN(string(decoded), ":", 2) + if len(arr) != 2 { + return "", "", errors.Errorf("Invalid auth configuration file") + } + password := strings.Trim(arr[1], "\x00") + return arr[0], password, nil +} diff --git a/internal/registry/registry.go b/internal/registry/registry.go index 1941093c..9fc7a6f9 100644 --- a/internal/registry/registry.go +++ b/internal/registry/registry.go @@ -29,7 +29,7 @@ func SaveImage(imagePath string, image kubernetes.ContainerImage) error { if image.LegacyAuth { cf = configfile.New("") - err = cf.LegacyLoadFromReader(bytes.NewReader(image.Auth)) + err = LegacyLoadFromReader(bytes.NewReader(image.Auth), cf) } else { cf, err = config.LoadFromReader(bytes.NewReader(image.Auth)) } diff --git a/internal/registry/registry_suite_test.go b/internal/registry/registry_suite_test.go index cd32fe75..c82eea54 100644 --- a/internal/registry/registry_suite_test.go +++ b/internal/registry/registry_suite_test.go @@ -72,9 +72,9 @@ var _ = Describe("Registry", func() { }) }) - Describe("Storing image from DockerHub - legacy .dockercfg", func() { + Describe("Storing image from GHCR - legacy .dockercfg", func() { It("should work correctly", func() { - testRegistry("legacy-hub", "docker.io/ckotzbauer/integration-test-image:1.0.0", true) + testRegistry("legacy-ghcr", "ghcr.io/ckotzbauer-kubernetes-bot/sbom-git-operator-integration-test:1.0.0", true) }) }) })