Merge pull request #12571 from ywk253100/200723_proxy_cache_secret

Limit the permission of secret used by proxy cache service
This commit is contained in:
stonezdj(Daojun Zhang) 2020-07-30 14:04:54 +08:00 committed by GitHub
commit 518a1721a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 539 additions and 43 deletions

View File

@ -17,8 +17,6 @@ package secret
const ( const (
// JobserviceUser is the name of jobservice user // JobserviceUser is the name of jobservice user
JobserviceUser = "harbor-jobservice" JobserviceUser = "harbor-jobservice"
// ProxyserviceUser is the name of proxyservice user
ProxyserviceUser = "harbor-proxyservice"
// CoreUser is the name of ui user // CoreUser is the name of ui user
CoreUser = "harbor-core" 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 false
} }
return s.store.GetUsername(s.secret) == secret.JobserviceUser || return s.store.GetUsername(s.secret) == secret.JobserviceUser ||
s.store.GetUsername(s.secret) == secret.CoreUser || s.store.GetUsername(s.secret) == secret.CoreUser
s.store.GetUsername(s.secret) == secret.ProxyserviceUser
} }

View File

@ -20,11 +20,11 @@ import (
"fmt" "fmt"
"github.com/docker/distribution" "github.com/docker/distribution"
"github.com/docker/distribution/manifest/manifestlist" "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/controller/artifact"
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/proxy/secret"
"github.com/goharbor/harbor/src/pkg/registry" "github.com/goharbor/harbor/src/pkg/registry"
"io" "io"
"time" "time"
@ -85,8 +85,7 @@ func (l *localHelper) init() {
log.Debugf("core url:%s, local core url: %v", config.GetCoreURL(), config.LocalCoreURL()) log.Debugf("core url:%s, local core url: %v", config.GetCoreURL(), config.LocalCoreURL())
// the traffic is internal only // the traffic is internal only
registryURL := config.LocalCoreURL() registryURL := config.LocalCoreURL()
authorizer := comHttpAuth.NewSecretAuthorizer(config.ProxyServiceSecret) l.registry = registry.NewClientWithAuthorizer(registryURL, secret.NewAuthorizer(), true)
l.registry = registry.NewClientWithAuthorizer(registryURL, authorizer, true)
} }
func (l *localHelper) PushBlob(localRepo string, desc distribution.Descriptor, bReader io.ReadCloser) error { 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"
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/local" "github.com/goharbor/harbor/src/core/promgr/pmsdriver/local"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/common/utils"
) )
const ( const (
@ -51,8 +49,6 @@ var (
// defined as a var for testing. // defined as a var for testing.
defaultCACertPath = "/etc/core/ca/ca.crt" defaultCACertPath = "/etc/core/ca/ca.crt"
cfgMgr *comcfg.CfgManager cfgMgr *comcfg.CfgManager
// ProxyServiceSecret is the secret used by proxy service
ProxyServiceSecret = utils.GenerateRandomStringWithLen(16)
) )
// Init configurations // Init configurations
@ -93,7 +89,6 @@ func initKeyProvider() {
func initSecretStore() { func initSecretStore() {
m := map[string]string{} m := map[string]string{}
m[JobserviceSecret()] = secret.JobserviceUser m[JobserviceSecret()] = secret.JobserviceUser
m[ProxyServiceSecret] = secret.ProxyserviceUser
SecretStore = secret.NewStore(m) SecretStore = secret.NewStore(m)
} }

View File

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

View File

@ -1,4 +1,4 @@
package middleware package lib
import ( import (
"fmt" "fmt"
@ -29,3 +29,33 @@ var (
// V2CatalogURLRe is the regular expression for mathing the request to v2 handler to list catalog // V2CatalogURLRe is the regular expression for mathing the request to v2 handler to list catalog
V2CatalogURLRe = regexp.MustCompile(`^/v2/_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"
"github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/server/middleware"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
) )
@ -39,10 +38,10 @@ const (
var ( var (
urlPatterns = map[string]*regexp.Regexp{ urlPatterns = map[string]*regexp.Regexp{
"manifest": middleware.V2ManifestURLRe, "manifest": lib.V2ManifestURLRe,
"tag_list": middleware.V2TagListURLRe, "tag_list": lib.V2TagListURLRe,
"blob_upload": middleware.V2BlobUploadURLRe, "blob_upload": lib.V2BlobUploadURLRe,
"blob": middleware.V2BlobURLRe, "blob": lib.V2BlobURLRe,
} }
) )
@ -56,7 +55,7 @@ func Middleware() func(http.Handler) http.Handler {
next.ServeHTTP(rw, req) next.ServeHTTP(rw, req)
return return
} }
repo := m[middleware.RepositorySubexp] repo := m[lib.RepositorySubexp]
pn, err := projectNameFromRepo(repo) pn, err := projectNameFromRepo(repo)
if err != nil { if err != nil {
lib_http.SendError(rw, errors.BadRequestError(err)) lib_http.SendError(rw, errors.BadRequestError(err))
@ -66,10 +65,10 @@ func Middleware() func(http.Handler) http.Handler {
Repository: repo, Repository: repo,
ProjectName: pn, ProjectName: pn,
} }
if d, ok := m[middleware.DigestSubexp]; ok { if d, ok := m[lib.DigestSubexp]; ok {
art.Digest = d art.Digest = d
} }
if ref, ok := m[middleware.ReferenceSubexp]; ok { if ref, ok := m[lib.ReferenceSubexp]; ok {
art.Reference = ref art.Reference = ref
} }
if t, ok := m[tag]; ok { if t, ok := m[tag]; ok {
@ -120,9 +119,9 @@ func parse(url *url.URL) (map[string]string, bool) {
break break
} }
} }
if digest.DigestRegexp.MatchString(m[middleware.ReferenceSubexp]) { if digest.DigestRegexp.MatchString(m[lib.ReferenceSubexp]) {
m[middleware.DigestSubexp] = m[middleware.ReferenceSubexp] m[lib.DigestSubexp] = m[lib.ReferenceSubexp]
} else if ref, ok := m[middleware.ReferenceSubexp]; ok { } else if ref, ok := m[lib.ReferenceSubexp]; ok {
m[tag] = ref m[tag] = ref
} }
return m, match return m, match

View File

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

View File

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

View File

@ -18,6 +18,7 @@ import (
"context" "context"
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/security" "github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/common/security/proxycachesecret"
securitySecret "github.com/goharbor/harbor/src/common/security/secret" securitySecret "github.com/goharbor/harbor/src/common/security/secret"
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
"testing" "testing"
@ -59,7 +60,7 @@ func TestIsProxySession(t *testing.T) {
sc1 := securitySecret.NewSecurityContext("123456789", config.SecretStore) sc1 := securitySecret.NewSecurityContext("123456789", config.SecretStore)
otherCtx := security.NewContext(context.Background(), sc1) 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) proxyCtx := security.NewContext(context.Background(), sc2)
cases := []struct { cases := []struct {
name string 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{}, &robot{},
&basicAuth{}, &basicAuth{},
&session{}, &session{},
&proxyCacheSecret{},
&unauthorized{}, &unauthorized{},
} }
) )

View File

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

View File

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