add immutable match in the repository/tag delete api

Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
wang yan 2019-10-18 11:43:58 +08:00
parent 71bb8815bf
commit 424f11e697
9 changed files with 283 additions and 60 deletions

View File

@ -1089,6 +1089,8 @@ paths:
description: Forbidden. description: Forbidden.
'404': '404':
description: Repository not found. description: Repository not found.
'412':
description: Precondition Failed.
put: put:
summary: Update description of the repository. summary: Update description of the repository.
description: | description: |

View File

@ -73,6 +73,7 @@ type TagDetail struct {
Author string `json:"author"` Author string `json:"author"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
Config *TagCfg `json:"config"` Config *TagCfg `json:"config"`
Immutable bool `json:"immutable"`
} }
// TagCfg ... // TagCfg ...

View File

@ -28,7 +28,7 @@ import (
"github.com/goharbor/harbor/src/jobservice/logger" "github.com/goharbor/harbor/src/jobservice/logger"
"github.com/goharbor/harbor/src/pkg/scan/api/scan" "github.com/goharbor/harbor/src/pkg/scan/api/scan"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
@ -45,6 +45,8 @@ import (
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
notifierEvt "github.com/goharbor/harbor/src/core/notifier/event" notifierEvt "github.com/goharbor/harbor/src/core/notifier/event"
coreutils "github.com/goharbor/harbor/src/core/utils" coreutils "github.com/goharbor/harbor/src/core/utils"
"github.com/goharbor/harbor/src/pkg/art"
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
"github.com/goharbor/harbor/src/replication" "github.com/goharbor/harbor/src/replication"
"github.com/goharbor/harbor/src/replication/event" "github.com/goharbor/harbor/src/replication/event"
"github.com/goharbor/harbor/src/replication/model" "github.com/goharbor/harbor/src/replication/model"
@ -283,11 +285,6 @@ func (ra *RepositoryAPI) Delete() {
} }
for _, t := range tags { for _, t := range tags {
image := fmt.Sprintf("%s:%s", repoName, t)
if err = dao.DeleteLabelsOfResource(common.ResourceTypeImage, image); err != nil {
ra.SendInternalServerError(fmt.Errorf("failed to delete labels of image %s: %v", image, err))
return
}
if err = rc.DeleteTag(t); err != nil { if err = rc.DeleteTag(t); err != nil {
if regErr, ok := err.(*commonhttp.Error); ok { if regErr, ok := err.(*commonhttp.Error); ok {
if regErr.Code == http.StatusNotFound { if regErr.Code == http.StatusNotFound {
@ -298,6 +295,11 @@ func (ra *RepositoryAPI) Delete() {
return return
} }
log.Infof("delete tag: %s:%s", repoName, t) log.Infof("delete tag: %s:%s", repoName, t)
image := fmt.Sprintf("%s:%s", repoName, t)
if err = dao.DeleteLabelsOfResource(common.ResourceTypeImage, image); err != nil {
ra.SendInternalServerError(fmt.Errorf("failed to delete labels of image %s: %v", image, err))
return
}
go func(tag string) { go func(tag string) {
e := &event.Event{ e := &event.Event{
@ -711,6 +713,9 @@ func assembleTag(c chan *models.TagResp, client *registry.Repository, projectID
} }
} }
// get immutable status
item.Immutable = isImmutable(projectID, repository, tag)
c <- item c <- item
} }
@ -791,6 +796,21 @@ func populateAuthor(detail *models.TagDetail) {
} }
} }
// check whether the tag is immutable
func isImmutable(projectID int64, repo string, tag string) bool {
_, repoName := utils.ParseRepository(repo)
matched, err := rule.NewRuleMatcher(projectID).Match(art.Candidate{
Repository: repoName,
Tag: tag,
NamespaceID: projectID,
})
if err != nil {
log.Error(err)
return false
}
return matched
}
// GetManifests returns the manifest of a tag // GetManifests returns the manifest of a tag
func (ra *RepositoryAPI) GetManifests() { func (ra *RepositoryAPI) GetManifests() {
repoName := ra.GetString(":splat") repoName := ra.GetString(":splat")

View File

@ -35,4 +35,4 @@ var ChartMiddlewares = []string{CHART}
var Middlewares = []string{READONLY, URL, MUITIPLEMANIFEST, LISTREPO, CONTENTTRUST, VULNERABLE, SIZEQUOTA, IMMUTABLE, COUNTQUOTA} var Middlewares = []string{READONLY, URL, MUITIPLEMANIFEST, LISTREPO, CONTENTTRUST, VULNERABLE, SIZEQUOTA, IMMUTABLE, COUNTQUOTA}
// MiddlewaresLocal ... // MiddlewaresLocal ...
var MiddlewaresLocal = []string{SIZEQUOTA, COUNTQUOTA} var MiddlewaresLocal = []string{SIZEQUOTA, IMMUTABLE, COUNTQUOTA}

View File

@ -0,0 +1,54 @@
package immutable
import (
"fmt"
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
"github.com/goharbor/harbor/src/core/middlewares/interceptor/immutable"
"github.com/goharbor/harbor/src/core/middlewares/util"
"net/http"
)
var (
defaultBuilders = []interceptor.Builder{
&manifestDeletionBuilder{},
&manifestCreationBuilder{},
}
)
type manifestDeletionBuilder struct{}
func (*manifestDeletionBuilder) Build(req *http.Request) (interceptor.Interceptor, error) {
if match, _, _ := util.MatchDeleteManifest(req); !match {
return nil, nil
}
info, ok := util.ManifestInfoFromContext(req.Context())
if !ok {
var err error
info, err = util.ParseManifestInfoFromPath(req)
if err != nil {
return nil, fmt.Errorf("failed to parse manifest, error %v", err)
}
}
return immutable.NewDeleteMFInteceptor(info), nil
}
type manifestCreationBuilder struct{}
func (*manifestCreationBuilder) Build(req *http.Request) (interceptor.Interceptor, error) {
if match, _, _ := util.MatchPushManifest(req); !match {
return nil, nil
}
info, ok := util.ManifestInfoFromContext(req.Context())
if !ok {
var err error
info, err = util.ParseManifestInfoFromReq(req)
if err != nil {
return nil, fmt.Errorf("failed to parse manifest, error %v", err)
}
}
return immutable.NewPushMFInteceptor(info), nil
}

View File

@ -16,78 +16,74 @@ package immutable
import ( import (
"fmt" "fmt"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
common_util "github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
"github.com/goharbor/harbor/src/core/middlewares/util" "github.com/goharbor/harbor/src/core/middlewares/util"
"github.com/goharbor/harbor/src/pkg/art" middlerware_err "github.com/goharbor/harbor/src/core/middlewares/util/error"
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
"net/http" "net/http"
) )
type immutableHandler struct { type immutableHandler struct {
builders []interceptor.Builder
next http.Handler next http.Handler
} }
// New ... // New ...
func New(next http.Handler) http.Handler { func New(next http.Handler, builders ...interceptor.Builder) http.Handler {
if len(builders) == 0 {
builders = defaultBuilders
}
return &immutableHandler{ return &immutableHandler{
builders: builders,
next: next, next: next,
} }
} }
// ServeHTTP ... // ServeHTTP ...
func (rh immutableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (rh *immutableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if match, _, _ := util.MatchPushManifest(req); !match {
rh.next.ServeHTTP(rw, req) interceptor, err := rh.getInterceptor(req)
return
}
info, ok := util.ManifestInfoFromContext(req.Context())
if !ok {
var err error
info, err = util.ParseManifestInfoFromPath(req)
if err != nil { if err != nil {
log.Error(err) log.Warningf("Error occurred when to handle request in immutable handler: %v", err)
rh.next.ServeHTTP(rw, req) http.Error(rw, util.MarshalError("InternalError", fmt.Sprintf("Error occurred when to handle request in immutable handler: %v", err)),
http.StatusInternalServerError)
return return
} }
}
_, repoName := common_util.ParseRepository(info.Repository) if interceptor == nil {
matched, err := rule.NewRuleMatcher(info.ProjectID).Match(art.Candidate{
Repository: repoName,
Tag: info.Tag,
NamespaceID: info.ProjectID,
})
if err != nil {
log.Error(err)
rh.next.ServeHTTP(rw, req)
return
}
if !matched {
rh.next.ServeHTTP(rw, req) rh.next.ServeHTTP(rw, req)
return return
} }
artifactQuery := &models.ArtifactQuery{ if err := interceptor.HandleRequest(req); err != nil {
PID: info.ProjectID, log.Warningf("Error occurred when to handle request in immutable handler: %v", err)
Repo: info.Repository, if _, ok := err.(middlerware_err.ErrImmutable); ok {
Tag: info.Tag,
}
afs, err := dao.ListArtifacts(artifactQuery)
if err != nil {
log.Error(err)
rh.next.ServeHTTP(rw, req)
return
}
if len(afs) == 0 {
rh.next.ServeHTTP(rw, req)
return
}
// rule matched and non-existent is a immutable tag
http.Error(rw, util.MarshalError("DENIED", http.Error(rw, util.MarshalError("DENIED",
fmt.Sprintf("The tag:%s:%s is immutable, cannot be overwrite.", info.Repository, info.Tag)), http.StatusPreconditionFailed) fmt.Sprintf("The tag is immutable, cannot be overwrite: %v", err)), http.StatusPreconditionFailed)
return return
}
http.Error(rw, util.MarshalError("InternalError", fmt.Sprintf("Error occurred when to handle request in immutable handler: %v", err)),
http.StatusInternalServerError)
return
}
rh.next.ServeHTTP(rw, req)
interceptor.HandleResponse(rw, req)
}
func (rh *immutableHandler) getInterceptor(req *http.Request) (interceptor.Interceptor, error) {
for _, builder := range rh.builders {
interceptor, err := builder.Build(req)
if err != nil {
return nil, err
}
if interceptor != nil {
return interceptor, nil
}
}
return nil, nil
} }

View File

@ -0,0 +1,65 @@
package immutable
import (
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
common_util "github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
"github.com/goharbor/harbor/src/core/middlewares/util"
middlerware_err "github.com/goharbor/harbor/src/core/middlewares/util/error"
"github.com/goharbor/harbor/src/pkg/art"
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
"net/http"
)
// NewDeleteMFInteceptor ....
func NewDeleteMFInteceptor(mf *util.ManifestInfo) interceptor.Interceptor {
return &delmfInterceptor{
mf: mf,
}
}
type delmfInterceptor struct {
mf *util.ManifestInfo
}
// HandleRequest ...
func (dmf *delmfInterceptor) HandleRequest(req *http.Request) (err error) {
artifactQuery := &models.ArtifactQuery{
Digest: dmf.mf.Digest,
}
var afs []*models.Artifact
afs, err = dao.ListArtifacts(artifactQuery)
if err != nil {
log.Error(err)
return
}
if len(afs) == 0 {
return
}
for _, af := range afs {
_, repoName := common_util.ParseRepository(dmf.mf.Repository)
var matched bool
matched, err = rule.NewRuleMatcher(dmf.mf.ProjectID).Match(art.Candidate{
Repository: repoName,
Tag: af.Tag,
NamespaceID: dmf.mf.ProjectID,
})
if err != nil {
log.Error(err)
return
}
if matched {
return middlerware_err.NewErrImmutable(repoName)
}
}
return
}
// HandleRequest ...
func (dmf *delmfInterceptor) HandleResponse(w http.ResponseWriter, r *http.Request) {
}

View File

@ -0,0 +1,65 @@
package immutable
import (
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
common_util "github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
"github.com/goharbor/harbor/src/core/middlewares/util"
middlerware_err "github.com/goharbor/harbor/src/core/middlewares/util/error"
"github.com/goharbor/harbor/src/pkg/art"
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
"net/http"
)
// NewPushMFInteceptor ....
func NewPushMFInteceptor(mf *util.ManifestInfo) interceptor.Interceptor {
return &pushmfInterceptor{
mf: mf,
}
}
type pushmfInterceptor struct {
mf *util.ManifestInfo
}
// HandleRequest ...
func (pmf *pushmfInterceptor) HandleRequest(req *http.Request) (err error) {
_, repoName := common_util.ParseRepository(pmf.mf.Repository)
var matched bool
matched, err = rule.NewRuleMatcher(pmf.mf.ProjectID).Match(art.Candidate{
Repository: repoName,
Tag: pmf.mf.Tag,
NamespaceID: pmf.mf.ProjectID,
})
if err != nil {
log.Error(err)
return
}
if !matched {
return
}
artifactQuery := &models.ArtifactQuery{
PID: pmf.mf.ProjectID,
Repo: pmf.mf.Repository,
Tag: pmf.mf.Tag,
}
var afs []*models.Artifact
afs, err = dao.ListArtifacts(artifactQuery)
if err != nil {
log.Error(err)
return
}
if len(afs) == 0 {
return
}
return middlerware_err.NewErrImmutable(repoName)
}
// HandleRequest ...
func (pmf *pushmfInterceptor) HandleResponse(w http.ResponseWriter, r *http.Request) {
}

View File

@ -0,0 +1,20 @@
package error
import (
"fmt"
)
// ErrImmutable ...
type ErrImmutable struct {
repo string
}
// Error ...
func (ei ErrImmutable) Error() string {
return fmt.Sprintf("Failed to process request, due to immutable. '%s'", ei.repo)
}
// NewErrImmutable ...
func NewErrImmutable(msg string) ErrImmutable {
return ErrImmutable{repo: msg}
}