Limit the permission of secret used by proxy cache service

Limit the permission of secret used by proxy cache service, fixes 

Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
Wenkai Yin 2020-07-24 15:42:48 +08:00
parent a6c7e15d7e
commit ced7b73322
20 changed files with 539 additions and 43 deletions

View File

@ -17,8 +17,6 @@ package secret
const (
// JobserviceUser is the name of jobservice user
JobserviceUser = "harbor-jobservice"
// ProxyserviceUser is the name of proxyservice user
ProxyserviceUser = "harbor-proxyservice"
// CoreUser is the name of ui user
CoreUser = "harbor-core"
)

View File

@ -0,0 +1,96 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proxycachesecret
import (
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/permission/types"
"github.com/goharbor/harbor/src/pkg/project"
)
// const definition
const (
// contains "#" to avoid the conflict with normal user
ProxyCacheService = "harbor#proxy-cache-service"
)
// SecurityContext is the security context for proxy cache secret
type SecurityContext struct {
repository string
mgr project.Manager
}
// NewSecurityContext returns an instance of the proxy cache secret security context
func NewSecurityContext(repository string) *SecurityContext {
return &SecurityContext{
repository: repository,
mgr: project.Mgr,
}
}
// Name returns the name of the security context
func (s *SecurityContext) Name() string {
return "proxy_cache_secret"
}
// IsAuthenticated always returns true
func (s *SecurityContext) IsAuthenticated() bool {
return true
}
// GetUsername returns the name of proxy cache service
func (s *SecurityContext) GetUsername() string {
return ProxyCacheService
}
// IsSysAdmin always returns false
func (s *SecurityContext) IsSysAdmin() bool {
return false
}
// IsSolutionUser always returns false
func (s *SecurityContext) IsSolutionUser() bool {
return false
}
// Can returns true only when requesting pull/push operation against the specific project
func (s *SecurityContext) Can(action types.Action, resource types.Resource) bool {
if !(action == rbac.ActionPull || action == rbac.ActionPush) {
log.Debugf("unauthorized for action %s", action)
return false
}
namespace, ok := rbac.ProjectNamespaceParse(resource)
if !ok {
log.Debugf("got no namespace from the resource %s", resource)
return false
}
project, err := s.mgr.Get(namespace.Identity())
if err != nil {
log.Errorf("failed to get project %v: %v", namespace.Identity(), err)
return false
}
if project == nil {
log.Debugf("project not found %v", namespace.Identity())
return false
}
pro, _ := utils.ParseRepository(s.repository)
if project.Name != pro {
log.Debugf("unauthorized for project %s", project.Name)
return false
}
return true
}

View File

