From 64931fbbbc117e7d4a6f33b3088d88aa2b6d1267 Mon Sep 17 00:00:00 2001 From: Ben Moss Date: Wed, 6 Jul 2022 12:53:25 -0400 Subject: [PATCH] Move URLsMatch fns to separate package https://github.com/vmware-tanzu/carvel-imgpkg/pull/409#discussion_r911136532 --- .../auth/credentialprovider/keyring.go | 87 +++++++++++++++++++ pkg/imgpkg/registry/auth/env_keychain.go | 80 +---------------- pkg/imgpkg/registry/keychain.go | 6 +- 3 files changed, 93 insertions(+), 80 deletions(-) create mode 100644 pkg/imgpkg/registry/auth/credentialprovider/keyring.go diff --git a/pkg/imgpkg/registry/auth/credentialprovider/keyring.go b/pkg/imgpkg/registry/auth/credentialprovider/keyring.go new file mode 100644 index 000000000..b33d70865 --- /dev/null +++ b/pkg/imgpkg/registry/auth/credentialprovider/keyring.go @@ -0,0 +1,87 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package credentialprovider + +import ( + "net" + "net/url" + "path/filepath" + "strings" +) + +// Extracted from https://github.com/kubernetes/kubernetes/blob/6b6558a4639b556f2723dea8df62bcaa7f9846ed/pkg/credentialprovider/keyring.go + +// URLsMatchStr is wrapper for URLsMatch, operating on strings instead of URLs. +func URLsMatchStr(glob string, target string) (bool, error) { + globURL, err := ParseSchemelessURL(glob) + if err != nil { + return false, err + } + targetURL, err := ParseSchemelessURL(target) + if err != nil { + return false, err + } + return URLsMatch(globURL, targetURL) +} + +// URLsMatch checks whether the given target url matches the glob url, which may have +// glob wild cards in the host name. +// +// Examples: +// globURL=*.docker.io, targetURL=blah.docker.io => match +// globURL=*.docker.io, targetURL=not.right.io => no match +// +// Note that we don't support wildcards in ports and paths yet. +func URLsMatch(globURL *url.URL, targetURL *url.URL) (bool, error) { + globURLParts, globPort := SplitURL(globURL) + targetURLParts, targetPort := SplitURL(targetURL) + if globPort != targetPort { + // port doesn't match + return false, nil + } + if len(globURLParts) != len(targetURLParts) { + // host name does not have the same number of parts + return false, nil + } + if !strings.HasPrefix(targetURL.Path, globURL.Path) { + // the path of the credential must be a prefix + return false, nil + } + for k, globURLPart := range globURLParts { + targetURLPart := targetURLParts[k] + matched, err := filepath.Match(globURLPart, targetURLPart) + if err != nil { + return false, err + } + if !matched { + // glob mismatch for some part + return false, nil + } + } + // everything matches + return true, nil +} + +// ParseSchemelessURL parses a schemeless url and returns a url.URL +// url.Parse require a scheme, but ours don't have schemes. Adding a +// scheme to make url.Parse happy, then clear out the resulting scheme. +func ParseSchemelessURL(schemelessURL string) (*url.URL, error) { + parsed, err := url.Parse("https://" + schemelessURL) + if err != nil { + return nil, err + } + // clear out the resulting scheme + parsed.Scheme = "" + return parsed, nil +} + +// SplitURL splits the host name into parts, as well as the port +func SplitURL(url *url.URL) (parts []string, port string) { + host, port, err := net.SplitHostPort(url.Host) + if err != nil { + // could not parse port + host, port = url.Host, "" + } + return strings.Split(host, "."), port +} diff --git a/pkg/imgpkg/registry/auth/env_keychain.go b/pkg/imgpkg/registry/auth/env_keychain.go index 089564bf0..5d8d77a8f 100644 --- a/pkg/imgpkg/registry/auth/env_keychain.go +++ b/pkg/imgpkg/registry/auth/env_keychain.go @@ -5,14 +5,14 @@ package auth import ( "fmt" - "net" "net/url" "os" - "path/filepath" "sort" "strings" "sync" + "github.com/vmware-tanzu/pkg/imgpkg/registry/auth/credentialprovider" + regauthn "github.com/google/go-containerregistry/pkg/authn" ) @@ -55,7 +55,7 @@ func (k *EnvKeychain) Resolve(target regauthn.Resource) (regauthn.Authenticator, } for _, info := range infos { - registryURLMatches, err := urlsMatchStr(info.URL, target.String()) + registryURLMatches, err := credentialprovider.URLsMatchStr(info.URL, target.String()) if err != nil { return nil, err } @@ -211,77 +211,3 @@ func (k *EnvKeychain) collect() ([]envKeychainInfo, error) { return append([]envKeychainInfo{}, k.infos...), nil } - -// urlsMatchStr is wrapper for URLsMatch, operating on strings instead of URLs. -func urlsMatchStr(glob string, target string) (bool, error) { - globURL, err := parseSchemelessURL(glob) - if err != nil { - return false, err - } - targetURL, err := parseSchemelessURL(target) - if err != nil { - return false, err - } - return urlsMatch(globURL, targetURL) -} - -// parseSchemelessURL parses a schemeless url and returns a url.URL -// url.Parse require a scheme, but ours don't have schemes. Adding a -// scheme to make url.Parse happy, then clear out the resulting scheme. -func parseSchemelessURL(schemelessURL string) (*url.URL, error) { - parsed, err := url.Parse("https://" + schemelessURL) - if err != nil { - return nil, err - } - // clear out the resulting scheme - parsed.Scheme = "" - return parsed, nil -} - -// splitURL splits the host name into parts, as well as the port -func splitURL(url *url.URL) (parts []string, port string) { - host, port, err := net.SplitHostPort(url.Host) - if err != nil { - // could not parse port - host, port = url.Host, "" - } - return strings.Split(host, "."), port -} - -// urlsMatch checks whether the given target url matches the glob url, which may have -// glob wild cards in the host name. -// -// Examples: -// globURL=*.docker.io, targetURL=blah.docker.io => match -// globURL=*.docker.io, targetURL=not.right.io => no match -// -// Note that we don't support wildcards in ports and paths yet. -func urlsMatch(globURL *url.URL, targetURL *url.URL) (bool, error) { - globURLParts, globPort := splitURL(globURL) - targetURLParts, targetPort := splitURL(targetURL) - if globPort != targetPort { - // port doesn't match - return false, nil - } - if len(globURLParts) != len(targetURLParts) { - // host name does not have the same number of parts - return false, nil - } - if !strings.HasPrefix(targetURL.Path, globURL.Path) { - // the path of the credential must be a prefix - return false, nil - } - for k, globURLPart := range globURLParts { - targetURLPart := targetURLParts[k] - matched, err := filepath.Match(globURLPart, targetURLPart) - if err != nil { - return false, err - } - if !matched { - // glob mismatch for some part - return false, nil - } - } - // everything matches - return true, nil -} diff --git a/pkg/imgpkg/registry/keychain.go b/pkg/imgpkg/registry/keychain.go index 98e4ccc1e..44b7e81d3 100644 --- a/pkg/imgpkg/registry/keychain.go +++ b/pkg/imgpkg/registry/keychain.go @@ -21,14 +21,14 @@ import ( // Since env keychain contains credentials per HOSTNAME, and custom keychain doesn't. func Keychain(keychainOpts auth.KeychainOpts, environFunc func() []string) (regauthn.Keychain, error) { // env keychain comes first - keychain := []authn.Keychain{auth.NewEnvKeychain(environFunc)} + keychain := []regauthn.Keychain{auth.NewEnvKeychain(environFunc)} if keychainOpts.EnableIaasAuthProviders { // if enabled, fall back to iaas keychains keychain = append(keychain, google.Keychain, - authn.NewKeychainFromHelper(ecr.NewECRHelper(ecr.WithLogger(ioutil.Discard))), - authn.NewKeychainFromHelper(credhelper.NewACRCredentialsHelper()), + regauthn.NewKeychainFromHelper(ecr.NewECRHelper(ecr.WithLogger(ioutil.Discard))), + regauthn.NewKeychainFromHelper(credhelper.NewACRCredentialsHelper()), github.Keychain, ) }