mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 21:17:58 +08:00
[feat]: add support for EC/ED25519 public keys for token authentication (#2998)
* feat: rework token auth to allow ED25519/EC public keys Signed-off-by: evanebb <git@evanus.nl> * fix: shadow err variable to hopefully avoid data race Signed-off-by: evanebb <git@evanus.nl> * fix: apply golangci-lint feedback Signed-off-by: evanebb <git@evanus.nl> * fix: simplify public key loading by only supporting certificates, fixes ED25519 certificate handling Signed-off-by: evanebb <git@evanus.nl> * test: add golang-jwt based test auth server and test RSA/EC/ED25519 keys Signed-off-by: evanebb <git@evanus.nl> * fix: restrict allowed signing algorithms as recommended by library Signed-off-by: evanebb <git@evanus.nl> * test: add more bearer authorizer tests Signed-off-by: evanebb <git@evanus.nl> * fix: apply more golangci-lint feedback Signed-off-by: evanebb <git@evanus.nl> * test: ensure chmod calls run on test failure for authn errors test Signed-off-by: evanebb <git@evanus.nl> * fix: verify issued-at in given token if present Pulls the validation in-line with the old library Signed-off-by: evanebb <git@evanus.nl> --------- Signed-off-by: evanebb <git@evanus.nl>
This commit is contained in:
+83
-1
@@ -1,15 +1,20 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chartmuseum/auth"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"zotregistry.dev/zot/pkg/api"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -24,7 +29,60 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func MakeAuthTestServer(serverKey string, unauthorizedNamespace string) *httptest.Server {
|
||||
func MakeAuthTestServer(serverKey, signAlg string, unauthorizedNamespace string) *httptest.Server {
|
||||
signingKey := loadPrivateKeyFromFile(serverKey)
|
||||
signingMethod := jwt.GetSigningMethod(signAlg)
|
||||
|
||||
authTestServer := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
var access []api.ResourceAccess
|
||||
|
||||
scope := request.URL.Query().Get("scope")
|
||||
if scope != "" {
|
||||
parts := strings.Split(scope, ":")
|
||||
name := parts[1]
|
||||
actions := strings.Split(parts[2], ",")
|
||||
|
||||
if name == unauthorizedNamespace {
|
||||
actions = []string{}
|
||||
}
|
||||
|
||||
access = []api.ResourceAccess{
|
||||
{
|
||||
Name: name,
|
||||
Type: "repository",
|
||||
Actions: actions,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
claims := api.ClaimsWithAccess{
|
||||
Access: access,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 1)),
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
Issuer: "Zot",
|
||||
Audience: []string{"Zot Registry"},
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(signingMethod, claims)
|
||||
|
||||
signedString, err := token.SignedString(signingKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(response, `{"access_token": "%s"}`, signedString)
|
||||
}))
|
||||
|
||||
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",
|
||||
@@ -85,3 +143,27 @@ func ParseBearerAuthHeader(authHeaderRaw string) *AuthHeader {
|
||||
|
||||
return &h
|
||||
}
|
||||
|
||||
func loadPrivateKeyFromFile(path string) crypto.PrivateKey {
|
||||
privateKeyBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rsaKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)
|
||||
if err == nil {
|
||||
return rsaKey
|
||||
}
|
||||
|
||||
ecKey, err := jwt.ParseECPrivateKeyFromPEM(privateKeyBytes)
|
||||
if err == nil {
|
||||
return ecKey
|
||||
}
|
||||
|
||||
edKey, err := jwt.ParseEdPrivateKeyFromPEM(privateKeyBytes)
|
||||
if err == nil {
|
||||
return edKey
|
||||
}
|
||||
|
||||
panic("no valid private key found in file " + path)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,12 @@ import (
|
||||
|
||||
func TestBearerServer(t *testing.T) {
|
||||
Convey("test MakeAuthTestServer() no serve key", t, func() {
|
||||
So(func() { auth.MakeAuthTestServer("", "") }, ShouldPanic)
|
||||
So(func() { auth.MakeAuthTestServer("", "", "") }, ShouldPanic)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBearerServerLegacy(t *testing.T) {
|
||||
Convey("test MakeAuthTestServerLegacy() no serve key", t, func() {
|
||||
So(func() { auth.MakeAuthTestServerLegacy("", "") }, ShouldPanic)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user