Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-06-11 13:32:59 -04:00
parent 67bc3ef6c3
commit 5f1abe4779
No known key found for this signature in database
GPG key ID: 18F3685C0022BFF3
3 changed files with 190 additions and 124 deletions

93
clair/layerutil.go Normal file
View file

@ -0,0 +1,93 @@
package clair
import (
"fmt"
"strings"
"github.com/coreos/clair/api/v3/clairpb"
"github.com/docker/distribution"
"github.com/genuinetools/reg/registry"
)
// NewClairLayer will form a layer struct required for a clair scan.
func (c *Clair) NewClairLayer(r *registry.Registry, image string, fsLayers []distribution.Descriptor, index int) (*Layer, error) {
var parentName string
if index < len(fsLayers)-1 {
parentName = fsLayers[index+1].Digest.String()
}
// Form the path.
p := strings.Join([]string{r.URL, "v2", image, "blobs", fsLayers[index].Digest.String()}, "/")
// Get the headers.
h, err := r.Headers(p)
if err != nil {
return nil, err
}
return &Layer{
Name: fsLayers[index].Digest.String(),
Path: p,
ParentName: parentName,
Format: "Docker",
Headers: h,
}, nil
}
// NewClairV3Layer will form a layer struct required for a clair scan.
func (c *Clair) NewClairV3Layer(r *registry.Registry, image string, fsLayer distribution.Descriptor) (*clairpb.PostAncestryRequest_PostLayer, error) {
// Form the path.
p := strings.Join([]string{r.URL, "v2", image, "blobs", fsLayer.Digest.String()}, "/")
// Get the headers.
h, err := r.Headers(p)
if err != nil {
return nil, err
}
return &clairpb.PostAncestryRequest_PostLayer{
Hash: fsLayer.Digest.String(),
Path: p,
Headers: h,
}, nil
}
func (c *Clair) getFilteredLayers(r *registry.Registry, repo, tag string) ([]distribution.Descriptor, error) {
ok := true
// Get the manifest to pass to clair.
mf, err := r.ManifestV2(repo, tag)
if err != nil {
ok = false
c.Logf("couldn't retrieve manifest v2, falling back to v1")
// return nil, fmt.Errorf("getting the v2 manifest for %s:%s failed: %v", repo, tag, err)
}
var filteredLayers []distribution.Descriptor
// Filter out the empty layers.
if ok {
for _, layer := range mf.Layers {
if !IsEmptyLayer(layer.Digest) {
filteredLayers = append(filteredLayers, layer)
}
}
return filteredLayers, nil
}
m, err := r.ManifestV1(repo, tag)
if err != nil {
return nil, fmt.Errorf("getting the v1 manifest for %s:%s failed: %v", repo, tag, err)
}
for _, layer := range m.FSLayers {
if !IsEmptyLayer(layer.BlobSum) {
newLayer := distribution.Descriptor{
Digest: layer.BlobSum,
}
filteredLayers = append(filteredLayers, newLayer)
}
}
return filteredLayers, nil
}

View file

