vul interceptor

update

fix http  no found

fix test

fix test

update

fix typo

fix travis

update per comment

update typo
This commit is contained in:
wangyan 2017-06-20 00:58:44 -07:00
parent 476494d122
commit 5a26ab1a53
3 changed files with 132 additions and 68 deletions

View File

@ -3,6 +3,7 @@ package proxy
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/vmware/harbor/src/adminserver/client"
"github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
notarytest "github.com/vmware/harbor/src/common/utils/notary/test" notarytest "github.com/vmware/harbor/src/common/utils/notary/test"
@ -19,6 +20,7 @@ import (
var endpoint = "10.117.4.142" var endpoint = "10.117.4.142"
var notaryServer *httptest.Server var notaryServer *httptest.Server
var adminServer *httptest.Server var adminServer *httptest.Server
var adminserverClient client.Client
var admiralEndpoint = "http://127.0.0.1:8282" var admiralEndpoint = "http://127.0.0.1:8282"
var token = "" var token = ""
@ -43,6 +45,7 @@ func TestMain(m *testing.M) {
if err := config.Init(); err != nil { if err := config.Init(); err != nil {
panic(err) panic(err)
} }
adminserverClient = client.NewClient(adminServer.URL, nil)
result := m.Run() result := m.Run()
if result != 0 { if result != 0 {
os.Exit(result) os.Exit(result)
@ -95,18 +98,46 @@ func TestEnvPolicyChecker(t *testing.T) {
if err := os.Setenv("PROJECT_CONTENT_TRUST", "1"); err != nil { if err := os.Setenv("PROJECT_CONTENT_TRUST", "1"); err != nil {
t.Fatalf("Failed to set env variable: %v", err) t.Fatalf("Failed to set env variable: %v", err)
} }
if err2 := os.Setenv("PROJECT_VULNERABLE", "1"); err2 != nil {
t.Fatalf("Failed to set env variable: %v", err2)
}
if err3 := os.Setenv("PROJECT_SEVERITY", "negligible"); err3 != nil {
t.Fatalf("Failed to set env variable: %v", err3)
}
contentTrustFlag := getPolicyChecker().contentTrustEnabled("whatever") contentTrustFlag := getPolicyChecker().contentTrustEnabled("whatever")
vulFlag := getPolicyChecker().vulnerableEnabled("whatever") vulFlag, sev := getPolicyChecker().vulnerablePolicy("whatever")
assert.True(contentTrustFlag) assert.True(contentTrustFlag)
assert.False(vulFlag) assert.True(vulFlag)
assert.Equal(sev, models.SevNone)
} }
func TestPMSPolicyChecker(t *testing.T) { func TestPMSPolicyChecker(t *testing.T) {
var defaultConfigAdmiral = map[string]interface{}{
common.ExtEndpoint: "https://" + endpoint,
common.WithNotary: true,
common.CfgExpiration: 5,
common.AdmiralEndpoint: admiralEndpoint,
}
adminServer, err := utilstest.NewAdminserver(defaultConfigAdmiral)
if err != nil {
panic(err)
}
defer adminServer.Close()
if err := os.Setenv("ADMIN_SERVER_URL", adminServer.URL); err != nil {
panic(err)
}
if err := config.Init(); err != nil {
panic(err)
}
pm := pms.NewProjectManager(admiralEndpoint, token) pm := pms.NewProjectManager(admiralEndpoint, token)
name := "project_for_test_get_true" name := "project_for_test_get_sev_low"
id, err := pm.Create(&models.Project{ id, err := pm.Create(&models.Project{
Name: name, Name: name,
EnableContentTrust: true, EnableContentTrust: true,
PreventVulnerableImagesFromRunning: false,
PreventVulnerableImagesFromRunningSeverity: "low",
}) })
require.Nil(t, err) require.Nil(t, err)
defer func(id int64) { defer func(id int64) {
@ -117,29 +148,27 @@ func TestPMSPolicyChecker(t *testing.T) {
project, err := pm.Get(id) project, err := pm.Get(id)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, id, project.ProjectID) assert.Equal(t, id, project.ProjectID)
server, err2 := utilstest.NewAdminserver(nil)
if err2 != nil { contentTrustFlag := getPolicyChecker().contentTrustEnabled("project_for_test_get_sev_low")
t.Fatalf("failed to create a mock admin server: %v", err2)
}
defer server.Close()
contentTrustFlag := getPolicyChecker().contentTrustEnabled("project_for_test_get_true")
assert.True(t, contentTrustFlag) assert.True(t, contentTrustFlag)
projectVulnerableEnabled, projectVulnerableSeverity := getPolicyChecker().vulnerablePolicy("project_for_test_get_sev_low")
assert.False(t, projectVulnerableEnabled)
assert.Equal(t, projectVulnerableSeverity, models.SevLow)
} }
func TestMatchNotaryDigest(t *testing.T) { func TestMatchNotaryDigest(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
//The data from common/utils/notary/helper_test.go //The data from common/utils/notary/helper_test.go
img1 := imageInfo{"notary-demo/busybox", "1.0", "notary-demo"} img1 := imageInfo{"notary-demo/busybox", "1.0", "notary-demo", "sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a7"}
img2 := imageInfo{"notary-demo/busybox", "2.0", "notary-demo"} img2 := imageInfo{"notary-demo/busybox", "2.0", "notary-demo", "sha256:12345678"}
res1, err := matchNotaryDigest(img1, "sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a7")
res1, err := matchNotaryDigest(img1)
assert.Nil(err, "Unexpected error: %v, image: %#v", err, img1) assert.Nil(err, "Unexpected error: %v, image: %#v", err, img1)
assert.True(res1) assert.True(res1)
res2, err := matchNotaryDigest(img1, "sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a8")
assert.Nil(err, "Unexpected error: %v, image: %#v, take 2", err, img1) res2, err := matchNotaryDigest(img2)
assert.Nil(err, "Unexpected error: %v, image: %#v, take 2", err, img2)
assert.False(res2) assert.False(res2)
res3, err := matchNotaryDigest(img2, "sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a7")
assert.Nil(err, "Unexpected error: %v, image: %#v", err, img2)
assert.False(res3)
} }
func TestCopyResp(t *testing.T) { func TestCopyResp(t *testing.T) {

View File

@ -1,9 +1,12 @@
package proxy package proxy
import ( import (
// "github.com/vmware/harbor/src/ui/api" "github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/clair"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/utils/notary" "github.com/vmware/harbor/src/common/utils/notary"
// "github.com/vmware/harbor/src/ui/api"
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/projectmanager" "github.com/vmware/harbor/src/ui/projectmanager"
"github.com/vmware/harbor/src/ui/projectmanager/pms" "github.com/vmware/harbor/src/ui/projectmanager/pms"
@ -26,6 +29,9 @@ const (
tokenUsername = "admin" tokenUsername = "admin"
) )
// Record the docker deamon raw response.
var rec *httptest.ResponseRecorder
// NotaryEndpoint , exported for testing. // NotaryEndpoint , exported for testing.
var NotaryEndpoint = config.InternalNotaryEndpoint() var NotaryEndpoint = config.InternalNotaryEndpoint()
@ -51,8 +57,8 @@ func MatchPullManifest(req *http.Request) (bool, string, string) {
type policyChecker interface { type policyChecker interface {
// contentTrustEnabled returns whether a project has enabled content trust. // contentTrustEnabled returns whether a project has enabled content trust.
contentTrustEnabled(name string) bool contentTrustEnabled(name string) bool
// vulnerableEnabled returns whether a project has enabled content trust. // vulnerablePolicy returns whether a project has enabled vulnerable, and the project's severity.
vulnerableEnabled(name string) bool vulnerablePolicy(name string) (bool, models.Severity)
} }
//For testing //For testing
@ -61,9 +67,8 @@ type envPolicyChecker struct{}
func (ec envPolicyChecker) contentTrustEnabled(name string) bool { func (ec envPolicyChecker) contentTrustEnabled(name string) bool {
return os.Getenv("PROJECT_CONTENT_TRUST") == "1" return os.Getenv("PROJECT_CONTENT_TRUST") == "1"
} }
func (ec envPolicyChecker) vulnerableEnabled(name string) bool { func (ec envPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity) {
// TODO: May need get more information in vulnerable policies. return os.Getenv("PROJECT_VULNERABLE") == "1", clair.ParseClairSev(os.Getenv("PROJECT_SEVERITY"))
return os.Getenv("PROJECT_VULNERABBLE") == "1"
} }
type pmsPolicyChecker struct { type pmsPolicyChecker struct {
@ -78,8 +83,13 @@ func (pc pmsPolicyChecker) contentTrustEnabled(name string) bool {
} }
return project.EnableContentTrust return project.EnableContentTrust
} }
func (pc pmsPolicyChecker) vulnerableEnabled(name string) bool { func (pc pmsPolicyChecker) vulnerablePolicy(name string) (bool, models.Severity) {
return true project, err := pc.pm.Get(name)
if err != nil {
log.Errorf("Unexpected error when getting the project, error: %v", err)
return true, models.SevUnknown
}
return project.PreventVulnerableImagesFromRunning, clair.ParseClairSev(project.PreventVulnerableImagesFromRunningSeverity)
} }
// newPMSPolicyChecker returns an instance of an pmsPolicyChecker // newPMSPolicyChecker returns an instance of an pmsPolicyChecker
@ -101,7 +111,7 @@ type imageInfo struct {
repository string repository string
tag string tag string
projectName string projectName string
// digest string digest string
} }
type urlHandler struct { type urlHandler struct {
@ -120,32 +130,18 @@ func (uh urlHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
http.Error(rw, fmt.Sprintf("Bad repository name: %s", repository), http.StatusBadRequest) http.Error(rw, fmt.Sprintf("Bad repository name: %s", repository), http.StatusBadRequest)
return return
} }
/* rec = httptest.NewRecorder()
//Need to get digest of the image. uh.next.ServeHTTP(rec, req)
endpoint, err := config.RegistryURL() if rec.Result().StatusCode != http.StatusOK {
if err != nil { copyResp(rec, rw)
log.Errorf("Error getting Registry URL: %v", err) return
http.Error(rw, fmt.Sprintf("Failed due to internal Error: %v", err), http.StatusInternalError) }
return digest := rec.Header().Get(http.CanonicalHeaderKey("Docker-Content-Digest"))
}
rc, err := api.NewRepositoryClient(endpoint, false, username, repository, "repository", repository, "pull")
if err != nil {
log.Errorf("Error creating repository Client: %v", err)
http.Error(rw, fmt.Sprintf("Failed due to internal Error: %v", err), http.StatusInternalError)
return
}
digest, exist, err := rc.ManifestExist(tag)
if err != nil {
log.Errorf("Failed to get digest for tag: %s, error: %v", tag, err)
http.Error(rw, fmt.Sprintf("Failed due to internal Error: %v", err), http.StatusInternalError)
return
}
*/
img := imageInfo{ img := imageInfo{
repository: repository, repository: repository,
tag: tag, tag: tag,
projectName: components[0], projectName: components[0],
digest: digest,
} }
log.Debugf("image info of the request: %#v", img) log.Debugf("image info of the request: %#v", img)
@ -171,31 +167,70 @@ func (cth contentTrustHandler) ServeHTTP(rw http.ResponseWriter, req *http.Reque
cth.next.ServeHTTP(rw, req) cth.next.ServeHTTP(rw, req)
return return
} }
//May need to update status code, let's use recorder match, err := matchNotaryDigest(img)
rec := httptest.NewRecorder()
cth.next.ServeHTTP(rec, req)
if rec.Result().StatusCode != http.StatusOK {
copyResp(rec, rw)
return
}
log.Debugf("showing digest")
digest := rec.Header().Get(http.CanonicalHeaderKey("Docker-Content-Digest"))
log.Debugf("digest: %s", digest)
match, err := matchNotaryDigest(img, digest)
if err != nil { if err != nil {
http.Error(rw, "Failed in communication with Notary please check the log", http.StatusInternalServerError) http.Error(rw, "Failed in communication with Notary please check the log", http.StatusInternalServerError)
return return
} }
if match { if !match {
log.Debugf("Passing the response to outter responseWriter")
copyResp(rec, rw)
} else {
log.Debugf("digest mismatch, failing the response.") log.Debugf("digest mismatch, failing the response.")
http.Error(rw, "The image is not signed in Notary.", http.StatusPreconditionFailed) http.Error(rw, "The image is not signed in Notary.", http.StatusPreconditionFailed)
return
} }
cth.next.ServeHTTP(rw, req)
} }
func matchNotaryDigest(img imageInfo, digest string) (bool, error) { type vulnerableHandler struct {
next http.Handler
}
func (vh vulnerableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
imgRaw := req.Context().Value(imageInfoCtxKey)
if imgRaw == nil || !config.WithClair() {
vh.next.ServeHTTP(rw, req)
return
}
img, _ := req.Context().Value(imageInfoCtxKey).(imageInfo)
projectVulnerableEnabled, projectVulnerableSeverity := getPolicyChecker().vulnerablePolicy(img.projectName)
if !projectVulnerableEnabled {
vh.next.ServeHTTP(rw, req)
return
}
overview, err := dao.GetImgScanOverview(img.digest)
if err != nil {
log.Errorf("failed to get ImgScanOverview with repo: %s, tag: %s, digest: %s. Error: %v", img.repository, img.tag, img.digest, err)
http.Error(rw, "Failed to get ImgScanOverview.", http.StatusPreconditionFailed)
return
}
if overview == nil {
log.Debugf("cannot get the image scan overview info, failing the response.")
http.Error(rw, "Cannot get the image scan overview info.", http.StatusPreconditionFailed)
return
}
imageSev := overview.Sev
if imageSev > int(projectVulnerableSeverity) {
log.Debugf("the image severity is higher then project setting, failing the response.")
http.Error(rw, "The image scan result doesn't pass the project setting.", http.StatusPreconditionFailed)
return
}
vh.next.ServeHTTP(rw, req)
}
type funnelHandler struct {
next http.Handler
}
func (fu funnelHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
imgRaw := req.Context().Value(imageInfoCtxKey)
if imgRaw != nil {
log.Debugf("Return the original response as no the interceptor takes action.")
copyResp(rec, rw)
return
}
fu.next.ServeHTTP(rw, req)
}
func matchNotaryDigest(img imageInfo) (bool, error) {
targets, err := notary.GetInternalTargets(NotaryEndpoint, tokenUsername, img.repository) targets, err := notary.GetInternalTargets(NotaryEndpoint, tokenUsername, img.repository)
if err != nil { if err != nil {
return false, err return false, err
@ -207,7 +242,7 @@ func matchNotaryDigest(img imageInfo, digest string) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
return digest == d, nil return img.digest == d, nil
} }
} }
log.Debugf("image: %#v, not found in notary", img) log.Debugf("image: %#v, not found in notary", img)

View File

@ -42,7 +42,7 @@ func Init(urls ...string) error {
} }
Proxy = httputil.NewSingleHostReverseProxy(targetURL) Proxy = httputil.NewSingleHostReverseProxy(targetURL)
//TODO: add vulnerable interceptor. //TODO: add vulnerable interceptor.
handlers = handlerChain{head: urlHandler{next: contentTrustHandler{next: Proxy}}} handlers = handlerChain{head: urlHandler{next: contentTrustHandler{next: vulnerableHandler{next: funnelHandler{next: Proxy}}}}}
return nil return nil
} }