Merge pull request #9875 from wy65701436/middleware-policy-checker

enable policy checker in response handler
This commit is contained in:
Wang Yan 2019-11-14 18:31:50 +08:00 committed by GitHub
commit 4bec9bbfc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 111 deletions

View File

@ -21,6 +21,7 @@ import (
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/core/middlewares/util" "github.com/goharbor/harbor/src/core/middlewares/util"
"net/http" "net/http"
"net/http/httptest"
) )
// NotaryEndpoint ... // NotaryEndpoint ...
@ -39,39 +40,45 @@ func New(next http.Handler) http.Handler {
// ServeHTTP ... // ServeHTTP ...
func (cth contentTrustHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 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) imgRaw := req.Context().Value(util.ImageInfoCtxKey)
if imgRaw == nil || !config.WithNotary() { if imgRaw == nil || !config.WithNotary() {
cth.next.ServeHTTP(rw, req) return false, img
return
} }
img, _ := req.Context().Value(util.ImageInfoCtxKey).(util.ImageInfo) img, _ = req.Context().Value(util.ImageInfoCtxKey).(util.ImageInfo)
if img.Digest == "" { if img.Digest == "" {
cth.next.ServeHTTP(rw, req) return false, img
return
}
if pullWithBearer, ok := util.DockerPullAuthFromContext(req.Context()); ok && !pullWithBearer {
cth.next.ServeHTTP(rw, req)
return
} }
if scannerPull, ok := util.ScannerPullFromContext(req.Context()); ok && scannerPull { if scannerPull, ok := util.ScannerPullFromContext(req.Context()); ok && scannerPull {
cth.next.ServeHTTP(rw, req) return false, img
return
} }
if !util.GetPolicyChecker().ContentTrustEnabled(img.ProjectName) { if !util.GetPolicyChecker().ContentTrustEnabled(img.ProjectName) {
cth.next.ServeHTTP(rw, req) return false, img
return
} }
match, err := matchNotaryDigest(img) return true, 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)
} }
func matchNotaryDigest(img util.ImageInfo) (bool, error) { func matchNotaryDigest(img util.ImageInfo) (bool, error) {

View File

@ -43,7 +43,6 @@ func (r *regTokenHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
r.next.ServeHTTP(rw, req) r.next.ServeHTTP(rw, req)
return return
} }
*req = *(req.WithContext(util.NewDockerPullAuthContext(req.Context(), true)))
rawToken := parts[1] rawToken := parts[1]
opt := pkg_token.DefaultTokenOptions() opt := pkg_token.DefaultTokenOptions()

View File

@ -51,8 +51,6 @@ const (
ImageInfoCtxKey = contextKey("ImageInfo") ImageInfoCtxKey = contextKey("ImageInfo")
// ScannerPullCtxKey the context key for robot account to bypass the pull policy check. // ScannerPullCtxKey the context key for robot account to bypass the pull policy check.
ScannerPullCtxKey = contextKey("ScannerPullCheck") ScannerPullCtxKey = contextKey("ScannerPullCheck")
// DockerPullAuthCtxKey the context key to index whether docker pull request with bearer token
DockerPullAuthCtxKey = contextKey("DockerPullWithBearer")
// TokenUsername ... // TokenUsername ...
// TODO: temp solution, remove after vmware/harbor#2242 is resolved. // TODO: temp solution, remove after vmware/harbor#2242 is resolved.
TokenUsername = "harbor-core" TokenUsername = "harbor-core"
@ -459,17 +457,6 @@ func ScannerPullFromContext(ctx context.Context) (bool, bool) {
return info, ok 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 // NewBlobInfoContext returns context with blob info
func NewBlobInfoContext(ctx context.Context, info *BlobInfo) context.Context { func NewBlobInfoContext(ctx context.Context, info *BlobInfo) context.Context {
return context.WithValue(ctx, blobInfoKey, info) return context.WithValue(ctx, blobInfoKey, info)

View File

@ -17,6 +17,7 @@ package vulnerable
import ( import (
"net/http" "net/http"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/middlewares/util" "github.com/goharbor/harbor/src/core/middlewares/util"
sc "github.com/goharbor/harbor/src/pkg/scan/api/scan" 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" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/goharbor/harbor/src/pkg/scan/vuln" "github.com/goharbor/harbor/src/pkg/scan/vuln"
"github.com/pkg/errors" "github.com/pkg/errors"
"net/http/httptest"
) )
type vulnerableHandler struct { type vulnerableHandler struct {
@ -39,94 +41,96 @@ func New(next http.Handler) http.Handler {
// ServeHTTP ... // ServeHTTP ...
func (vh vulnerableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (vh vulnerableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
imgRaw := req.Context().Value(util.ImageInfoCtxKey) doVulCheck, img, projectVulnerableSeverity, wl := validate(req)
if imgRaw == nil { if !doVulCheck {
vh.next.ServeHTTP(rw, req) vh.next.ServeHTTP(rw, req)
return 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? // Expected artifact specified?
img, ok := imgRaw.(util.ImageInfo) img, ok := imgRaw.(util.ImageInfo)
if !ok || len(img.Digest) == 0 { if !ok || len(img.Digest) == 0 {
vh.next.ServeHTTP(rw, req) return false, img, vs, wl
return
}
if pullWithBearer, ok := util.DockerPullAuthFromContext(req.Context()); ok && !pullWithBearer {
vh.next.ServeHTTP(rw, req)
return
} }
if scannerPull, ok := util.ScannerPullFromContext(req.Context()); ok && scannerPull { if scannerPull, ok := util.ScannerPullFromContext(req.Context()); ok && scannerPull {
vh.next.ServeHTTP(rw, req) return false, img, vs, wl
return
} }
// Is vulnerable policy set? // Is vulnerable policy set?
projectVulnerableEnabled, projectVulnerableSeverity, wl := util.GetPolicyChecker().VulnerablePolicy(img.ProjectName) projectVulnerableEnabled, projectVulnerableSeverity, wl := util.GetPolicyChecker().VulnerablePolicy(img.ProjectName)
if !projectVulnerableEnabled { if !projectVulnerableEnabled {
vh.next.ServeHTTP(rw, req) return false, img, vs, wl
return
} }
return true, img, projectVulnerableSeverity, 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)
} }
func (vh vulnerableHandler) sendError(err error, rw http.ResponseWriter) { func (vh vulnerableHandler) sendError(err error, rw http.ResponseWriter) {