From a39e1a2a346ad058c22ec5305624d676022d492b Mon Sep 17 00:00:00 2001 From: wang yan Date: Thu, 14 Nov 2019 13:18:12 +0800 Subject: [PATCH] enable policy checker in response handler Signed-off-by: wang yan --- src/core/middlewares/contenttrust/handler.go | 55 ++++--- src/core/middlewares/regtoken/handler.go | 1 - src/core/middlewares/util/util.go | 13 -- src/core/middlewares/vulnerable/handler.go | 150 ++++++++++--------- 4 files changed, 108 insertions(+), 111 deletions(-) diff --git a/src/core/middlewares/contenttrust/handler.go b/src/core/middlewares/contenttrust/handler.go index af2be936d..ba6ed37ca 100644 --- a/src/core/middlewares/contenttrust/handler.go +++ b/src/core/middlewares/contenttrust/handler.go @@ -21,6 +21,7 @@ import ( "github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/middlewares/util" "net/http" + "net/http/httptest" ) // NotaryEndpoint ... @@ -39,39 +40,45 @@ func New(next http.Handler) http.Handler { // ServeHTTP ... func (cth contentTrustHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + doContentTrustCheck, image := validate(req) + if !doContentTrustCheck { + cth.next.ServeHTTP(rw, req) + return + } + rec := httptest.NewRecorder() + cth.next.ServeHTTP(rec, req) + if rec.Result().StatusCode == http.StatusOK { + match, err := matchNotaryDigest(image) + if err != nil { + http.Error(rw, util.MarshalError("PROJECT_POLICY_VIOLATION", "Failed in communication with Notary please check the log"), http.StatusInternalServerError) + return + } + if !match { + log.Debugf("digest mismatch, failing the response.") + http.Error(rw, util.MarshalError("PROJECT_POLICY_VIOLATION", "The image is not signed in Notary."), http.StatusPreconditionFailed) + return + } + } + util.CopyResp(rec, rw) +} + +func validate(req *http.Request) (bool, util.ImageInfo) { + var img util.ImageInfo imgRaw := req.Context().Value(util.ImageInfoCtxKey) if imgRaw == nil || !config.WithNotary() { - cth.next.ServeHTTP(rw, req) - return + return false, img } - img, _ := req.Context().Value(util.ImageInfoCtxKey).(util.ImageInfo) + img, _ = req.Context().Value(util.ImageInfoCtxKey).(util.ImageInfo) if img.Digest == "" { - cth.next.ServeHTTP(rw, req) - return - } - if pullWithBearer, ok := util.DockerPullAuthFromContext(req.Context()); ok && !pullWithBearer { - cth.next.ServeHTTP(rw, req) - return + return false, img } if scannerPull, ok := util.ScannerPullFromContext(req.Context()); ok && scannerPull { - cth.next.ServeHTTP(rw, req) - return + return false, img } if !util.GetPolicyChecker().ContentTrustEnabled(img.ProjectName) { - cth.next.ServeHTTP(rw, req) - return + return false, img } - match, err := matchNotaryDigest(img) - if err != nil { - http.Error(rw, util.MarshalError("PROJECT_POLICY_VIOLATION", "Failed in communication with Notary please check the log"), http.StatusInternalServerError) - return - } - if !match { - log.Debugf("digest mismatch, failing the response.") - http.Error(rw, util.MarshalError("PROJECT_POLICY_VIOLATION", "The image is not signed in Notary."), http.StatusPreconditionFailed) - return - } - cth.next.ServeHTTP(rw, req) + return true, img } func matchNotaryDigest(img util.ImageInfo) (bool, error) { diff --git a/src/core/middlewares/regtoken/handler.go b/src/core/middlewares/regtoken/handler.go index 1fa53ec8b..0225c0338 100644 --- a/src/core/middlewares/regtoken/handler.go +++ b/src/core/middlewares/regtoken/handler.go @@ -43,7 +43,6 @@ func (r *regTokenHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { r.next.ServeHTTP(rw, req) return } - *req = *(req.WithContext(util.NewDockerPullAuthContext(req.Context(), true))) rawToken := parts[1] opt := pkg_token.DefaultTokenOptions() diff --git a/src/core/middlewares/util/util.go b/src/core/middlewares/util/util.go index 2130c3d35..52d203071 100644 --- a/src/core/middlewares/util/util.go +++ b/src/core/middlewares/util/util.go @@ -51,8 +51,6 @@ const ( ImageInfoCtxKey = contextKey("ImageInfo") // ScannerPullCtxKey the context key for robot account to bypass the pull policy check. ScannerPullCtxKey = contextKey("ScannerPullCheck") - // DockerPullAuthCtxKey the context key to index whether docker pull request with bearer token - DockerPullAuthCtxKey = contextKey("DockerPullWithBearer") // TokenUsername ... // TODO: temp solution, remove after vmware/harbor#2242 is resolved. TokenUsername = "harbor-core" @@ -459,17 +457,6 @@ func ScannerPullFromContext(ctx context.Context) (bool, bool) { return info, ok } -// NewDockerPullAuthContext returns context with bearer token -func NewDockerPullAuthContext(ctx context.Context, withBearer bool) context.Context { - return context.WithValue(ctx, DockerPullAuthCtxKey, withBearer) -} - -// DockerPullAuthFromContext returns whether the docker pull with bearer -func DockerPullAuthFromContext(ctx context.Context) (bool, bool) { - info, ok := ctx.Value(DockerPullAuthCtxKey).(bool) - return info, ok -} - // NewBlobInfoContext returns context with blob info func NewBlobInfoContext(ctx context.Context, info *BlobInfo) context.Context { return context.WithValue(ctx, blobInfoKey, info) diff --git a/src/core/middlewares/vulnerable/handler.go b/src/core/middlewares/vulnerable/handler.go index 3c9128b55..f76989d8b 100644 --- a/src/core/middlewares/vulnerable/handler.go +++ b/src/core/middlewares/vulnerable/handler.go @@ -17,6 +17,7 @@ package vulnerable import ( "net/http" + "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/core/middlewares/util" sc "github.com/goharbor/harbor/src/pkg/scan/api/scan" @@ -24,6 +25,7 @@ import ( v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/pkg/scan/vuln" "github.com/pkg/errors" + "net/http/httptest" ) type vulnerableHandler struct { @@ -39,94 +41,96 @@ func New(next http.Handler) http.Handler { // ServeHTTP ... func (vh vulnerableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - imgRaw := req.Context().Value(util.ImageInfoCtxKey) - if imgRaw == nil { + doVulCheck, img, projectVulnerableSeverity, wl := validate(req) + if !doVulCheck { vh.next.ServeHTTP(rw, req) return } + rec := httptest.NewRecorder() + vh.next.ServeHTTP(rec, req) + // only enable vul policy check the response 200 + if rec.Result().StatusCode == http.StatusOK { + // Invalid project ID + if wl.ProjectID == 0 { + err := errors.Errorf("project verification error: project %s", img.ProjectName) + vh.sendError(err, rw) + return + } + + // Get the vulnerability summary + artifact := &v1.Artifact{ + NamespaceID: wl.ProjectID, + Repository: img.Repository, + Tag: img.Reference, + Digest: img.Digest, + MimeType: v1.MimeTypeDockerArtifact, + } + + cve := report.CVESet(wl.CVESet()) + summaries, err := sc.DefaultController.GetSummary( + artifact, + []string{v1.MimeTypeNativeReport}, + report.WithCVEWhitelist(&cve), + ) + + if err != nil { + err = errors.Wrap(err, "middleware: vulnerable handler") + vh.sendError(err, rw) + return + } + + rawSummary, ok := summaries[v1.MimeTypeNativeReport] + // No report yet? + if !ok { + err = errors.Errorf("no scan report existing for the artifact: %s:%s@%s", img.Repository, img.Reference, img.Digest) + vh.sendError(err, rw) + return + } + + summary := rawSummary.(*vuln.NativeReportSummary) + + // Do judgement + if summary.Severity.Code() >= projectVulnerableSeverity.Code() { + err = errors.Errorf("the pulling image severity %q is higher than or equal with the project setting %q, reject the response.", summary.Severity, projectVulnerableSeverity) + vh.sendError(err, rw) + return + } + + // Print scannerPull CVE list + if len(summary.CVEBypassed) > 0 { + for _, cve := range summary.CVEBypassed { + log.Infof("Vulnerable policy check: scannerPull CVE %s", cve) + } + } + } + util.CopyResp(rec, rw) +} + +func validate(req *http.Request) (bool, util.ImageInfo, vuln.Severity, models.CVEWhitelist) { + var vs vuln.Severity + var wl models.CVEWhitelist + var img util.ImageInfo + imgRaw := req.Context().Value(util.ImageInfoCtxKey) + if imgRaw == nil { + return false, img, vs, wl + } + // Expected artifact specified? img, ok := imgRaw.(util.ImageInfo) if !ok || len(img.Digest) == 0 { - vh.next.ServeHTTP(rw, req) - return - } - - if pullWithBearer, ok := util.DockerPullAuthFromContext(req.Context()); ok && !pullWithBearer { - vh.next.ServeHTTP(rw, req) - return + return false, img, vs, wl } if scannerPull, ok := util.ScannerPullFromContext(req.Context()); ok && scannerPull { - vh.next.ServeHTTP(rw, req) - return + return false, img, vs, wl } - // Is vulnerable policy set? projectVulnerableEnabled, projectVulnerableSeverity, wl := util.GetPolicyChecker().VulnerablePolicy(img.ProjectName) if !projectVulnerableEnabled { - vh.next.ServeHTTP(rw, req) - return + return false, img, vs, wl } - - // Invalid project ID - if wl.ProjectID == 0 { - err := errors.Errorf("project verification error: project %s", img.ProjectName) - vh.sendError(err, rw) - - return - } - - // Get the vulnerability summary - artifact := &v1.Artifact{ - NamespaceID: wl.ProjectID, - Repository: img.Repository, - Tag: img.Reference, - Digest: img.Digest, - MimeType: v1.MimeTypeDockerArtifact, - } - - cve := report.CVESet(wl.CVESet()) - summaries, err := sc.DefaultController.GetSummary( - artifact, - []string{v1.MimeTypeNativeReport}, - report.WithCVEWhitelist(&cve), - ) - - if err != nil { - err = errors.Wrap(err, "middleware: vulnerable handler") - vh.sendError(err, rw) - - return - } - - rawSummary, ok := summaries[v1.MimeTypeNativeReport] - // No report yet? - if !ok { - err = errors.Errorf("no scan report existing for the artifact: %s:%s@%s", img.Repository, img.Reference, img.Digest) - vh.sendError(err, rw) - - return - } - - summary := rawSummary.(*vuln.NativeReportSummary) - - // Do judgement - if summary.Severity.Code() >= projectVulnerableSeverity.Code() { - err = errors.Errorf("the pulling image severity %q is higher than or equal with the project setting %q, reject the response.", summary.Severity, projectVulnerableSeverity) - vh.sendError(err, rw) - - return - } - - // Print scannerPull CVE list - if len(summary.CVEBypassed) > 0 { - for _, cve := range summary.CVEBypassed { - log.Infof("Vulnerable policy check: scannerPull CVE %s", cve) - } - } - - vh.next.ServeHTTP(rw, req) + return true, img, projectVulnerableSeverity, wl } func (vh vulnerableHandler) sendError(err error, rw http.ResponseWriter) {