mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-26 01:22:51 +00:00
bugfix : fix image auth bug (#2900)
* bugfix : fix image auth bug Signed-off-by: xuesongzuo@yunify.com <xuesongzuo@yunify.com> bugfix : fix image auth bug Signed-off-by: xuesongzuo@yunify.com <xuesongzuo@yunify.com> * bugfix : fix image auth bug Signed-off-by: xuesongzuo@yunify.com <xuesongzuo@yunify.com> bugfix : fix image auth bug Signed-off-by: xuesongzuo@yunify.com <xuesongzuo@yunify.com> --------- Signed-off-by: xuesongzuo@yunify.com <xuesongzuo@yunify.com>
This commit is contained in:
parent
0b73e0a125
commit
782575f49c
|
|
@ -25,6 +25,7 @@ import (
|
|||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
|
@ -177,16 +178,17 @@ func (i imagePullArgs) pull(ctx context.Context, platform string) error {
|
|||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get remote image %s", img)
|
||||
}
|
||||
selectedAuth := selectAuth(img, i.auths)
|
||||
src.Client = &auth.Client{
|
||||
Client: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: skipTLSVerifyFunc(img, i.auths, *i.skipTLSVerify),
|
||||
InsecureSkipVerify: skipTLSVerifyFunc(selectedAuth, *i.skipTLSVerify),
|
||||
},
|
||||
},
|
||||
},
|
||||
Cache: auth.NewCache(),
|
||||
Credential: authFunc(i.auths),
|
||||
Credential: authFunc(selectedAuth),
|
||||
}
|
||||
|
||||
dst, err := newLocalRepository(filepath.Join(src.Reference.Registry, src.Reference.Repository)+":"+src.Reference.Reference, i.imagesDir)
|
||||
|
|
@ -204,7 +206,7 @@ func (i imagePullArgs) pull(ctx context.Context, platform string) error {
|
|||
}
|
||||
}
|
||||
|
||||
src.PlainHTTP = plainHTTPFunc(img, i.auths, false)
|
||||
src.PlainHTTP = plainHTTPFunc(selectedAuth, false)
|
||||
|
||||
if _, err = oras.Copy(ctx, src, src.Reference.Reference, dst, "", copyOption); err != nil {
|
||||
return errors.Wrapf(err, "failed to pull image %q to local dir", img)
|
||||
|
|
@ -228,49 +230,186 @@ func dockerHostParser(img string) string {
|
|||
return strings.Join(splitedImg, "/")
|
||||
}
|
||||
|
||||
func authFunc(auths []imageAuth) func(ctx context.Context, hostport string) (auth.Credential, error) {
|
||||
var creds = make(map[string]auth.Credential)
|
||||
for _, inputAuth := range auths {
|
||||
var rp = inputAuth.Repo
|
||||
if rp == "docker.io" || rp == "" {
|
||||
rp = "registry-1.docker.io"
|
||||
func authFunc(selectedAuth *imageAuth) func(ctx context.Context, hostport string) (auth.Credential, error) {
|
||||
return func(_ context.Context, _ string) (auth.Credential, error) {
|
||||
if selectedAuth == nil {
|
||||
return auth.Credential{
|
||||
Username: "",
|
||||
Password: "",
|
||||
}, nil
|
||||
}
|
||||
creds[rp] = auth.Credential{
|
||||
Username: inputAuth.Username,
|
||||
Password: inputAuth.Password,
|
||||
}
|
||||
}
|
||||
return func(_ context.Context, hostport string) (auth.Credential, error) {
|
||||
cred, ok := creds[hostport]
|
||||
if !ok {
|
||||
cred = auth.EmptyCredential
|
||||
}
|
||||
return cred, nil
|
||||
return auth.Credential{
|
||||
Username: selectedAuth.Username,
|
||||
Password: selectedAuth.Password,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func skipTLSVerifyFunc(img string, auths []imageAuth, defaults bool) bool {
|
||||
imgHost := strings.Split(img, "/")[0]
|
||||
for _, a := range auths {
|
||||
if imgHost == a.Repo {
|
||||
if a.Insecure != nil {
|
||||
return *a.Insecure
|
||||
func selectAuth(image string, authList []imageAuth) *imageAuth {
|
||||
// remove tag and hash
|
||||
repoPart := extractRepoFromImage(image)
|
||||
|
||||
// parse image to url
|
||||
imageURL, err := normalizeImageToURL(repoPart)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return defaults
|
||||
|
||||
imageHost := imageURL.Host
|
||||
imagePath := strings.TrimPrefix(imageURL.Path, "/")
|
||||
|
||||
// split port
|
||||
imageHostWithoutPort := imageHost
|
||||
if colonIdx := strings.Index(imageHost, ":"); colonIdx != -1 {
|
||||
imageHostWithoutPort = imageHost[:colonIdx]
|
||||
}
|
||||
|
||||
var bestMatch *imageAuth
|
||||
var bestMatchScore = -1
|
||||
|
||||
// find biggest match point auth
|
||||
for i := range authList {
|
||||
authRepo := authList[i].Repo
|
||||
|
||||
authURL, err := normalizeImageToURL(authRepo)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
authHost := authURL.Host
|
||||
authPath := strings.TrimPrefix(authURL.Path, "/")
|
||||
|
||||
authHostWithoutPort := authHost
|
||||
if colonIdx := strings.Index(authHost, ":"); colonIdx != -1 {
|
||||
authHostWithoutPort = authHost[:colonIdx]
|
||||
}
|
||||
|
||||
score := calculateMatchScore(imageHost, imageHostWithoutPort, imagePath,
|
||||
authHost, authHostWithoutPort, authPath)
|
||||
|
||||
if score > bestMatchScore {
|
||||
bestMatchScore = score
|
||||
bestMatch = &authList[i]
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch
|
||||
}
|
||||
|
||||
func normalizeImageToURL(image string) (*url.URL, error) {
|
||||
if strings.HasPrefix(image, "http://") || strings.HasPrefix(image, "https://") {
|
||||
return url.Parse(image)
|
||||
}
|
||||
return url.Parse("http://" + image)
|
||||
}
|
||||
|
||||
// extractRepoFromImage handle image ,ignore tag and hash
|
||||
// like xxx/xxx:tag@sha256:xxx to xxx/xxx
|
||||
// like xxx/xxx:tag to xxx/xxx
|
||||
func extractRepoFromImage(image string) string {
|
||||
if image == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
repoPart := image
|
||||
|
||||
// handle image with hash
|
||||
atIdx := strings.LastIndex(repoPart, "@")
|
||||
if atIdx != -1 && atIdx > 0 {
|
||||
afterAt := repoPart[atIdx+1:]
|
||||
if isLikelyDigest(afterAt) {
|
||||
repoPart = repoPart[:atIdx]
|
||||
}
|
||||
}
|
||||
|
||||
// search /
|
||||
firstSlashIdx := strings.Index(repoPart, "/")
|
||||
|
||||
if firstSlashIdx == -1 {
|
||||
return repoPart
|
||||
}
|
||||
|
||||
// search : for tag or port
|
||||
lastColonIdx := strings.LastIndex(repoPart, ":")
|
||||
if lastColonIdx == -1 {
|
||||
return repoPart
|
||||
}
|
||||
|
||||
if lastColonIdx < firstSlashIdx {
|
||||
return repoPart
|
||||
}
|
||||
|
||||
if lastColonIdx+1 < len(repoPart) {
|
||||
afterColon := repoPart[lastColonIdx+1:]
|
||||
if strings.Contains(afterColon, "/") {
|
||||
return repoPart
|
||||
}
|
||||
}
|
||||
return repoPart[:lastColonIdx]
|
||||
}
|
||||
|
||||
func isLikelyDigest(s string) bool {
|
||||
colonIdx := strings.Index(s, ":")
|
||||
if colonIdx == -1 {
|
||||
return false
|
||||
}
|
||||
algorithm := s[:colonIdx]
|
||||
knownAlgorithms := []string{"sha256", "sha512", "sha384", "sha1", "md5"}
|
||||
for _, algo := range knownAlgorithms {
|
||||
if algorithm == algo {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// calculateMatchScore calculate image and path match point
|
||||
// 0 for not matched
|
||||
// 1 for host matched
|
||||
// 2 for host:port matched
|
||||
// 3 for host:port and prefix path matched
|
||||
// 4 for host:port and full path matched
|
||||
func calculateMatchScore(imgHost, imgHostNoPort, imgPath,
|
||||
authHost, authHostNoPort, authPath string) int {
|
||||
if imgHostNoPort != authHostNoPort {
|
||||
return 0
|
||||
}
|
||||
score := 1
|
||||
imgHasPort := strings.Contains(imgHost, ":")
|
||||
authHasPort := strings.Contains(authHost, ":")
|
||||
if imgHasPort && authHasPort {
|
||||
if imgHost != authHost {
|
||||
return 1
|
||||
}
|
||||
score = 2
|
||||
} else if !imgHasPort && !authHasPort {
|
||||
score = 2
|
||||
}
|
||||
if authPath == "" {
|
||||
return score
|
||||
}
|
||||
if imgPath == authPath {
|
||||
return score + 2
|
||||
}
|
||||
if strings.HasPrefix(imgPath, authPath) {
|
||||
if len(imgPath) == len(authPath) ||
|
||||
(len(imgPath) > len(authPath) && imgPath[len(authPath)] == '/') {
|
||||
return score + 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func skipTLSVerifyFunc(selectedAuth *imageAuth, defaults bool) bool {
|
||||
if selectedAuth != nil && selectedAuth.Insecure != nil {
|
||||
return *selectedAuth.Insecure
|
||||
}
|
||||
return defaults
|
||||
}
|
||||
|
||||
func plainHTTPFunc(img string, auths []imageAuth, defaults bool) bool {
|
||||
imgHost := strings.Split(img, "/")[0]
|
||||
for _, a := range auths {
|
||||
if imgHost == a.Repo {
|
||||
if a.PlainHTTP != nil {
|
||||
return *a.PlainHTTP
|
||||
}
|
||||
return defaults
|
||||
}
|
||||
func plainHTTPFunc(selectedAuth *imageAuth, defaults bool) bool {
|
||||
if selectedAuth != nil && selectedAuth.PlainHTTP != nil {
|
||||
return *selectedAuth.PlainHTTP
|
||||
}
|
||||
return defaults
|
||||
}
|
||||
|
|
@ -337,19 +476,20 @@ func (i imagePushArgs) push(ctx context.Context, hostVars map[string]any) error
|
|||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get remote repository %q", dest)
|
||||
}
|
||||
selectedAuth := selectAuth(dest, i.auths)
|
||||
dst.Client = &auth.Client{
|
||||
Client: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: skipTLSVerifyFunc(dest, i.auths, *i.skipTLSVerify),
|
||||
InsecureSkipVerify: skipTLSVerifyFunc(selectedAuth, *i.skipTLSVerify),
|
||||
},
|
||||
},
|
||||
},
|
||||
Cache: auth.NewCache(),
|
||||
Credential: authFunc(i.auths),
|
||||
Credential: authFunc(selectedAuth),
|
||||
}
|
||||
|
||||
dst.PlainHTTP = plainHTTPFunc(dest, i.auths, false)
|
||||
dst.PlainHTTP = plainHTTPFunc(selectedAuth, false)
|
||||
|
||||
if _, err = oras.Copy(ctx, src, src.Reference.Reference, dst, dst.Reference.Reference, oras.DefaultCopyOptions); err != nil {
|
||||
return errors.Wrapf(err, "failed to push image %q to remote", img)
|
||||
|
|
|
|||
Loading…
Reference in New Issue