add internal fork of docker/docker/registry
This adds an internal fork of [github.com/docker/docker/registry], taken at commit [moby@f651a5d]. Git history was not preserved in this fork, but can be found using the URLs provided. This fork was created to remove the dependency on the "Moby" codebase, and because the CLI only needs a subset of its features. The original package was written specifically for use in the daemon code, and includes functionality that cannot be used in the CLI. [github.com/docker/docker/registry]: https://pkg.go.dev/github.com/docker/docker@v28.3.2+incompatible/registry [moby@49306c6]:49306c607b/registrySigned-off-by: Sebastiaan van Stijn <github@gone.nl> (cherry picked from commitf6b90bc253) Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
28
vendor/github.com/docker/docker/pkg/homedir/homedir.go
generated
vendored
28
vendor/github.com/docker/docker/pkg/homedir/homedir.go
generated
vendored
@ -1,28 +0,0 @@
|
||||
package homedir
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Get returns the home directory of the current user with the help of
|
||||
// environment variables depending on the target operating system.
|
||||
// Returned path should be used with "path/filepath" to form new paths.
|
||||
//
|
||||
// On non-Windows platforms, it falls back to nss lookups, if the home
|
||||
// directory cannot be obtained from environment-variables.
|
||||
//
|
||||
// If linking statically with cgo enabled against glibc, ensure the
|
||||
// osusergo build tag is used.
|
||||
//
|
||||
// If needing to do nss lookups, do not disable cgo or set osusergo.
|
||||
func Get() string {
|
||||
home, _ := os.UserHomeDir()
|
||||
if home == "" && runtime.GOOS != "windows" {
|
||||
if u, err := user.Current(); err == nil {
|
||||
return u.HomeDir
|
||||
}
|
||||
}
|
||||
return home
|
||||
}
|
||||
105
vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go
generated
vendored
105
vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go
generated
vendored
@ -1,105 +0,0 @@
|
||||
package homedir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetRuntimeDir returns XDG_RUNTIME_DIR.
|
||||
// XDG_RUNTIME_DIR is typically configured via pam_systemd.
|
||||
// GetRuntimeDir returns non-nil error if XDG_RUNTIME_DIR is not set.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func GetRuntimeDir() (string, error) {
|
||||
if xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR"); xdgRuntimeDir != "" {
|
||||
return xdgRuntimeDir, nil
|
||||
}
|
||||
return "", errors.New("could not get XDG_RUNTIME_DIR")
|
||||
}
|
||||
|
||||
// StickRuntimeDirContents sets the sticky bit on files that are under
|
||||
// XDG_RUNTIME_DIR, so that the files won't be periodically removed by the system.
|
||||
//
|
||||
// StickyRuntimeDir returns slice of sticked files.
|
||||
// StickyRuntimeDir returns nil error if XDG_RUNTIME_DIR is not set.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func StickRuntimeDirContents(files []string) ([]string, error) {
|
||||
runtimeDir, err := GetRuntimeDir()
|
||||
if err != nil {
|
||||
// ignore error if runtimeDir is empty
|
||||
return nil, nil
|
||||
}
|
||||
runtimeDir, err = filepath.Abs(runtimeDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var sticked []string
|
||||
for _, f := range files {
|
||||
f, err = filepath.Abs(f)
|
||||
if err != nil {
|
||||
return sticked, err
|
||||
}
|
||||
if strings.HasPrefix(f, runtimeDir+"/") {
|
||||
if err = stick(f); err != nil {
|
||||
return sticked, err
|
||||
}
|
||||
sticked = append(sticked, f)
|
||||
}
|
||||
}
|
||||
return sticked, nil
|
||||
}
|
||||
|
||||
func stick(f string) error {
|
||||
st, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m := st.Mode()
|
||||
m |= os.ModeSticky
|
||||
return os.Chmod(f, m)
|
||||
}
|
||||
|
||||
// GetDataHome returns XDG_DATA_HOME.
|
||||
// GetDataHome returns $HOME/.local/share and nil error if XDG_DATA_HOME is not set.
|
||||
// If HOME and XDG_DATA_HOME are not set, getpwent(3) is consulted to determine the users home directory.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func GetDataHome() (string, error) {
|
||||
if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" {
|
||||
return xdgDataHome, nil
|
||||
}
|
||||
home := Get()
|
||||
if home == "" {
|
||||
return "", errors.New("could not get either XDG_DATA_HOME or HOME")
|
||||
}
|
||||
return filepath.Join(home, ".local", "share"), nil
|
||||
}
|
||||
|
||||
// GetConfigHome returns XDG_CONFIG_HOME.
|
||||
// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set.
|
||||
// If HOME and XDG_CONFIG_HOME are not set, getpwent(3) is consulted to determine the users home directory.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func GetConfigHome() (string, error) {
|
||||
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
|
||||
return xdgConfigHome, nil
|
||||
}
|
||||
home := Get()
|
||||
if home == "" {
|
||||
return "", errors.New("could not get either XDG_CONFIG_HOME or HOME")
|
||||
}
|
||||
return filepath.Join(home, ".config"), nil
|
||||
}
|
||||
|
||||
// GetLibHome returns $HOME/.local/lib
|
||||
// If HOME is not set, getpwent(3) is consulted to determine the users home directory.
|
||||
func GetLibHome() (string, error) {
|
||||
home := Get()
|
||||
if home == "" {
|
||||
return "", errors.New("could not get HOME")
|
||||
}
|
||||
return filepath.Join(home, ".local/lib"), nil
|
||||
}
|
||||
32
vendor/github.com/docker/docker/pkg/homedir/homedir_others.go
generated
vendored
32
vendor/github.com/docker/docker/pkg/homedir/homedir_others.go
generated
vendored
@ -1,32 +0,0 @@
|
||||
//go:build !linux
|
||||
|
||||
package homedir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// GetRuntimeDir is unsupported on non-linux system.
|
||||
func GetRuntimeDir() (string, error) {
|
||||
return "", errors.New("homedir.GetRuntimeDir() is not supported on this system")
|
||||
}
|
||||
|
||||
// StickRuntimeDirContents is unsupported on non-linux system.
|
||||
func StickRuntimeDirContents(files []string) ([]string, error) {
|
||||
return nil, errors.New("homedir.StickRuntimeDirContents() is not supported on this system")
|
||||
}
|
||||
|
||||
// GetDataHome is unsupported on non-linux system.
|
||||
func GetDataHome() (string, error) {
|
||||
return "", errors.New("homedir.GetDataHome() is not supported on this system")
|
||||
}
|
||||
|
||||
// GetConfigHome is unsupported on non-linux system.
|
||||
func GetConfigHome() (string, error) {
|
||||
return "", errors.New("homedir.GetConfigHome() is not supported on this system")
|
||||
}
|
||||
|
||||
// GetLibHome is unsupported on non-linux system.
|
||||
func GetLibHome() (string, error) {
|
||||
return "", errors.New("homedir.GetLibHome() is not supported on this system")
|
||||
}
|
||||
202
vendor/github.com/docker/docker/registry/auth.go
generated
vendored
202
vendor/github.com/docker/docker/registry/auth.go
generated
vendored
@ -1,202 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/distribution/registry/client/auth"
|
||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// AuthClientID is used the ClientID used for the token server
|
||||
const AuthClientID = "docker"
|
||||
|
||||
type loginCredentialStore struct {
|
||||
authConfig *registry.AuthConfig
|
||||
}
|
||||
|
||||
func (lcs loginCredentialStore) Basic(*url.URL) (string, string) {
|
||||
return lcs.authConfig.Username, lcs.authConfig.Password
|
||||
}
|
||||
|
||||
func (lcs loginCredentialStore) RefreshToken(*url.URL, string) string {
|
||||
return lcs.authConfig.IdentityToken
|
||||
}
|
||||
|
||||
func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token string) {
|
||||
lcs.authConfig.IdentityToken = token
|
||||
}
|
||||
|
||||
type staticCredentialStore struct {
|
||||
auth *registry.AuthConfig
|
||||
}
|
||||
|
||||
// NewStaticCredentialStore returns a credential store
|
||||
// which always returns the same credential values.
|
||||
func NewStaticCredentialStore(auth *registry.AuthConfig) auth.CredentialStore {
|
||||
return staticCredentialStore{
|
||||
auth: auth,
|
||||
}
|
||||
}
|
||||
|
||||
func (scs staticCredentialStore) Basic(*url.URL) (string, string) {
|
||||
if scs.auth == nil {
|
||||
return "", ""
|
||||
}
|
||||
return scs.auth.Username, scs.auth.Password
|
||||
}
|
||||
|
||||
func (scs staticCredentialStore) RefreshToken(*url.URL, string) string {
|
||||
if scs.auth == nil {
|
||||
return ""
|
||||
}
|
||||
return scs.auth.IdentityToken
|
||||
}
|
||||
|
||||
func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) {
|
||||
}
|
||||
|
||||
// loginV2 tries to login to the v2 registry server. The given registry
|
||||
// endpoint will be pinged to get authorization challenges. These challenges
|
||||
// will be used to authenticate against the registry to validate credentials.
|
||||
func loginV2(ctx context.Context, authConfig *registry.AuthConfig, endpoint APIEndpoint, userAgent string) (token string, _ error) {
|
||||
endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
|
||||
log.G(ctx).WithField("endpoint", endpointStr).Debug("attempting v2 login to registry endpoint")
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpointStr, http.NoBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var (
|
||||
modifiers = Headers(userAgent, nil)
|
||||
authTrans = transport.NewTransport(newTransport(endpoint.TLSConfig), modifiers...)
|
||||
credentialAuthConfig = *authConfig
|
||||
creds = loginCredentialStore{authConfig: &credentialAuthConfig}
|
||||
)
|
||||
|
||||
loginClient, err := v2AuthHTTPClient(endpoint.URL, authTrans, modifiers, creds, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := loginClient.Do(req)
|
||||
if err != nil {
|
||||
err = translateV2AuthError(err)
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
|
||||
return "", errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
}
|
||||
|
||||
return credentialAuthConfig.IdentityToken, nil
|
||||
}
|
||||
|
||||
func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, error) {
|
||||
challengeManager, err := PingV2Registry(endpoint, authTransport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authHandlers := []auth.AuthenticationHandler{
|
||||
auth.NewTokenHandlerWithOptions(auth.TokenHandlerOptions{
|
||||
Transport: authTransport,
|
||||
Credentials: creds,
|
||||
OfflineAccess: true,
|
||||
ClientID: AuthClientID,
|
||||
Scopes: scopes,
|
||||
}),
|
||||
auth.NewBasicHandler(creds),
|
||||
}
|
||||
|
||||
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, authHandlers...))
|
||||
|
||||
return &http.Client{
|
||||
Transport: transport.NewTransport(authTransport, modifiers...),
|
||||
Timeout: 15 * time.Second,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ConvertToHostname normalizes a registry URL which has http|https prepended
|
||||
// to just its hostname. It is used to match credentials, which may be either
|
||||
// stored as hostname or as hostname including scheme (in legacy configuration
|
||||
// files).
|
||||
func ConvertToHostname(url string) string {
|
||||
stripped := url
|
||||
if strings.HasPrefix(stripped, "http://") {
|
||||
stripped = strings.TrimPrefix(stripped, "http://")
|
||||
} else if strings.HasPrefix(stripped, "https://") {
|
||||
stripped = strings.TrimPrefix(stripped, "https://")
|
||||
}
|
||||
stripped, _, _ = strings.Cut(stripped, "/")
|
||||
return stripped
|
||||
}
|
||||
|
||||
// ResolveAuthConfig matches an auth configuration to a server address or a URL
|
||||
func ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, index *registry.IndexInfo) registry.AuthConfig {
|
||||
configKey := GetAuthConfigKey(index)
|
||||
// First try the happy case
|
||||
if c, found := authConfigs[configKey]; found || index.Official {
|
||||
return c
|
||||
}
|
||||
|
||||
// Maybe they have a legacy config file, we will iterate the keys converting
|
||||
// them to the new format and testing
|
||||
for registryURL, ac := range authConfigs {
|
||||
if configKey == ConvertToHostname(registryURL) {
|
||||
return ac
|
||||
}
|
||||
}
|
||||
|
||||
// When all else fails, return an empty auth config
|
||||
return registry.AuthConfig{}
|
||||
}
|
||||
|
||||
// PingResponseError is used when the response from a ping
|
||||
// was received but invalid.
|
||||
type PingResponseError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (err PingResponseError) Error() string {
|
||||
return err.Err.Error()
|
||||
}
|
||||
|
||||
// PingV2Registry attempts to ping a v2 registry and on success return a
|
||||
// challenge manager for the supported authentication types.
|
||||
// If a response is received but cannot be interpreted, a PingResponseError will be returned.
|
||||
func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.Manager, error) {
|
||||
pingClient := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: 15 * time.Second,
|
||||
}
|
||||
endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/"
|
||||
req, err := http.NewRequest(http.MethodGet, endpointStr, http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := pingClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
challengeManager := challenge.NewSimpleManager()
|
||||
if err := challengeManager.AddResponse(resp); err != nil {
|
||||
return nil, PingResponseError{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return challengeManager, nil
|
||||
}
|
||||
481
vendor/github.com/docker/docker/registry/config.go
generated
vendored
481
vendor/github.com/docker/docker/registry/config.go
generated
vendored
@ -1,481 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/internal/lazyregexp"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
)
|
||||
|
||||
// ServiceOptions holds command line options.
|
||||
type ServiceOptions struct {
|
||||
Mirrors []string `json:"registry-mirrors,omitempty"`
|
||||
InsecureRegistries []string `json:"insecure-registries,omitempty"`
|
||||
}
|
||||
|
||||
// serviceConfig holds daemon configuration for the registry service.
|
||||
type serviceConfig registry.ServiceConfig
|
||||
|
||||
// TODO(thaJeztah) both the "index.docker.io" and "registry-1.docker.io" domains
|
||||
// are here for historic reasons and backward-compatibility. These domains
|
||||
// are still supported by Docker Hub (and will continue to be supported), but
|
||||
// there are new domains already in use, and plans to consolidate all legacy
|
||||
// domains to new "canonical" domains. Once those domains are decided on, we
|
||||
// should update these consts (but making sure to preserve compatibility with
|
||||
// existing installs, clients, and user configuration).
|
||||
const (
|
||||
// DefaultNamespace is the default namespace
|
||||
DefaultNamespace = "docker.io"
|
||||
// DefaultRegistryHost is the hostname for the default (Docker Hub) registry
|
||||
// used for pushing and pulling images. This hostname is hard-coded to handle
|
||||
// the conversion from image references without registry name (e.g. "ubuntu",
|
||||
// or "ubuntu:latest"), as well as references using the "docker.io" domain
|
||||
// name, which is used as canonical reference for images on Docker Hub, but
|
||||
// does not match the domain-name of Docker Hub's registry.
|
||||
DefaultRegistryHost = "registry-1.docker.io"
|
||||
// IndexHostname is the index hostname, used for authentication and image search.
|
||||
IndexHostname = "index.docker.io"
|
||||
// IndexServer is used for user auth and image search
|
||||
IndexServer = "https://" + IndexHostname + "/v1/"
|
||||
// IndexName is the name of the index
|
||||
IndexName = "docker.io"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultV2Registry is the URI of the default (Docker Hub) registry.
|
||||
DefaultV2Registry = &url.URL{
|
||||
Scheme: "https",
|
||||
Host: DefaultRegistryHost,
|
||||
}
|
||||
|
||||
validHostPortRegex = lazyregexp.New(`^` + reference.DomainRegexp.String() + `$`)
|
||||
|
||||
// certsDir is used to override defaultCertsDir when running with rootlessKit.
|
||||
//
|
||||
// TODO(thaJeztah): change to a sync.OnceValue once we remove [SetCertsDir]
|
||||
// TODO(thaJeztah): certsDir should not be a package variable, but stored in our config, and passed when needed.
|
||||
setCertsDirOnce sync.Once
|
||||
certsDir string
|
||||
)
|
||||
|
||||
func setCertsDir(dir string) string {
|
||||
setCertsDirOnce.Do(func() {
|
||||
if dir != "" {
|
||||
certsDir = dir
|
||||
return
|
||||
}
|
||||
if os.Getenv("ROOTLESSKIT_STATE_DIR") != "" {
|
||||
// Configure registry.CertsDir() when running in rootless-mode
|
||||
// This is the equivalent of [rootless.RunningWithRootlessKit],
|
||||
// but inlining it to prevent adding that as a dependency
|
||||
// for docker/cli.
|
||||
//
|
||||
// [rootless.RunningWithRootlessKit]: https://github.com/moby/moby/blob/b4bdf12daec84caaf809a639f923f7370d4926ad/pkg/rootless/rootless.go#L5-L8
|
||||
if configHome, _ := homedir.GetConfigHome(); configHome != "" {
|
||||
certsDir = filepath.Join(configHome, "docker/certs.d")
|
||||
return
|
||||
}
|
||||
}
|
||||
certsDir = defaultCertsDir
|
||||
})
|
||||
return certsDir
|
||||
}
|
||||
|
||||
// SetCertsDir allows the default certs directory to be changed. This function
|
||||
// is used at daemon startup to set the correct location when running in
|
||||
// rootless mode.
|
||||
//
|
||||
// Deprecated: the cert-directory is now automatically selected when running with rootlessKit, and should no longer be set manually.
|
||||
func SetCertsDir(path string) {
|
||||
setCertsDir(path)
|
||||
}
|
||||
|
||||
// CertsDir is the directory where certificates are stored.
|
||||
func CertsDir() string {
|
||||
// call setCertsDir with an empty path to synchronise with [SetCertsDir]
|
||||
return setCertsDir("")
|
||||
}
|
||||
|
||||
// newServiceConfig returns a new instance of ServiceConfig
|
||||
func newServiceConfig(options ServiceOptions) (*serviceConfig, error) {
|
||||
config := &serviceConfig{}
|
||||
if err := config.loadMirrors(options.Mirrors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := config.loadInsecureRegistries(options.InsecureRegistries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// copy constructs a new ServiceConfig with a copy of the configuration in config.
|
||||
func (config *serviceConfig) copy() *registry.ServiceConfig {
|
||||
ic := make(map[string]*registry.IndexInfo)
|
||||
for key, value := range config.IndexConfigs {
|
||||
ic[key] = value
|
||||
}
|
||||
return ®istry.ServiceConfig{
|
||||
InsecureRegistryCIDRs: append([]*registry.NetIPNet(nil), config.InsecureRegistryCIDRs...),
|
||||
IndexConfigs: ic,
|
||||
Mirrors: append([]string(nil), config.Mirrors...),
|
||||
}
|
||||
}
|
||||
|
||||
// loadMirrors loads mirrors to config, after removing duplicates.
|
||||
// Returns an error if mirrors contains an invalid mirror.
|
||||
func (config *serviceConfig) loadMirrors(mirrors []string) error {
|
||||
mMap := map[string]struct{}{}
|
||||
unique := []string{}
|
||||
|
||||
for _, mirror := range mirrors {
|
||||
m, err := ValidateMirror(mirror)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, exist := mMap[m]; !exist {
|
||||
mMap[m] = struct{}{}
|
||||
unique = append(unique, m)
|
||||
}
|
||||
}
|
||||
|
||||
config.Mirrors = unique
|
||||
|
||||
// Configure public registry since mirrors may have changed.
|
||||
config.IndexConfigs = map[string]*registry.IndexInfo{
|
||||
IndexName: {
|
||||
Name: IndexName,
|
||||
Mirrors: unique,
|
||||
Secure: true,
|
||||
Official: true,
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadInsecureRegistries loads insecure registries to config
|
||||
func (config *serviceConfig) loadInsecureRegistries(registries []string) error {
|
||||
// Localhost is by default considered as an insecure registry. This is a
|
||||
// stop-gap for people who are running a private registry on localhost.
|
||||
registries = append(registries, "::1/128", "127.0.0.0/8")
|
||||
|
||||
var (
|
||||
insecureRegistryCIDRs = make([]*registry.NetIPNet, 0)
|
||||
indexConfigs = make(map[string]*registry.IndexInfo)
|
||||
)
|
||||
|
||||
skip:
|
||||
for _, r := range registries {
|
||||
// validate insecure registry
|
||||
if _, err := ValidateIndexName(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(r), "http://") {
|
||||
log.G(context.TODO()).Warnf("insecure registry %s should not contain 'http://' and 'http://' has been removed from the insecure registry config", r)
|
||||
r = r[7:]
|
||||
} else if strings.HasPrefix(strings.ToLower(r), "https://") {
|
||||
log.G(context.TODO()).Warnf("insecure registry %s should not contain 'https://' and 'https://' has been removed from the insecure registry config", r)
|
||||
r = r[8:]
|
||||
} else if hasScheme(r) {
|
||||
return invalidParamf("insecure registry %s should not contain '://'", r)
|
||||
}
|
||||
// Check if CIDR was passed to --insecure-registry
|
||||
_, ipnet, err := net.ParseCIDR(r)
|
||||
if err == nil {
|
||||
// Valid CIDR. If ipnet is already in config.InsecureRegistryCIDRs, skip.
|
||||
data := (*registry.NetIPNet)(ipnet)
|
||||
for _, value := range insecureRegistryCIDRs {
|
||||
if value.IP.String() == data.IP.String() && value.Mask.String() == data.Mask.String() {
|
||||
continue skip
|
||||
}
|
||||
}
|
||||
// ipnet is not found, add it in config.InsecureRegistryCIDRs
|
||||
insecureRegistryCIDRs = append(insecureRegistryCIDRs, data)
|
||||
} else {
|
||||
if err := validateHostPort(r); err != nil {
|
||||
return invalidParamWrapf(err, "insecure registry %s is not valid", r)
|
||||
}
|
||||
// Assume `host:port` if not CIDR.
|
||||
indexConfigs[r] = ®istry.IndexInfo{
|
||||
Name: r,
|
||||
Mirrors: []string{},
|
||||
Secure: false,
|
||||
Official: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure public registry.
|
||||
indexConfigs[IndexName] = ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Mirrors: config.Mirrors,
|
||||
Secure: true,
|
||||
Official: true,
|
||||
}
|
||||
config.InsecureRegistryCIDRs = insecureRegistryCIDRs
|
||||
config.IndexConfigs = indexConfigs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isSecureIndex returns false if the provided indexName is part of the list of insecure registries
|
||||
// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
|
||||
//
|
||||
// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
|
||||
// If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered
|
||||
// insecure.
|
||||
//
|
||||
// indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
|
||||
// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
|
||||
// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
|
||||
// of insecureRegistries.
|
||||
func (config *serviceConfig) isSecureIndex(indexName string) bool {
|
||||
// Check for configured index, first. This is needed in case isSecureIndex
|
||||
// is called from anything besides newIndexInfo, in order to honor per-index configurations.
|
||||
if index, ok := config.IndexConfigs[indexName]; ok {
|
||||
return index.Secure
|
||||
}
|
||||
|
||||
return !isCIDRMatch(config.InsecureRegistryCIDRs, indexName)
|
||||
}
|
||||
|
||||
// for mocking in unit tests.
|
||||
var lookupIP = net.LookupIP
|
||||
|
||||
// isCIDRMatch returns true if URLHost matches an element of cidrs. URLHost is a URL.Host (`host:port` or `host`)
|
||||
// where the `host` part can be either a domain name or an IP address. If it is a domain name, then it will be
|
||||
// resolved to IP addresses for matching. If resolution fails, false is returned.
|
||||
func isCIDRMatch(cidrs []*registry.NetIPNet, URLHost string) bool {
|
||||
if len(cidrs) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(URLHost)
|
||||
if err != nil {
|
||||
// Assume URLHost is a host without port and go on.
|
||||
host = URLHost
|
||||
}
|
||||
|
||||
var addresses []net.IP
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
// Host is an IP-address.
|
||||
addresses = append(addresses, ip)
|
||||
} else {
|
||||
// Try to resolve the host's IP-address.
|
||||
addresses, err = lookupIP(host)
|
||||
if err != nil {
|
||||
// We failed to resolve the host; assume there's no match.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, addr := range addresses {
|
||||
for _, ipnet := range cidrs {
|
||||
// check if the addr falls in the subnet
|
||||
if (*net.IPNet)(ipnet).Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateMirror validates and normalizes an HTTP(S) registry mirror. It
|
||||
// returns an error if the given mirrorURL is invalid, or the normalized
|
||||
// format for the URL otherwise.
|
||||
//
|
||||
// It is used by the daemon to validate the daemon configuration.
|
||||
func ValidateMirror(mirrorURL string) (string, error) {
|
||||
// Fast path for missing scheme, as url.Parse splits by ":", which can
|
||||
// cause the hostname to be considered the "scheme" when using "hostname:port".
|
||||
if scheme, _, ok := strings.Cut(mirrorURL, "://"); !ok || scheme == "" {
|
||||
return "", invalidParamf("invalid mirror: no scheme specified for %q: must use either 'https://' or 'http://'", mirrorURL)
|
||||
}
|
||||
uri, err := url.Parse(mirrorURL)
|
||||
if err != nil {
|
||||
return "", invalidParamWrapf(err, "invalid mirror: %q is not a valid URI", mirrorURL)
|
||||
}
|
||||
if uri.Scheme != "http" && uri.Scheme != "https" {
|
||||
return "", invalidParamf("invalid mirror: unsupported scheme %q in %q: must use either 'https://' or 'http://'", uri.Scheme, uri)
|
||||
}
|
||||
if uri.RawQuery != "" || uri.Fragment != "" {
|
||||
return "", invalidParamf("invalid mirror: query or fragment at end of the URI %q", uri)
|
||||
}
|
||||
if uri.User != nil {
|
||||
// strip password from output
|
||||
uri.User = url.UserPassword(uri.User.Username(), "xxxxx")
|
||||
return "", invalidParamf("invalid mirror: username/password not allowed in URI %q", uri)
|
||||
}
|
||||
return strings.TrimSuffix(mirrorURL, "/") + "/", nil
|
||||
}
|
||||
|
||||
// ValidateIndexName validates an index name. It is used by the daemon to
|
||||
// validate the daemon configuration.
|
||||
func ValidateIndexName(val string) (string, error) {
|
||||
val = normalizeIndexName(val)
|
||||
if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
|
||||
return "", invalidParamf("invalid index name (%s). Cannot begin or end with a hyphen", val)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func normalizeIndexName(val string) string {
|
||||
// TODO(thaJeztah): consider normalizing other known options, such as "(https://)registry-1.docker.io", "https://index.docker.io/v1/".
|
||||
// TODO: upstream this to check to reference package
|
||||
if val == "index.docker.io" {
|
||||
return "docker.io"
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func hasScheme(reposName string) bool {
|
||||
return strings.Contains(reposName, "://")
|
||||
}
|
||||
|
||||
func validateHostPort(s string) error {
|
||||
// Split host and port, and in case s can not be split, assume host only
|
||||
host, port, err := net.SplitHostPort(s)
|
||||
if err != nil {
|
||||
host = s
|
||||
port = ""
|
||||
}
|
||||
// If match against the `host:port` pattern fails,
|
||||
// it might be `IPv6:port`, which will be captured by net.ParseIP(host)
|
||||
if !validHostPortRegex.MatchString(s) && net.ParseIP(host) == nil {
|
||||
return invalidParamf("invalid host %q", host)
|
||||
}
|
||||
if port != "" {
|
||||
v, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v < 0 || v > 65535 {
|
||||
return invalidParamf("invalid port %q", port)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// newIndexInfo returns IndexInfo configuration from indexName
|
||||
func newIndexInfo(config *serviceConfig, indexName string) *registry.IndexInfo {
|
||||
indexName = normalizeIndexName(indexName)
|
||||
|
||||
// Return any configured index info, first.
|
||||
if index, ok := config.IndexConfigs[indexName]; ok {
|
||||
return index
|
||||
}
|
||||
|
||||
// Construct a non-configured index info.
|
||||
return ®istry.IndexInfo{
|
||||
Name: indexName,
|
||||
Mirrors: []string{},
|
||||
Secure: config.isSecureIndex(indexName),
|
||||
}
|
||||
}
|
||||
|
||||
// GetAuthConfigKey special-cases using the full index address of the official
|
||||
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
|
||||
func GetAuthConfigKey(index *registry.IndexInfo) string {
|
||||
if index.Official {
|
||||
return IndexServer
|
||||
}
|
||||
return index.Name
|
||||
}
|
||||
|
||||
// newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
|
||||
func newRepositoryInfo(config *serviceConfig, name reference.Named) *RepositoryInfo {
|
||||
index := newIndexInfo(config, reference.Domain(name))
|
||||
var officialRepo bool
|
||||
if index.Official {
|
||||
// RepositoryInfo.Official indicates whether the image repository
|
||||
// is an official (docker library official images) repository.
|
||||
//
|
||||
// We only need to check this if the image-repository is on Docker Hub.
|
||||
officialRepo = !strings.ContainsRune(reference.FamiliarName(name), '/')
|
||||
}
|
||||
|
||||
return &RepositoryInfo{
|
||||
Name: reference.TrimNamed(name),
|
||||
Index: index,
|
||||
Official: officialRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseRepositoryInfo performs the breakdown of a repository name into a
|
||||
// [RepositoryInfo], but lacks registry configuration.
|
||||
//
|
||||
// It is used by the Docker cli to interact with registry-related endpoints.
|
||||
func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
|
||||
indexName := normalizeIndexName(reference.Domain(reposName))
|
||||
if indexName == IndexName {
|
||||
return &RepositoryInfo{
|
||||
Name: reference.TrimNamed(reposName),
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Mirrors: []string{},
|
||||
Secure: true,
|
||||
Official: true,
|
||||
},
|
||||
Official: !strings.ContainsRune(reference.FamiliarName(reposName), '/'),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &RepositoryInfo{
|
||||
Name: reference.TrimNamed(reposName),
|
||||
Index: ®istry.IndexInfo{
|
||||
Name: indexName,
|
||||
Mirrors: []string{},
|
||||
Secure: !isInsecure(indexName),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// isInsecure is used to detect whether a registry domain or IP-address is allowed
|
||||
// to use an insecure (non-TLS, or self-signed cert) connection according to the
|
||||
// defaults, which allows for insecure connections with registries running on a
|
||||
// loopback address ("localhost", "::1/128", "127.0.0.0/8").
|
||||
//
|
||||
// It is used in situations where we don't have access to the daemon's configuration,
|
||||
// for example, when used from the client / CLI.
|
||||
func isInsecure(hostNameOrIP string) bool {
|
||||
// Attempt to strip port if present; this also strips brackets for
|
||||
// IPv6 addresses with a port (e.g. "[::1]:5000").
|
||||
//
|
||||
// This is best-effort; we'll continue using the address as-is if it fails.
|
||||
if host, _, err := net.SplitHostPort(hostNameOrIP); err == nil {
|
||||
hostNameOrIP = host
|
||||
}
|
||||
if hostNameOrIP == "127.0.0.1" || hostNameOrIP == "::1" || strings.EqualFold(hostNameOrIP, "localhost") {
|
||||
// Fast path; no need to resolve these, assuming nobody overrides
|
||||
// "localhost" for anything else than a loopback address (sorry, not sorry).
|
||||
return true
|
||||
}
|
||||
|
||||
var addresses []net.IP
|
||||
if ip := net.ParseIP(hostNameOrIP); ip != nil {
|
||||
addresses = append(addresses, ip)
|
||||
} else {
|
||||
// Try to resolve the host's IP-addresses.
|
||||
addrs, _ := lookupIP(hostNameOrIP)
|
||||
addresses = append(addresses, addrs...)
|
||||
}
|
||||
|
||||
for _, addr := range addresses {
|
||||
if addr.IsLoopback() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
16
vendor/github.com/docker/docker/registry/config_unix.go
generated
vendored
16
vendor/github.com/docker/docker/registry/config_unix.go
generated
vendored
@ -1,16 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package registry
|
||||
|
||||
// defaultCertsDir is the platform-specific default directory where certificates
|
||||
// are stored. On Linux, it may be overridden through certsDir, for example, when
|
||||
// running in rootless mode.
|
||||
const defaultCertsDir = "/etc/docker/certs.d"
|
||||
|
||||
// cleanPath is used to ensure that a directory name is valid on the target
|
||||
// platform. It will be passed in something *similar* to a URL such as
|
||||
// https:/index.docker.io/v1. Not all platforms support directory names
|
||||
// which contain those characters (such as : on Windows)
|
||||
func cleanPath(s string) string {
|
||||
return s
|
||||
}
|
||||
20
vendor/github.com/docker/docker/registry/config_windows.go
generated
vendored
20
vendor/github.com/docker/docker/registry/config_windows.go
generated
vendored
@ -1,20 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// defaultCertsDir is the platform-specific default directory where certificates
|
||||
// are stored. On Linux, it may be overridden through certsDir, for example, when
|
||||
// running in rootless mode.
|
||||
var defaultCertsDir = os.Getenv("programdata") + `\docker\certs.d`
|
||||
|
||||
// cleanPath is used to ensure that a directory name is valid on the target
|
||||
// platform. It will be passed in something *similar* to a URL such as
|
||||
// https:\index.docker.io\v1. Not all platforms support directory names
|
||||
// which contain those characters (such as : on Windows)
|
||||
func cleanPath(s string) string {
|
||||
return filepath.FromSlash(strings.ReplaceAll(s, ":", ""))
|
||||
}
|
||||
71
vendor/github.com/docker/docker/registry/errors.go
generated
vendored
71
vendor/github.com/docker/docker/registry/errors.go
generated
vendored
@ -1,71 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func translateV2AuthError(err error) error {
|
||||
switch e := err.(type) {
|
||||
case *url.Error:
|
||||
switch e2 := e.Err.(type) {
|
||||
case errcode.Error:
|
||||
switch e2.Code {
|
||||
case errcode.ErrorCodeUnauthorized:
|
||||
return unauthorizedErr{err}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func invalidParam(err error) error {
|
||||
return invalidParameterErr{err}
|
||||
}
|
||||
|
||||
func invalidParamf(format string, args ...interface{}) error {
|
||||
return invalidParameterErr{errors.Errorf(format, args...)}
|
||||
}
|
||||
|
||||
func invalidParamWrapf(err error, format string, args ...interface{}) error {
|
||||
return invalidParameterErr{errors.Wrapf(err, format, args...)}
|
||||
}
|
||||
|
||||
type unauthorizedErr struct{ error }
|
||||
|
||||
func (unauthorizedErr) Unauthorized() {}
|
||||
|
||||
func (e unauthorizedErr) Cause() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
func (e unauthorizedErr) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
type invalidParameterErr struct{ error }
|
||||
|
||||
func (invalidParameterErr) InvalidParameter() {}
|
||||
|
||||
func (e invalidParameterErr) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
type systemErr struct{ error }
|
||||
|
||||
func (systemErr) System() {}
|
||||
|
||||
func (e systemErr) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
type errUnknown struct{ error }
|
||||
|
||||
func (errUnknown) Unknown() {}
|
||||
|
||||
func (e errUnknown) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
154
vendor/github.com/docker/docker/registry/registry.go
generated
vendored
154
vendor/github.com/docker/docker/registry/registry.go
generated
vendored
@ -1,154 +0,0 @@
|
||||
// Package registry contains client primitives to interact with a remote Docker registry.
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
)
|
||||
|
||||
// HostCertsDir returns the config directory for a specific host.
|
||||
//
|
||||
// Deprecated: this function was only used internally, and will be removed in a future release.
|
||||
func HostCertsDir(hostname string) string {
|
||||
return hostCertsDir(hostname)
|
||||
}
|
||||
|
||||
// hostCertsDir returns the config directory for a specific host.
|
||||
func hostCertsDir(hostname string) string {
|
||||
return filepath.Join(CertsDir(), cleanPath(hostname))
|
||||
}
|
||||
|
||||
// newTLSConfig constructs a client TLS configuration based on server defaults
|
||||
func newTLSConfig(ctx context.Context, hostname string, isSecure bool) (*tls.Config, error) {
|
||||
// PreferredServerCipherSuites should have no effect
|
||||
tlsConfig := tlsconfig.ServerDefault()
|
||||
tlsConfig.InsecureSkipVerify = !isSecure
|
||||
|
||||
if isSecure {
|
||||
hostDir := hostCertsDir(hostname)
|
||||
log.G(ctx).Debugf("hostDir: %s", hostDir)
|
||||
if err := loadTLSConfig(ctx, hostDir, tlsConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
func hasFile(files []os.DirEntry, name string) bool {
|
||||
for _, f := range files {
|
||||
if f.Name() == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ReadCertsDirectory reads the directory for TLS certificates
|
||||
// including roots and certificate pairs and updates the
|
||||
// provided TLS configuration.
|
||||
func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
|
||||
return loadTLSConfig(context.TODO(), directory, tlsConfig)
|
||||
}
|
||||
|
||||
// loadTLSConfig reads the directory for TLS certificates including roots and
|
||||
// certificate pairs, and updates the provided TLS configuration.
|
||||
func loadTLSConfig(ctx context.Context, directory string, tlsConfig *tls.Config) error {
|
||||
fs, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return invalidParam(err)
|
||||
}
|
||||
|
||||
for _, f := range fs {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
switch filepath.Ext(f.Name()) {
|
||||
case ".crt":
|
||||
if tlsConfig.RootCAs == nil {
|
||||
systemPool, err := tlsconfig.SystemCertPool()
|
||||
if err != nil {
|
||||
return invalidParamWrapf(err, "unable to get system cert pool")
|
||||
}
|
||||
tlsConfig.RootCAs = systemPool
|
||||
}
|
||||
fileName := filepath.Join(directory, f.Name())
|
||||
log.G(ctx).Debugf("crt: %s", fileName)
|
||||
data, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tlsConfig.RootCAs.AppendCertsFromPEM(data)
|
||||
case ".cert":
|
||||
certName := f.Name()
|
||||
keyName := certName[:len(certName)-5] + ".key"
|
||||
log.G(ctx).Debugf("cert: %s", filepath.Join(directory, certName))
|
||||
if !hasFile(fs, keyName) {
|
||||
return invalidParamf("missing key %s for client certificate %s. CA certificates must use the extension .crt", keyName, certName)
|
||||
}
|
||||
cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
case ".key":
|
||||
keyName := f.Name()
|
||||
certName := keyName[:len(keyName)-4] + ".cert"
|
||||
log.G(ctx).Debugf("key: %s", filepath.Join(directory, keyName))
|
||||
if !hasFile(fs, certName) {
|
||||
return invalidParamf("missing client certificate %s for key %s", certName, keyName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Headers returns request modifiers with a User-Agent and metaHeaders
|
||||
func Headers(userAgent string, metaHeaders http.Header) []transport.RequestModifier {
|
||||
modifiers := []transport.RequestModifier{}
|
||||
if userAgent != "" {
|
||||
modifiers = append(modifiers, transport.NewHeaderRequestModifier(http.Header{
|
||||
"User-Agent": []string{userAgent},
|
||||
}))
|
||||
}
|
||||
if metaHeaders != nil {
|
||||
modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
|
||||
}
|
||||
return modifiers
|
||||
}
|
||||
|
||||
// newTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
|
||||
// default TLS configuration.
|
||||
func newTransport(tlsConfig *tls.Config) http.RoundTripper {
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = tlsconfig.ServerDefault()
|
||||
}
|
||||
|
||||
return otelhttp.NewTransport(
|
||||
&http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
// TODO(dmcgowan): Call close idle connections when complete and use keep alive
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
170
vendor/github.com/docker/docker/registry/search.go
generated
vendored
170
vendor/github.com/docker/docker/registry/search.go
generated
vendored
@ -1,170 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/distribution/registry/client/auth"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var acceptedSearchFilterTags = map[string]bool{
|
||||
"is-automated": true, // Deprecated: the "is_automated" field is deprecated and will always be false in the future.
|
||||
"is-official": true,
|
||||
"stars": true,
|
||||
}
|
||||
|
||||
// Search queries the public registry for repositories matching the specified
|
||||
// search term and filters.
|
||||
func (s *Service) Search(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, headers map[string][]string) ([]registry.SearchResult, error) {
|
||||
if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isAutomated, err := searchFilters.GetBoolOrDefault("is-automated", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// "is-automated" is deprecated and filtering for `true` will yield no results.
|
||||
if isAutomated {
|
||||
return []registry.SearchResult{}, nil
|
||||
}
|
||||
|
||||
isOfficial, err := searchFilters.GetBoolOrDefault("is-official", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hasStarFilter := 0
|
||||
if searchFilters.Contains("stars") {
|
||||
hasStars := searchFilters.Get("stars")
|
||||
for _, hasStar := range hasStars {
|
||||
iHasStar, err := strconv.Atoi(hasStar)
|
||||
if err != nil {
|
||||
return nil, invalidParameterErr{errors.Wrapf(err, "invalid filter 'stars=%s'", hasStar)}
|
||||
}
|
||||
if iHasStar > hasStarFilter {
|
||||
hasStarFilter = iHasStar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unfilteredResult, err := s.searchUnfiltered(ctx, term, limit, authConfig, headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filteredResults := []registry.SearchResult{}
|
||||
for _, result := range unfilteredResult.Results {
|
||||
if searchFilters.Contains("is-official") {
|
||||
if isOfficial != result.IsOfficial {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if searchFilters.Contains("stars") {
|
||||
if result.StarCount < hasStarFilter {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// "is-automated" is deprecated and the value in Docker Hub search
|
||||
// results is untrustworthy. Force it to false so as to not mislead our
|
||||
// clients.
|
||||
result.IsAutomated = false //nolint:staticcheck // ignore SA1019 (field is deprecated)
|
||||
filteredResults = append(filteredResults, result)
|
||||
}
|
||||
|
||||
return filteredResults, nil
|
||||
}
|
||||
|
||||
func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int, authConfig *registry.AuthConfig, headers http.Header) (*registry.SearchResults, error) {
|
||||
if hasScheme(term) {
|
||||
return nil, invalidParamf("invalid repository name: repository name (%s) should not have a scheme", term)
|
||||
}
|
||||
|
||||
indexName, remoteName := splitReposSearchTerm(term)
|
||||
|
||||
// Search is a long-running operation, just lock s.config to avoid block others.
|
||||
s.mu.RLock()
|
||||
index := newIndexInfo(s.config, indexName)
|
||||
s.mu.RUnlock()
|
||||
if index.Official {
|
||||
// If pull "library/foo", it's stored locally under "foo"
|
||||
remoteName = strings.TrimPrefix(remoteName, "library/")
|
||||
}
|
||||
|
||||
endpoint, err := newV1Endpoint(ctx, index, headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var client *http.Client
|
||||
if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
|
||||
creds := NewStaticCredentialStore(authConfig)
|
||||
|
||||
// TODO(thaJeztah); is there a reason not to include other headers here? (originally added in 19d48f0b8ba59eea9f2cac4ad1c7977712a6b7ac)
|
||||
modifiers := Headers(headers.Get("User-Agent"), nil)
|
||||
v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, []auth.Scope{
|
||||
auth.RegistryScope{Name: "catalog", Actions: []string{"search"}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Copy non transport http client features
|
||||
v2Client.Timeout = endpoint.client.Timeout
|
||||
v2Client.CheckRedirect = endpoint.client.CheckRedirect
|
||||
v2Client.Jar = endpoint.client.Jar
|
||||
|
||||
log.G(ctx).Debugf("using v2 client for search to %s", endpoint.URL)
|
||||
client = v2Client
|
||||
} else {
|
||||
client = endpoint.client
|
||||
if err := authorizeClient(ctx, client, authConfig, endpoint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return newSession(client, endpoint).searchRepositories(ctx, remoteName, limit)
|
||||
}
|
||||
|
||||
// splitReposSearchTerm breaks a search term into an index name and remote name
|
||||
func splitReposSearchTerm(reposName string) (string, string) {
|
||||
nameParts := strings.SplitN(reposName, "/", 2)
|
||||
if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
|
||||
!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
|
||||
// This is a Docker Hub repository (ex: samalba/hipache or ubuntu),
|
||||
// use the default Docker Hub registry (docker.io)
|
||||
return IndexName, reposName
|
||||
}
|
||||
return nameParts[0], nameParts[1]
|
||||
}
|
||||
|
||||
// ParseSearchIndexInfo will use repository name to get back an indexInfo.
|
||||
//
|
||||
// TODO(thaJeztah) this function is only used by the CLI, and used to get
|
||||
// information of the registry (to provide credentials if needed). We should
|
||||
// move this function (or equivalent) to the CLI, as it's doing too much just
|
||||
// for that.
|
||||
func ParseSearchIndexInfo(reposName string) (*registry.IndexInfo, error) {
|
||||
indexName, _ := splitReposSearchTerm(reposName)
|
||||
indexName = normalizeIndexName(indexName)
|
||||
if indexName == IndexName {
|
||||
return ®istry.IndexInfo{
|
||||
Name: IndexName,
|
||||
Mirrors: []string{},
|
||||
Secure: true,
|
||||
Official: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return ®istry.IndexInfo{
|
||||
Name: indexName,
|
||||
Mirrors: []string{},
|
||||
Secure: !isInsecure(indexName),
|
||||
}, nil
|
||||
}
|
||||
207
vendor/github.com/docker/docker/registry/search_endpoint_v1.go
generated
vendored
207
vendor/github.com/docker/docker/registry/search_endpoint_v1.go
generated
vendored
@ -1,207 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
)
|
||||
|
||||
// v1PingResult contains the information returned when pinging a registry. It
|
||||
// indicates whether the registry claims to be a standalone registry.
|
||||
type v1PingResult struct {
|
||||
// Standalone is set to true if the registry indicates it is a
|
||||
// standalone registry in the X-Docker-Registry-Standalone
|
||||
// header
|
||||
Standalone bool `json:"standalone"`
|
||||
}
|
||||
|
||||
// v1Endpoint stores basic information about a V1 registry endpoint.
|
||||
type v1Endpoint struct {
|
||||
client *http.Client
|
||||
URL *url.URL
|
||||
IsSecure bool
|
||||
}
|
||||
|
||||
// newV1Endpoint parses the given address to return a registry endpoint.
|
||||
// TODO: remove. This is only used by search.
|
||||
func newV1Endpoint(ctx context.Context, index *registry.IndexInfo, headers http.Header) (*v1Endpoint, error) {
|
||||
tlsConfig, err := newTLSConfig(ctx, index.Name, index.Secure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoint, err := newV1EndpointFromStr(GetAuthConfigKey(index), tlsConfig, headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if endpoint.String() == IndexServer {
|
||||
// Skip the check, we know this one is valid
|
||||
// (and we never want to fall back to http in case of error)
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
// Try HTTPS ping to registry
|
||||
endpoint.URL.Scheme = "https"
|
||||
if _, err := endpoint.ping(ctx); err != nil {
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return nil, err
|
||||
}
|
||||
if endpoint.IsSecure {
|
||||
// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
|
||||
// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fall back to HTTP.
|
||||
return nil, invalidParamf("invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
|
||||
}
|
||||
|
||||
// registry is insecure and HTTPS failed, fallback to HTTP.
|
||||
log.G(ctx).WithError(err).Debugf("error from registry %q marked as insecure - insecurely falling back to HTTP", endpoint)
|
||||
endpoint.URL.Scheme = "http"
|
||||
if _, err2 := endpoint.ping(ctx); err2 != nil {
|
||||
return nil, invalidParamf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
|
||||
}
|
||||
}
|
||||
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
// trimV1Address trims the "v1" version suffix off the address and returns
|
||||
// the trimmed address. It returns an error on "v2" endpoints.
|
||||
func trimV1Address(address string) (string, error) {
|
||||
trimmed := strings.TrimSuffix(address, "/")
|
||||
if strings.HasSuffix(trimmed, "/v2") {
|
||||
return "", invalidParamf("search is not supported on v2 endpoints: %s", address)
|
||||
}
|
||||
return strings.TrimSuffix(trimmed, "/v1"), nil
|
||||
}
|
||||
|
||||
func newV1EndpointFromStr(address string, tlsConfig *tls.Config, headers http.Header) (*v1Endpoint, error) {
|
||||
if !strings.HasPrefix(address, "http://") && !strings.HasPrefix(address, "https://") {
|
||||
address = "https://" + address
|
||||
}
|
||||
|
||||
address, err := trimV1Address(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uri, err := url.Parse(address)
|
||||
if err != nil {
|
||||
return nil, invalidParam(err)
|
||||
}
|
||||
|
||||
// TODO(tiborvass): make sure a ConnectTimeout transport is used
|
||||
tr := newTransport(tlsConfig)
|
||||
|
||||
return &v1Endpoint{
|
||||
IsSecure: tlsConfig == nil || !tlsConfig.InsecureSkipVerify,
|
||||
URL: uri,
|
||||
client: httpClient(transport.NewTransport(tr, Headers("", headers)...)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get the formatted URL for the root of this registry Endpoint
|
||||
func (e *v1Endpoint) String() string {
|
||||
return e.URL.String() + "/v1/"
|
||||
}
|
||||
|
||||
// ping returns a v1PingResult which indicates whether the registry is standalone or not.
|
||||
func (e *v1Endpoint) ping(ctx context.Context) (v1PingResult, error) {
|
||||
if e.String() == IndexServer {
|
||||
// Skip the check, we know this one is valid
|
||||
// (and we never want to fallback to http in case of error)
|
||||
return v1PingResult{}, nil
|
||||
}
|
||||
|
||||
pingURL := e.String() + "_ping"
|
||||
log.G(ctx).WithField("url", pingURL).Debug("attempting v1 ping for registry endpoint")
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, pingURL, http.NoBody)
|
||||
if err != nil {
|
||||
return v1PingResult{}, invalidParam(err)
|
||||
}
|
||||
|
||||
resp, err := e.client.Do(req)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return v1PingResult{}, err
|
||||
}
|
||||
return v1PingResult{}, invalidParam(err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if v := resp.Header.Get("X-Docker-Registry-Standalone"); v != "" {
|
||||
info := v1PingResult{}
|
||||
// Accepted values are "1", and "true" (case-insensitive).
|
||||
if v == "1" || strings.EqualFold(v, "true") {
|
||||
info.Standalone = true
|
||||
}
|
||||
log.G(ctx).Debugf("v1PingResult.Standalone (from X-Docker-Registry-Standalone header): %t", info.Standalone)
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// If the header is absent, we assume true for compatibility with earlier
|
||||
// versions of the registry. default to true
|
||||
info := v1PingResult{
|
||||
Standalone: true,
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
|
||||
log.G(ctx).WithError(err).Debug("error unmarshaling _ping response")
|
||||
// don't stop here. Just assume sane defaults
|
||||
}
|
||||
|
||||
log.G(ctx).Debugf("v1PingResult.Standalone: %t", info.Standalone)
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// httpClient returns an HTTP client structure which uses the given transport
|
||||
// and contains the necessary headers for redirected requests
|
||||
func httpClient(transport http.RoundTripper) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: transport,
|
||||
CheckRedirect: addRequiredHeadersToRedirectedRequests,
|
||||
}
|
||||
}
|
||||
|
||||
func trustedLocation(req *http.Request) bool {
|
||||
var (
|
||||
trusteds = []string{"docker.com", "docker.io"}
|
||||
hostname = strings.SplitN(req.Host, ":", 2)[0]
|
||||
)
|
||||
if req.URL.Scheme != "https" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, trusted := range trusteds {
|
||||
if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// addRequiredHeadersToRedirectedRequests adds the necessary redirection headers
|
||||
// for redirected requests
|
||||
func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
|
||||
if len(via) != 0 && via[0] != nil {
|
||||
if trustedLocation(req) && trustedLocation(via[0]) {
|
||||
req.Header = via[0].Header
|
||||
return nil
|
||||
}
|
||||
for k, v := range via[0].Header {
|
||||
if k != "Authorization" {
|
||||
for _, vv := range v {
|
||||
req.Header.Add(k, vv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
247
vendor/github.com/docker/docker/registry/search_session.go
generated
vendored
247
vendor/github.com/docker/docker/registry/search_session.go
generated
vendored
@ -1,247 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
// this is required for some certificates
|
||||
"context"
|
||||
_ "crypto/sha512"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// A session is used to communicate with a V1 registry
|
||||
type session struct {
|
||||
indexEndpoint *v1Endpoint
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
type authTransport struct {
|
||||
base http.RoundTripper
|
||||
authConfig *registry.AuthConfig
|
||||
|
||||
alwaysSetBasicAuth bool
|
||||
token []string
|
||||
|
||||
mu sync.Mutex // guards modReq
|
||||
modReq map[*http.Request]*http.Request // original -> modified
|
||||
}
|
||||
|
||||
// newAuthTransport handles the auth layer when communicating with a v1 registry (private or official)
|
||||
//
|
||||
// For private v1 registries, set alwaysSetBasicAuth to true.
|
||||
//
|
||||
// For the official v1 registry, if there isn't already an Authorization header in the request,
|
||||
// but there is an X-Docker-Token header set to true, then Basic Auth will be used to set the Authorization header.
|
||||
// After sending the request with the provided base http.RoundTripper, if an X-Docker-Token header, representing
|
||||
// a token, is present in the response, then it gets cached and sent in the Authorization header of all subsequent
|
||||
// requests.
|
||||
//
|
||||
// If the server sends a token without the client having requested it, it is ignored.
|
||||
//
|
||||
// This RoundTripper also has a CancelRequest method important for correct timeout handling.
|
||||
func newAuthTransport(base http.RoundTripper, authConfig *registry.AuthConfig, alwaysSetBasicAuth bool) *authTransport {
|
||||
if base == nil {
|
||||
base = http.DefaultTransport
|
||||
}
|
||||
return &authTransport{
|
||||
base: base,
|
||||
authConfig: authConfig,
|
||||
alwaysSetBasicAuth: alwaysSetBasicAuth,
|
||||
modReq: make(map[*http.Request]*http.Request),
|
||||
}
|
||||
}
|
||||
|
||||
// cloneRequest returns a clone of the provided *http.Request.
|
||||
// The clone is a shallow copy of the struct and its Header map.
|
||||
func cloneRequest(r *http.Request) *http.Request {
|
||||
// shallow copy of the struct
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
// deep copy of the Header
|
||||
r2.Header = make(http.Header, len(r.Header))
|
||||
for k, s := range r.Header {
|
||||
r2.Header[k] = append([]string(nil), s...)
|
||||
}
|
||||
|
||||
return r2
|
||||
}
|
||||
|
||||
// onEOFReader wraps an io.ReadCloser and a function
|
||||
// the function will run at the end of file or close the file.
|
||||
type onEOFReader struct {
|
||||
Rc io.ReadCloser
|
||||
Fn func()
|
||||
}
|
||||
|
||||
func (r *onEOFReader) Read(p []byte) (int, error) {
|
||||
n, err := r.Rc.Read(p)
|
||||
if err == io.EOF {
|
||||
r.runFunc()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close closes the file and run the function.
|
||||
func (r *onEOFReader) Close() error {
|
||||
err := r.Rc.Close()
|
||||
r.runFunc()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *onEOFReader) runFunc() {
|
||||
if fn := r.Fn; fn != nil {
|
||||
fn()
|
||||
r.Fn = nil
|
||||
}
|
||||
}
|
||||
|
||||
// RoundTrip changes an HTTP request's headers to add the necessary
|
||||
// authentication-related headers
|
||||
func (tr *authTransport) RoundTrip(orig *http.Request) (*http.Response, error) {
|
||||
// Authorization should not be set on 302 redirect for untrusted locations.
|
||||
// This logic mirrors the behavior in addRequiredHeadersToRedirectedRequests.
|
||||
// As the authorization logic is currently implemented in RoundTrip,
|
||||
// a 302 redirect is detected by looking at the Referrer header as go http package adds said header.
|
||||
// This is safe as Docker doesn't set Referrer in other scenarios.
|
||||
if orig.Header.Get("Referer") != "" && !trustedLocation(orig) {
|
||||
return tr.base.RoundTrip(orig)
|
||||
}
|
||||
|
||||
req := cloneRequest(orig)
|
||||
tr.mu.Lock()
|
||||
tr.modReq[orig] = req
|
||||
tr.mu.Unlock()
|
||||
|
||||
if tr.alwaysSetBasicAuth {
|
||||
if tr.authConfig == nil {
|
||||
return nil, errors.New("unexpected error: empty auth config")
|
||||
}
|
||||
req.SetBasicAuth(tr.authConfig.Username, tr.authConfig.Password)
|
||||
return tr.base.RoundTrip(req)
|
||||
}
|
||||
|
||||
// Don't override
|
||||
if req.Header.Get("Authorization") == "" {
|
||||
if req.Header.Get("X-Docker-Token") == "true" && tr.authConfig != nil && tr.authConfig.Username != "" {
|
||||
req.SetBasicAuth(tr.authConfig.Username, tr.authConfig.Password)
|
||||
} else if len(tr.token) > 0 {
|
||||
req.Header.Set("Authorization", "Token "+strings.Join(tr.token, ","))
|
||||
}
|
||||
}
|
||||
resp, err := tr.base.RoundTrip(req)
|
||||
if err != nil {
|
||||
tr.mu.Lock()
|
||||
delete(tr.modReq, orig)
|
||||
tr.mu.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.Header["X-Docker-Token"]) > 0 {
|
||||
tr.token = resp.Header["X-Docker-Token"]
|
||||
}
|
||||
resp.Body = &onEOFReader{
|
||||
Rc: resp.Body,
|
||||
Fn: func() {
|
||||
tr.mu.Lock()
|
||||
delete(tr.modReq, orig)
|
||||
tr.mu.Unlock()
|
||||
},
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
func (tr *authTransport) CancelRequest(req *http.Request) {
|
||||
type canceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
if cr, ok := tr.base.(canceler); ok {
|
||||
tr.mu.Lock()
|
||||
modReq := tr.modReq[req]
|
||||
delete(tr.modReq, req)
|
||||
tr.mu.Unlock()
|
||||
cr.CancelRequest(modReq)
|
||||
}
|
||||
}
|
||||
|
||||
func authorizeClient(ctx context.Context, client *http.Client, authConfig *registry.AuthConfig, endpoint *v1Endpoint) error {
|
||||
var alwaysSetBasicAuth bool
|
||||
|
||||
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
|
||||
// alongside all our requests.
|
||||
if endpoint.String() != IndexServer && endpoint.URL.Scheme == "https" {
|
||||
info, err := endpoint.ping(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Standalone && authConfig != nil {
|
||||
log.G(ctx).WithField("endpoint", endpoint.String()).Debug("Endpoint is eligible for private registry; enabling alwaysSetBasicAuth")
|
||||
alwaysSetBasicAuth = true
|
||||
}
|
||||
}
|
||||
|
||||
// Annotate the transport unconditionally so that v2 can
|
||||
// properly fallback on v1 when an image is not found.
|
||||
client.Transport = newAuthTransport(client.Transport, authConfig, alwaysSetBasicAuth)
|
||||
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
return systemErr{errors.New("cookiejar.New is not supposed to return an error")}
|
||||
}
|
||||
client.Jar = jar
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newSession(client *http.Client, endpoint *v1Endpoint) *session {
|
||||
return &session{
|
||||
client: client,
|
||||
indexEndpoint: endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// defaultSearchLimit is the default value for maximum number of returned search results.
|
||||
const defaultSearchLimit = 25
|
||||
|
||||
// searchRepositories performs a search against the remote repository
|
||||
func (r *session) searchRepositories(ctx context.Context, term string, limit int) (*registry.SearchResults, error) {
|
||||
if limit == 0 {
|
||||
limit = defaultSearchLimit
|
||||
}
|
||||
if limit < 1 || limit > 100 {
|
||||
return nil, invalidParamf("limit %d is outside the range of [1, 100]", limit)
|
||||
}
|
||||
u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(fmt.Sprintf("%d", limit))
|
||||
log.G(ctx).WithField("url", u).Debug("searchRepositories")
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, http.NoBody)
|
||||
if err != nil {
|
||||
return nil, invalidParamWrapf(err, "error building request")
|
||||
}
|
||||
// Have the AuthTransport send authentication, when logged in.
|
||||
req.Header.Set("X-Docker-Token", "true")
|
||||
res, err := r.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, systemErr{err}
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
// TODO(thaJeztah): return upstream response body for errors (see https://github.com/moby/moby/issues/27286).
|
||||
// TODO(thaJeztah): handle other status-codes to return correct error-type
|
||||
return nil, errUnknown{fmt.Errorf("Unexpected status code %d", res.StatusCode)}
|
||||
}
|
||||
result := ®istry.SearchResults{}
|
||||
err = json.NewDecoder(res.Body).Decode(result)
|
||||
if err != nil {
|
||||
return nil, systemErr{errors.Wrap(err, "error decoding registry search results")}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
174
vendor/github.com/docker/docker/registry/service.go
generated
vendored
174
vendor/github.com/docker/docker/registry/service.go
generated
vendored
@ -1,174 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/containerd/log"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
)
|
||||
|
||||
// Service is a registry service. It tracks configuration data such as a list
|
||||
// of mirrors.
|
||||
type Service struct {
|
||||
config *serviceConfig
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewService returns a new instance of [Service] ready to be installed into
|
||||
// an engine.
|
||||
func NewService(options ServiceOptions) (*Service, error) {
|
||||
config, err := newServiceConfig(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{config: config}, err
|
||||
}
|
||||
|
||||
// ServiceConfig returns a copy of the public registry service's configuration.
|
||||
func (s *Service) ServiceConfig() *registry.ServiceConfig {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.config.copy()
|
||||
}
|
||||
|
||||
// ReplaceConfig prepares a transaction which will atomically replace the
|
||||
// registry service's configuration when the returned commit function is called.
|
||||
func (s *Service) ReplaceConfig(options ServiceOptions) (commit func(), _ error) {
|
||||
config, err := newServiceConfig(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.config = config
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Auth contacts the public registry with the provided credentials,
|
||||
// and returns OK if authentication was successful.
|
||||
// It can be used to verify the validity of a client's credentials.
|
||||
func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (statusMessage, token string, _ error) {
|
||||
// TODO Use ctx when searching for repositories
|
||||
registryHostName := IndexHostname
|
||||
|
||||
if authConfig.ServerAddress != "" {
|
||||
serverAddress := authConfig.ServerAddress
|
||||
if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") {
|
||||
serverAddress = "https://" + serverAddress
|
||||
}
|
||||
u, err := url.Parse(serverAddress)
|
||||
if err != nil {
|
||||
return "", "", invalidParamWrapf(err, "unable to parse server address")
|
||||
}
|
||||
registryHostName = u.Host
|
||||
}
|
||||
|
||||
// Lookup endpoints for authentication but exclude mirrors to prevent
|
||||
// sending credentials of the upstream registry to a mirror.
|
||||
s.mu.RLock()
|
||||
endpoints, err := s.lookupV2Endpoints(ctx, registryHostName, false)
|
||||
s.mu.RUnlock()
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return "", "", err
|
||||
}
|
||||
return "", "", invalidParam(err)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, endpoint := range endpoints {
|
||||
authToken, err := loginV2(ctx, authConfig, endpoint, userAgent)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || cerrdefs.IsUnauthorized(err) {
|
||||
// Failed to authenticate; don't continue with (non-TLS) endpoints.
|
||||
return "", "", err
|
||||
}
|
||||
// Try next endpoint
|
||||
log.G(ctx).WithFields(log.Fields{
|
||||
"error": err,
|
||||
"endpoint": endpoint,
|
||||
}).Infof("Error logging in to endpoint, trying next endpoint")
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO(thaJeztah): move the statusMessage to the API endpoint; we don't need to produce that here?
|
||||
return "Login Succeeded", authToken, nil
|
||||
}
|
||||
|
||||
return "", "", lastErr
|
||||
}
|
||||
|
||||
// ResolveRepository splits a repository name into its components
|
||||
// and configuration of the associated registry.
|
||||
//
|
||||
// Deprecated: this function was only used internally and is no longer used. It will be removed in the next release.
|
||||
func (s *Service) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
// TODO(thaJeztah): remove error return as it's no longer used.
|
||||
return newRepositoryInfo(s.config, name), nil
|
||||
}
|
||||
|
||||
// ResolveAuthConfig looks up authentication for the given reference from the
|
||||
// given authConfigs.
|
||||
//
|
||||
// IMPORTANT: This function is for internal use and should not be used by external projects.
|
||||
func (s *Service) ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, ref reference.Named) registry.AuthConfig {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
// Simplified version of "newIndexInfo" without handling of insecure
|
||||
// registries and mirrors, as we don't need that information to resolve
|
||||
// the auth-config.
|
||||
indexName := normalizeIndexName(reference.Domain(ref))
|
||||
registryInfo, ok := s.config.IndexConfigs[indexName]
|
||||
if !ok {
|
||||
registryInfo = ®istry.IndexInfo{Name: indexName}
|
||||
}
|
||||
return ResolveAuthConfig(authConfigs, registryInfo)
|
||||
}
|
||||
|
||||
// APIEndpoint represents a remote API endpoint
|
||||
type APIEndpoint struct {
|
||||
Mirror bool
|
||||
URL *url.URL
|
||||
AllowNondistributableArtifacts bool // Deprecated: non-distributable artifacts are deprecated and enabled by default. This field will be removed in the next release.
|
||||
Official bool // Deprecated: this field was only used internally, and will be removed in the next release.
|
||||
TrimHostname bool // Deprecated: hostname is now trimmed unconditionally for remote names. This field will be removed in the next release.
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
// LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference.
|
||||
// It gives preference to mirrors over the actual registry, and HTTPS over plain HTTP.
|
||||
func (s *Service) LookupPullEndpoints(hostname string) ([]APIEndpoint, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
return s.lookupV2Endpoints(context.TODO(), hostname, true)
|
||||
}
|
||||
|
||||
// LookupPushEndpoints creates a list of v2 endpoints to try to push to, in order of preference.
|
||||
// It gives preference to HTTPS over plain HTTP. Mirrors are not included.
|
||||
func (s *Service) LookupPushEndpoints(hostname string) ([]APIEndpoint, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
return s.lookupV2Endpoints(context.TODO(), hostname, false)
|
||||
}
|
||||
|
||||
// IsInsecureRegistry returns true if the registry at given host is configured as
|
||||
// insecure registry.
|
||||
func (s *Service) IsInsecureRegistry(host string) bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return !s.config.isSecureIndex(host)
|
||||
}
|
||||
74
vendor/github.com/docker/docker/registry/service_v2.go
generated
vendored
74
vendor/github.com/docker/docker/registry/service_v2.go
generated
vendored
@ -1,74 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
)
|
||||
|
||||
func (s *Service) lookupV2Endpoints(ctx context.Context, hostname string, includeMirrors bool) ([]APIEndpoint, error) {
|
||||
var endpoints []APIEndpoint
|
||||
if hostname == DefaultNamespace || hostname == IndexHostname {
|
||||
if includeMirrors {
|
||||
for _, mirror := range s.config.Mirrors {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") {
|
||||
mirror = "https://" + mirror
|
||||
}
|
||||
mirrorURL, err := url.Parse(mirror)
|
||||
if err != nil {
|
||||
return nil, invalidParam(err)
|
||||
}
|
||||
// TODO(thaJeztah); this should all be memoized when loading the config. We're resolving mirrors and loading TLS config every time.
|
||||
mirrorTLSConfig, err := newTLSConfig(ctx, mirrorURL.Host, s.config.isSecureIndex(mirrorURL.Host))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoints = append(endpoints, APIEndpoint{
|
||||
URL: mirrorURL,
|
||||
Mirror: true,
|
||||
TLSConfig: mirrorTLSConfig,
|
||||
})
|
||||
}
|
||||
}
|
||||
endpoints = append(endpoints, APIEndpoint{
|
||||
URL: DefaultV2Registry,
|
||||
Official: true,
|
||||
TLSConfig: tlsconfig.ServerDefault(),
|
||||
})
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
tlsConfig, err := newTLSConfig(ctx, hostname, s.config.isSecureIndex(hostname))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoints = []APIEndpoint{
|
||||
{
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: hostname,
|
||||
},
|
||||
TLSConfig: tlsConfig,
|
||||
},
|
||||
}
|
||||
|
||||
if tlsConfig.InsecureSkipVerify {
|
||||
endpoints = append(endpoints, APIEndpoint{
|
||||
URL: &url.URL{
|
||||
Scheme: "http",
|
||||
Host: hostname,
|
||||
},
|
||||
// used to check if supposed to be secure via InsecureSkipVerify
|
||||
TLSConfig: tlsConfig,
|
||||
})
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
24
vendor/github.com/docker/docker/registry/types.go
generated
vendored
24
vendor/github.com/docker/docker/registry/types.go
generated
vendored
@ -1,24 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
)
|
||||
|
||||
// RepositoryInfo describes a repository
|
||||
type RepositoryInfo struct {
|
||||
Name reference.Named
|
||||
// Index points to registry information
|
||||
Index *registry.IndexInfo
|
||||
// Official indicates whether the repository is considered official.
|
||||
// If the registry is official, and the normalized name does not
|
||||
// contain a '/' (e.g. "foo"), then it is considered an official repo.
|
||||
//
|
||||
// Deprecated: this field is no longer used and will be removed in the next release. The information captured in this field can be obtained from the [Name] field instead.
|
||||
Official bool
|
||||
// Class represents the class of the repository, such as "plugin"
|
||||
// or "image".
|
||||
//
|
||||
// Deprecated: this field is no longer used, and will be removed in the next release.
|
||||
Class string
|
||||
}
|
||||
Reference in New Issue
Block a user