diff --git a/cmd/skopeo/copy.go b/cmd/skopeo/copy.go index a0f1602b..68ea5a2e 100644 --- a/cmd/skopeo/copy.go +++ b/cmd/skopeo/copy.go @@ -19,31 +19,34 @@ import ( ) type copyOptions struct { - global *globalOptions - srcImage *imageOptions - destImage *imageDestOptions - retryOpts *retry.RetryOptions - additionalTags []string // For docker-archive: destinations, in addition to the name:tag specified as destination, also add these - removeSignatures bool // Do not copy signatures from the source image - signByFingerprint string // Sign the image using a GPG key with the specified fingerprint - digestFile string // Write digest to this file - format optionalString // Force conversion of the image to a specified format - quiet bool // Suppress output information when copying images - all bool // Copy all of the images if the source is a list - encryptLayer []int // The list of layers to encrypt - encryptionKeys []string // Keys needed to encrypt the image - decryptionKeys []string // Keys needed to decrypt the image + global *globalOptions + deprecatedTLSVerify *deprecatedTLSVerifyOption + srcImage *imageOptions + destImage *imageDestOptions + retryOpts *retry.RetryOptions + additionalTags []string // For docker-archive: destinations, in addition to the name:tag specified as destination, also add these + removeSignatures bool // Do not copy signatures from the source image + signByFingerprint string // Sign the image using a GPG key with the specified fingerprint + digestFile string // Write digest to this file + format optionalString // Force conversion of the image to a specified format + quiet bool // Suppress output information when copying images + all bool // Copy all of the images if the source is a list + encryptLayer []int // The list of layers to encrypt + encryptionKeys []string // Keys needed to encrypt the image + decryptionKeys []string // Keys needed to decrypt the image } func copyCmd(global *globalOptions) *cobra.Command { sharedFlags, sharedOpts := sharedImageFlags() - srcFlags, srcOpts := imageFlags(global, sharedOpts, "src-", "screds") - destFlags, destOpts := imageDestFlags(global, sharedOpts, "dest-", "dcreds") + deprecatedTLSVerifyFlags, deprecatedTLSVerifyOpt := deprecatedTLSVerifyFlags() + srcFlags, srcOpts := imageFlags(global, sharedOpts, deprecatedTLSVerifyOpt, "src-", "screds") + destFlags, destOpts := imageDestFlags(global, sharedOpts, deprecatedTLSVerifyOpt, "dest-", "dcreds") retryFlags, retryOpts := retryFlags() opts := copyOptions{global: global, - srcImage: srcOpts, - destImage: destOpts, - retryOpts: retryOpts, + deprecatedTLSVerify: deprecatedTLSVerifyOpt, + srcImage: srcOpts, + destImage: destOpts, + retryOpts: retryOpts, } cmd := &cobra.Command{ Use: "copy [command options] SOURCE-IMAGE DESTINATION-IMAGE", @@ -61,6 +64,7 @@ See skopeo(1) section "IMAGE NAMES" for the expected format adjustUsage(cmd) flags := cmd.Flags() flags.AddFlagSet(&sharedFlags) + flags.AddFlagSet(&deprecatedTLSVerifyFlags) flags.AddFlagSet(&srcFlags) flags.AddFlagSet(&destFlags) flags.AddFlagSet(&retryFlags) @@ -81,6 +85,7 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error { if len(args) != 2 { return errorShouldDisplayUsage{errors.New("Exactly two arguments expected")} } + opts.deprecatedTLSVerify.warnIfUsed([]string{"--src-tls-verify", "--dest-tls-verify"}) imageNames := args if err := reexecIfNecessaryForImages(imageNames...); err != nil { diff --git a/cmd/skopeo/delete.go b/cmd/skopeo/delete.go index 444b5697..b02fd9bf 100644 --- a/cmd/skopeo/delete.go +++ b/cmd/skopeo/delete.go @@ -20,7 +20,7 @@ type deleteOptions struct { func deleteCmd(global *globalOptions) *cobra.Command { sharedFlags, sharedOpts := sharedImageFlags() - imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "") + imageFlags, imageOpts := imageFlags(global, sharedOpts, nil, "", "") retryFlags, retryOpts := retryFlags() opts := deleteOptions{ global: global, diff --git a/cmd/skopeo/inspect.go b/cmd/skopeo/inspect.go index d7d51238..a8f72473 100644 --- a/cmd/skopeo/inspect.go +++ b/cmd/skopeo/inspect.go @@ -34,7 +34,7 @@ type inspectOptions struct { func inspectCmd(global *globalOptions) *cobra.Command { sharedFlags, sharedOpts := sharedImageFlags() - imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "") + imageFlags, imageOpts := imageFlags(global, sharedOpts, nil, "", "") retryFlags, retryOpts := retryFlags() opts := inspectOptions{ global: global, diff --git a/cmd/skopeo/layers.go b/cmd/skopeo/layers.go index bbb0e456..514572f7 100644 --- a/cmd/skopeo/layers.go +++ b/cmd/skopeo/layers.go @@ -25,7 +25,7 @@ type layersOptions struct { func layersCmd(global *globalOptions) *cobra.Command { sharedFlags, sharedOpts := sharedImageFlags() - imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "") + imageFlags, imageOpts := imageFlags(global, sharedOpts, nil, "", "") retryFlags, retryOpts := retryFlags() opts := layersOptions{ global: global, diff --git a/cmd/skopeo/list_tags.go b/cmd/skopeo/list_tags.go index 96c112a6..4486429d 100644 --- a/cmd/skopeo/list_tags.go +++ b/cmd/skopeo/list_tags.go @@ -30,7 +30,7 @@ type tagsOptions struct { func tagsCmd(global *globalOptions) *cobra.Command { sharedFlags, sharedOpts := sharedImageFlags() - imageFlags, imageOpts := dockerImageFlags(global, sharedOpts, "", "") + imageFlags, imageOpts := dockerImageFlags(global, sharedOpts, nil, "", "") retryFlags, retryOpts := retryFlags() opts := tagsOptions{ diff --git a/cmd/skopeo/main.go b/cmd/skopeo/main.go index 89c4f9f0..85a80e1e 100644 --- a/cmd/skopeo/main.go +++ b/cmd/skopeo/main.go @@ -50,6 +50,12 @@ func createApp() (*cobra.Command, *globalOptions) { // already making us of that. If Skopeo decides to follow, please // remove the line below (and hide the `completion` command). CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true}, + // This is documented to parse "local" (non-PersistentFlags) flags of parent commands before + // running subcommands and handling their options. We don't really run into such cases, + // because all of our flags on rootCommand are in PersistentFlags, except for the deprecated --tls-verify; + // in that case we need TraverseChildren so that we can distinguish between + // (skopeo --tls-verify inspect) (causes a warning) and (skopeo inspect --tls-verify) (no warning). + TraverseChildren: true, } if gitCommit != "" { rootCommand.Version = fmt.Sprintf("%s commit: %s", version.Version, gitCommit) @@ -60,8 +66,6 @@ func createApp() (*cobra.Command, *globalOptions) { var dummyVersion bool rootCommand.Flags().BoolVarP(&dummyVersion, "version", "v", false, "Version for Skopeo") rootCommand.PersistentFlags().BoolVar(&opts.debug, "debug", false, "enable debug output") - flag := optionalBoolFlag(rootCommand.PersistentFlags(), &opts.tlsVerify, "tls-verify", "Require HTTPS and verify certificates when accessing the registry") - flag.Hidden = true rootCommand.PersistentFlags().StringVar(&opts.policyPath, "policy", "", "Path to a trust policy file") rootCommand.PersistentFlags().BoolVar(&opts.insecurePolicy, "insecure-policy", false, "run the tool without any policy check") rootCommand.PersistentFlags().StringVar(&opts.registriesDirPath, "registries.d", "", "use registry configuration files in `DIR` (e.g. for container signature storage)") @@ -74,6 +78,8 @@ func createApp() (*cobra.Command, *globalOptions) { logrus.Fatal("unable to mark registries-conf flag as hidden") } rootCommand.PersistentFlags().StringVar(&opts.tmpDir, "tmpdir", "", "directory used to store temporary files") + flag := optionalBoolFlag(rootCommand.Flags(), &opts.tlsVerify, "tls-verify", "Require HTTPS and verify certificates when accessing the registry") + flag.Hidden = true rootCommand.AddCommand( copyCmd(&opts), deleteCmd(&opts), diff --git a/cmd/skopeo/sync.go b/cmd/skopeo/sync.go index ccc6f59f..f6b04483 100644 --- a/cmd/skopeo/sync.go +++ b/cmd/skopeo/sync.go @@ -27,17 +27,18 @@ import ( // syncOptions contains information retrieved from the skopeo sync command line. type syncOptions struct { - global *globalOptions // Global (not command dependent) skopeo options - srcImage *imageOptions // Source image options - destImage *imageDestOptions // Destination image options - retryOpts *retry.RetryOptions - removeSignatures bool // Do not copy signatures from the source image - signByFingerprint string // Sign the image using a GPG key with the specified fingerprint - format optionalString // Force conversion of the image to a specified format - source string // Source repository name - destination string // Destination registry name - scoped bool // When true, namespace copied images at destination using the source repository name - all bool // Copy all of the images if an image in the source is a list + global *globalOptions // Global (not command dependent) skopeo options + deprecatedTLSVerify *deprecatedTLSVerifyOption + srcImage *imageOptions // Source image options + destImage *imageDestOptions // Destination image options + retryOpts *retry.RetryOptions + removeSignatures bool // Do not copy signatures from the source image + signByFingerprint string // Sign the image using a GPG key with the specified fingerprint + format optionalString // Force conversion of the image to a specified format + source string // Source repository name + destination string // Destination registry name + scoped bool // When true, namespace copied images at destination using the source repository name + all bool // Copy all of the images if an image in the source is a list } // repoDescriptor contains information of a single repository used as a sync source. @@ -68,15 +69,17 @@ type sourceConfig map[string]registrySyncConfig func syncCmd(global *globalOptions) *cobra.Command { sharedFlags, sharedOpts := sharedImageFlags() - srcFlags, srcOpts := dockerImageFlags(global, sharedOpts, "src-", "screds") - destFlags, destOpts := dockerImageFlags(global, sharedOpts, "dest-", "dcreds") + deprecatedTLSVerifyFlags, deprecatedTLSVerifyOpt := deprecatedTLSVerifyFlags() + srcFlags, srcOpts := dockerImageFlags(global, sharedOpts, deprecatedTLSVerifyOpt, "src-", "screds") + destFlags, destOpts := dockerImageFlags(global, sharedOpts, deprecatedTLSVerifyOpt, "dest-", "dcreds") retryFlags, retryOpts := retryFlags() opts := syncOptions{ - global: global, - srcImage: srcOpts, - destImage: &imageDestOptions{imageOptions: destOpts}, - retryOpts: retryOpts, + global: global, + deprecatedTLSVerify: deprecatedTLSVerifyOpt, + srcImage: srcOpts, + destImage: &imageDestOptions{imageOptions: destOpts}, + retryOpts: retryOpts, } cmd := &cobra.Command{ @@ -102,6 +105,7 @@ See skopeo-sync(1) for details. flags.BoolVar(&opts.scoped, "scoped", false, "Images at DESTINATION are prefix using the full source image path as scope") flags.BoolVarP(&opts.all, "all", "a", false, "Copy all images if SOURCE-IMAGE is a list") flags.AddFlagSet(&sharedFlags) + flags.AddFlagSet(&deprecatedTLSVerifyFlags) flags.AddFlagSet(&srcFlags) flags.AddFlagSet(&destFlags) flags.AddFlagSet(&retryFlags) @@ -492,6 +496,7 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) error { if len(args) != 2 { return errorShouldDisplayUsage{errors.New("Exactly two arguments expected")} } + opts.deprecatedTLSVerify.warnIfUsed([]string{"--src-tls-verify", "--dest-tls-verify"}) policyContext, err := opts.global.getPolicyContext() if err != nil { diff --git a/cmd/skopeo/utils.go b/cmd/skopeo/utils.go index dae03663..d679a842 100644 --- a/cmd/skopeo/utils.go +++ b/cmd/skopeo/utils.go @@ -14,6 +14,7 @@ import ( "github.com/containers/image/v5/types" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -38,6 +39,35 @@ func commandAction(handler func(args []string, stdout io.Writer) error) func(cmd } } +// deprecatedTLSVerifyOption represents a deprecated --tls-verify option, +// which was accepted for all subcommands, for a time. +// Every user should call deprecatedTLSVerifyOption.warnIfUsed() as part of handling the CLI, +// whether or not the value actually ends up being used. +// DO NOT ADD ANY NEW USES OF THIS; just call dockerImageFlags with an appropriate, possibly empty, flagPrefix. +type deprecatedTLSVerifyOption struct { + tlsVerify optionalBool // FIXME FIXME: Warn if this is used, or even if it is ignored. +} + +// warnIfUsed warns if tlsVerify was set by the user, and suggests alternatives (which should +// start with "--"). +// Every user should call this as part of handling the CLI, whether or not the value actually +// ends up being used. +func (opts *deprecatedTLSVerifyOption) warnIfUsed(alternatives []string) { + if opts.tlsVerify.present { + logrus.Warnf("'--tls-verify' is deprecated, instead use: %s", strings.Join(alternatives, ", ")) + } +} + +// deprecatedTLSVerifyFlags prepares the CLI flag writing into deprecatedTLSVerifyOption, and the managed deprecatedTLSVerifyOption structure. +// DO NOT ADD ANY NEW USES OF THIS; just call dockerImageFlags with an appropriate, possibly empty, flagPrefix. +func deprecatedTLSVerifyFlags() (pflag.FlagSet, *deprecatedTLSVerifyOption) { + opts := deprecatedTLSVerifyOption{} + fs := pflag.FlagSet{} + flag := optionalBoolFlag(&fs, &opts.tlsVerify, "tls-verify", "require HTTPS and verify certificates when accessing the container registry (defaults to true)") + flag.Hidden = true + return fs, &opts +} + // sharedImageOptions collects CLI flags which are image-related, but do not change across images. // This really should be a part of globalOptions, but that would break existing users of (skopeo copy --authfile=). type sharedImageOptions struct { @@ -56,14 +86,15 @@ func sharedImageFlags() (pflag.FlagSet, *sharedImageOptions) { // the same across subcommands, but may be different for each image // (e.g. may differ between the source and destination of a copy) type dockerImageOptions struct { - global *globalOptions // May be shared across several imageOptions instances. - shared *sharedImageOptions // May be shared across several imageOptions instances. - authFilePath optionalString // Path to a */containers/auth.json (prefixed version to override shared image option). - credsOption optionalString // username[:password] for accessing a registry - registryToken optionalString // token to be used directly as a Bearer token when accessing the registry - dockerCertPath string // A directory using Docker-like *.{crt,cert,key} files for connecting to a registry or a daemon - tlsVerify optionalBool // Require HTTPS and verify certificates (for docker: and docker-daemon:) - noCreds bool // Access the registry anonymously + global *globalOptions // May be shared across several imageOptions instances. + shared *sharedImageOptions // May be shared across several imageOptions instances. + deprecatedTLSVerify *deprecatedTLSVerifyOption // May be shared across several imageOptions instances, or nil. + authFilePath optionalString // Path to a */containers/auth.json (prefixed version to override shared image option). + credsOption optionalString // username[:password] for accessing a registry + registryToken optionalString // token to be used directly as a Bearer token when accessing the registry + dockerCertPath string // A directory using Docker-like *.{crt,cert,key} files for connecting to a registry or a daemon + tlsVerify optionalBool // Require HTTPS and verify certificates (for docker: and docker-daemon:) + noCreds bool // Access the registry anonymously } // imageOptions collects CLI flags which are the same across subcommands, but may be different for each image @@ -76,11 +107,12 @@ type imageOptions struct { // dockerImageFlags prepares a collection of docker-transport specific CLI flags // writing into imageOptions, and the managed imageOptions structure. -func dockerImageFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) (pflag.FlagSet, *imageOptions) { +func dockerImageFlags(global *globalOptions, shared *sharedImageOptions, deprecatedTLSVerify *deprecatedTLSVerifyOption, flagPrefix, credsOptionAlias string) (pflag.FlagSet, *imageOptions) { flags := imageOptions{ dockerImageOptions: dockerImageOptions{ - global: global, - shared: shared, + global: global, + shared: shared, + deprecatedTLSVerify: deprecatedTLSVerify, }, } @@ -104,8 +136,8 @@ func dockerImageFlags(global *globalOptions, shared *sharedImageOptions, flagPre } // imageFlags prepares a collection of CLI flags writing into imageOptions, and the managed imageOptions structure. -func imageFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) (pflag.FlagSet, *imageOptions) { - dockerFlags, opts := dockerImageFlags(global, shared, flagPrefix, credsOptionAlias) +func imageFlags(global *globalOptions, shared *sharedImageOptions, deprecatedTLSVerify *deprecatedTLSVerifyOption, flagPrefix, credsOptionAlias string) (pflag.FlagSet, *imageOptions) { + dockerFlags, opts := dockerImageFlags(global, shared, deprecatedTLSVerify, flagPrefix, credsOptionAlias) fs := pflag.FlagSet{} fs.StringVar(&opts.sharedBlobDir, flagPrefix+"shared-blob-dir", "", "`DIRECTORY` to use to share blobs across OCI repositories") @@ -135,6 +167,10 @@ func (opts *imageOptions) newSystemContext() (*types.SystemContext, error) { if opts.dockerImageOptions.authFilePath.present { ctx.AuthFilePath = opts.dockerImageOptions.authFilePath.value } + if opts.deprecatedTLSVerify != nil && opts.deprecatedTLSVerify.tlsVerify.present { + // If both this deprecated option and a non-deprecated option is present, we use the latter value. + ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.deprecatedTLSVerify.tlsVerify.value) + } if opts.tlsVerify.present { ctx.DockerDaemonInsecureSkipTLSVerify = !opts.tlsVerify.value } @@ -171,8 +207,8 @@ type imageDestOptions struct { } // imageDestFlags prepares a collection of CLI flags writing into imageDestOptions, and the managed imageDestOptions structure. -func imageDestFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) (pflag.FlagSet, *imageDestOptions) { - genericFlags, genericOptions := imageFlags(global, shared, flagPrefix, credsOptionAlias) +func imageDestFlags(global *globalOptions, shared *sharedImageOptions, deprecatedTLSVerify *deprecatedTLSVerifyOption, flagPrefix, credsOptionAlias string) (pflag.FlagSet, *imageDestOptions) { + genericFlags, genericOptions := imageFlags(global, shared, deprecatedTLSVerify, flagPrefix, credsOptionAlias) opts := imageDestOptions{imageOptions: genericOptions} fs := pflag.FlagSet{} fs.AddFlagSet(&genericFlags) diff --git a/cmd/skopeo/utils_test.go b/cmd/skopeo/utils_test.go index 0a00ed01..a6076b94 100644 --- a/cmd/skopeo/utils_test.go +++ b/cmd/skopeo/utils_test.go @@ -8,6 +8,7 @@ import ( "github.com/containers/image/v5/types" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -17,17 +18,26 @@ func fakeGlobalOptions(t *testing.T, flags []string) (*globalOptions, *cobra.Com app, opts := createApp() cmd := &cobra.Command{} app.AddCommand(cmd) - err := cmd.ParseFlags(flags) + err := app.ParseFlags(flags) require.NoError(t, err) return opts, cmd } // fakeImageOptions creates imageOptions and sets it according to globalFlags/cmdFlags. -func fakeImageOptions(t *testing.T, flagPrefix string, globalFlags []string, cmdFlags []string) *imageOptions { +func fakeImageOptions(t *testing.T, flagPrefix string, useDeprecatedTLSVerify bool, + globalFlags []string, cmdFlags []string) *imageOptions { globalOpts, cmd := fakeGlobalOptions(t, globalFlags) sharedFlags, sharedOpts := sharedImageFlags() - imageFlags, imageOpts := imageFlags(globalOpts, sharedOpts, flagPrefix, "") + var deprecatedTLSVerifyFlag pflag.FlagSet + var deprecatedTLSVerifyOpt *deprecatedTLSVerifyOption + if useDeprecatedTLSVerify { + deprecatedTLSVerifyFlag, deprecatedTLSVerifyOpt = deprecatedTLSVerifyFlags() + } + imageFlags, imageOpts := imageFlags(globalOpts, sharedOpts, deprecatedTLSVerifyOpt, flagPrefix, "") cmd.Flags().AddFlagSet(&sharedFlags) + if useDeprecatedTLSVerify { + cmd.Flags().AddFlagSet(&deprecatedTLSVerifyFlag) + } cmd.Flags().AddFlagSet(&imageFlags) err := cmd.ParseFlags(cmdFlags) require.NoError(t, err) @@ -36,7 +46,7 @@ func fakeImageOptions(t *testing.T, flagPrefix string, globalFlags []string, cmd func TestImageOptionsNewSystemContext(t *testing.T) { // Default state - opts := fakeImageOptions(t, "dest-", []string{}, []string{}) + opts := fakeImageOptions(t, "dest-", true, []string{}, []string{}) res, err := opts.newSystemContext() require.NoError(t, err) assert.Equal(t, &types.SystemContext{ @@ -44,7 +54,7 @@ func TestImageOptionsNewSystemContext(t *testing.T) { }, res) // Set everything to non-default values. - opts = fakeImageOptions(t, "dest-", []string{ + opts = fakeImageOptions(t, "dest-", true, []string{ "--registries.d", "/srv/registries.d", "--override-arch", "overridden-arch", "--override-os", "overridden-os", @@ -83,17 +93,26 @@ func TestImageOptionsNewSystemContext(t *testing.T) { // Global/per-command tlsVerify behavior is tested in TestTLSVerifyFlags. // Invalid option values - opts = fakeImageOptions(t, "dest-", []string{}, []string{"--dest-creds", ""}) + opts = fakeImageOptions(t, "dest-", true, []string{}, []string{"--dest-creds", ""}) _, err = opts.newSystemContext() assert.Error(t, err) } // fakeImageDestOptions creates imageDestOptions and sets it according to globalFlags/cmdFlags. -func fakeImageDestOptions(t *testing.T, flagPrefix string, globalFlags []string, cmdFlags []string) *imageDestOptions { +func fakeImageDestOptions(t *testing.T, flagPrefix string, useDeprecatedTLSVerify bool, + globalFlags []string, cmdFlags []string) *imageDestOptions { globalOpts, cmd := fakeGlobalOptions(t, globalFlags) sharedFlags, sharedOpts := sharedImageFlags() - imageFlags, imageOpts := imageDestFlags(globalOpts, sharedOpts, flagPrefix, "") + var deprecatedTLSVerifyFlag pflag.FlagSet + var deprecatedTLSVerifyOpt *deprecatedTLSVerifyOption + if useDeprecatedTLSVerify { + deprecatedTLSVerifyFlag, deprecatedTLSVerifyOpt = deprecatedTLSVerifyFlags() + } + imageFlags, imageOpts := imageDestFlags(globalOpts, sharedOpts, deprecatedTLSVerifyOpt, flagPrefix, "") cmd.Flags().AddFlagSet(&sharedFlags) + if useDeprecatedTLSVerify { + cmd.Flags().AddFlagSet(&deprecatedTLSVerifyFlag) + } cmd.Flags().AddFlagSet(&imageFlags) err := cmd.ParseFlags(cmdFlags) require.NoError(t, err) @@ -102,7 +121,7 @@ func fakeImageDestOptions(t *testing.T, flagPrefix string, globalFlags []string, func TestImageDestOptionsNewSystemContext(t *testing.T) { // Default state - opts := fakeImageDestOptions(t, "dest-", []string{}, []string{}) + opts := fakeImageDestOptions(t, "dest-", true, []string{}, []string{}) res, err := opts.newSystemContext() require.NoError(t, err) assert.Equal(t, &types.SystemContext{ @@ -122,7 +141,7 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) { os.Setenv("REGISTRY_AUTH_FILE", authFile) // Explicitly set everything to default, except for when the default is “not present” - opts = fakeImageDestOptions(t, "dest-", []string{}, []string{ + opts = fakeImageDestOptions(t, "dest-", true, []string{}, []string{ "--dest-compress=false", }) res, err = opts.newSystemContext() @@ -133,7 +152,7 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) { }, res) // Set everything to non-default values. - opts = fakeImageDestOptions(t, "dest-", []string{ + opts = fakeImageDestOptions(t, "dest-", true, []string{ "--registries.d", "/srv/registries.d", "--override-arch", "overridden-arch", "--override-os", "overridden-os", @@ -173,7 +192,7 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) { // Global/per-command tlsVerify behavior is tested in TestTLSVerifyFlags. // Invalid option values in imageOptions - opts = fakeImageDestOptions(t, "dest-", []string{}, []string{"--dest-creds", ""}) + opts = fakeImageDestOptions(t, "dest-", true, []string{}, []string{"--dest-creds", ""}) _, err = opts.newSystemContext() assert.Error(t, err) } @@ -185,50 +204,84 @@ func TestTLSVerifyFlags(t *testing.T) { for _, creator := range []struct { name string - newOpts func(globalFlags, cmdFlags []string) systemContextOpts + newOpts func(useDeprecatedTLSVerify bool, globalFlags, cmdFlags []string) systemContextOpts }{ { "imageFlags", - func(globalFlags, cmdFlags []string) systemContextOpts { - return fakeImageOptions(t, "dest-", globalFlags, cmdFlags) + func(useDeprecatedTLSVerify bool, globalFlags, cmdFlags []string) systemContextOpts { + return fakeImageOptions(t, "dest-", useDeprecatedTLSVerify, globalFlags, cmdFlags) }, }, { "imageDestFlags", - func(globalFlags, cmdFlags []string) systemContextOpts { - return fakeImageDestOptions(t, "dest-", globalFlags, cmdFlags) + func(useDeprecatedTLSVerify bool, globalFlags, cmdFlags []string) systemContextOpts { + return fakeImageDestOptions(t, "dest-", useDeprecatedTLSVerify, globalFlags, cmdFlags) }, }, } { t.Run(creator.name, func(t *testing.T) { for _, c := range []struct { - global, cmd string - expectedDocker types.OptionalBool - expectedDockerDaemon bool + global, deprecatedCmd, cmd string + expectedDocker types.OptionalBool + expectedDockerDaemon bool }{ - {"", "", types.OptionalBoolUndefined, false}, - {"", "false", types.OptionalBoolTrue, true}, - {"", "true", types.OptionalBoolFalse, false}, - {"false", "", types.OptionalBoolTrue, false}, - {"false", "false", types.OptionalBoolTrue, true}, - {"false", "true", types.OptionalBoolFalse, false}, - {"true", "", types.OptionalBoolFalse, false}, - {"true", "false", types.OptionalBoolTrue, true}, - {"true", "true", types.OptionalBoolFalse, false}, + {"", "", "", types.OptionalBoolUndefined, false}, + {"", "", "false", types.OptionalBoolTrue, true}, + {"", "", "true", types.OptionalBoolFalse, false}, + {"", "false", "", types.OptionalBoolTrue, false}, + {"", "false", "false", types.OptionalBoolTrue, true}, + {"", "false", "true", types.OptionalBoolFalse, false}, + {"", "true", "", types.OptionalBoolFalse, false}, + {"", "true", "false", types.OptionalBoolTrue, true}, + {"", "true", "true", types.OptionalBoolFalse, false}, + {"false", "", "", types.OptionalBoolTrue, false}, + {"false", "", "false", types.OptionalBoolTrue, true}, + {"false", "", "true", types.OptionalBoolFalse, false}, + {"false", "false", "", types.OptionalBoolTrue, false}, + {"false", "false", "false", types.OptionalBoolTrue, true}, + {"false", "false", "true", types.OptionalBoolFalse, false}, + {"false", "true", "", types.OptionalBoolFalse, false}, + {"false", "true", "false", types.OptionalBoolTrue, true}, + {"false", "true", "true", types.OptionalBoolFalse, false}, + {"true", "", "", types.OptionalBoolFalse, false}, + {"true", "", "false", types.OptionalBoolTrue, true}, + {"true", "", "true", types.OptionalBoolFalse, false}, + {"true", "false", "", types.OptionalBoolTrue, false}, + {"true", "false", "false", types.OptionalBoolTrue, true}, + {"true", "false", "true", types.OptionalBoolFalse, false}, + {"true", "true", "", types.OptionalBoolFalse, false}, + {"true", "true", "false", types.OptionalBoolTrue, true}, + {"true", "true", "true", types.OptionalBoolFalse, false}, } { globalFlags := []string{} if c.global != "" { globalFlags = append(globalFlags, "--tls-verify="+c.global) } cmdFlags := []string{} + if c.deprecatedCmd != "" { + cmdFlags = append(cmdFlags, "--tls-verify="+c.deprecatedCmd) + } if c.cmd != "" { cmdFlags = append(cmdFlags, "--dest-tls-verify="+c.cmd) } - opts := creator.newOpts(globalFlags, cmdFlags) + opts := creator.newOpts(true, globalFlags, cmdFlags) res, err := opts.newSystemContext() require.NoError(t, err) assert.Equal(t, c.expectedDocker, res.DockerInsecureSkipTLSVerify, "%#v", c) assert.Equal(t, c.expectedDockerDaemon, res.DockerDaemonInsecureSkipTLSVerify, "%#v", c) + + if c.deprecatedCmd == "" { // Test also the behavior when deprecatedTLSFlag is not recognized + // Use globalFlags from the previous test + cmdFlags := []string{} + if c.cmd != "" { + cmdFlags = append(cmdFlags, "--dest-tls-verify="+c.cmd) + } + opts := creator.newOpts(false, globalFlags, cmdFlags) + res, err = opts.newSystemContext() + require.NoError(t, err) + assert.Equal(t, c.expectedDocker, res.DockerInsecureSkipTLSVerify, "%#v", c) + assert.Equal(t, c.expectedDockerDaemon, res.DockerDaemonInsecureSkipTLSVerify, "%#v", c) + } } }) } @@ -300,7 +353,7 @@ func TestImageOptionsAuthfileOverride(t *testing.T) { }, "/srv/dest-authfile", }, } { - opts := fakeImageOptions(t, testCase.flagPrefix, []string{}, testCase.cmdFlags) + opts := fakeImageOptions(t, testCase.flagPrefix, false, []string{}, testCase.cmdFlags) res, err := opts.newSystemContext() require.NoError(t, err) diff --git a/docs/skopeo-delete.1.md b/docs/skopeo-delete.1.md index b2d22599..35931d35 100644 --- a/docs/skopeo-delete.1.md +++ b/docs/skopeo-delete.1.md @@ -60,6 +60,10 @@ The number of times to retry. Retry wait time will be exponentially increased ba Directory to use to share blobs across OCI repositories. +**--tls-verify**=_bool_ + +Require HTTPS and verify certificates when talking to the container registry or daemon (defaults to true) + ## EXAMPLES Mark image example/pause for deletion from the registry.example.com registry: diff --git a/docs/skopeo-inspect.1.md b/docs/skopeo-inspect.1.md index 7035f0c5..20a69bd9 100644 --- a/docs/skopeo-inspect.1.md +++ b/docs/skopeo-inspect.1.md @@ -65,6 +65,10 @@ The number of times to retry; retry wait time will be exponentially increased ba Directory to use to share blobs across OCI repositories. +**--tls-verify**=_bool_ + +Require HTTPS and verify certificates when talking to the container registry or daemon (defaults to true) + ## EXAMPLES To review information for the image fedora from the docker.io registry: diff --git a/docs/skopeo-list-tags.1.md b/docs/skopeo-list-tags.1.md index f8757da1..ee5679f4 100644 --- a/docs/skopeo-list-tags.1.md +++ b/docs/skopeo-list-tags.1.md @@ -39,6 +39,10 @@ Bearer token for accessing the registry. The number of times to retry. Retry wait time will be exponentially increased based on the number of failed attempts. +**--tls-verify**=_bool_ + +Require HTTPS and verify certificates when talking to the container registry or daemon (defaults to true) + ## REPOSITORY NAMES Repository names are transport-specific references as each transport may have its own concept of a "repository" and "tags". Currently, only the Docker transport is supported. diff --git a/docs/skopeo-login.1.md b/docs/skopeo-login.1.md index e1025b62..8e77e6ca 100644 --- a/docs/skopeo-login.1.md +++ b/docs/skopeo-login.1.md @@ -47,6 +47,10 @@ Default certificates directory is _/etc/containers/certs.d_. Print usage statement +**--tls-verify**=_bool_ + +Require HTTPS and verify certificates when talking to the container registry or daemon (defaults to true) + **--verbose**, **-v** Write more detailed information to stdout diff --git a/docs/skopeo-logout.1.md b/docs/skopeo-logout.1.md index fc562755..c5a1d783 100644 --- a/docs/skopeo-logout.1.md +++ b/docs/skopeo-logout.1.md @@ -29,6 +29,10 @@ Remove the cached credentials for all registries in the auth file Print usage statement +**--tls-verify**=_bool_ + +Require HTTPS and verify certificates when talking to the container registry or daemon (defaults to true) + ## EXAMPLES ```