99 "log"
1010 "net"
1111 "net/http"
12+ "net/url"
1213 "os"
1314 "path"
1415 "path/filepath"
@@ -24,6 +25,9 @@ import (
2425 "golang.org/x/term"
2526 "k8s.io/kubectl/pkg/util/templates"
2627
28+ "github.com/containers/image/v5/docker"
29+ "github.com/containers/image/v5/docker/reference"
30+ imgconfig "github.com/containers/image/v5/pkg/docker/config"
2731 "github.com/distribution/distribution/v3/configuration"
2832 "github.com/distribution/distribution/v3/registry"
2933 _ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
@@ -242,6 +246,14 @@ func NewMirrorCmd(log clog.PluggableLoggerInterface) *cobra.Command {
242246 defer ex .logFile .Close ()
243247 cmd .SetOutput (ex .logFile )
244248
249+ // NOTE: this is not in the `ex.Validate` function because it breaks unit tests
250+ if strings .Contains (args [0 ], dockerProtocol ) {
251+ registry := strings .TrimPrefix (args [0 ], dockerProtocol )
252+ if err := ex .checkRegistryAccess (cmd .Context (), registry ); err != nil {
253+ return fmt .Errorf ("checking registry %q access: %w" , registry , err )
254+ }
255+ }
256+
245257 // prepare internal storage
246258 if err := ex .setupLocalStorage (cmd .Context ()); err != nil {
247259 return err
@@ -399,6 +411,64 @@ func (o ExecutorSchema) Validate(dest []string) error {
399411
400412}
401413
414+ func (o * ExecutorSchema ) checkRegistryAccess (ctx context.Context , registry string ) error {
415+ o .Log .Debug ("Checking registry access to %q..." , registry )
416+
417+ sctx , err := o .Opts .DestImage .NewSystemContext ()
418+ if err != nil {
419+ return fmt .Errorf ("failed to get system context: %w" , err )
420+ }
421+
422+ key , reg , err := parseCredentialsKey (registry )
423+ if err != nil {
424+ return err
425+ }
426+
427+ authConfig , err := imgconfig .GetCredentials (sctx , key )
428+ if err != nil {
429+ return fmt .Errorf ("failed to find credentials: %w" , err )
430+ }
431+
432+ ctx , cancel := context .WithTimeout (ctx , 2 * time .Minute )
433+ defer cancel ()
434+ if err := docker .CheckAuth (ctx , sctx , authConfig .Username , authConfig .Password , reg ); err != nil {
435+ return fmt .Errorf ("failed to authenticate: %w" , err )
436+ }
437+
438+ o .Log .Info ("Verified we can authenticate against registry %q" , registry )
439+ return nil
440+ }
441+
442+ // See containers/common/pkg/auth.parseCredentialsKey
443+ func parseCredentialsKey (arg string ) (key string , registry string , err error ) {
444+ if strings .HasPrefix (arg , "http://" ) || strings .HasPrefix (arg , "https://" ) {
445+ url , err := url .Parse (arg )
446+ if err != nil {
447+ return "" , "" , fmt .Errorf ("failed to parse: %w" , err )
448+ }
449+ key = url .Host
450+ } else {
451+ key = arg
452+ }
453+ split := strings .Split (key , "/" )
454+ registry = split [0 ]
455+ if registry == key {
456+ return key , registry , nil
457+ }
458+ ref , err := reference .ParseNormalizedNamed (key )
459+ if err != nil {
460+ return "" , "" , fmt .Errorf ("parse reference %q: %w" , key , err )
461+ }
462+ if ! reference .IsNameOnly (ref ) {
463+ return "" , "" , fmt .Errorf ("reference %q contains tag or digest" , ref .String ())
464+ }
465+ refRegistry := reference .Domain (ref )
466+ if refRegistry != registry {
467+ return "" , "" , fmt .Errorf ("key %q registry mismatch %q vs %q" , key , registry , refRegistry )
468+ }
469+ return key , registry , nil
470+ }
471+
402472// Complete - do the final setup of modules
403473func (o * ExecutorSchema ) Complete (args []string ) error {
404474 if envOverride , ok := os .LookupEnv ("CONTAINERS_REGISTRIES_CONF" ); ok {
0 commit comments