@ -0,0 +1,108 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proxycachesecret
import (
"testing"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/goharbor/harbor/src/testing/pkg/project"
"github.com/stretchr/testify/suite"
)
type proxyCacheSecretTestSuite struct {
suite.Suite
sc *SecurityContext
mgr *project.FakeManager
}
func (p *proxyCacheSecretTestSuite) SetupTest() {
p.mgr = &project.FakeManager{}
p.sc = &SecurityContext{
repository: "library/hello-world",
mgr: p.mgr,
}
}
func (p *proxyCacheSecretTestSuite) TestName() {
p.Equal("proxy_cache_secret", p.sc.Name())
}
func (p *proxyCacheSecretTestSuite) TestIsAuthenticated() {
p.True(p.sc.IsAuthenticated())
}
func (p *proxyCacheSecretTestSuite) TestGetUsername() {
p.Equal(ProxyCacheService, p.sc.GetUsername())
}
func (p *proxyCacheSecretTestSuite) TestIsSysAdmin() {
p.False(p.sc.IsSysAdmin())
}
func (p *proxyCacheSecretTestSuite) TestIsSolutionUser() {
p.False(p.sc.IsSolutionUser())
}
func (p *proxyCacheSecretTestSuite) TestCan() {
// the action isn't pull/push
action := rbac.ActionDelete
resource := rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
p.False(p.sc.Can(action, resource))
// the resource isn't repository
action = rbac.ActionPull
resource = rbac.ResourceConfiguration
p.False(p.sc.Can(action, resource))
// the requested project not found
action = rbac.ActionPull
resource = rbac.NewProjectNamespace(2).Resource(rbac.ResourceRepository)
p.mgr.On("Get", mock.Anything).Return(nil, nil)
p.False(p.sc.Can(action, resource))
p.mgr.AssertExpectations(p.T())
// reset the mock
p.SetupTest()
// pass for action pull
action = rbac.ActionPull
resource = rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
p.mgr.On("Get", mock.Anything).Return(&models.Project{
ProjectID: 1,
Name: "library",
}, nil)
p.True(p.sc.Can(action, resource))
p.mgr.AssertExpectations(p.T())
// reset the mock
p.SetupTest()
// pass for action push
action = rbac.ActionPush
resource = rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
p.mgr.On("Get", mock.Anything).Return(&models.Project{
ProjectID: 1,
Name: "library",
}, nil)
p.True(p.sc.Can(action, resource))
p.mgr.AssertExpectations(p.T())
}
func TestProxyCacheSecretTestSuite(t *testing.T) {
suite.Run(t, &proxyCacheSecretTestSuite{})
}

View File

@ -80,6 +80,5 @@ func (s *SecurityContext) Can(action types.Action, resource types.Resource) bool
return false
}
return s.store.GetUsername(s.secret) == secret.JobserviceUser ||
s.store.GetUsername(s.secret) == secret.CoreUser ||
s.store.GetUsername(s.secret) == secret.ProxyserviceUser
s.store.GetUsername(s.secret) == secret.CoreUser
}

View File