@ -1,13 +1,10 @@
package clair
import (
"encoding/base64"
"fmt"
"strings"
"time"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/schema1"
"github.com/coreos/clair/api/v3/clairpb"
"github.com/genuinetools/reg/registry"
)
@ -33,7 +30,7 @@ func (c *Clair) Vulnerabilities(r *registry.Registry, repo, tag string) (Vulnera
for i := len(filteredLayers) - 1; i >= 0; i-- {
// Form the clair layer.
l, err := c.NewClairLayerV2(r, repo, filteredLayers, i)
l, err := c.NewClairLayer(r, repo, filteredLayers, i)
if err != nil {
return report, err
}
@ -44,7 +41,7 @@ func (c *Clair) Vulnerabilities(r *registry.Registry, repo, tag string) (Vulnera
}
}
vl, err := c.GetLayer(filteredLayers[0].Digest.String(), false, true)
vl, err := c.GetLayer(filteredLayers[0].Digest.String(), true, true)
if err != nil {
return report, err
}
@ -75,133 +72,81 @@ func (c *Clair) Vulnerabilities(r *registry.Registry, repo, tag string) (Vulnera
return report, nil
}
// NewClairLayer will form a layer struct required for a clair scan.
func (c *Clair) NewClairLayer(r *registry.Registry, image string, fsLayers []schema1.FSLayer, index int) (*Layer, error) {
var parentName string
if index < len(fsLayers)-1 {
parentName = fsLayers[index+1].BlobSum.String()
// VulnerabilitiesV3 scans the given repo and tag using the clair v3 API.
func (c *Clair) VulnerabilitiesV3(r *registry.Registry, repo, tag string) (VulnerabilityReport, error) {
report := VulnerabilityReport{
RegistryURL: r.Domain,
Repo: repo,
Tag: tag,
Date: time.Now().Local().Format(time.RFC1123),
VulnsBySeverity: make(map[string][]Vulnerability),
}
// form the path
p := strings.Join([]string{r.URL, "v2", image, "blobs", fsLayers[index].BlobSum.String()}, "/")
useBasicAuth := false
// get the token
token, err := r.Token(p)
filteredLayers, err := c.getFilteredLayers(r, repo, tag)
if err != nil {
// if we get an error here of type: malformed auth challenge header: 'Basic realm="Registry Realm"'
// we need to use basic auth for the registry
if !strings.Contains(err.Error(), `malformed auth challenge header: 'Basic realm="Registry`) && err.Error() != "basic auth required" {
return nil, err
}
useBasicAuth = true
return report, fmt.Errorf("getting filtered layers failed: %v", err)
}
h := make(map[string]string)
if token != "" && !useBasicAuth {
h = map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", token),
if len(filteredLayers) == 0 {
fmt.Printf("No need to analyse image %s:%s as there is no non-emtpy layer", repo, tag)
return report, nil
}
clairLayers := []*clairpb.PostAncestryRequest_PostLayer{}
for i := len(filteredLayers) - 1; i >= 0; i-- {
// Form the clair layer.
l, err := c.NewClairV3Layer(r, repo, filteredLayers[i])
if err != nil {
return report, err
}
// Append the layer.
clairLayers = append(clairLayers, l)
}
// Post the ancestry.
if err := c.PostAncestry(filteredLayers[0].Digest.String(), clairLayers); err != nil {
return report, fmt.Errorf("posting ancestry failed: %v", err)
}
// Get the ancestry.
vl, err := c.GetAncestry(filteredLayers[0].Digest.String(), true, true)
if err != nil {
return report, err
}
// Get the vulns.
for _, f := range vl.Features {
for _, v := range f.Vulnerabilities {
report.Vulns = append(report.Vulns, Vulnerability{
Name: v.Name,
NamespaceName: v.NamespaceName,
Description: v.Description,
Link: v.Link,
Severity: v.Severity,
Metadata: map[string]interface{}{v.Metadata: ""},
FixedBy: v.FixedBy,
})
}
}
if token == "" || useBasicAuth {
c.Logf("clair.vulns using basic auth")
h = map[string]string{
"Authorization": fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(r.Username+":"+r.Password))),
vulnsBy := func(sev string, store map[string][]Vulnerability) []Vulnerability {
items, found := store[sev]
if !found {
items = make([]Vulnerability, 0)
store[sev] = items
}
return items
}
return &Layer{
Name: fsLayers[index].BlobSum.String(),
Path: p,
ParentName: parentName,
Format: "Docker",
Headers: h,
}, nil
}
// NewClairLayerV2 will form a layer struct required for a clair scan.
func (c *Clair) NewClairLayerV2(r *registry.Registry, image string, fsLayers []distribution.Descriptor, index int) (*Layer, error) {
var parentName string
if index < len(fsLayers)-1 {
parentName = fsLayers[index+1].Digest.String()
}
// Form the path.
p := strings.Join([]string{r.URL, "v2", image, "blobs", fsLayers[index].Digest.String()}, "/")
useBasicAuth := false
// Get the token.
token, err := r.Token(p)
if err != nil {
// If we get an error here of type: malformed auth challenge header: 'Basic realm="Registry Realm"'
// We need to use basic auth for the registry.
if !strings.Contains(err.Error(), `malformed auth challenge header: 'Basic realm="Registry Realm"'`) && !strings.Contains(err.Error(), "basic auth required") {
return nil, err
}
useBasicAuth = true
}
h := make(map[string]string)
if token != "" && !useBasicAuth {
h = map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", token),
}
}
if useBasicAuth {
h = map[string]string{
"Authorization": fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(r.Username+":"+r.Password))),
}
}
return &Layer{
Name: fsLayers[index].Digest.String(),
Path: p,
ParentName: parentName,
Format: "Docker",
Headers: h,
}, nil
}
func (c *Clair) getFilteredLayers(r *registry.Registry, repo, tag string) ([]distribution.Descriptor, error) {
ok := true
// Get the manifest to pass to clair.
mf, err := r.ManifestV2(repo, tag)
if err != nil {
ok = false
c.Logf("couldn't retrieve manifest v2, falling back to v1")
// return nil, fmt.Errorf("getting the v2 manifest for %s:%s failed: %v", repo, tag, err)
}
var filteredLayers []distribution.Descriptor
// Filter out the empty layers.
if ok {
for _, layer := range mf.Layers {
if !IsEmptyLayer(layer.Digest) {
filteredLayers = append(filteredLayers, layer)
}
}
return filteredLayers, nil
}
m, err := r.ManifestV1(repo, tag)
if err != nil {
return nil, fmt.Errorf("getting the v1 manifest for %s:%s failed: %v", repo, tag, err)
}
for _, layer := range m.FSLayers {
if !IsEmptyLayer(layer.BlobSum) {
newLayer := distribution.Descriptor{
Digest: layer.BlobSum,
}
filteredLayers = append(filteredLayers, newLayer)
}
}
return filteredLayers, nil
// Group by severity.
for _, v := range report.Vulns {
sevRow := vulnsBy(v.Severity, report.VulnsBySeverity)
report.VulnsBySeverity[v.Severity] = append(sevRow, v)
}
// calculate number of bad vulns
report.BadVulns = len(report.VulnsBySeverity["High"]) + len(report.VulnsBySeverity["Critical"]) + len(report.VulnsBySeverity["Defcon1"])
return report, nil
}

View file

@ -2,11 +2,13 @@ package registry
import (
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
)
// TokenTransport defines the data structure for authentication via tokens.
@ -177,3 +179,29 @@ func (r *Registry) Token(url string) (string, error) {
return authToken.Token, nil
}
// Headers returns the authorization headers for a specific uri.
func (r *Registry) Headers(uri string) (map[string]string, error) {
// Get the token.
token, err := r.Token(uri)
if err != nil {
// If we get an error here of type: malformed auth challenge header: 'Basic realm="Registry Realm"'
// We need to use basic auth for the registry.
if !strings.Contains(err.Error(), `malformed auth challenge header: 'Basic realm="Registry Realm"'`) && !strings.Contains(err.Error(), "basic auth required") {
return nil, err
}
// Return basic auth headers.
return map[string]string{
"Authorization": fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(r.Username+":"+r.Password))),
}, nil
}
if len(token) < 1 {
return nil, fmt.Errorf("got empty token for %s", uri)
}
return map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", token),
}, nil
}