diff --git a/cmd/oras/internal/option/remote.go b/cmd/oras/internal/option/remote.go index f56b90bf5..c56282cc2 100644 --- a/cmd/oras/internal/option/remote.go +++ b/cmd/oras/internal/option/remote.go @@ -43,7 +43,6 @@ import ( // Remote options struct. type Remote struct { CACertFilePath string - PlainHTTP bool Insecure bool Configs []string Username string @@ -56,6 +55,7 @@ type Remote struct { headerFlags []string headers http.Header warned map[string]*sync.Map + plainHTTP func() (plainHTTP bool, enforced bool) } // EnableDistributionSpecFlag set distribution specification flag as applicable. @@ -98,7 +98,11 @@ func (opts *Remote) ApplyFlagsWithPrefix(fs *pflag.FlagSet, prefix, description fs.StringVarP(&opts.Username, flagPrefix+"username", shortUser, "", notePrefix+"registry username") fs.StringVarP(&opts.Password, flagPrefix+"password", shortPassword, "", notePrefix+"registry password or identity token") fs.BoolVarP(&opts.Insecure, flagPrefix+"insecure", "", false, "allow connections to "+notePrefix+"SSL registry without certs") - fs.BoolVarP(&opts.PlainHTTP, flagPrefix+"plain-http", "", false, "allow insecure connections to "+notePrefix+"registry without SSL check") + plainHTTPFlagName := flagPrefix + "plain-http" + plainHTTP := fs.Bool(plainHTTPFlagName, false, "allow insecure connections to "+notePrefix+"registry without SSL check") + opts.plainHTTP = func() (bool, bool) { + return *plainHTTP, fs.Changed(plainHTTPFlagName) + } fs.StringVarP(&opts.CACertFilePath, flagPrefix+"ca-file", "", "", "server certificate authority file for the remote "+notePrefix+"registry") fs.StringArrayVarP(&opts.resolveFlag, flagPrefix+"resolve", "", nil, "customized DNS for "+notePrefix+"registry, formatted in `host:port:address[:address_port]`") fs.StringArrayVarP(&opts.Configs, flagPrefix+"registry-config", "", nil, "`path` of the authentication file for "+notePrefix+"registry") @@ -305,9 +309,14 @@ func (opts *Remote) NewRepository(reference string, common Common, logger logrus // isPlainHttp returns the plain http flag for a given registry. func (opts *Remote) isPlainHttp(registry string) bool { + plainHTTP, enforced := opts.plainHTTP() + if enforced { + return plainHTTP + } host, _, _ := net.SplitHostPort(registry) if host == "localhost" || registry == "localhost" { + // not specified, defaults to plain http for localhost return true } - return opts.PlainHTTP + return plainHTTP } diff --git a/cmd/oras/internal/option/remote_test.go b/cmd/oras/internal/option/remote_test.go index 9c60d939b..a55a4edc3 100644 --- a/cmd/oras/internal/option/remote_test.go +++ b/cmd/oras/internal/option/remote_test.go @@ -162,6 +162,16 @@ func TestRemote_authClient_resolve(t *testing.T) { } } +func plainHTTPEnabled() (plainHTTP bool, fromFlag bool) { + return true, true +} +func HTTPSEnabled() (plainHTTP bool, fromFlag bool) { + return false, true +} +func plainHTTPNotSpecified() (plainHTTP bool, fromFlag bool) { + return false, false +} + func TestRemote_NewRegistry(t *testing.T) { caPath := filepath.Join(t.TempDir(), "oras-test.pem") if err := os.WriteFile(caPath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ts.Certificate().Raw}), 0644); err != nil { @@ -174,6 +184,7 @@ func TestRemote_NewRegistry(t *testing.T) { }{ Remote{ CACertFilePath: caPath, + plainHTTP: plainHTTPNotSpecified, }, Common{}, } @@ -201,6 +212,7 @@ func TestRemote_NewRepository(t *testing.T) { }{ Remote{ CACertFilePath: caPath, + plainHTTP: plainHTTPNotSpecified, }, Common{}, } @@ -248,6 +260,7 @@ func TestRemote_NewRepository_Retry(t *testing.T) { }{ Remote{ CACertFilePath: caPath, + plainHTTP: plainHTTPNotSpecified, }, Common{}, } @@ -276,8 +289,8 @@ func TestRemote_NewRepository_Retry(t *testing.T) { } } -func TestRemote_isPlainHttp_localhost(t *testing.T) { - opts := Remote{PlainHTTP: false} +func TestRemote_default_localhost(t *testing.T) { + opts := Remote{plainHTTP: plainHTTPNotSpecified} got := opts.isPlainHttp("localhost") if got != true { t.Fatalf("tls should be disabled when domain is localhost") @@ -291,6 +304,36 @@ func TestRemote_isPlainHttp_localhost(t *testing.T) { } } +func TestRemote_isPlainHTTP_localhost(t *testing.T) { + opts := Remote{plainHTTP: plainHTTPEnabled} + isplainHTTP := opts.isPlainHttp("localhost") + if isplainHTTP != true { + t.Fatalf("tls should be disabled when domain is localhost and --plain-http is used") + + } + + isplainHTTP = opts.isPlainHttp("localhost:9090") + if isplainHTTP != true { + t.Fatalf("tls should be disabled when domain is localhost and --plain-http is used") + + } +} + +func TestRemote_isHTTPS_localhost(t *testing.T) { + opts := Remote{plainHTTP: HTTPSEnabled} + got := opts.isPlainHttp("localhost") + if got != false { + t.Fatalf("tls should be enabled when domain is localhost and --plain-http=false is used") + + } + + got = opts.isPlainHttp("localhost:9090") + if got != false { + t.Fatalf("tls should be enabled when domain is localhost and --plain-http=false is used") + + } +} + func TestRemote_parseResolve_err(t *testing.T) { tests := []struct { name string