diff --git a/cmd/skopeo/sync.go b/cmd/skopeo/sync.go index ce930f26..6ba05f5b 100644 --- a/cmd/skopeo/sync.go +++ b/cmd/skopeo/sync.go @@ -289,8 +289,11 @@ func imagesToCopyFromRegistry(registryName string, cfg registrySyncConfig, sourc // override ctx with per-registryName options serverCtx.DockerCertPath = cfg.CertDir serverCtx.DockerDaemonCertPath = cfg.CertDir - serverCtx.DockerDaemonInsecureSkipTLSVerify = (cfg.TLSVerify.skip == types.OptionalBoolTrue) - serverCtx.DockerInsecureSkipTLSVerify = cfg.TLSVerify.skip + // Only override TLS verification if explicitly specified in YAML; otherwise, keep CLI/global settings. + if cfg.TLSVerify.skip != types.OptionalBoolUndefined { + serverCtx.DockerDaemonInsecureSkipTLSVerify = (cfg.TLSVerify.skip == types.OptionalBoolTrue) + serverCtx.DockerInsecureSkipTLSVerify = cfg.TLSVerify.skip + } if cfg.Credentials != (types.DockerAuthConfig{}) { serverCtx.DockerAuthConfig = &cfg.Credentials } diff --git a/cmd/skopeo/sync_test.go b/cmd/skopeo/sync_test.go index c61a0cdb..c0ecf696 100644 --- a/cmd/skopeo/sync_test.go +++ b/cmd/skopeo/sync_test.go @@ -59,3 +59,132 @@ func TestSync(t *testing.T) { // FIXME: Much more test coverage // Actual feature tests exist in integration and systemtest } + +// TestTLSPrecedence_YAMLOmitted verifies that when YAML omits tls-verify, +// imagesToCopyFromRegistry preserves the incoming SystemContext values +// (e.g., from CLI/global flags) for both DockerInsecureSkipTLSVerify and +// DockerDaemonInsecureSkipTLSVerify. +func TestTLSPrecedence_YAMLOmitted(t *testing.T) { + baseRegistry := "example.com" + imageName := "repo" + cfgBase := registrySyncConfig{ + Images: map[string][]string{imageName: {"latest"}}, // avoid network + } + + tests := []struct { + name string + incomingSkip types.OptionalBool + incomingDaemonSkip bool + yamlSkip types.OptionalBool // OptionalBoolUndefined means YAML omitted + wantSkip types.OptionalBool + wantDaemonSkip bool + }{ + { + name: "YAML omitted preserves incoming skip=true", + incomingSkip: types.OptionalBoolTrue, + incomingDaemonSkip: true, + yamlSkip: types.OptionalBoolUndefined, + wantSkip: types.OptionalBoolTrue, + wantDaemonSkip: true, + }, + { + name: "YAML omitted preserves incoming skip=false (CLI pass verify)", + incomingSkip: types.OptionalBoolFalse, + incomingDaemonSkip: false, + yamlSkip: types.OptionalBoolUndefined, + wantSkip: types.OptionalBoolFalse, + wantDaemonSkip: false, + }, + { + name: "YAML omitted preserves daemon skip=true while docker skip=undefined", + incomingSkip: types.OptionalBoolUndefined, + incomingDaemonSkip: true, + yamlSkip: types.OptionalBoolUndefined, + wantSkip: types.OptionalBoolUndefined, + wantDaemonSkip: true, + }, + { + name: "YAML omitted preserves mismatched incoming (docker skip=true, daemon skip=false)", + incomingSkip: types.OptionalBoolTrue, + incomingDaemonSkip: false, + yamlSkip: types.OptionalBoolUndefined, + wantSkip: types.OptionalBoolTrue, + wantDaemonSkip: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + src := types.SystemContext{ + DockerInsecureSkipTLSVerify: tt.incomingSkip, + DockerDaemonInsecureSkipTLSVerify: tt.incomingDaemonSkip, + } + cfg := cfgBase + cfg.TLSVerify = tlsVerifyConfig{skip: tt.yamlSkip} + + descs, err := imagesToCopyFromRegistry(baseRegistry, cfg, src) + require.NoError(t, err) + require.NotEmpty(t, descs) + ctx := descs[0].Context + require.NotNil(t, ctx) + assert.Equal(t, tt.wantSkip, ctx.DockerInsecureSkipTLSVerify) + assert.Equal(t, tt.wantDaemonSkip, ctx.DockerDaemonInsecureSkipTLSVerify) + }) + } +} + +// TestTLSPrecedence_YAMLSpecified verifies that when YAML explicitly specifies +// tls-verify, it overrides incoming SystemContext values (e.g., CLI/global flags) +// for both DockerInsecureSkipTLSVerify and DockerDaemonInsecureSkipTLSVerify. +func TestTLSPrecedence_YAMLSpecified(t *testing.T) { + baseRegistry := "example.com" + imageName := "repo" + cfgBase := registrySyncConfig{ + Images: map[string][]string{imageName: {"latest"}}, // avoid network + } + + tests := []struct { + name string + incomingSkip types.OptionalBool + incomingDaemonSkip bool + yamlSkip types.OptionalBool // YAML explicitly sets this + wantSkip types.OptionalBool + wantDaemonSkip bool + }{ + { + name: "YAML tls-verify:true enforces verification", + incomingSkip: types.OptionalBoolTrue, + incomingDaemonSkip: true, + yamlSkip: types.OptionalBoolFalse, + wantSkip: types.OptionalBoolFalse, + wantDaemonSkip: false, + }, + { + name: "YAML tls-verify:false disables verification", + incomingSkip: types.OptionalBoolFalse, + incomingDaemonSkip: false, + yamlSkip: types.OptionalBoolTrue, + wantSkip: types.OptionalBoolTrue, + wantDaemonSkip: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + src := types.SystemContext{ + DockerInsecureSkipTLSVerify: tt.incomingSkip, + DockerDaemonInsecureSkipTLSVerify: tt.incomingDaemonSkip, + } + cfg := cfgBase + cfg.TLSVerify = tlsVerifyConfig{skip: tt.yamlSkip} + + descs, err := imagesToCopyFromRegistry(baseRegistry, cfg, src) + require.NoError(t, err) + require.NotEmpty(t, descs) + ctx := descs[0].Context + require.NotNil(t, ctx) + assert.Equal(t, tt.wantSkip, ctx.DockerInsecureSkipTLSVerify) + assert.Equal(t, tt.wantDaemonSkip, ctx.DockerDaemonInsecureSkipTLSVerify) + }) + } +}