summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorGusted <postmaster@gusted.xyz>2024-08-28 08:23:48 +0200
committerGusted <postmaster@gusted.xyz>2024-08-28 10:33:32 +0200
commit5a871f6095a5aee88336a5128bd0e0f1477474a6 (patch)
tree52f5ba268462160f78fd0afda957c79e01d330c4 /tests
parentMerge pull request 'Remove 15 unused strings' (#5139) from 0ko/forgejo:i18n-c... (diff)
downloadforgejo-5a871f6095a5aee88336a5128bd0e0f1477474a6.tar.xz
forgejo-5a871f6095a5aee88336a5128bd0e0f1477474a6.zip
[SEC] Ensure propagation of API scopes for Conan and Container authentication
- The Conan and Container packages use a different type of authentication. It first authenticates via the regular way (api tokens or user:password, handled via `auth.Basic`) and then generates a JWT token that is used by the package software (such as Docker) to do the action they wanted to do. This JWT token didn't properly propagate the API scopes that the token was generated for, and thus could lead to a 'scope escalation' within the Conan and Container packages, read access to write access. - Store the API scope in the JWT token, so it can be propagated on subsequent calls that uses that JWT token. - Integration test added. - Resolves #5128
Diffstat (limited to 'tests')
-rw-r--r--tests/integration/api_packages_conan_test.go79
-rw-r--r--tests/integration/api_packages_container_test.go38
2 files changed, 116 insertions, 1 deletions
diff --git a/tests/integration/api_packages_conan_test.go b/tests/integration/api_packages_conan_test.go
index 003867441b..9d8f435068 100644
--- a/tests/integration/api_packages_conan_test.go
+++ b/tests/integration/api_packages_conan_test.go
@@ -11,6 +11,7 @@ import (
"testing"
"time"
+ auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/packages"
conan_model "code.gitea.io/gitea/models/packages/conan"
@@ -224,6 +225,45 @@ func TestPackageConan(t *testing.T) {
assert.Equal(t, "revisions", resp.Header().Get("X-Conan-Server-Capabilities"))
})
+ t.Run("Token Scope Authentication", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ session := loginUser(t, user.Name)
+
+ testCase := func(t *testing.T, scope auth_model.AccessTokenScope, expectedStatusCode int) {
+ t.Helper()
+
+ token := getTokenForLoggedInUser(t, session, scope)
+
+ req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/authenticate", url)).
+ AddTokenAuth(token)
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ body := resp.Body.String()
+ assert.NotEmpty(t, body)
+
+ recipeURL := fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, "TestScope", version1, "testing", channel1)
+
+ req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL), map[string]int64{
+ conanfileName: 64,
+ "removed.txt": 0,
+ }).AddTokenAuth(token)
+ MakeRequest(t, req, expectedStatusCode)
+ }
+
+ t.Run("Read permission", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ testCase(t, auth_model.AccessTokenScopeReadPackage, http.StatusUnauthorized)
+ })
+
+ t.Run("Write permission", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ testCase(t, auth_model.AccessTokenScopeWritePackage, http.StatusOK)
+ })
+ })
+
token := ""
t.Run("Authenticate", func(t *testing.T) {
@@ -481,6 +521,43 @@ func TestPackageConan(t *testing.T) {
token := ""
+ t.Run("Token Scope Authentication", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ session := loginUser(t, user.Name)
+
+ testCase := func(t *testing.T, scope auth_model.AccessTokenScope, expectedStatusCode int) {
+ t.Helper()
+
+ token := getTokenForLoggedInUser(t, session, scope)
+
+ req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/authenticate", url)).
+ AddTokenAuth(token)
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ body := resp.Body.String()
+ assert.NotEmpty(t, body)
+
+ recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, "TestScope", version1, "testing", channel1, revision1)
+
+ req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/files/%s", recipeURL, conanfileName), strings.NewReader("Doesn't need to be valid")).
+ AddTokenAuth("Bearer " + body)
+ MakeRequest(t, req, expectedStatusCode)
+ }
+
+ t.Run("Read permission", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ testCase(t, auth_model.AccessTokenScopeReadPackage, http.StatusUnauthorized)
+ })
+
+ t.Run("Write permission", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ testCase(t, auth_model.AccessTokenScopeWritePackage, http.StatusCreated)
+ })
+ })
+
t.Run("Authenticate", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
@@ -512,7 +589,7 @@ func TestPackageConan(t *testing.T) {
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeConan)
require.NoError(t, err)
- assert.Len(t, pvs, 2)
+ assert.Len(t, pvs, 3)
})
})
diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go
index 1197408830..3c28f45660 100644
--- a/tests/integration/api_packages_container_test.go
+++ b/tests/integration/api_packages_container_test.go
@@ -78,6 +78,7 @@ func TestPackageContainer(t *testing.T) {
indexManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageIndex + `","manifests":[{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"` + manifestDigest + `","platform":{"os":"linux","architecture":"arm","variant":"v7"}},{"mediaType":"` + oci.MediaTypeImageManifest + `","digest":"` + untaggedManifestDigest + `","platform":{"os":"linux","architecture":"arm64","variant":"v8"}}]}`
anonymousToken := ""
+ readUserToken := ""
userToken := ""
t.Run("Authenticate", func(t *testing.T) {
@@ -140,6 +141,30 @@ func TestPackageContainer(t *testing.T) {
req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)).
AddTokenAuth(userToken)
MakeRequest(t, req, http.StatusOK)
+
+ // Token that should enforce the read scope.
+ t.Run("Read scope", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ session := loginUser(t, user.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage)
+
+ req := NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL))
+ req.SetBasicAuth(user.Name, token)
+
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ tokenResponse := &TokenResponse{}
+ DecodeJSON(t, resp, &tokenResponse)
+
+ assert.NotEmpty(t, tokenResponse.Token)
+
+ readUserToken = fmt.Sprintf("Bearer %s", tokenResponse.Token)
+
+ req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)).
+ AddTokenAuth(readUserToken)
+ MakeRequest(t, req, http.StatusOK)
+ })
})
})
@@ -163,6 +188,10 @@ func TestPackageContainer(t *testing.T) {
AddTokenAuth(anonymousToken)
MakeRequest(t, req, http.StatusUnauthorized)
+ req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)).
+ AddTokenAuth(readUserToken)
+ MakeRequest(t, req, http.StatusUnauthorized)
+
req = NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, unknownDigest), bytes.NewReader(blobContent)).
AddTokenAuth(userToken)
MakeRequest(t, req, http.StatusBadRequest)
@@ -319,6 +348,11 @@ func TestPackageContainer(t *testing.T) {
MakeRequest(t, req, http.StatusUnauthorized)
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, tag), strings.NewReader(manifestContent)).
+ AddTokenAuth(readUserToken).
+ SetHeader("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
+ MakeRequest(t, req, http.StatusUnauthorized)
+
+ req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, tag), strings.NewReader(manifestContent)).
AddTokenAuth(userToken).
SetHeader("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
resp := MakeRequest(t, req, http.StatusCreated)
@@ -521,6 +555,10 @@ func TestPackageContainer(t *testing.T) {
req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, blobDigest)).
AddTokenAuth(anonymousToken)
MakeRequest(t, req, http.StatusOK)
+
+ req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, blobDigest)).
+ AddTokenAuth(readUserToken)
+ MakeRequest(t, req, http.StatusOK)
})
t.Run("GetBlob", func(t *testing.T) {