package api_test import ( "context" "crypto/tls" "crypto/x509" "encoding/json" "io/ioutil" "os" "testing" "time" "github.com/anuvu/zot/pkg/api" . "github.com/smartystreets/goconvey/convey" "gopkg.in/resty.v1" ) const ( BaseURL1 = "http://127.0.0.1:8081" BaseURL2 = "http://127.0.0.1:8082" BaseSecureURL2 = "https://127.0.0.1:8082" SecurePort1 = "8081" SecurePort2 = "8082" username = "test" passphrase = "test" htpasswdPath = "../../test/data/htpasswd" // nolint (gosec) - this is just test data ServerCert = "../../test/data/server.cert" ServerKey = "../../test/data/server.key" CACert = "../../test/data/ca.crt" ) func TestNew(t *testing.T) { Convey("Make a new controller", t, func() { config := api.NewConfig() So(config, ShouldNotBeNil) So(api.NewController(config), ShouldNotBeNil) }) } func TestBasicAuth(t *testing.T) { Convey("Make a new controller", t, func() { config := api.NewConfig() config.HTTP.Port = SecurePort1 config.HTTP.Auth.HTPasswd.Path = htpasswdPath c := api.NewController(config) dir, err := ioutil.TempDir("", "oci-repo-test") if err != nil { panic(err) } defer os.RemoveAll(dir) c.Config.Storage.RootDirectory = dir go func() { // this blocks if err := c.Run(); err != nil { return } }() // wait till ready for { _, err := resty.R().Get(BaseURL1) if err == nil { break } time.Sleep(100 * time.Millisecond) } defer func() { ctx := context.Background() _ = c.Server.Shutdown(ctx) }() // without creds, should get access error resp, err := resty.R().Get(BaseURL1 + "/v2/") So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 401) var e api.Error err = json.Unmarshal(resp.Body(), &e) So(err, ShouldBeNil) // with creds, should get expected status code resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseURL1) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 404) resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseURL1 + "/v2/") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) } func TestTLSWithBasicAuth(t *testing.T) { Convey("Make a new controller", t, func() { caCert, err := ioutil.ReadFile(CACert) So(err, ShouldBeNil) caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool}) defer func() { resty.SetTLSClientConfig(nil) }() config := api.NewConfig() config.HTTP.Port = SecurePort2 config.HTTP.Auth.HTPasswd.Path = htpasswdPath config.HTTP.TLS.Cert = ServerCert config.HTTP.TLS.Key = ServerKey c := api.NewController(config) dir, err := ioutil.TempDir("", "oci-repo-test") if err != nil { panic(err) } defer os.RemoveAll(dir) c.Config.Storage.RootDirectory = dir go func() { // this blocks if err := c.Run(); err != nil { return } }() // wait till ready for { _, err := resty.R().Get(BaseURL2) if err == nil { break } time.Sleep(100 * time.Millisecond) } defer func() { ctx := context.Background() _ = c.Server.Shutdown(ctx) }() // accessing insecure HTTP site should fail resp, err := resty.R().Get(BaseURL2) So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 400) // without creds, should get access error resp, err = resty.R().Get(BaseSecureURL2 + "/v2/") So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 401) var e api.Error err = json.Unmarshal(resp.Body(), &e) So(err, ShouldBeNil) // with creds, should get expected status code resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 404) resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2 + "/v2/") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) } func TestTLSWithBasicAuthAllowReadAccess(t *testing.T) { Convey("Make a new controller", t, func() { caCert, err := ioutil.ReadFile(CACert) So(err, ShouldBeNil) caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool}) defer func() { resty.SetTLSClientConfig(nil) }() config := api.NewConfig() config.HTTP.Port = SecurePort2 config.HTTP.Auth.HTPasswd.Path = htpasswdPath config.HTTP.TLS.Cert = ServerCert config.HTTP.TLS.Key = ServerKey config.HTTP.AllowReadAccess = true c := api.NewController(config) dir, err := ioutil.TempDir("", "oci-repo-test") if err != nil { panic(err) } defer os.RemoveAll(dir) c.Config.Storage.RootDirectory = dir go func() { // this blocks if err := c.Run(); err != nil { return } }() // wait till ready for { _, err := resty.R().Get(BaseURL2) if err == nil { break } time.Sleep(100 * time.Millisecond) } defer func() { ctx := context.Background() _ = c.Server.Shutdown(ctx) }() // accessing insecure HTTP site should fail resp, err := resty.R().Get(BaseURL2) So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 400) // without creds, should still be allowed to access resp, err = resty.R().Get(BaseSecureURL2 + "/v2/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) // with creds, should get expected status code resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 404) resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2 + "/v2/") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) // without creds, writes should fail resp, err = resty.R().Post(BaseSecureURL2 + "/v2/repo/blobs/uploads/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 401) }) } func TestTLSMutualAuth(t *testing.T) { Convey("Make a new controller", t, func() { caCert, err := ioutil.ReadFile(CACert) So(err, ShouldBeNil) caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool}) defer func() { resty.SetTLSClientConfig(nil) }() config := api.NewConfig() config.HTTP.Port = SecurePort2 config.HTTP.TLS.Cert = ServerCert config.HTTP.TLS.Key = ServerKey config.HTTP.TLS.CACert = CACert c := api.NewController(config) dir, err := ioutil.TempDir("", "oci-repo-test") if err != nil { panic(err) } defer os.RemoveAll(dir) c.Config.Storage.RootDirectory = dir go func() { // this blocks if err := c.Run(); err != nil { return } }() // wait till ready for { _, err := resty.R().Get(BaseURL2) if err == nil { break } time.Sleep(100 * time.Millisecond) } defer func() { ctx := context.Background() _ = c.Server.Shutdown(ctx) }() // accessing insecure HTTP site should fail resp, err := resty.R().Get(BaseURL2) So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 400) // without client certs and creds, should get conn error _, err = resty.R().Get(BaseSecureURL2) So(err, ShouldNotBeNil) // with creds but without certs, should get conn error _, err = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2) So(err, ShouldNotBeNil) // setup TLS mutual auth cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key") So(err, ShouldBeNil) resty.SetCertificates(cert) defer func() { resty.SetCertificates(tls.Certificate{}) }() // with client certs but without creds, should succeed resp, err = resty.R().Get(BaseSecureURL2 + "/v2/") So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) // with client certs and creds, should get expected status code resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 404) // with client certs, creds shouldn't matter resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2 + "/v2/") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) } func TestTLSMutualAuthAllowReadAccess(t *testing.T) { Convey("Make a new controller", t, func() { caCert, err := ioutil.ReadFile(CACert) So(err, ShouldBeNil) caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool}) defer func() { resty.SetTLSClientConfig(nil) }() config := api.NewConfig() config.HTTP.Port = SecurePort2 config.HTTP.TLS.Cert = ServerCert config.HTTP.TLS.Key = ServerKey config.HTTP.TLS.CACert = CACert config.HTTP.AllowReadAccess = true c := api.NewController(config) dir, err := ioutil.TempDir("", "oci-repo-test") if err != nil { panic(err) } defer os.RemoveAll(dir) c.Config.Storage.RootDirectory = dir go func() { // this blocks if err := c.Run(); err != nil { return } }() // wait till ready for { _, err := resty.R().Get(BaseURL2) if err == nil { break } time.Sleep(100 * time.Millisecond) } defer func() { ctx := context.Background() _ = c.Server.Shutdown(ctx) }() // accessing insecure HTTP site should fail resp, err := resty.R().Get(BaseURL2) So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 400) // without client certs and creds, reads are allowed resp, err = resty.R().Get(BaseSecureURL2 + "/v2/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) // with creds but without certs, reads are allowed resp, err = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2 + "/v2/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) // without creds, writes should fail resp, err = resty.R().Post(BaseSecureURL2 + "/v2/repo/blobs/uploads/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 401) // setup TLS mutual auth cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key") So(err, ShouldBeNil) resty.SetCertificates(cert) defer func() { resty.SetCertificates(tls.Certificate{}) }() // with client certs but without creds, should succeed resp, err = resty.R().Get(BaseSecureURL2 + "/v2/") So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) // with client certs and creds, should get expected status code resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 404) // with client certs, creds shouldn't matter resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2 + "/v2/") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) } func TestTLSMutualAndBasicAuth(t *testing.T) { Convey("Make a new controller", t, func() { caCert, err := ioutil.ReadFile(CACert) So(err, ShouldBeNil) caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool}) defer func() { resty.SetTLSClientConfig(nil) }() config := api.NewConfig() config.HTTP.Port = SecurePort2 config.HTTP.TLS.Cert = ServerCert config.HTTP.TLS.Key = ServerKey config.HTTP.TLS.CACert = CACert config.HTTP.Auth.HTPasswd.Path = htpasswdPath c := api.NewController(config) dir, err := ioutil.TempDir("", "oci-repo-test") if err != nil { panic(err) } defer os.RemoveAll(dir) c.Config.Storage.RootDirectory = dir go func() { // this blocks if err := c.Run(); err != nil { return } }() // wait till ready for { _, err := resty.R().Get(BaseURL2) if err == nil { break } time.Sleep(100 * time.Millisecond) } defer func() { ctx := context.Background() _ = c.Server.Shutdown(ctx) }() // accessing insecure HTTP site should fail resp, err := resty.R().Get(BaseURL2) So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 400) // without client certs and creds, should fail _, err = resty.R().Get(BaseSecureURL2) So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 400) // with creds but without certs, should succeed _, err = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2) So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 400) // setup TLS mutual auth cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key") So(err, ShouldBeNil) resty.SetCertificates(cert) defer func() { resty.SetCertificates(tls.Certificate{}) }() // with client certs but without creds, should get access error resp, err = resty.R().Get(BaseSecureURL2 + "/v2/") So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 401) // with client certs and creds, should get expected status code resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 404) resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2 + "/v2/") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) } func TestTLSMutualAndBasicAuthAllowReadAccess(t *testing.T) { Convey("Make a new controller", t, func() { caCert, err := ioutil.ReadFile(CACert) So(err, ShouldBeNil) caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool}) defer func() { resty.SetTLSClientConfig(nil) }() config := api.NewConfig() config.HTTP.Port = SecurePort2 config.HTTP.TLS.Cert = ServerCert config.HTTP.TLS.Key = ServerKey config.HTTP.TLS.CACert = CACert config.HTTP.Auth.HTPasswd.Path = htpasswdPath config.HTTP.AllowReadAccess = true c := api.NewController(config) dir, err := ioutil.TempDir("", "oci-repo-test") if err != nil { panic(err) } defer os.RemoveAll(dir) c.Config.Storage.RootDirectory = dir go func() { // this blocks if err := c.Run(); err != nil { return } }() // wait till ready for { _, err := resty.R().Get(BaseURL2) if err == nil { break } time.Sleep(100 * time.Millisecond) } defer func() { ctx := context.Background() _ = c.Server.Shutdown(ctx) }() // accessing insecure HTTP site should fail resp, err := resty.R().Get(BaseURL2) So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 400) // without client certs and creds, should fail _, err = resty.R().Get(BaseSecureURL2) So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 400) // with creds but without certs, should succeed _, err = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2) So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 400) // setup TLS mutual auth cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key") So(err, ShouldBeNil) resty.SetCertificates(cert) defer func() { resty.SetCertificates(tls.Certificate{}) }() // with client certs but without creds, reads should succeed resp, err = resty.R().Get(BaseSecureURL2 + "/v2/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) // with only client certs, writes should fail resp, err = resty.R().Post(BaseSecureURL2 + "/v2/repo/blobs/uploads/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 401) // with client certs and creds, should get expected status code resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 404) resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseSecureURL2 + "/v2/") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) }