mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-20 23:57:42 +01:00
Merge pull request #9875 from wy65701436/middleware-policy-checker
enable policy checker in response handler
This commit is contained in:
commit
4bec9bbfc6
@ -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) {
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user