@ -20,11 +20,11 @@ import (
"fmt"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/manifestlist"
comHttpAuth "github.com/goharbor/harbor/src/common/http/modifier/auth"
"github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/proxy/secret"
"github.com/goharbor/harbor/src/pkg/registry"
"io"
"time"
@ -85,8 +85,7 @@ func (l *localHelper) init() {
log.Debugf("core url:%s, local core url: %v", config.GetCoreURL(), config.LocalCoreURL())
// the traffic is internal only
registryURL := config.LocalCoreURL()
authorizer := comHttpAuth.NewSecretAuthorizer(config.ProxyServiceSecret)
l.registry = registry.NewClientWithAuthorizer(registryURL, authorizer, true)
l.registry = registry.NewClientWithAuthorizer(registryURL, secret.NewAuthorizer(), true)
}
func (l *localHelper) PushBlob(localRepo string, desc distribution.Descriptor, bReader io.ReadCloser) error {

View File

@ -30,8 +30,6 @@ import (
"github.com/goharbor/harbor/src/core/promgr"
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/local"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/common/utils"
)
const (
@ -51,8 +49,6 @@ var (
// defined as a var for testing.
defaultCACertPath = "/etc/core/ca/ca.crt"
cfgMgr *comcfg.CfgManager
// ProxyServiceSecret is the secret used by proxy service
ProxyServiceSecret = utils.GenerateRandomStringWithLen(16)
)
// Init configurations
@ -93,7 +89,6 @@ func initKeyProvider() {
func initSecretStore() {
m := map[string]string{}
m[JobserviceSecret()] = secret.JobserviceUser
m[ProxyServiceSecret] = secret.ProxyserviceUser
SecretStore = secret.NewStore(m)
}

View File

@ -21,6 +21,7 @@ import (
"github.com/astaxie/beego"
"github.com/goharbor/harbor/src/pkg/distribution"
"github.com/goharbor/harbor/src/server/middleware"
"github.com/goharbor/harbor/src/server/middleware/artifactinfo"
"github.com/goharbor/harbor/src/server/middleware/csrf"
"github.com/goharbor/harbor/src/server/middleware/log"
"github.com/goharbor/harbor/src/server/middleware/notification"
@ -74,6 +75,7 @@ func MiddleWares() []beego.MiddleWare {
orm.Middleware(),
notification.Middleware(), // notification must ahead of transaction ensure the DB transaction execution complete
transaction.Middleware(dbTxSkippers...),
artifactinfo.Middleware(),
security.Middleware(),
readonly.Middleware(readonlySkippers...),
}

View File

@ -1,4 +1,4 @@
package middleware
package lib
import (
"fmt"
@ -29,3 +29,33 @@ var (
// V2CatalogURLRe is the regular expression for mathing the request to v2 handler to list catalog
V2CatalogURLRe = regexp.MustCompile(`^/v2/_catalog$`)
)
// MatchManifestURLPattern checks whether the provided path matches the manifest URL pattern,
// if does, returns the repository and reference as well
func MatchManifestURLPattern(path string) (repository, reference string, match bool) {
strs := V2ManifestURLRe.FindStringSubmatch(path)
if len(strs) < 3 {
return "", "", false
}
return strs[1], strs[2], true
}
// MatchBlobURLPattern checks whether the provided path matches the blob URL pattern,
// if does, returns the repository and reference as well
func MatchBlobURLPattern(path string) (repository, digest string, match bool) {
strs := V2BlobURLRe.FindStringSubmatch(path)
if len(strs) < 3 {
return "", "", false
}
return strs[1], strs[2], true
}
// MatchBlobUploadURLPattern checks whether the provided path matches the blob upload URL pattern,
// if does, returns the repository as well
func MatchBlobUploadURLPattern(path string) (repository string, match bool) {
strs := V2BlobUploadURLRe.FindStringSubmatch(path)
if len(strs) < 2 {
return "", false
}
return strs[1], true
}

68
src/lib/patterns_test.go Normal file
View File

@ -0,0 +1,68 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lib
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMatchManifestURLPattern(t *testing.T) {
_, _, ok := MatchManifestURLPattern("")
assert.False(t, ok)
_, _, ok = MatchManifestURLPattern("/v2/")
assert.False(t, ok)
repository, reference, ok := MatchManifestURLPattern("/v2/library/hello-world/manifests/latest")
assert.True(t, ok)
assert.Equal(t, "library/hello-world", repository)
assert.Equal(t, "latest", reference)
repository, reference, ok = MatchManifestURLPattern("/v2/library/hello-world/manifests/sha256:e5785cb0c62cebbed4965129bae371f0589cadd6d84798fb58c2c5f9e237efd9")
assert.True(t, ok)
assert.Equal(t, "library/hello-world", repository)
assert.Equal(t, "sha256:e5785cb0c62cebbed4965129bae371f0589cadd6d84798fb58c2c5f9e237efd9", reference)
}
func TestMatchBlobURLPattern(t *testing.T) {
_, _, ok := MatchBlobURLPattern("")
assert.False(t, ok)
_, _, ok = MatchBlobURLPattern("/v2/")
assert.False(t, ok)
repository, digest, ok := MatchBlobURLPattern("/v2/library/hello-world/blobs/sha256:e5785cb0c62cebbed4965129bae371f0589cadd6d84798fb58c2c5f9e237efd9")
assert.True(t, ok)
assert.Equal(t, "library/hello-world", repository)
assert.Equal(t, "sha256:e5785cb0c62cebbed4965129bae371f0589cadd6d84798fb58c2c5f9e237efd9", digest)
}
func TestMatchBlobUploadURLPattern(t *testing.T) {
_, ok := MatchBlobUploadURLPattern("")
assert.False(t, ok)
_, ok = MatchBlobUploadURLPattern("/v2/")
assert.False(t, ok)
repository, ok := MatchBlobUploadURLPattern("/v2/library/hello-world/blobs/uploads/")
assert.True(t, ok)
assert.Equal(t, "library/hello-world", repository)
repository, ok = MatchBlobUploadURLPattern("/v2/library/hello-world/blobs/uploads/uuid")
assert.True(t, ok)
assert.Equal(t, "library/hello-world", repository)
}

View File

@ -0,0 +1,63 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"errors"
"fmt"
"net/http"
"strings"
"github.com/goharbor/harbor/src/lib"
)
const (
secretPrefix = "Proxy-Cache-Secret"
)
// NewAuthorizer returns an instance of the authorizer
func NewAuthorizer() lib.Authorizer {
return &authorizer{}
}
type authorizer struct{}
func (s *authorizer) Modify(req *http.Request) error {
if req == nil {
return errors.New("the request is null")
}
repository, _, ok := lib.MatchManifestURLPattern(req.URL.Path)
if !ok {
repository, _, ok = lib.MatchBlobURLPattern(req.URL.Path)
if !ok {
repository, ok = lib.MatchBlobUploadURLPattern(req.URL.Path)
if !ok {
return nil
}
}
}
secret := GetManager().Generate(repository)
req.Header.Set("Authorization", fmt.Sprintf("%s %s", secretPrefix, secret))
return nil
}
// GetSecret gets the secret from the request authorization header
func GetSecret(req *http.Request) string {
auth := req.Header.Get("Authorization")
if !strings.HasPrefix(auth, secretPrefix) {
return ""
}
return strings.TrimSpace(strings.TrimPrefix(auth, secretPrefix))
}

View File

@ -0,0 +1,50 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net/http"
"testing"
)
func TestAuthorizer(t *testing.T) {
authorizer := &authorizer{}
// not manifest/blob requests
req, _ := http.NewRequest(http.MethodGet, "http://127.0.0.1/v2/_catalog", nil)
err := authorizer.Modify(req)
require.Nil(t, err)
assert.Empty(t, GetSecret(req))
// pass, manifest URL
req, _ = http.NewRequest(http.MethodGet, "http://127.0.0.1/v2/library/hello-world/manifests/latest", nil)
err = authorizer.Modify(req)
require.Nil(t, err)
assert.NotEmpty(t, GetSecret(req))
// pass, blob URL
req, _ = http.NewRequest(http.MethodGet, "http://127.0.0.1/v2/library/hello-world/blobs/sha256:e5785cb0c62cebbed4965129bae371f0589cadd6d84798fb58c2c5f9e237efd9", nil)
err = authorizer.Modify(req)
require.Nil(t, err)
assert.NotEmpty(t, GetSecret(req))
// pass, blob upload URL
req, _ = http.NewRequest(http.MethodGet, "http://127.0.0.1/v2/library/hello-world/blobs/uploads/uuid", nil)
err = authorizer.Modify(req)
require.Nil(t, err)
assert.NotEmpty(t, GetSecret(req))
}

View File

@ -25,7 +25,6 @@ import (
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/server/middleware"
"github.com/opencontainers/go-digest"
)
@ -39,10 +38,10 @@ const (
var (
urlPatterns = map[string]*regexp.Regexp{
"manifest": middleware.V2ManifestURLRe,
"tag_list": middleware.V2TagListURLRe,
"blob_upload": middleware.V2BlobUploadURLRe,
"blob": middleware.V2BlobURLRe,
"manifest": lib.V2ManifestURLRe,
"tag_list": lib.V2TagListURLRe,
"blob_upload": lib.V2BlobUploadURLRe,
"blob": lib.V2BlobURLRe,
}
)
@ -56,7 +55,7 @@ func Middleware() func(http.Handler) http.Handler {
next.ServeHTTP(rw, req)
return
}
repo := m[middleware.RepositorySubexp]
repo := m[lib.RepositorySubexp]
pn, err := projectNameFromRepo(repo)
if err != nil {
lib_http.SendError(rw, errors.BadRequestError(err))
@ -66,10 +65,10 @@ func Middleware() func(http.Handler) http.Handler {
Repository: repo,
ProjectName: pn,
}
if d, ok := m[middleware.DigestSubexp]; ok {
if d, ok := m[lib.DigestSubexp]; ok {
art.Digest = d
}
if ref, ok := m[middleware.ReferenceSubexp]; ok {
if ref, ok := m[lib.ReferenceSubexp]; ok {
art.Reference = ref
}
if t, ok := m[tag]; ok {
@ -120,9 +119,9 @@ func parse(url *url.URL) (map[string]string, bool) {
break
}
}
if digest.DigestRegexp.MatchString(m[middleware.ReferenceSubexp]) {
m[middleware.DigestSubexp] = m[middleware.ReferenceSubexp]
} else if ref, ok := m[middleware.ReferenceSubexp]; ok {
if digest.DigestRegexp.MatchString(m[lib.ReferenceSubexp]) {
m[lib.DigestSubexp] = m[lib.ReferenceSubexp]
} else if ref, ok := m[lib.ReferenceSubexp]; ok {
m[tag] = ref
}
return m, match

View File

@ -22,7 +22,6 @@ import (
"testing"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/server/middleware"
"github.com/stretchr/testify/assert"
)
@ -45,16 +44,16 @@ func TestParseURL(t *testing.T) {
{
input: "/v2/no-project-repo/tags/list",
expect: map[string]string{
middleware.RepositorySubexp: "no-project-repo",
lib.RepositorySubexp: "no-project-repo",
},
match: true,
},
{
input: "/v2/development/golang/manifests/sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
expect: map[string]string{
middleware.RepositorySubexp: "development/golang",
middleware.ReferenceSubexp: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
middleware.DigestSubexp: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
lib.RepositorySubexp: "development/golang",
lib.ReferenceSubexp: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
lib.DigestSubexp: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
},
match: true,
},
@ -67,8 +66,8 @@ func TestParseURL(t *testing.T) {
{
input: "/v2/multi/sector/repository/blobs/sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
expect: map[string]string{
middleware.RepositorySubexp: "multi/sector/repository",
middleware.DigestSubexp: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
lib.RepositorySubexp: "multi/sector/repository",
lib.DigestSubexp: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
},
match: true,
},
@ -80,23 +79,23 @@ func TestParseURL(t *testing.T) {
{
input: "/v2/library/ubuntu/blobs/uploads",
expect: map[string]string{
middleware.RepositorySubexp: "library/ubuntu",
lib.RepositorySubexp: "library/ubuntu",
},
match: true,
},
{
input: "/v2/library/ubuntu/blobs/uploads/?mount=sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f&from=old/ubuntu",
expect: map[string]string{
middleware.RepositorySubexp: "library/ubuntu",
blobMountDigest: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
blobMountRepo: "old/ubuntu",
lib.RepositorySubexp: "library/ubuntu",
blobMountDigest: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
blobMountRepo: "old/ubuntu",
},
match: true,
},
{
input: "/v2/library/centos/blobs/uploads/u-12345",
expect: map[string]string{
middleware.RepositorySubexp: "library/centos",
lib.RepositorySubexp: "library/centos",
},
match: true,
},

View File

@ -17,8 +17,8 @@ package repoproxy
import (
"context"
"fmt"
"github.com/goharbor/harbor/src/common/secret"
"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/common/security/proxycachesecret"
"github.com/goharbor/harbor/src/lib/errors"
httpLib "github.com/goharbor/harbor/src/lib/http"
"github.com/goharbor/harbor/src/replication/model"
@ -158,7 +158,7 @@ func isProxySession(ctx context.Context) bool {
log.Error("Failed to get security context")
return false
}
if sc.IsSolutionUser() && sc.GetUsername() == secret.ProxyserviceUser {
if sc.GetUsername() == proxycachesecret.ProxyCacheService {
return true
}
return false

View File

@ -18,6 +18,7 @@ import (
"context"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/common/security/proxycachesecret"
securitySecret "github.com/goharbor/harbor/src/common/security/secret"
"github.com/goharbor/harbor/src/core/config"
"testing"
@ -59,7 +60,7 @@ func TestIsProxySession(t *testing.T) {
sc1 := securitySecret.NewSecurityContext("123456789", config.SecretStore)
otherCtx := security.NewContext(context.Background(), sc1)
sc2 := securitySecret.NewSecurityContext(config.ProxyServiceSecret, config.SecretStore)
sc2 := proxycachesecret.NewSecurityContext("library/hello-world")
proxyCtx := security.NewContext(context.Background(), sc2)
cases := []struct {
name string

View File

@ -0,0 +1,42 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package security
import (
"net/http"
"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/common/security/proxycachesecret"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/log"
ps "github.com/goharbor/harbor/src/pkg/proxy/secret"
)
type proxyCacheSecret struct{}
func (p *proxyCacheSecret) Generate(req *http.Request) security.Context {
log := log.G(req.Context())
artifact := lib.GetArtifactInfo(req.Context())
if artifact == (lib.ArtifactInfo{}) {
return nil
}
secret := ps.GetSecret(req)
if !ps.GetManager().Verify(secret, artifact.Repository) {
return nil
}
log.Debugf("a proxy cache secret security context generated for request %s %s", req.Method, req.URL.Path)
return proxycachesecret.NewSecurityContext(artifact.Repository)
}

View File

@ -0,0 +1,49 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package security
import (
"fmt"
"github.com/goharbor/harbor/src/lib"
"github.com/stretchr/testify/assert"
"net/http"
"testing"
ps "github.com/goharbor/harbor/src/pkg/proxy/secret"
)
func TestProxyCacheSecret(t *testing.T) {
psc := &proxyCacheSecret{}
// request without artifact info in context
req, _ := http.NewRequest(http.MethodGet, "http://127.0.0.1/v2/library/hello-world/manifests/latest", nil)
sc := psc.Generate(req)
assert.Nil(t, sc)
// request with invalid secret
ctx := lib.WithArtifactInfo(req.Context(), lib.ArtifactInfo{
Repository: "library/hello-world",
})
req = req.WithContext(ctx)
req.Header.Set("Authorization", fmt.Sprintf("Proxy-Cache-Secret %s", "invalid-secret"))
sc = psc.Generate(req)
assert.Nil(t, sc)
// pass
secret := ps.GetManager().Generate("library/hello-world")
req.Header.Set("Authorization", fmt.Sprintf("Proxy-Cache-Secret %s", secret))
sc = psc.Generate(req)
assert.NotNil(t, sc)
}

View File

@ -33,6 +33,7 @@ var (
&robot{},
&basicAuth{},
&session{},
&proxyCacheSecret{},
&unauthorized{},
}
)

View File

@ -8,7 +8,6 @@ import (
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/server/middleware"
)
type target int
@ -72,7 +71,7 @@ func accessList(req *http.Request) []access {
})
return l
}
if len(middleware.V2CatalogURLRe.FindStringSubmatch(req.URL.Path)) == 1 {
if len(lib.V2CatalogURLRe.FindStringSubmatch(req.URL.Path)) == 1 {
l = append(l, access{
target: catalog,
})

View File

@ -17,7 +17,6 @@ package registry
import (
"net/http"
"github.com/goharbor/harbor/src/server/middleware/artifactinfo"
"github.com/goharbor/harbor/src/server/middleware/blob"
"github.com/goharbor/harbor/src/server/middleware/contenttrust"
"github.com/goharbor/harbor/src/server/middleware/immutable"
@ -32,7 +31,6 @@ import (
func RegisterRoutes() {
root := router.NewRoute().
Path("/v2").
Middleware(artifactinfo.Middleware()).
Middleware(v2auth.Middleware())
// catalog
root.NewRoute().