mirror of
https://github.com/project-zot/zot.git
synced 2026-06-18 05:28:07 +08:00
708adf63d4
* fix: CVE-2025-30204 - golang-jwt DoS vulnerability via excessive memory allocation Signed-off-by: Asgeir Nilsen <asgeir@twingine.no> * fix: linting Signed-off-by: Asgeir Nilsen <asgeir@twingine.no> * chore: update project-zot/mockoidc to remove golang-jwt v3 Signed-off-by: Asgeir Nilsen <asgeir@twingine.no> * test: Add more tests for bearer tokens Signed-off-by: Asgeir Nilsen <asgeir@twingine.no> * fix: Rewrite tests to remove MakeAuthTestServerLegacy Signed-off-by: Asgeir Nilsen <asgeir@twingine.no> --------- Signed-off-by: Asgeir Nilsen <asgeir@twingine.no>
151 lines
4.1 KiB
Go
151 lines
4.1 KiB
Go
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"
|
|
)
|
|
|
|
func TestBearerServer(t *testing.T) {
|
|
Convey("test MakeAuthTestServer() no serve key", t, func() {
|
|
So(func() { auth.MakeAuthTestServer("", "", "") }, 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, "")
|
|
})
|
|
})
|
|
}
|