mirror of
https://github.com/project-zot/zot.git
synced 2025-04-01 02:42:32 -05: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:
parent
e7fb9c5e60
commit
d465690630
11 changed files with 1413 additions and 759 deletions
|
@ -175,4 +175,8 @@ var (
|
|||
ErrImageNotFound = errors.New("image not found")
|
||||
ErrAmbiguousInput = errors.New("input is not specific enough")
|
||||
ErrReceivedUnexpectedAuthHeader = errors.New("received unexpected www-authenticate header")
|
||||
ErrNoBearerToken = errors.New("no bearer token given")
|
||||
ErrInvalidBearerToken = errors.New("invalid bearer token given")
|
||||
ErrInsufficientScope = errors.New("bearer token does not have sufficient scope")
|
||||
ErrCouldNotLoadCertificate = errors.New("failed to load certificate")
|
||||
)
|
||||
|
|
2
go.mod
2
go.mod
|
@ -33,6 +33,7 @@ require (
|
|||
github.com/go-redis/redismock/v9 v9.2.0
|
||||
github.com/go-redsync/redsync/v4 v4.13.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/go-containerregistry v0.20.3
|
||||
github.com/google/go-github/v62 v62.0.0
|
||||
github.com/google/uuid v1.6.0
|
||||
|
@ -273,7 +274,6 @@ require (
|
|||
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.1 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
|
|
124
pkg/api/authn.go
124
pkg/api/authn.go
|
@ -6,17 +6,16 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chartmuseum/auth"
|
||||
guuid "github.com/gofrs/uuid"
|
||||
"github.com/google/go-github/v62/github"
|
||||
"github.com/google/uuid"
|
||||
|
@ -39,9 +38,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
bearerAuthDefaultAccessEntryType = "repository"
|
||||
issuedAtOffset = 5 * time.Second
|
||||
relyingPartyCookieMaxAge = 120
|
||||
issuedAtOffset = 5 * time.Second
|
||||
relyingPartyCookieMaxAge = 120
|
||||
)
|
||||
|
||||
type AuthnMiddleware struct {
|
||||
|
@ -404,17 +402,17 @@ func (amw *AuthnMiddleware) tryAuthnHandlers(ctlr *Controller) mux.MiddlewareFun
|
|||
}
|
||||
|
||||
func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
|
||||
authorizer, err := auth.NewAuthorizer(&auth.AuthorizerOptions{
|
||||
Realm: ctlr.Config.HTTP.Auth.Bearer.Realm,
|
||||
Service: ctlr.Config.HTTP.Auth.Bearer.Service,
|
||||
PublicKeyPath: ctlr.Config.HTTP.Auth.Bearer.Cert,
|
||||
AccessEntryType: bearerAuthDefaultAccessEntryType,
|
||||
EmptyDefaultNamespace: true,
|
||||
})
|
||||
certificate, err := loadCertificateFromFile(ctlr.Config.HTTP.Auth.Bearer.Cert)
|
||||
if err != nil {
|
||||
ctlr.Log.Panic().Err(err).Msg("failed to create bearer authorizer")
|
||||
ctlr.Log.Panic().Err(err).Msg("failed to load certificate for bearer authentication")
|
||||
}
|
||||
|
||||
authorizer := NewBearerAuthorizer(
|
||||
ctlr.Config.HTTP.Auth.Bearer.Realm,
|
||||
ctlr.Config.HTTP.Auth.Bearer.Service,
|
||||
certificate.PublicKey,
|
||||
)
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
if request.Method == http.MethodOptions {
|
||||
|
@ -425,8 +423,6 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
|
|||
}
|
||||
|
||||
acCtrlr := NewAccessController(ctlr.Config)
|
||||
vars := mux.Vars(request)
|
||||
name := vars["name"]
|
||||
|
||||
// we want to bypass auth for mgmt route
|
||||
isMgmtRequested := request.RequestURI == constants.FullMgmt
|
||||
|
@ -439,67 +435,40 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
|
|||
return
|
||||
}
|
||||
|
||||
action := auth.PullAction
|
||||
if m := request.Method; m != http.MethodGet && m != http.MethodHead {
|
||||
action = auth.PushAction
|
||||
var requestedAccess *ResourceAction
|
||||
|
||||
if request.RequestURI != "/v2/" {
|
||||
// if this is not the base route, the requested repository/action must be authorized
|
||||
vars := mux.Vars(request)
|
||||
name := vars["name"]
|
||||
|
||||
action := "pull"
|
||||
if m := request.Method; m != http.MethodGet && m != http.MethodHead {
|
||||
action = "push"
|
||||
}
|
||||
|
||||
requestedAccess = &ResourceAction{
|
||||
Type: "repository",
|
||||
Name: name,
|
||||
Action: action,
|
||||
}
|
||||
}
|
||||
|
||||
var permissions *auth.Permission
|
||||
|
||||
// Empty scope should be allowed according to the distribution auth spec
|
||||
// This is only necessary for the bearer auth type
|
||||
if request.RequestURI == "/v2/" && authorizer.Type == auth.BearerAuthAuthorizerType {
|
||||
if header == "" {
|
||||
// first request that clients make (without any header)
|
||||
WWWAuthenticateHeader := fmt.Sprintf("Bearer realm=\"%s\",service=\"%s\",scope=\"\"",
|
||||
authorizer.Realm, authorizer.Service)
|
||||
|
||||
permissions = &auth.Permission{
|
||||
// challenge for the client to use to authenticate to /v2/
|
||||
WWWAuthenticateHeader: WWWAuthenticateHeader,
|
||||
Allowed: false,
|
||||
}
|
||||
} else {
|
||||
// subsequent requests with token on /v2/
|
||||
bearerTokenMatch := regexp.MustCompile("(?i)bearer (.*)")
|
||||
|
||||
signedString := bearerTokenMatch.ReplaceAllString(header, "$1")
|
||||
|
||||
// If the token is valid, our job is done
|
||||
// Since this is the /v2 base path and we didn't pass a scope to the auth header in the previous step
|
||||
// there is no access check to enforce
|
||||
_, err := authorizer.TokenDecoder.DecodeToken(signedString)
|
||||
if err != nil {
|
||||
ctlr.Log.Error().Err(err).Msg("failed to parse Authorization header")
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNSUPPORTED))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
permissions = &auth.Permission{
|
||||
Allowed: true,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
|
||||
// subsequent requests with token on /v2/<resource>/
|
||||
permissions, err = authorizer.Authorize(header, action, name)
|
||||
if err != nil {
|
||||
ctlr.Log.Error().Err(err).Msg("failed to parse Authorization header")
|
||||
err := authorizer.Authorize(header, requestedAccess)
|
||||
if err != nil {
|
||||
var challenge *AuthChallengeError
|
||||
if errors.As(err, &challenge) {
|
||||
ctlr.Log.Debug().Err(challenge).Msg("bearer token authorization failed")
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
zcommon.WriteJSON(response, http.StatusInternalServerError, apiErr.NewError(apiErr.UNSUPPORTED))
|
||||
response.Header().Set("WWW-Authenticate", challenge.Header())
|
||||
zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNAUTHORIZED))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !permissions.Allowed {
|
||||
ctlr.Log.Error().Err(err).Msg("failed to parse Authorization header")
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
response.Header().Set("WWW-Authenticate", permissions.WWWAuthenticateHeader)
|
||||
|
||||
zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNAUTHORIZED))
|
||||
zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNSUPPORTED))
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -932,3 +901,22 @@ func GenerateAPIKey(uuidGenerator guuid.Generator, log log.Logger,
|
|||
|
||||
return apiKey, apiKeyID.String(), err
|
||||
}
|
||||
|
||||
func loadCertificateFromFile(path string) (*x509.Certificate, error) {
|
||||
rawCert, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w, path %s", zerr.ErrCouldNotLoadCertificate, err, path)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(rawCert)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("%w: no valid PEM data found", zerr.ErrCouldNotLoadCertificate)
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", zerr.ErrCouldNotLoadCertificate, err)
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
|
136
pkg/api/bearer.go
Normal file
136
pkg/api/bearer.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
|
||||
zerr "zotregistry.dev/zot/errors"
|
||||
)
|
||||
|
||||
var bearerTokenMatch = regexp.MustCompile("(?i)bearer (.*)")
|
||||
|
||||
// ResourceAccess is a single entry in the private 'access' claim specified by the distribution token authentication
|
||||
// specification.
|
||||
type ResourceAccess struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Actions []string `json:"actions"`
|
||||
}
|
||||
|
||||
type ResourceAction struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
|
||||
// ClaimsWithAccess is a claim set containing the private 'access' claim specified by the distribution token
|
||||
// authentication specification, in addition to the standard registered claims.
|
||||
// https://distribution.github.io/distribution/spec/auth/jwt/
|
||||
type ClaimsWithAccess struct {
|
||||
Access []ResourceAccess `json:"access"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type AuthChallengeError struct {
|
||||
err error
|
||||
realm string
|
||||
service string
|
||||
resourceAction *ResourceAction
|
||||
}
|
||||
|
||||
func (c AuthChallengeError) Error() string {
|
||||
return c.err.Error()
|
||||
}
|
||||
|
||||
// Header constructs an appropriate value for the WWW-Authenticate header to be returned to the client.
|
||||
func (c AuthChallengeError) Header() string {
|
||||
if c.resourceAction == nil {
|
||||
// no access was requested, so return an empty scope
|
||||
return fmt.Sprintf("Bearer realm=\"%s\",service=\"%s\",scope=\"\"",
|
||||
c.realm, c.service)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Bearer realm=\"%s\",service=\"%s\",scope=\"%s:%s:%s\"",
|
||||
c.realm, c.service, c.resourceAction.Type, c.resourceAction.Name, c.resourceAction.Action)
|
||||
}
|
||||
|
||||
type BearerAuthorizer struct {
|
||||
realm string
|
||||
service string
|
||||
key crypto.PublicKey
|
||||
}
|
||||
|
||||
func NewBearerAuthorizer(realm string, service string, key crypto.PublicKey) BearerAuthorizer {
|
||||
return BearerAuthorizer{
|
||||
realm: realm,
|
||||
service: service,
|
||||
key: key,
|
||||
}
|
||||
}
|
||||
|
||||
// Authorize verifies whether the bearer token in the given Authorization header is valid, and whether it has sufficient
|
||||
// scope for the requested resource action. If an authorization error occurs (e.g. no token is given or the token has
|
||||
// insufficient scope), an AuthChallengeError is returned as the error.
|
||||
func (a *BearerAuthorizer) Authorize(header string, requested *ResourceAction) error {
|
||||
challenge := &AuthChallengeError{
|
||||
realm: a.realm,
|
||||
service: a.service,
|
||||
resourceAction: requested,
|
||||
}
|
||||
|
||||
if header == "" {
|
||||
// if no bearer token is set in the authorization header, return the authentication challenge
|
||||
challenge.err = zerr.ErrNoBearerToken
|
||||
|
||||
return challenge
|
||||
}
|
||||
|
||||
signedString := bearerTokenMatch.ReplaceAllString(header, "$1")
|
||||
|
||||
token, err := jwt.ParseWithClaims(signedString, &ClaimsWithAccess{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return a.key, nil
|
||||
}, jwt.WithValidMethods(a.allowedSigningAlgorithms()), jwt.WithIssuedAt())
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", zerr.ErrInvalidBearerToken, err)
|
||||
}
|
||||
|
||||
if requested == nil {
|
||||
// the token is valid and no access is requested, so we do not have to validate the access claim
|
||||
return nil
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(*ClaimsWithAccess)
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: invalid claims type", zerr.ErrInvalidBearerToken)
|
||||
}
|
||||
|
||||
// check whether the requested access is allowed by the scope of the token
|
||||
for _, allowed := range claims.Access {
|
||||
if allowed.Type != requested.Type {
|
||||
continue
|
||||
}
|
||||
|
||||
if allowed.Name != requested.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
if !slices.Contains(allowed.Actions, requested.Action) {
|
||||
continue
|
||||
}
|
||||
|
||||
// requested action is allowed, so don't return an error
|
||||
return nil
|
||||
}
|
||||
|
||||
challenge.err = zerr.ErrInsufficientScope
|
||||
|
||||
return challenge
|
||||
}
|
||||
|
||||
func (a *BearerAuthorizer) allowedSigningAlgorithms() []string {
|
||||
return []string{"EdDSA", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512"}
|
||||
}
|
121
pkg/api/bearer_test.go
Normal file
121
pkg/api/bearer_test.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package api_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
zerr "zotregistry.dev/zot/errors"
|
||||
"zotregistry.dev/zot/pkg/api"
|
||||
)
|
||||
|
||||
func TestBearerAuthorizer(t *testing.T) {
|
||||
Convey("Test bearer token authorization", t, func() {
|
||||
signingMethod := jwt.SigningMethodRS256
|
||||
|
||||
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pubKey := privKey.Public()
|
||||
|
||||
authorizer := api.NewBearerAuthorizer("realm", "service", pubKey)
|
||||
|
||||
Convey("Empty authorization header given", func() {
|
||||
err := authorizer.Authorize("", nil)
|
||||
So(err, ShouldBeError, zerr.ErrNoBearerToken)
|
||||
})
|
||||
|
||||
Convey("Valid token", func() {
|
||||
access := []api.ResourceAccess{
|
||||
{
|
||||
Name: "authorized-repository",
|
||||
Type: "repository",
|
||||
Actions: []string{"pull"},
|
||||
},
|
||||
}
|
||||
|
||||
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, err := jwt.NewWithClaims(signingMethod, claims).SignedString(privKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
authHeader := "Bearer " + token
|
||||
|
||||
Convey("Unauthorized type", func() {
|
||||
requested := &api.ResourceAction{
|
||||
Type: "registry",
|
||||
Name: "catalog",
|
||||
Action: "*",
|
||||
}
|
||||
|
||||
err := authorizer.Authorize(authHeader, requested)
|
||||
So(err, ShouldHaveSameTypeAs, &api.AuthChallengeError{})
|
||||
So(err, ShouldBeError, zerr.ErrInsufficientScope)
|
||||
})
|
||||
|
||||
Convey("Unauthorized name", func() {
|
||||
requested := &api.ResourceAction{
|
||||
Type: "repository",
|
||||
Name: "unauthorized-repository",
|
||||
Action: "pull",
|
||||
}
|
||||
|
||||
err := authorizer.Authorize(authHeader, requested)
|
||||
So(err, ShouldHaveSameTypeAs, &api.AuthChallengeError{})
|
||||
So(err, ShouldBeError, zerr.ErrInsufficientScope)
|
||||
})
|
||||
|
||||
Convey("Unauthorized action", func() {
|
||||
requested := &api.ResourceAction{
|
||||
Type: "repository",
|
||||
Name: "authorized-repository",
|
||||
Action: "push",
|
||||
}
|
||||
|
||||
err := authorizer.Authorize(authHeader, requested)
|
||||
So(err, ShouldHaveSameTypeAs, &api.AuthChallengeError{})
|
||||
So(err, ShouldBeError, zerr.ErrInsufficientScope)
|
||||
})
|
||||
|
||||
Convey("Successful authorization with requested access", func() {
|
||||
requested := &api.ResourceAction{
|
||||
Type: "repository",
|
||||
Name: "authorized-repository",
|
||||
Action: "pull",
|
||||
}
|
||||
|
||||
err := authorizer.Authorize(authHeader, requested)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Successful authorization without requested access", func() {
|
||||
err := authorizer.Authorize(authHeader, nil)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Invalid token", func() {
|
||||
authHeader := "invalid"
|
||||
|
||||
err := authorizer.Authorize(authHeader, nil)
|
||||
So(err, ShouldWrap, zerr.ErrInvalidBearerToken)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -78,6 +78,10 @@ const (
|
|||
ServerCert = "../../test/data/server.cert"
|
||||
ServerKey = "../../test/data/server.key"
|
||||
CACert = "../../test/data/ca.crt"
|
||||
ServerCertECDSA = "../../test/data/server-ecdsa.cert"
|
||||
ServerKeyECDSA = "../../test/data/server-ecdsa.key"
|
||||
ServerCertED25519 = "../../test/data/server-ed25519.cert"
|
||||
ServerKeyED25519 = "../../test/data/server-ed25519.key"
|
||||
UnauthorizedNamespace = "fortknox/notallowed"
|
||||
AuthorizationNamespace = "authz/image"
|
||||
LDAPAddress = "127.0.0.1"
|
||||
|
@ -2310,6 +2314,11 @@ func TestAuthnErrors(t *testing.T) {
|
|||
err := os.WriteFile(tmpFile, []byte("test"), 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
defer func() {
|
||||
err := os.Chmod(tmpFile, 0o644)
|
||||
So(err, ShouldBeNil)
|
||||
}()
|
||||
|
||||
conf.HTTP.Auth.LDAP = (&config.LDAPConfig{
|
||||
Insecure: true,
|
||||
Address: LDAPAddress,
|
||||
|
@ -2324,9 +2333,6 @@ func TestAuthnErrors(t *testing.T) {
|
|||
So(func() {
|
||||
api.AuthHandler(ctlr)
|
||||
}, ShouldPanic)
|
||||
|
||||
err = os.Chmod(tmpFile, 0o644)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("ldap CA certs is empty", t, func() {
|
||||
|
@ -2387,6 +2393,11 @@ func TestAuthnErrors(t *testing.T) {
|
|||
err := os.WriteFile(tmpFile, []byte("test"), 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
defer func() {
|
||||
err := os.Chmod(tmpFile, 0o644)
|
||||
So(err, ShouldBeNil)
|
||||
}()
|
||||
|
||||
conf.HTTP.Auth.HTPasswd = config.AuthHTPasswd{
|
||||
Path: tmpFile,
|
||||
}
|
||||
|
@ -2396,9 +2407,63 @@ func TestAuthnErrors(t *testing.T) {
|
|||
So(func() {
|
||||
api.AuthHandler(ctlr)
|
||||
}, ShouldPanic)
|
||||
})
|
||||
|
||||
err = os.Chmod(tmpFile, 0o644)
|
||||
Convey("Bearer auth invalid PEM data", t, func() {
|
||||
port := test.GetFreePort()
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
tmpDir := t.TempDir()
|
||||
tmpFile := path.Join(tmpDir, "invalid-server.cert")
|
||||
|
||||
err := os.WriteFile(tmpFile, []byte("invalid"), 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
defer func() {
|
||||
err := os.Chmod(tmpFile, 0o644)
|
||||
So(err, ShouldBeNil)
|
||||
}()
|
||||
|
||||
conf.HTTP.Auth.Bearer = &config.BearerConfig{
|
||||
Realm: "realm",
|
||||
Service: "service",
|
||||
Cert: tmpFile,
|
||||
}
|
||||
|
||||
ctlr := makeController(conf, t.TempDir())
|
||||
|
||||
So(func() {
|
||||
api.AuthHandler(ctlr)
|
||||
}, ShouldPanic)
|
||||
})
|
||||
|
||||
Convey("Bearer auth invalid certificate", t, func() {
|
||||
port := test.GetFreePort()
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
tmpDir := t.TempDir()
|
||||
tmpFile := path.Join(tmpDir, "invalid-server.cert")
|
||||
|
||||
// 'invalid' encoded as PEM
|
||||
err := os.WriteFile(tmpFile, []byte("-----BEGIN CERTIFICATE-----\naW52YWxpZA==\n-----END CERTIFICATE-----"), 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
defer func() {
|
||||
err := os.Chmod(tmpFile, 0o644)
|
||||
So(err, ShouldBeNil)
|
||||
}()
|
||||
|
||||
conf.HTTP.Auth.Bearer = &config.BearerConfig{
|
||||
Realm: "realm",
|
||||
Service: "service",
|
||||
Cert: tmpFile,
|
||||
}
|
||||
|
||||
ctlr := makeController(conf, t.TempDir())
|
||||
|
||||
So(func() {
|
||||
api.AuthHandler(ctlr)
|
||||
}, ShouldPanic)
|
||||
})
|
||||
|
||||
Convey("NewRelyingPartyGithub fail", t, func() {
|
||||
|
@ -2411,6 +2476,11 @@ func TestAuthnErrors(t *testing.T) {
|
|||
err := os.WriteFile(tmpFile, []byte("test"), 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
defer func() {
|
||||
err := os.Chmod(tmpFile, 0o644)
|
||||
So(err, ShouldBeNil)
|
||||
}()
|
||||
|
||||
conf.HTTP.Auth.HTPasswd = config.AuthHTPasswd{
|
||||
Path: tmpFile,
|
||||
}
|
||||
|
@ -2418,9 +2488,6 @@ func TestAuthnErrors(t *testing.T) {
|
|||
So(func() {
|
||||
api.NewRelyingPartyGithub(conf, "prov", nil, nil, log.NewLogger("debug", ""))
|
||||
}, ShouldPanic)
|
||||
|
||||
err = os.Chmod(tmpFile, 0o644)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -3846,190 +3913,294 @@ func TestLDAPClient(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestBearerAuthMultipleAlgorithms(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
key string
|
||||
cert string
|
||||
alg string
|
||||
}{
|
||||
{
|
||||
"RSA signing key",
|
||||
ServerKey,
|
||||
ServerCert,
|
||||
"RS256",
|
||||
},
|
||||
{
|
||||
"ECDSA signing key",
|
||||
ServerKeyECDSA,
|
||||
ServerCertECDSA,
|
||||
"ES256",
|
||||
},
|
||||
{
|
||||
"ED25519 signing key",
|
||||
ServerKeyED25519,
|
||||
ServerCertED25519,
|
||||
"EdDSA",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
Convey("Make a new controller with "+testCase.name, t, func() {
|
||||
authTestServer := authutils.MakeAuthTestServer(testCase.key, testCase.alg, UnauthorizedNamespace)
|
||||
defer authTestServer.Close()
|
||||
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
|
||||
aurl, err := url.Parse(authTestServer.URL)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
Bearer: &config.BearerConfig{
|
||||
Cert: testCase.cert,
|
||||
Realm: authTestServer.URL + "/auth/token",
|
||||
Service: aurl.Host,
|
||||
},
|
||||
}
|
||||
ctlr := makeController(conf, t.TempDir())
|
||||
|
||||
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)
|
||||
|
||||
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 = resty.R().
|
||||
SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
|
||||
Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBearerAuth(t *testing.T) {
|
||||
Convey("Make a new controller", t, func() {
|
||||
authTestServer := authutils.MakeAuthTestServer(ServerKey, UnauthorizedNamespace)
|
||||
defer authTestServer.Close()
|
||||
testCases := []struct {
|
||||
name string
|
||||
useLegacyAuthTestServer bool
|
||||
}{
|
||||
{
|
||||
name: "new authentication test server",
|
||||
useLegacyAuthTestServer: false,
|
||||
},
|
||||
{
|
||||
name: "legacy authentication test server",
|
||||
useLegacyAuthTestServer: true,
|
||||
},
|
||||
}
|
||||
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
for _, testCase := range testCases {
|
||||
Convey("Make a new controller with "+testCase.name, t, func() {
|
||||
var authTestServer *httptest.Server
|
||||
if testCase.useLegacyAuthTestServer {
|
||||
authTestServer = authutils.MakeAuthTestServerLegacy(ServerKey, UnauthorizedNamespace)
|
||||
} else {
|
||||
authTestServer = authutils.MakeAuthTestServer(ServerKey, "RS256", UnauthorizedNamespace)
|
||||
}
|
||||
defer authTestServer.Close()
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
||||
aurl, err := url.Parse(authTestServer.URL)
|
||||
So(err, ShouldBeNil)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
Bearer: &config.BearerConfig{
|
||||
Cert: ServerCert,
|
||||
Realm: authTestServer.URL + "/auth/token",
|
||||
Service: aurl.Host,
|
||||
},
|
||||
}
|
||||
ctlr := makeController(conf, t.TempDir())
|
||||
aurl, err := url.Parse(authTestServer.URL)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(port)
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
Bearer: &config.BearerConfig{
|
||||
Cert: ServerCert,
|
||||
Realm: authTestServer.URL + "/auth/token",
|
||||
Service: aurl.Host,
|
||||
},
|
||||
}
|
||||
ctlr := makeController(conf, t.TempDir())
|
||||
|
||||
defer cm.StopServer()
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(port)
|
||||
|
||||
blob := []byte("hello, blob!")
|
||||
digest := godigest.FromBytes(blob).String()
|
||||
defer cm.StopServer()
|
||||
|
||||
resp, err := resty.R().Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
blob := []byte("hello, blob!")
|
||||
digest := godigest.FromBytes(blob).String()
|
||||
|
||||
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)
|
||||
resp, err := resty.R().Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
var goodToken authutils.AccessTokenResponse
|
||||
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)
|
||||
|
||||
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||
So(err, ShouldBeNil)
|
||||
var goodToken authutils.AccessTokenResponse
|
||||
|
||||
resp, err = resty.R().
|
||||
SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
|
||||
Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// 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).
|
||||
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)
|
||||
// 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)
|
||||
|
||||
s1, seed1 := test.GenerateRandomName()
|
||||
s2, seed2 := test.GenerateRandomName()
|
||||
repoName := s1 + "/" + s2
|
||||
resp, err = resty.R().SetHeader("Authorization",
|
||||
"Bearer "+goodToken.AccessToken).Options(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
|
||||
|
||||
ctlr.Log.Info().Int64("seed1", seed1).Int64("seed2", seed2).Msg("random seeds for repoName")
|
||||
s1, seed1 := test.GenerateRandomName()
|
||||
s2, seed2 := test.GenerateRandomName()
|
||||
repoName := s1 + "/" + s2
|
||||
|
||||
resp, err = resty.R().Post(baseURL + "/v2/" + repoName + "/blobs/uploads/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
ctlr.Log.Info().Int64("seed1", seed1).Int64("seed2", seed2).Msg("random seeds for repoName")
|
||||
|
||||
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().Post(baseURL + "/v2/" + repoName + "/blobs/uploads/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
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")
|
||||
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").
|
||||
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("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")
|
||||
|
||||
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").
|
||||
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").
|
||||
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)
|
||||
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.StatusUnauthorized)
|
||||
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)
|
||||
|
||||
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.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.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)
|
||||
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
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().
|
||||
SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
|
||||
Get(baseURL + "/v2/" + repoName + "/tags/list")
|
||||
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)
|
||||
resp, err = resty.R().
|
||||
Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
var badToken authutils.AccessTokenResponse
|
||||
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(), &badToken)
|
||||
So(err, ShouldBeNil)
|
||||
var badToken authutils.AccessTokenResponse
|
||||
|
||||
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)
|
||||
})
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBearerAuthWrongAuthorizer(t *testing.T) {
|
||||
|
@ -4054,183 +4225,204 @@ func TestBearerAuthWrongAuthorizer(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
||||
Convey("Make a new controller", t, func() {
|
||||
authTestServer := authutils.MakeAuthTestServer(ServerKey, UnauthorizedNamespace)
|
||||
defer authTestServer.Close()
|
||||
testCases := []struct {
|
||||
name string
|
||||
useLegacyAuthTestServer bool
|
||||
}{
|
||||
{
|
||||
name: "new authentication test server",
|
||||
useLegacyAuthTestServer: false,
|
||||
},
|
||||
{
|
||||
name: "legacy authentication test server",
|
||||
useLegacyAuthTestServer: true,
|
||||
},
|
||||
}
|
||||
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
for _, testCase := range testCases {
|
||||
Convey("Make a new controller with"+testCase.name, t, func() {
|
||||
var authTestServer *httptest.Server
|
||||
if testCase.useLegacyAuthTestServer {
|
||||
authTestServer = authutils.MakeAuthTestServerLegacy(ServerKey, UnauthorizedNamespace)
|
||||
} else {
|
||||
authTestServer = authutils.MakeAuthTestServer(ServerKey, "RS256", UnauthorizedNamespace)
|
||||
}
|
||||
defer authTestServer.Close()
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
||||
aurl, err := url.Parse(authTestServer.URL)
|
||||
So(err, ShouldBeNil)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
Bearer: &config.BearerConfig{
|
||||
Cert: ServerCert,
|
||||
Realm: authTestServer.URL + "/auth/token",
|
||||
Service: aurl.Host,
|
||||
},
|
||||
}
|
||||
ctlr := makeController(conf, t.TempDir())
|
||||
aurl, err := url.Parse(authTestServer.URL)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
conf.HTTP.AccessControl = &config.AccessControlConfig{
|
||||
Repositories: config.Repositories{
|
||||
test.AuthorizationAllRepos: config.PolicyGroup{
|
||||
AnonymousPolicy: []string{"read"},
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
Bearer: &config.BearerConfig{
|
||||
Cert: ServerCert,
|
||||
Realm: authTestServer.URL + "/auth/token",
|
||||
Service: aurl.Host,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
ctlr := makeController(conf, t.TempDir())
|
||||
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(port)
|
||||
conf.HTTP.AccessControl = &config.AccessControlConfig{
|
||||
Repositories: config.Repositories{
|
||||
test.AuthorizationAllRepos: config.PolicyGroup{
|
||||
AnonymousPolicy: []string{"read"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
defer cm.StopServer()
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(port)
|
||||
|
||||
blob := []byte("hello, blob!")
|
||||
digest := godigest.FromBytes(blob).String()
|
||||
defer cm.StopServer()
|
||||
|
||||
resp, err := resty.R().Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
blob := []byte("hello, blob!")
|
||||
digest := godigest.FromBytes(blob).String()
|
||||
|
||||
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)
|
||||
resp, err := resty.R().Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
var goodToken authutils.AccessTokenResponse
|
||||
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)
|
||||
var goodToken authutils.AccessTokenResponse
|
||||
|
||||
resp, err = resty.R().
|
||||
SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
|
||||
Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
s1, seed1 := test.GenerateRandomName()
|
||||
s2, seed2 := test.GenerateRandomName()
|
||||
repoName := s1 + "/" + s2
|
||||
resp, err = resty.R().
|
||||
SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
|
||||
Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
ctlr.Log.Info().Int64("seed1", seed1).Int64("seed2", seed2).Msg("random seeds for repoName")
|
||||
s1, seed1 := test.GenerateRandomName()
|
||||
s2, seed2 := test.GenerateRandomName()
|
||||
repoName := s1 + "/" + s2
|
||||
|
||||
resp, err = resty.R().Post(baseURL + "/v2/" + repoName + "/blobs/uploads/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
ctlr.Log.Info().Int64("seed1", seed1).Int64("seed2", seed2).Msg("random seeds for repoName")
|
||||
|
||||
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().Post(baseURL + "/v2/" + repoName + "/blobs/uploads/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
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")
|
||||
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").
|
||||
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("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")
|
||||
|
||||
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").
|
||||
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").
|
||||
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)
|
||||
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.StatusUnauthorized)
|
||||
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)
|
||||
|
||||
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.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.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)
|
||||
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
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().
|
||||
SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
|
||||
Get(baseURL + "/v2/" + repoName + "/tags/list")
|
||||
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)
|
||||
resp, err = resty.R().
|
||||
Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
var badToken authutils.AccessTokenResponse
|
||||
err = json.Unmarshal(resp.Body(), &badToken)
|
||||
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)
|
||||
|
||||
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)
|
||||
})
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRelyingPartyOIDC(t *testing.T) {
|
||||
|
|
|
@ -6,6 +6,7 @@ package extensions_test
|
|||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
@ -868,160 +869,181 @@ func TestMgmtExtension(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMgmtWithBearer(t *testing.T) {
|
||||
Convey("Make a new controller", t, func() {
|
||||
authorizedNamespace := "allowedrepo"
|
||||
unauthorizedNamespace := "notallowedrepo"
|
||||
testCases := []struct {
|
||||
name string
|
||||
useLegacyAuthTestServer bool
|
||||
}{
|
||||
{
|
||||
name: "new authentication test server",
|
||||
useLegacyAuthTestServer: false,
|
||||
},
|
||||
{
|
||||
name: "legacy authentication test server",
|
||||
useLegacyAuthTestServer: true,
|
||||
},
|
||||
}
|
||||
|
||||
authTestServer := authutils.MakeAuthTestServer(ServerKey, unauthorizedNamespace)
|
||||
defer authTestServer.Close()
|
||||
for _, testCase := range testCases {
|
||||
Convey("Make a new controller with "+testCase.name, t, func() {
|
||||
authorizedNamespace := "allowedrepo"
|
||||
unauthorizedNamespace := "notallowedrepo"
|
||||
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
var authTestServer *httptest.Server
|
||||
if testCase.useLegacyAuthTestServer {
|
||||
authTestServer = authutils.MakeAuthTestServerLegacy(ServerKey, unauthorizedNamespace)
|
||||
} else {
|
||||
authTestServer = authutils.MakeAuthTestServer(ServerKey, "RS256", unauthorizedNamespace)
|
||||
}
|
||||
defer authTestServer.Close()
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
||||
aurl, err := url.Parse(authTestServer.URL)
|
||||
So(err, ShouldBeNil)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
Bearer: &config.BearerConfig{
|
||||
Cert: ServerCert,
|
||||
Realm: authTestServer.URL + "/auth/token",
|
||||
Service: aurl.Host,
|
||||
},
|
||||
}
|
||||
aurl, err := url.Parse(authTestServer.URL)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
defaultValue := true
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
Bearer: &config.BearerConfig{
|
||||
Cert: ServerCert,
|
||||
Realm: authTestServer.URL + "/auth/token",
|
||||
Service: aurl.Host,
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
defaultValue := true
|
||||
|
||||
conf.Storage.RootDirectory = t.TempDir()
|
||||
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
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
conf.Storage.RootDirectory = t.TempDir()
|
||||
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(port)
|
||||
defer cm.StopServer()
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
resp, err := resty.R().Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(port)
|
||||
defer cm.StopServer()
|
||||
|
||||
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)
|
||||
resp, err := resty.R().Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
var goodToken authutils.AccessTokenResponse
|
||||
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)
|
||||
var goodToken authutils.AccessTokenResponse
|
||||
|
||||
resp, err = resty.R().
|
||||
SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
|
||||
Get(baseURL + "/v2/")
|
||||
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).Options(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
|
||||
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().Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/")
|
||||
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)
|
||||
|
||||
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().Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
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)
|
||||
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().
|
||||
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 "+goodToken.AccessToken).
|
||||
Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
|
||||
|
||||
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)
|
||||
resp, err = resty.R().
|
||||
Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
var badToken authutils.AccessTokenResponse
|
||||
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(), &badToken)
|
||||
So(err, ShouldBeNil)
|
||||
var badToken authutils.AccessTokenResponse
|
||||
|
||||
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)
|
||||
err = json.Unmarshal(resp.Body(), &badToken)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// test mgmt route
|
||||
resp, err = resty.R().Get(baseURL + constants.FullMgmt)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
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)
|
||||
|
||||
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)
|
||||
// test mgmt route
|
||||
resp, err = resty.R().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)
|
||||
})
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedMethodsHeaderMgmt(t *testing.T) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -2553,124 +2554,301 @@ func TestTLS(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBearerAuth(t *testing.T) {
|
||||
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
|
||||
testCases := []struct {
|
||||
name string
|
||||
useLegacyAuthTestServer bool
|
||||
}{
|
||||
{
|
||||
name: "new authentication test server",
|
||||
useLegacyAuthTestServer: false,
|
||||
},
|
||||
{
|
||||
name: "legacy authentication test server",
|
||||
useLegacyAuthTestServer: true,
|
||||
},
|
||||
}
|
||||
|
||||
authTestServer := authutils.MakeAuthTestServer(ServerKey, unauthorizedNamespace)
|
||||
defer authTestServer.Close()
|
||||
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
|
||||
|
||||
sctlr, srcBaseURL, _, _, srcClient := makeUpstreamServer(t, false, false)
|
||||
var authTestServer *httptest.Server
|
||||
if testCase.useLegacyAuthTestServer {
|
||||
authTestServer = authutils.MakeAuthTestServerLegacy(ServerKey, unauthorizedNamespace)
|
||||
} else {
|
||||
authTestServer = authutils.MakeAuthTestServer(ServerKey, "RS256", unauthorizedNamespace)
|
||||
}
|
||||
defer authTestServer.Close()
|
||||
|
||||
aurl, err := url.Parse(authTestServer.URL)
|
||||
So(err, ShouldBeNil)
|
||||
sctlr, srcBaseURL, _, _, srcClient := makeUpstreamServer(t, false, false)
|
||||
|
||||
sctlr.Config.HTTP.Auth = &config.AuthConfig{
|
||||
Bearer: &config.BearerConfig{
|
||||
Cert: ServerCert,
|
||||
Realm: authTestServer.URL + "/auth/token",
|
||||
Service: aurl.Host,
|
||||
},
|
||||
}
|
||||
aurl, err := url.Parse(authTestServer.URL)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
scm := test.NewControllerManager(sctlr)
|
||||
scm.StartAndWait(sctlr.Config.HTTP.Port)
|
||||
|
||||
defer scm.StopServer()
|
||||
|
||||
registryName := sync.StripRegistryTransport(srcBaseURL)
|
||||
credentialsFile := makeCredentialsFile(fmt.Sprintf(`{"%s":{"username": "%s", "password": "%s"}}`,
|
||||
registryName, username, password))
|
||||
|
||||
var tlsVerify bool
|
||||
|
||||
syncRegistryConfig := syncconf.RegistryConfig{
|
||||
Content: []syncconf.Content{
|
||||
{
|
||||
Prefix: "**", // sync everything
|
||||
sctlr.Config.HTTP.Auth = &config.AuthConfig{
|
||||
Bearer: &config.BearerConfig{
|
||||
Cert: ServerCert,
|
||||
Realm: authTestServer.URL + "/auth/token",
|
||||
Service: aurl.Host,
|
||||
},
|
||||
},
|
||||
URLs: []string{srcBaseURL},
|
||||
PollInterval: updateDuration,
|
||||
TLSVerify: &tlsVerify,
|
||||
CertDir: "",
|
||||
}
|
||||
}
|
||||
|
||||
defaultVal := true
|
||||
syncConfig := &syncconf.Config{
|
||||
Enable: &defaultVal,
|
||||
CredentialsFile: credentialsFile,
|
||||
Registries: []syncconf.RegistryConfig{syncRegistryConfig},
|
||||
}
|
||||
scm := test.NewControllerManager(sctlr)
|
||||
scm.StartAndWait(sctlr.Config.HTTP.Port)
|
||||
|
||||
dctlr, destBaseURL, _, destClient := makeDownstreamServer(t, false, syncConfig)
|
||||
defer scm.StopServer()
|
||||
|
||||
dcm := test.NewControllerManager(dctlr)
|
||||
dcm.StartAndWait(dctlr.Config.HTTP.Port)
|
||||
registryName := sync.StripRegistryTransport(srcBaseURL)
|
||||
credentialsFile := makeCredentialsFile(fmt.Sprintf(`{"%s":{"username": "%s", "password": "%s"}}`,
|
||||
registryName, username, password))
|
||||
|
||||
defer dcm.StopServer()
|
||||
var tlsVerify bool
|
||||
|
||||
var (
|
||||
srcTagsList TagsList
|
||||
destTagsList TagsList
|
||||
)
|
||||
syncRegistryConfig := syncconf.RegistryConfig{
|
||||
Content: []syncconf.Content{
|
||||
{
|
||||
Prefix: "**", // sync everything
|
||||
},
|
||||
},
|
||||
URLs: []string{srcBaseURL},
|
||||
PollInterval: updateDuration,
|
||||
TLSVerify: &tlsVerify,
|
||||
CertDir: "",
|
||||
}
|
||||
|
||||
resp, err := srcClient.R().Get(srcBaseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
defaultVal := true
|
||||
syncConfig := &syncconf.Config{
|
||||
Enable: &defaultVal,
|
||||
CredentialsFile: credentialsFile,
|
||||
Registries: []syncconf.RegistryConfig{syncRegistryConfig},
|
||||
}
|
||||
|
||||
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)
|
||||
dctlr, destBaseURL, _, destClient := makeDownstreamServer(t, false, syncConfig)
|
||||
|
||||
var goodToken authutils.AccessTokenResponse
|
||||
dcm := test.NewControllerManager(dctlr)
|
||||
dcm.StartAndWait(dctlr.Config.HTTP.Port)
|
||||
|
||||
err = json.Unmarshal(resp.Body(), &goodToken)
|
||||
So(err, ShouldBeNil)
|
||||
defer dcm.StopServer()
|
||||
|
||||
resp, err = srcClient.R().
|
||||
SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
|
||||
Get(srcBaseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
var (
|
||||
srcTagsList TagsList
|
||||
destTagsList TagsList
|
||||
)
|
||||
|
||||
resp, err = srcClient.R().Get(srcBaseURL + "/v2/" + testImage + "/tags/list")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
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).
|
||||
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).
|
||||
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)
|
||||
var goodToken authutils.AccessTokenResponse
|
||||
|
||||
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(), &goodToken)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = json.Unmarshal(resp.Body(), &srcTagsList)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
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
|
||||
|
||||
var authTestServer *httptest.Server
|
||||
if testCase.useLegacyAuthTestServer {
|
||||
authTestServer = authutils.MakeAuthTestServerLegacy(ServerKey, unauthorizedNamespace)
|
||||
} else {
|
||||
authTestServer = authutils.MakeAuthTestServer(ServerKey, "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: ServerCert,
|
||||
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(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: "",
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
for {
|
||||
resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/tags/list")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -2681,165 +2859,14 @@ func TestBearerAuth(t *testing.T) {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
if len(destTagsList.Tags) > 0 {
|
||||
break
|
||||
}
|
||||
So(destTagsList, ShouldResemble, srcTagsList)
|
||||
|
||||
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
|
||||
|
||||
authTestServer := authutils.MakeAuthTestServer(ServerKey, 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: ServerCert,
|
||||
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(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: "",
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
// 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) {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
set -xe
|
||||
|
||||
# RSA
|
||||
openssl req \
|
||||
-newkey rsa:2048 \
|
||||
-nodes \
|
||||
|
@ -45,3 +46,78 @@ openssl x509 \
|
|||
-CAkey ca.key \
|
||||
-CAcreateserial \
|
||||
-out client.cert
|
||||
|
||||
# ECDSA
|
||||
openssl ecparam \
|
||||
-name prime256v1 \
|
||||
-genkey \
|
||||
-noout \
|
||||
-out ca-ecdsa.key
|
||||
|
||||
openssl req \
|
||||
-new \
|
||||
-key ca-ecdsa.key \
|
||||
-nodes \
|
||||
-days 3650 \
|
||||
-x509 \
|
||||
-out ca-ecdsa.crt \
|
||||
-subj "/CN=*"
|
||||
|
||||
openssl ecparam \
|
||||
-name prime256v1 \
|
||||
-genkey \
|
||||
-noout \
|
||||
-out server-ecdsa.key
|
||||
|
||||
openssl req \
|
||||
-new \
|
||||
-key server-ecdsa.key \
|
||||
-nodes \
|
||||
-out server-ecdsa.csr \
|
||||
-subj "/OU=TestServer/CN=*"
|
||||
|
||||
openssl x509 \
|
||||
-req \
|
||||
-days 3650 \
|
||||
-sha256 \
|
||||
-in server-ecdsa.csr \
|
||||
-CA ca-ecdsa.crt \
|
||||
-CAkey ca-ecdsa.key \
|
||||
-CAcreateserial \
|
||||
-out server-ecdsa.cert \
|
||||
-extfile <(echo subjectAltName = IP:127.0.0.1)
|
||||
|
||||
# ED25519
|
||||
openssl genpkey \
|
||||
-algorithm ed25519 \
|
||||
-out ca-ed25519.key
|
||||
|
||||
openssl req \
|
||||
-new \
|
||||
-key ca-ed25519.key \
|
||||
-nodes \
|
||||
-days 3650 \
|
||||
-x509 \
|
||||
-out ca-ed25519.crt \
|
||||
-subj "/CN=*"
|
||||
|
||||
openssl genpkey \
|
||||
-algorithm ed25519 \
|
||||
-out server-ed25519.key
|
||||
|
||||
openssl req \
|
||||
-new \
|
||||
-key server-ed25519.key \
|
||||
-nodes \
|
||||
-out server-ed25519.csr \
|
||||
-subj "/OU=TestServer/CN=*"
|
||||
|
||||
openssl x509 \
|
||||
-req \
|
||||
-days 3650 \
|
||||
-in server-ed25519.csr \
|
||||
-CA ca-ed25519.crt \
|
||||
-CAkey ca-ed25519.key \
|
||||
-CAcreateserial \
|
||||
-out server-ed25519.cert \
|
||||
-extfile <(echo subjectAltName = IP:127.0.0.1)
|
||||
|
|
Loading…
Add table
Reference in a new issue