-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: Move url globber to own package
Copied some functionality from upstream kubernetes, and add unit tests to globber.
- Loading branch information
1 parent
ec9b20c
commit b64783a
Showing
11 changed files
with
378 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// Copyright 2022 D2iQ, Inc. All rights reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// This package provides utilities to work with URL globs for credential providers. | ||
// | ||
// Duplicating documentation from | ||
// https://github.com/kubernetes/kubelet/blob/v0.25.4/pkg/apis/credentialprovider/v1beta1/types.go#L73-L101 | ||
// for visibility: | ||
// | ||
// auth is a map containing authentication information passed into the kubelet. | ||
// Each key is a match image string (more on this below). The corresponding authConfig value | ||
// should be valid for all images that match against this key. A plugin should set | ||
// this field to null if no valid credentials can be returned for the requested image. | ||
// | ||
// Each key in the map is a pattern which can optionally contain a port and a path. | ||
// Globs can be used in the domain, but not in the port or the path. Globs are supported | ||
// as subdomains like '*.k8s.io' or 'k8s.*.io', and top-level-domains such as 'k8s.*'. | ||
// Matching partial subdomains like 'app*.k8s.io' is also supported. Each glob can only match | ||
// a single subdomain segment, so *.io does not match *.k8s.io. | ||
// | ||
// The kubelet will match images against the key when all of the below are true: | ||
// - Both contain the same number of domain parts and each part matches. | ||
// - The URL path of an imageMatch must be a prefix of the target image URL path. | ||
// - If the imageMatch contains a port, then the port must match in the image as well. | ||
// | ||
// When multiple keys are returned, the kubelet will traverse all keys in reverse order so that: | ||
// - longer keys come before shorter keys with the same prefix | ||
// - non-wildcard keys come before wildcard keys with the same prefix. | ||
// | ||
// For any given match, the kubelet will attempt an image pull with the provided credentials, | ||
// stopping after the first successfully authenticated pull. | ||
// | ||
// Example keys: | ||
// - 123456789.dkr.ecr.us-east-1.amazonaws.com | ||
// - *.azurecr.io | ||
// - gcr.io | ||
// - *.*.registry.io | ||
// - registry.io:8080/path | ||
package urlglobber |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// Copyright 2022 D2iQ, Inc. All rights reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package urlglobber | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"net" | ||
"regexp" | ||
|
||
"github.com/distribution/distribution/v3/reference" | ||
) | ||
|
||
var ( | ||
domainSegmentRE = regexp.MustCompile(`[^.]+`) | ||
|
||
ErrInvalidImageReference = errors.New("invalid image reference") | ||
) | ||
|
||
func GlobbedDomainForImage(img string) (string, error) { | ||
namedImg, err := reference.ParseNormalizedNamed(img) | ||
if err != nil { | ||
return "", fmt.Errorf("%w: %v", ErrInvalidImageReference, err) | ||
} | ||
|
||
domain := reference.Domain(namedImg) | ||
|
||
domainWithoutPort, port, errSplitPort := net.SplitHostPort(domain) | ||
if errSplitPort == nil { | ||
domain = domainWithoutPort | ||
} | ||
globbedDomain := domainSegmentRE.ReplaceAllLiteralString(domain, "*") | ||
if errSplitPort == nil { | ||
globbedDomain = net.JoinHostPort(globbedDomain, port) | ||
} | ||
|
||
return globbedDomain, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Copyright 2022 D2iQ, Inc. All rights reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package urlglobber_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/mesosphere/kubelet-image-credential-provider-shim/pkg/credentialprovider/urlglobber" | ||
) | ||
|
||
func TestGlobbedDomainForImage(t *testing.T) { | ||
t.Parallel() | ||
|
||
tests := []struct { | ||
name string | ||
img string | ||
wantGlobbed string | ||
wantErr error | ||
}{{ | ||
name: "Empty URL", | ||
wantErr: urlglobber.ErrInvalidImageReference, | ||
}, { | ||
name: "Simple image", | ||
img: prefixKubernetesIO + "/foo/bar:v1.2.3", | ||
wantGlobbed: "*.*.*", | ||
}, { | ||
name: "Simple image with port", | ||
img: prefixKubernetesIO + ":1111/foo/bar:v1.2.3", | ||
wantGlobbed: "*.*.*:1111", | ||
}, { | ||
name: "Image from docker hub with no domain", | ||
img: "foo/bar:v1.2.3", | ||
wantGlobbed: "*.*", // To match docker.io. | ||
}, { | ||
name: "Image from docker hub with no domain or path", | ||
img: "bar:v1.2.3", | ||
wantGlobbed: "*.*", // To match docker.io. | ||
}} | ||
for _, tt := range tests { | ||
tt := tt // Capture range variable. | ||
t.Run(tt.name, func(t *testing.T) { | ||
t.Parallel() | ||
globbed, err := urlglobber.GlobbedDomainForImage(tt.img) | ||
require.ErrorIs(t, err, tt.wantErr) | ||
assert.Equal(t, tt.wantGlobbed, globbed) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// Copyright 2022 D2iQ, Inc. All rights reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
/* | ||
Copyright 2014 The Kubernetes Authors. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
// The content of this file is copied from | ||
// https://github.com/kubernetes/kubernetes/blob/v1.25.4/pkg/credentialprovider/keyring.go#L160-L233. | ||
// Copied rather than imported as go mod because it lives in k8s.io/kubernetes which would pull in | ||
// lots of unnecessary dependencies. | ||
|
||
package urlglobber | ||
|
||
import ( | ||
"net" | ||
"net/url" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
// 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(u *url.URL) (parts []string, port string) { | ||
host, port, err := net.SplitHostPort(u.Host) | ||
if err != nil { | ||
// could not parse port | ||
host, port = u.Host, "" | ||
} | ||
return strings.Split(host, "."), port | ||
} | ||
|
||
// URLsMatchStr is wrapper for URLsMatch, operating on strings instead of URLs. | ||
func URLsMatchStr(glob, 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, 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 | ||
} |
Oops, something went wrong.