diff --git a/go.mod b/go.mod index ab05418f..300eb012 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,6 @@ require ( github.com/aws/smithy-go v1.24.0 github.com/bmatcuk/doublestar/v4 v4.9.1 github.com/briandowns/spinner v1.23.2 - github.com/chartmuseum/auth v0.5.0 github.com/cloudevents/sdk-go/protocol/nats/v2 v2.16.2 github.com/cloudevents/sdk-go/v2 v2.16.2 github.com/dchest/siphash v1.2.3 @@ -55,7 +54,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 - github.com/project-zot/mockoidc v0.0.0-20240610203808-d69d9e02020a + github.com/project-zot/mockoidc v0.0.0-20260112114452-7ac7156d2581 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 github.com/rbcervilla/redisstore/v9 v9.0.0 @@ -299,7 +298,6 @@ require ( github.com/goccy/go-yaml v1.19.1 // indirect github.com/gocsaf/csaf/v3 v3.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v0.0.4 // indirect diff --git a/go.sum b/go.sum index ba4640e5..feb10cef 100644 --- a/go.sum +++ b/go.sum @@ -972,8 +972,6 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80= github.com/chai2010/gettext-go v1.0.3/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/chartmuseum/auth v0.5.0 h1:ENNmoxvjxcR/JR0HrghAEtGQe7hToMNj16+UoS5CK9Y= -github.com/chartmuseum/auth v0.5.0/go.mod h1:BvoSXHyvbsq+/bbhNgVTDQsModM+HERBTNY5o9Vyrig= github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI= github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ= github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 h1:krfRl01rzPzxSxyLyrChD+U+MzsBXbm0OwYYB67uF+4= @@ -1324,8 +1322,6 @@ github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -1907,8 +1903,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/project-zot/mockoidc v0.0.0-20240610203808-d69d9e02020a h1:525aNEKSyDcqJcawiGtA2NPNApJMta8bUe9SoYuhQ+o= -github.com/project-zot/mockoidc v0.0.0-20240610203808-d69d9e02020a/go.mod h1:ltIE6ZO/czh/g4xdNQlFGkl7DAfaLLFYmitB4taA5ys= +github.com/project-zot/mockoidc v0.0.0-20260112114452-7ac7156d2581 h1:d6SN2sBhinGS9krAqo4IXSvVJAEFK1yP7xRsbL8ZNwY= +github.com/project-zot/mockoidc v0.0.0-20260112114452-7ac7156d2581/go.mod h1:S5WDVu5XmNpu4xwMsPf4R5fexaiekegUYjOcxqBfUBw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= diff --git a/pkg/api/controller_test.go b/pkg/api/controller_test.go index b526db62..57a2b886 100644 --- a/pkg/api/controller_test.go +++ b/pkg/api/controller_test.go @@ -3524,213 +3524,192 @@ func TestBearerAuthMultipleAlgorithms(t *testing.T) { } func TestBearerAuth(t *testing.T) { - testCases := []struct { - name string - useLegacyAuthTestServer bool - }{ - { - name: "new authentication test server", - useLegacyAuthTestServer: false, - }, - { - name: "legacy authentication test server", - useLegacyAuthTestServer: true, - }, - } + Convey("Make a new controller", t, func() { + // Generate certificates dynamically for the test + serverCertPath, serverKeyPath, _, _ := setupBearerAuthServerCerts(t, tlsutils.KeyTypeRSA) - for _, testCase := range testCases { - Convey("Make a new controller with "+testCase.name, t, func() { - // Generate certificates dynamically for the test - serverCertPath, serverKeyPath, _, _ := setupBearerAuthServerCerts(t, tlsutils.KeyTypeRSA) + authTestServer := authutils.MakeAuthTestServer(serverKeyPath, "RS256", UnauthorizedNamespace) + defer authTestServer.Close() - var authTestServer *httptest.Server - if testCase.useLegacyAuthTestServer { - authTestServer = authutils.MakeAuthTestServerLegacy(serverKeyPath, UnauthorizedNamespace) - } else { - authTestServer = authutils.MakeAuthTestServer(serverKeyPath, "RS256", UnauthorizedNamespace) - } - defer authTestServer.Close() + port := test.GetFreePort() + baseURL := test.GetBaseURL(port) - port := test.GetFreePort() - baseURL := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port - conf := config.New() - conf.HTTP.Port = port + aurl, err := url.Parse(authTestServer.URL) + So(err, ShouldBeNil) - aurl, err := url.Parse(authTestServer.URL) - So(err, ShouldBeNil) + conf.HTTP.Auth = &config.AuthConfig{ + Bearer: &config.BearerConfig{ + Cert: serverCertPath, + Realm: authTestServer.URL + "/auth/token", + Service: aurl.Host, + }, + } + ctlr := makeController(conf, t.TempDir()) - conf.HTTP.Auth = &config.AuthConfig{ - Bearer: &config.BearerConfig{ - Cert: serverCertPath, - Realm: authTestServer.URL + "/auth/token", - Service: aurl.Host, - }, - } - ctlr := makeController(conf, t.TempDir()) + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(port) - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(port) + defer cm.StopServer() - defer cm.StopServer() + blob := []byte("hello, blob!") + digest := godigest.FromBytes(blob).String() - blob := []byte("hello, blob!") - digest := godigest.FromBytes(blob).String() + resp, err := resty.R().Get(baseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err := resty.R().Get(baseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + var goodToken authutils.AccessTokenResponse - var goodToken authutils.AccessTokenResponse + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Get(baseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Get(baseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + // trigger decode error + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+"invalidToken"). + Get(baseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - // trigger decode error - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+"invalidToken"). - Get(baseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + resp, err = resty.R().SetHeader("Authorization", + "Bearer "+goodToken.AccessToken).Options(baseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusNoContent) - resp, err = resty.R().SetHeader("Authorization", - "Bearer "+goodToken.AccessToken).Options(baseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusNoContent) + s1, seed1 := test.GenerateRandomName() + s2, seed2 := test.GenerateRandomName() + repoName := s1 + "/" + s2 - s1, seed1 := test.GenerateRandomName() - s2, seed2 := test.GenerateRandomName() - repoName := s1 + "/" + s2 + ctlr.Log.Info().Int64("seed1", seed1).Int64("seed2", seed2).Msg("random seeds for repoName") - ctlr.Log.Info().Int64("seed1", seed1).Int64("seed2", seed2).Msg("random seeds for repoName") + resp, err = resty.R().Post(baseURL + "/v2/" + repoName + "/blobs/uploads/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err = resty.R().Post(baseURL + "/v2/" + repoName + "/blobs/uploads/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) - authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Post(baseURL + "/v2/" + repoName + "/blobs/uploads/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusAccepted) + loc := resp.Header().Get("Location") - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Post(baseURL + "/v2/" + repoName + "/blobs/uploads/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusAccepted) - loc := resp.Header().Get("Location") + resp, err = resty.R(). + SetHeader("Content-Length", strconv.Itoa(len(blob))). + SetHeader("Content-Type", "application/octet-stream"). + SetQueryParam("digest", digest). + SetBody(blob). + Put(baseURL + loc) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err = resty.R(). - SetHeader("Content-Length", strconv.Itoa(len(blob))). - SetHeader("Content-Type", "application/octet-stream"). - SetQueryParam("digest", digest). - SetBody(blob). - Put(baseURL + loc) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) - authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) + resp, err = resty.R(). + SetHeader("Content-Length", strconv.Itoa(len(blob))). + SetHeader("Content-Type", "application/octet-stream"). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + SetQueryParam("digest", digest). + SetBody(blob). + Put(baseURL + loc) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusCreated) - resp, err = resty.R(). - SetHeader("Content-Length", strconv.Itoa(len(blob))). - SetHeader("Content-Type", "application/octet-stream"). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - SetQueryParam("digest", digest). - SetBody(blob). - Put(baseURL + loc) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusCreated) + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Get(baseURL + "/v2/" + repoName + "/tags/list") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Get(baseURL + "/v2/" + repoName + "/tags/list") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) - authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Get(baseURL + "/v2/" + repoName + "/tags/list") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Get(baseURL + "/v2/" + repoName + "/tags/list") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + resp, err = resty.R(). + Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err = resty.R(). - Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + var badToken authutils.AccessTokenResponse - var badToken authutils.AccessTokenResponse + err = json.Unmarshal(resp.Body(), &badToken) + So(err, ShouldBeNil) - err = json.Unmarshal(resp.Body(), &badToken) - So(err, ShouldBeNil) - - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+badToken.AccessToken). - Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - }) - } + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+badToken.AccessToken). + Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + }) } func TestBearerAuthWrongAuthorizer(t *testing.T) { @@ -3755,207 +3734,186 @@ func TestBearerAuthWrongAuthorizer(t *testing.T) { } func TestBearerAuthWithAllowReadAccess(t *testing.T) { - testCases := []struct { - name string - useLegacyAuthTestServer bool - }{ - { - name: "new authentication test server", - useLegacyAuthTestServer: false, - }, - { - name: "legacy authentication test server", - useLegacyAuthTestServer: true, - }, - } + Convey("Make a new controller", t, func() { + // Generate certificates dynamically for the test + serverCertPath, serverKeyPath, _, _ := setupBearerAuthServerCerts(t, tlsutils.KeyTypeRSA) - for _, testCase := range testCases { - Convey("Make a new controller with"+testCase.name, t, func() { - // Generate certificates dynamically for the test - serverCertPath, serverKeyPath, _, _ := setupBearerAuthServerCerts(t, tlsutils.KeyTypeRSA) + authTestServer := authutils.MakeAuthTestServer(serverKeyPath, "RS256", UnauthorizedNamespace) + defer authTestServer.Close() - var authTestServer *httptest.Server - if testCase.useLegacyAuthTestServer { - authTestServer = authutils.MakeAuthTestServerLegacy(serverKeyPath, UnauthorizedNamespace) - } else { - authTestServer = authutils.MakeAuthTestServer(serverKeyPath, "RS256", UnauthorizedNamespace) - } - defer authTestServer.Close() + port := test.GetFreePort() + baseURL := test.GetBaseURL(port) - port := test.GetFreePort() - baseURL := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port - conf := config.New() - conf.HTTP.Port = port + aurl, err := url.Parse(authTestServer.URL) + So(err, ShouldBeNil) - aurl, err := url.Parse(authTestServer.URL) - So(err, ShouldBeNil) + conf.HTTP.Auth = &config.AuthConfig{ + Bearer: &config.BearerConfig{ + Cert: serverCertPath, + Realm: authTestServer.URL + "/auth/token", + Service: aurl.Host, + }, + } + ctlr := makeController(conf, t.TempDir()) - conf.HTTP.Auth = &config.AuthConfig{ - Bearer: &config.BearerConfig{ - Cert: serverCertPath, - Realm: authTestServer.URL + "/auth/token", - Service: aurl.Host, + conf.HTTP.AccessControl = &config.AccessControlConfig{ + Repositories: config.Repositories{ + test.AuthorizationAllRepos: config.PolicyGroup{ + AnonymousPolicy: []string{"read"}, }, - } - ctlr := makeController(conf, t.TempDir()) + }, + } - conf.HTTP.AccessControl = &config.AccessControlConfig{ - Repositories: config.Repositories{ - test.AuthorizationAllRepos: config.PolicyGroup{ - AnonymousPolicy: []string{"read"}, - }, - }, - } + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(port) - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(port) + defer cm.StopServer() - defer cm.StopServer() + blob := []byte("hello, blob!") + digest := godigest.FromBytes(blob).String() - blob := []byte("hello, blob!") - digest := godigest.FromBytes(blob).String() + resp, err := resty.R().Get(baseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err := resty.R().Get(baseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + var goodToken authutils.AccessTokenResponse - var goodToken authutils.AccessTokenResponse + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Get(baseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Get(baseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + s1, seed1 := test.GenerateRandomName() + s2, seed2 := test.GenerateRandomName() + repoName := s1 + "/" + s2 - s1, seed1 := test.GenerateRandomName() - s2, seed2 := test.GenerateRandomName() - repoName := s1 + "/" + s2 + ctlr.Log.Info().Int64("seed1", seed1).Int64("seed2", seed2).Msg("random seeds for repoName") - ctlr.Log.Info().Int64("seed1", seed1).Int64("seed2", seed2).Msg("random seeds for repoName") + resp, err = resty.R().Post(baseURL + "/v2/" + repoName + "/blobs/uploads/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err = resty.R().Post(baseURL + "/v2/" + repoName + "/blobs/uploads/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) - authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Post(baseURL + "/v2/" + repoName + "/blobs/uploads/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusAccepted) + loc := resp.Header().Get("Location") - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Post(baseURL + "/v2/" + repoName + "/blobs/uploads/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusAccepted) - loc := resp.Header().Get("Location") + resp, err = resty.R(). + SetHeader("Content-Length", strconv.Itoa(len(blob))). + SetHeader("Content-Type", "application/octet-stream"). + SetQueryParam("digest", digest). + SetBody(blob). + Put(baseURL + loc) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err = resty.R(). - SetHeader("Content-Length", strconv.Itoa(len(blob))). - SetHeader("Content-Type", "application/octet-stream"). - SetQueryParam("digest", digest). - SetBody(blob). - Put(baseURL + loc) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) - authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) + resp, err = resty.R(). + SetHeader("Content-Length", strconv.Itoa(len(blob))). + SetHeader("Content-Type", "application/octet-stream"). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + SetQueryParam("digest", digest). + SetBody(blob). + Put(baseURL + loc) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusCreated) - resp, err = resty.R(). - SetHeader("Content-Length", strconv.Itoa(len(blob))). - SetHeader("Content-Type", "application/octet-stream"). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - SetQueryParam("digest", digest). - SetBody(blob). - Put(baseURL + loc) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusCreated) + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Get(baseURL + "/v2/" + repoName + "/tags/list") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Get(baseURL + "/v2/" + repoName + "/tags/list") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) - authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Get(baseURL + "/v2/" + repoName + "/tags/list") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Get(baseURL + "/v2/" + repoName + "/tags/list") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + resp, err = resty.R(). + Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err = resty.R(). - Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + var badToken authutils.AccessTokenResponse + err = json.Unmarshal(resp.Body(), &badToken) + So(err, ShouldBeNil) - var badToken authutils.AccessTokenResponse - err = json.Unmarshal(resp.Body(), &badToken) - So(err, ShouldBeNil) - - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+badToken.AccessToken). - Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - }) - } + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+badToken.AccessToken). + Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + }) } func TestNewRelyingPartyOIDC(t *testing.T) { diff --git a/pkg/extensions/extensions_test.go b/pkg/extensions/extensions_test.go index 31471cd4..5a11e099 100644 --- a/pkg/extensions/extensions_test.go +++ b/pkg/extensions/extensions_test.go @@ -5,7 +5,6 @@ package extensions_test import ( "encoding/json" "net/http" - "net/http/httptest" "net/url" "os" "path" @@ -875,184 +874,163 @@ func TestMgmtExtension(t *testing.T) { } func TestMgmtWithBearer(t *testing.T) { - testCases := []struct { - name string - useLegacyAuthTestServer bool - }{ - { - name: "new authentication test server", - useLegacyAuthTestServer: false, - }, - { - name: "legacy authentication test server", - useLegacyAuthTestServer: true, - }, - } + Convey("Make a new controller", t, func() { + // Generate certificates dynamically for the test + serverCertPath, serverKeyPath := setupTestServerCerts(t) - for _, testCase := range testCases { - Convey("Make a new controller with "+testCase.name, t, func() { - // Generate certificates dynamically for the test - serverCertPath, serverKeyPath := setupTestServerCerts(t) + authorizedNamespace := "allowedrepo" + unauthorizedNamespace := "notallowedrepo" - authorizedNamespace := "allowedrepo" - unauthorizedNamespace := "notallowedrepo" + authTestServer := authutils.MakeAuthTestServer(serverKeyPath, "RS256", unauthorizedNamespace) + defer authTestServer.Close() - var authTestServer *httptest.Server - if testCase.useLegacyAuthTestServer { - authTestServer = authutils.MakeAuthTestServerLegacy(serverKeyPath, unauthorizedNamespace) - } else { - authTestServer = authutils.MakeAuthTestServer(serverKeyPath, "RS256", unauthorizedNamespace) - } - defer authTestServer.Close() + port := test.GetFreePort() + baseURL := test.GetBaseURL(port) - port := test.GetFreePort() - baseURL := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port - conf := config.New() - conf.HTTP.Port = port + aurl, err := url.Parse(authTestServer.URL) + So(err, ShouldBeNil) - aurl, err := url.Parse(authTestServer.URL) - So(err, ShouldBeNil) + conf.HTTP.Auth = &config.AuthConfig{ + Bearer: &config.BearerConfig{ + Cert: serverCertPath, + Realm: authTestServer.URL + "/auth/token", + Service: aurl.Host, + }, + } - conf.HTTP.Auth = &config.AuthConfig{ - Bearer: &config.BearerConfig{ - Cert: serverCertPath, - Realm: authTestServer.URL + "/auth/token", - Service: aurl.Host, - }, - } + defaultValue := true - defaultValue := true + conf.Extensions = &extconf.ExtensionConfig{} + conf.Extensions.Search = &extconf.SearchConfig{} + conf.Extensions.Search.Enable = &defaultValue + conf.Extensions.Search.CVE = nil + conf.Extensions.UI = &extconf.UIConfig{} + conf.Extensions.UI.Enable = &defaultValue - conf.Extensions = &extconf.ExtensionConfig{} - conf.Extensions.Search = &extconf.SearchConfig{} - conf.Extensions.Search.Enable = &defaultValue - conf.Extensions.Search.CVE = nil - conf.Extensions.UI = &extconf.UIConfig{} - conf.Extensions.UI.Enable = &defaultValue + conf.Storage.RootDirectory = t.TempDir() - conf.Storage.RootDirectory = t.TempDir() + ctlr := api.NewController(conf) - ctlr := api.NewController(conf) + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(port) + defer cm.StopServer() - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(port) - defer cm.StopServer() + resp, err := resty.R().Get(baseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err := resty.R().Get(baseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + var goodToken authutils.AccessTokenResponse - var goodToken authutils.AccessTokenResponse + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Get(baseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Get(baseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + resp, err = resty.R().SetHeader("Authorization", + "Bearer "+goodToken.AccessToken).Options(baseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusNoContent) - resp, err = resty.R().SetHeader("Authorization", - "Bearer "+goodToken.AccessToken).Options(baseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusNoContent) + resp, err = resty.R().Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err = resty.R().Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) - authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusAccepted) - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusAccepted) + resp, err = resty.R(). + Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err = resty.R(). - Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + var badToken authutils.AccessTokenResponse - var badToken authutils.AccessTokenResponse + err = json.Unmarshal(resp.Body(), &badToken) + So(err, ShouldBeNil) - err = json.Unmarshal(resp.Body(), &badToken) - So(err, ShouldBeNil) + resp, err = resty.R(). + SetHeader("Authorization", "Bearer "+badToken.AccessToken). + Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - resp, err = resty.R(). - SetHeader("Authorization", "Bearer "+badToken.AccessToken). - Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + // test mgmt route + resp, err = resty.R().Get(baseURL + constants.FullMgmt) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - // test mgmt route - resp, err = resty.R().Get(baseURL + constants.FullMgmt) - So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + mgmtResp := extensions.StrippedConfig{} + err = json.Unmarshal(resp.Body(), &mgmtResp) + So(err, ShouldBeNil) + So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion) + So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil) + So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, conf.HTTP.Auth.Bearer.Realm) + So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, conf.HTTP.Auth.Bearer.Service) + So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil) + So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil) + So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse) - mgmtResp := extensions.StrippedConfig{} - err = json.Unmarshal(resp.Body(), &mgmtResp) - So(err, ShouldBeNil) - So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion) - So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil) - So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, conf.HTTP.Auth.Bearer.Realm) - So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, conf.HTTP.Auth.Bearer.Service) - So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil) - So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil) - So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse) + resp, err = resty.R().SetBasicAuth("", "").Get(baseURL + constants.FullMgmt) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - resp, err = resty.R().SetBasicAuth("", "").Get(baseURL + constants.FullMgmt) - So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - - mgmtResp = extensions.StrippedConfig{} - err = json.Unmarshal(resp.Body(), &mgmtResp) - So(err, ShouldBeNil) - So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion) - So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil) - So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, conf.HTTP.Auth.Bearer.Realm) - So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, conf.HTTP.Auth.Bearer.Service) - So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil) - So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil) - So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse) - }) - } + mgmtResp = extensions.StrippedConfig{} + err = json.Unmarshal(resp.Body(), &mgmtResp) + So(err, ShouldBeNil) + So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion) + So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil) + So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, conf.HTTP.Auth.Bearer.Realm) + So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, conf.HTTP.Auth.Bearer.Service) + So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil) + So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil) + So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse) + }) } func TestAllowedMethodsHeaderMgmt(t *testing.T) { diff --git a/pkg/extensions/sync/sync_test.go b/pkg/extensions/sync/sync_test.go index f5eea5a1..44f14ea8 100644 --- a/pkg/extensions/sync/sync_test.go +++ b/pkg/extensions/sync/sync_test.go @@ -10,7 +10,6 @@ import ( "errors" "fmt" "net/http" - "net/http/httptest" "net/url" "os" "path" @@ -2725,311 +2724,129 @@ func TestTLS(t *testing.T) { } func TestBearerAuth(t *testing.T) { - testCases := []struct { - name string - useLegacyAuthTestServer bool - }{ - { - name: "new authentication test server", - useLegacyAuthTestServer: false, - }, - { - name: "legacy authentication test server", - useLegacyAuthTestServer: true, - }, - } + Convey("Verify periodically sync bearer auth", t, func() { + updateDuration, _ := time.ParseDuration("1h") + // a repo for which clients do not have access, sync shouldn't be able to sync it + unauthorizedNamespace := testCveImage - for _, testCase := range testCases { - Convey("Verify periodically sync bearer auth with "+testCase.name, t, func() { - updateDuration, _ := time.ParseDuration("1h") - // a repo for which clients do not have access, sync shouldn't be able to sync it - unauthorizedNamespace := testCveImage + // Generate certificates for bearer auth + tempDir := t.TempDir() + _, serverCertPath, serverKeyPath, _, _, _ := setupTestCertsForSync(t, tempDir) - // Generate certificates for bearer auth - tempDir := t.TempDir() - _, serverCertPath, serverKeyPath, _, _, _ := setupTestCertsForSync(t, tempDir) + authTestServer := authutils.MakeAuthTestServer(serverKeyPath, "RS256", unauthorizedNamespace) + defer authTestServer.Close() - var authTestServer *httptest.Server - if testCase.useLegacyAuthTestServer { - authTestServer = authutils.MakeAuthTestServerLegacy(serverKeyPath, unauthorizedNamespace) - } else { - authTestServer = authutils.MakeAuthTestServer(serverKeyPath, "RS256", unauthorizedNamespace) - } - defer authTestServer.Close() + sctlr, srcBaseURL, _, srcClient := makeUpstreamServer(t, false, false) - sctlr, srcBaseURL, _, srcClient := makeUpstreamServer(t, false, false) + aurl, err := url.Parse(authTestServer.URL) + So(err, ShouldBeNil) - aurl, err := url.Parse(authTestServer.URL) - So(err, ShouldBeNil) + sctlr.Config.HTTP.Auth = &config.AuthConfig{ + Bearer: &config.BearerConfig{ + Cert: serverCertPath, + Realm: authTestServer.URL + "/auth/token", + Service: aurl.Host, + }, + } - sctlr.Config.HTTP.Auth = &config.AuthConfig{ - Bearer: &config.BearerConfig{ - Cert: serverCertPath, - Realm: authTestServer.URL + "/auth/token", - Service: aurl.Host, + scm := test.NewControllerManager(sctlr) + scm.StartAndWait(sctlr.Config.HTTP.Port) + + defer scm.StopServer() + + registryName := sync.StripRegistryTransport(srcBaseURL) + credentialsFile := makeCredentialsFile(t.TempDir(), fmt.Sprintf(`{"%s":{"username": "%s", "password": "%s"}}`, + registryName, username, password)) + + var tlsVerify bool + + syncRegistryConfig := syncconf.RegistryConfig{ + Content: []syncconf.Content{ + { + Prefix: "**", // sync everything }, - } + }, + URLs: []string{srcBaseURL}, + PollInterval: updateDuration, + TLSVerify: &tlsVerify, + CertDir: "", + MaxRetries: &maxRetries, + } - scm := test.NewControllerManager(sctlr) - scm.StartAndWait(sctlr.Config.HTTP.Port) + defaultVal := true + syncConfig := &syncconf.Config{ + Enable: &defaultVal, + CredentialsFile: credentialsFile, + Registries: []syncconf.RegistryConfig{syncRegistryConfig}, + } - defer scm.StopServer() + dctlr, destBaseURL, _, destClient := makeDownstreamServer(t, false, syncConfig) - registryName := sync.StripRegistryTransport(srcBaseURL) - credentialsFile := makeCredentialsFile(t.TempDir(), fmt.Sprintf(`{"%s":{"username": "%s", "password": "%s"}}`, - registryName, username, password)) + dcm := test.NewControllerManager(dctlr) + dcm.StartAndWait(dctlr.Config.HTTP.Port) - var tlsVerify bool + defer dcm.StopServer() - syncRegistryConfig := syncconf.RegistryConfig{ - Content: []syncconf.Content{ - { - Prefix: "**", // sync everything - }, - }, - URLs: []string{srcBaseURL}, - PollInterval: updateDuration, - TLSVerify: &tlsVerify, - CertDir: "", - MaxRetries: &maxRetries, - } + var ( + srcTagsList TagsList + destTagsList TagsList + ) - defaultVal := true - syncConfig := &syncconf.Config{ - Enable: &defaultVal, - CredentialsFile: credentialsFile, - Registries: []syncconf.RegistryConfig{syncRegistryConfig}, - } + resp, err := srcClient.R().Get(srcBaseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - dctlr, destBaseURL, _, destClient := makeDownstreamServer(t, false, syncConfig) + authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - dcm := test.NewControllerManager(dctlr) - dcm.StartAndWait(dctlr.Config.HTTP.Port) + var goodToken authutils.AccessTokenResponse - defer dcm.StopServer() + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) - var ( - srcTagsList TagsList - destTagsList TagsList - ) + resp, err = srcClient.R(). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Get(srcBaseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - resp, err := srcClient.R().Get(srcBaseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + resp, err = srcClient.R().Get(srcBaseURL + "/v2/" + testImage + "/tags/list") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - var goodToken authutils.AccessTokenResponse + goodToken = authutils.AccessTokenResponse{} + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) + resp, err = srcClient.R().SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Get(srcBaseURL + "/v2/" + testImage + "/tags/list") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) - resp, err = srcClient.R(). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Get(srcBaseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - - resp, err = srcClient.R().Get(srcBaseURL + "/v2/" + testImage + "/tags/list") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - - authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - - goodToken = authutils.AccessTokenResponse{} - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) - - resp, err = srcClient.R().SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Get(srcBaseURL + "/v2/" + testImage + "/tags/list") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - - err = json.Unmarshal(resp.Body(), &srcTagsList) - if err != nil { - panic(err) - } - - for { - resp, err := destClient.R().Get(destBaseURL + "/v2/" + testImage + "/tags/list") - if err != nil { - panic(err) - } - - err = json.Unmarshal(resp.Body(), &destTagsList) - if err != nil { - panic(err) - } - - if len(destTagsList.Tags) > 0 { - break - } - - time.Sleep(500 * time.Millisecond) - } - - So(destTagsList, ShouldResemble, srcTagsList) - - waitSyncFinish(dctlr.Config.Log.Output) - - resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag) - So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - - // unauthorized namespace - resp, err = destClient.R().Get(destBaseURL + "/v2/" + testCveImage + "/manifests/" + testImageTag) - So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusNotFound) - }) - - Convey("Verify ondemand sync bearer auth", t, func() { - // a repo for which clients do not have access, sync shouldn't be able to sync it - unauthorizedNamespace := testCveImage - - // Generate certificates for bearer auth - tempDir := t.TempDir() - _, serverCertPath, serverKeyPath, _, _, _ := setupTestCertsForSync(t, tempDir) - - var authTestServer *httptest.Server - if testCase.useLegacyAuthTestServer { - authTestServer = authutils.MakeAuthTestServerLegacy(serverKeyPath, unauthorizedNamespace) - } else { - authTestServer = authutils.MakeAuthTestServer(serverKeyPath, "RS256", unauthorizedNamespace) - } - defer authTestServer.Close() - - sctlr, srcBaseURL, _, srcClient := makeUpstreamServer(t, false, false) - - aurl, err := url.Parse(authTestServer.URL) - So(err, ShouldBeNil) - - sctlr.Config.HTTP.Auth = &config.AuthConfig{ - Bearer: &config.BearerConfig{ - Cert: serverCertPath, - Realm: authTestServer.URL + "/auth/token", - Service: aurl.Host, - }, - } - - scm := test.NewControllerManager(sctlr) - scm.StartAndWait(sctlr.Config.HTTP.Port) - - defer scm.StopServer() - - registryName := sync.StripRegistryTransport(srcBaseURL) - credentialsFile := makeCredentialsFile(t.TempDir(), fmt.Sprintf(`{"%s":{"username": "%s", "password": "%s"}}`, - registryName, username, password)) - - var tlsVerify bool - - syncRegistryConfig := syncconf.RegistryConfig{ - Content: []syncconf.Content{ - { - Prefix: "**", // sync everything - }, - }, - URLs: []string{srcBaseURL}, - TLSVerify: &tlsVerify, - OnDemand: true, - CertDir: "", - MaxRetries: &maxRetries, - } - - defaultVal := true - syncConfig := &syncconf.Config{ - Enable: &defaultVal, - CredentialsFile: credentialsFile, - Registries: []syncconf.RegistryConfig{syncRegistryConfig}, - } - - dctlr, destBaseURL, _, destClient := makeDownstreamServer(t, false, syncConfig) - - dcm := test.NewControllerManager(dctlr) - dcm.StartAndWait(dctlr.Config.HTTP.Port) - - defer dcm.StopServer() - - var ( - srcTagsList TagsList - destTagsList TagsList - ) - - resp, err := srcClient.R().Get(srcBaseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - - authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - - var goodToken authutils.AccessTokenResponse - - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) - - resp, err = srcClient.R(). - SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Get(srcBaseURL + "/v2/") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - - resp, err = srcClient.R().Get(srcBaseURL + "/v2/" + testImage + "/tags/list") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) - - authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) - resp, err = resty.R(). - SetQueryParam("service", authorizationHeader.Service). - SetQueryParam("scope", authorizationHeader.Scope). - Get(authorizationHeader.Realm) - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - - goodToken = authutils.AccessTokenResponse{} - err = json.Unmarshal(resp.Body(), &goodToken) - So(err, ShouldBeNil) - - resp, err = srcClient.R().SetHeader("Authorization", "Bearer "+goodToken.AccessToken). - Get(srcBaseURL + "/v2/" + testImage + "/tags/list") - So(err, ShouldBeNil) - So(resp, ShouldNotBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) - - err = json.Unmarshal(resp.Body(), &srcTagsList) - if err != nil { - panic(err) - } - - // sync on demand - resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag) - So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusOK) + err = json.Unmarshal(resp.Body(), &srcTagsList) + if err != nil { + panic(err) + } + for { resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/tags/list") if err != nil { panic(err) @@ -3040,14 +2857,170 @@ func TestBearerAuth(t *testing.T) { panic(err) } - So(destTagsList, ShouldResemble, srcTagsList) + if len(destTagsList.Tags) > 0 { + break + } - // unauthorized namespace - resp, err = destClient.R().Get(destBaseURL + "/v2/" + testCveImage + "/manifests/" + testImageTag) - So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, http.StatusNotFound) - }) - } + time.Sleep(500 * time.Millisecond) + } + + So(destTagsList, ShouldResemble, srcTagsList) + + waitSyncFinish(dctlr.Config.Log.Output) + + resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + + // unauthorized namespace + resp, err = destClient.R().Get(destBaseURL + "/v2/" + testCveImage + "/manifests/" + testImageTag) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusNotFound) + }) + + Convey("Verify ondemand sync bearer auth", t, func() { + // a repo for which clients do not have access, sync shouldn't be able to sync it + unauthorizedNamespace := testCveImage + + // Generate certificates for bearer auth + tempDir := t.TempDir() + _, serverCertPath, serverKeyPath, _, _, _ := setupTestCertsForSync(t, tempDir) + + authTestServer := authutils.MakeAuthTestServer(serverKeyPath, "RS256", unauthorizedNamespace) + defer authTestServer.Close() + + sctlr, srcBaseURL, _, srcClient := makeUpstreamServer(t, false, false) + + aurl, err := url.Parse(authTestServer.URL) + So(err, ShouldBeNil) + + sctlr.Config.HTTP.Auth = &config.AuthConfig{ + Bearer: &config.BearerConfig{ + Cert: serverCertPath, + Realm: authTestServer.URL + "/auth/token", + Service: aurl.Host, + }, + } + + scm := test.NewControllerManager(sctlr) + scm.StartAndWait(sctlr.Config.HTTP.Port) + + defer scm.StopServer() + + registryName := sync.StripRegistryTransport(srcBaseURL) + credentialsFile := makeCredentialsFile(t.TempDir(), fmt.Sprintf(`{"%s":{"username": "%s", "password": "%s"}}`, + registryName, username, password)) + + var tlsVerify bool + + syncRegistryConfig := syncconf.RegistryConfig{ + Content: []syncconf.Content{ + { + Prefix: "**", // sync everything + }, + }, + URLs: []string{srcBaseURL}, + TLSVerify: &tlsVerify, + OnDemand: true, + CertDir: "", + MaxRetries: &maxRetries, + } + + defaultVal := true + syncConfig := &syncconf.Config{ + Enable: &defaultVal, + CredentialsFile: credentialsFile, + Registries: []syncconf.RegistryConfig{syncRegistryConfig}, + } + + dctlr, destBaseURL, _, destClient := makeDownstreamServer(t, false, syncConfig) + + dcm := test.NewControllerManager(dctlr) + dcm.StartAndWait(dctlr.Config.HTTP.Port) + + defer dcm.StopServer() + + var ( + srcTagsList TagsList + destTagsList TagsList + ) + + resp, err := srcClient.R().Get(srcBaseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + + authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + + var goodToken authutils.AccessTokenResponse + + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) + + resp, err = srcClient.R(). + SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Get(srcBaseURL + "/v2/") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + + resp, err = srcClient.R().Get(srcBaseURL + "/v2/" + testImage + "/tags/list") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized) + + authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate")) + resp, err = resty.R(). + SetQueryParam("service", authorizationHeader.Service). + SetQueryParam("scope", authorizationHeader.Scope). + Get(authorizationHeader.Realm) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + + goodToken = authutils.AccessTokenResponse{} + err = json.Unmarshal(resp.Body(), &goodToken) + So(err, ShouldBeNil) + + resp, err = srcClient.R().SetHeader("Authorization", "Bearer "+goodToken.AccessToken). + Get(srcBaseURL + "/v2/" + testImage + "/tags/list") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + + err = json.Unmarshal(resp.Body(), &srcTagsList) + if err != nil { + panic(err) + } + + // sync on demand + resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusOK) + + resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/tags/list") + if err != nil { + panic(err) + } + + err = json.Unmarshal(resp.Body(), &destTagsList) + if err != nil { + panic(err) + } + + So(destTagsList, ShouldResemble, srcTagsList) + + // unauthorized namespace + resp, err = destClient.R().Get(destBaseURL + "/v2/" + testCveImage + "/manifests/" + testImageTag) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, http.StatusNotFound) + }) } func TestBasicAuth(t *testing.T) { diff --git a/pkg/test/auth/bearer.go b/pkg/test/auth/bearer.go index 8746ded8..79ee9759 100644 --- a/pkg/test/auth/bearer.go +++ b/pkg/test/auth/bearer.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "github.com/chartmuseum/auth" "github.com/golang-jwt/jwt/v5" "github.com/mitchellh/mapstructure" @@ -86,62 +85,6 @@ func MakeAuthTestServer(serverKey, signAlg string, unauthorizedNamespace string) return authTestServer } -// MakeAuthTestServerLegacy makes a test HTTP server to generate bearer tokens using the github.com/chartmuseum/auth -// package, to verify backward compatibility of the token authentication process with older versions of zot. -func MakeAuthTestServerLegacy(serverKey string, unauthorizedNamespace string) *httptest.Server { - cmTokenGenerator, err := auth.NewTokenGenerator(&auth.TokenGeneratorOptions{ - PrivateKeyPath: serverKey, - Audience: "Zot Registry", - Issuer: "Zot", - AddKIDHeader: true, - }) - if err != nil { - panic(err) - } - - authTestServer := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { - if request.Method != http.MethodGet { - response.WriteHeader(http.StatusMethodNotAllowed) - - return - } - - var access []auth.AccessEntry - - scopes := request.URL.Query()["scope"] - - for _, scope := range scopes { - if scope == "" { - continue - } - - parts := strings.Split(scope, ":") - name := parts[1] - actions := strings.Split(parts[2], ",") - - if name == unauthorizedNamespace { - actions = []string{} - } - - access = append(access, auth.AccessEntry{ - Name: name, - Type: "repository", - Actions: actions, - }) - } - - token, err := cmTokenGenerator.GenerateToken(access, time.Minute*1) - if err != nil { - panic(err) - } - - response.Header().Set("Content-Type", "application/json") - fmt.Fprintf(response, `{"access_token": "%s"}`, token) - })) - - return authTestServer -} - func ParseBearerAuthHeader(authHeaderRaw string) *AuthHeader { re := regexp.MustCompile(`([a-zA-z]+)="(.+?)"`) matches := re.FindAllStringSubmatch(authHeaderRaw, -1) diff --git a/pkg/test/auth/bearer_test.go b/pkg/test/auth/bearer_test.go index 95582fc3..ee7620fc 100644 --- a/pkg/test/auth/bearer_test.go +++ b/pkg/test/auth/bearer_test.go @@ -1,8 +1,21 @@ package auth_test import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "encoding/pem" + "math/big" + "net/http" + "os" + "path/filepath" "testing" + "time" + "github.com/golang-jwt/jwt/v5" . "github.com/smartystreets/goconvey/convey" auth "zotregistry.dev/zot/v2/pkg/test/auth" @@ -14,8 +27,124 @@ func TestBearerServer(t *testing.T) { }) } -func TestBearerServerLegacy(t *testing.T) { - Convey("test MakeAuthTestServerLegacy() no serve key", t, func() { - So(func() { auth.MakeAuthTestServerLegacy("", "") }, ShouldPanic) +// doGet performs an HTTP GET request with context. +func doGet(url string) (*http.Response, error) { + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + return http.DefaultClient.Do(req) +} + +// doPost performs an HTTP POST request with context. +func doPost(url string) (*http.Response, error) { + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, nil) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + + return http.DefaultClient.Do(req) +} + +func TestNewTokenGeneration(t *testing.T) { + Convey("test new token generation", t, func() { + tempDir := t.TempDir() + keyPath := filepath.Join(tempDir, "server.key") + certPath := filepath.Join(tempDir, "server.crt") + + // Generate an RSA key pair + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + So(err, ShouldBeNil) + + // Write the private key to a file + keyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + keyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: keyBytes, + }) + err = os.WriteFile(keyPath, keyPEM, 0o600) + So(err, ShouldBeNil) + + // Create a self-signed certificate + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "test", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + } + certDER, err := x509.CreateCertificate( + rand.Reader, template, template, &privateKey.PublicKey, privateKey, + ) + So(err, ShouldBeNil) + + certPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certDER, + }) + err = os.WriteFile(certPath, certPEM, 0o600) + So(err, ShouldBeNil) + + Convey("new server should generate valid tokens", func() { + server := auth.MakeAuthTestServer(keyPath, "RS256", "unauthorized-repo") + defer server.Close() + + resp, err := doGet(server.URL + "?scope=repository:test-repo:pull,push") + So(err, ShouldBeNil) + So(resp.StatusCode, ShouldEqual, http.StatusOK) + + defer resp.Body.Close() + + var tokenResp auth.AccessTokenResponse + err = json.NewDecoder(resp.Body).Decode(&tokenResp) + So(err, ShouldBeNil) + So(tokenResp.AccessToken, ShouldNotBeEmpty) + + // Parse and verify the token + token, err := jwt.Parse(tokenResp.AccessToken, func(token *jwt.Token) (any, error) { + return &privateKey.PublicKey, nil + }) + So(err, ShouldBeNil) + So(token.Valid, ShouldBeTrue) + So(token.Method.Alg(), ShouldEqual, "RS256") + }) + + Convey("new server should reject non-GET requests", func() { + server := auth.MakeAuthTestServer(keyPath, "RS256", "unauthorized-repo") + defer server.Close() + + resp, err := doPost(server.URL) + So(err, ShouldBeNil) + So(resp.StatusCode, ShouldEqual, http.StatusMethodNotAllowed) + resp.Body.Close() + }) + }) +} + +func TestParseBearerAuthHeader(t *testing.T) { + Convey("test ParseBearerAuthHeader", t, func() { + Convey("should parse valid bearer auth header", func() { + header := `Bearer realm="https://auth.example.com/token",` + + `service="registry.example.com",scope="repository:myrepo:pull"` + parsed := auth.ParseBearerAuthHeader(header) + + So(parsed.Realm, ShouldEqual, "https://auth.example.com/token") + So(parsed.Service, ShouldEqual, "registry.example.com") + So(parsed.Scope, ShouldEqual, "repository:myrepo:pull") + }) + + Convey("should handle empty scope", func() { + header := `Bearer realm="https://auth.example.com/token",` + + `service="registry.example.com",scope=""` + parsed := auth.ParseBearerAuthHeader(header) + + So(parsed.Realm, ShouldEqual, "https://auth.example.com/token") + So(parsed.Service, ShouldEqual, "registry.example.com") + So(parsed.Scope, ShouldEqual, "") + }) }) }