github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/image/distributionutil/login.go (about)

     1  // Copyright © 2021 Alibaba Group Holding Ltd.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package distributionutil
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"net/http"
    22  	"net/url"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/docker/distribution/registry/client/auth"
    29  	"github.com/docker/distribution/registry/client/transport"
    30  	"github.com/docker/docker/api/types"
    31  	"github.com/docker/docker/dockerversion"
    32  	dockerRegistry "github.com/docker/docker/registry"
    33  	"github.com/pkg/errors"
    34  	"github.com/sirupsen/logrus"
    35  )
    36  
    37  func Login(ctx context.Context, authConfig *types.AuthConfig) error {
    38  	domain := authConfig.ServerAddress
    39  	if !strings.HasPrefix(domain, "http://") && !strings.HasPrefix(domain, "https://") {
    40  		domain = "https://" + domain
    41  	}
    42  	endpointURL, err := url.Parse(domain)
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	modifiers := dockerRegistry.Headers(dockerversion.DockerUserAgent(ctx), nil)
    48  	base := dockerRegistry.NewTransport(nil)
    49  	base.TLSClientConfig.InsecureSkipVerify = os.Getenv("SKIP_TLS_VERIFY") == "true"
    50  	if err := dockerRegistry.ReadCertsDirectory(base.TLSClientConfig, filepath.Join(dockerRegistry.CertsDir(), endpointURL.Host)); err != nil {
    51  		return err
    52  	}
    53  	authTransport := transport.NewTransport(base, modifiers...)
    54  
    55  	credentialAuthConfig := *authConfig
    56  	creds := loginCredentialStore{
    57  		authConfig: &credentialAuthConfig,
    58  	}
    59  	loginClient, err := authHTTPClient(endpointURL, authTransport, modifiers, creds, nil)
    60  	if err != nil {
    61  		return err
    62  	}
    63  
    64  	endpointStr := strings.TrimRight(endpointURL.String(), "/") + "/v2/"
    65  	req, err := http.NewRequest("GET", endpointStr, nil)
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	resp, err := loginClient.Do(req)
    71  	if err != nil {
    72  		if strings.Contains(err.Error(), "x509") {
    73  			return fmt.Errorf("%v, if you want to skip TLS verification, set the environment variable 'SKIP_TLS_VERIFY=true' ", err)
    74  		}
    75  		return err
    76  	}
    77  	defer func(Body io.ReadCloser) {
    78  		err := Body.Close()
    79  		if err != nil {
    80  			logrus.Warnf("failed to close http reader")
    81  		}
    82  	}(resp.Body)
    83  
    84  	if resp.StatusCode == http.StatusOK {
    85  		return nil
    86  	}
    87  
    88  	// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
    89  	err = errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
    90  	return err
    91  }
    92  
    93  func authHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, error) {
    94  	challengeManager, _, err := dockerRegistry.PingV2Registry(endpoint, authTransport)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	tokenHandlerOptions := auth.TokenHandlerOptions{
   100  		Transport:     authTransport,
   101  		Credentials:   creds,
   102  		OfflineAccess: true,
   103  		ClientID:      dockerRegistry.AuthClientID,
   104  		Scopes:        scopes,
   105  	}
   106  	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
   107  	basicHandler := auth.NewBasicHandler(creds)
   108  	modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
   109  	tr := transport.NewTransport(authTransport, modifiers...)
   110  
   111  	return &http.Client{
   112  		Transport: tr,
   113  		Timeout:   15 * time.Second,
   114  	}, nil
   115  }
   116  
   117  type loginCredentialStore struct {
   118  	authConfig *types.AuthConfig
   119  }
   120  
   121  func (lcs loginCredentialStore) Basic(*url.URL) (string, string) {
   122  	return lcs.authConfig.Username, lcs.authConfig.Password
   123  }
   124  
   125  func (lcs loginCredentialStore) RefreshToken(*url.URL, string) string {
   126  	return lcs.authConfig.IdentityToken
   127  }
   128  
   129  func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token string) {
   130  	lcs.authConfig.IdentityToken = token
   131  }