mirror of
https://github.com/genuinetools/reg.git
synced 2024-05-20 03:58:32 -04:00
cleanup
Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
parent
67bc3ef6c3
commit
5f1abe4779
93
clair/layerutil.go
Normal file
93
clair/layerutil.go
Normal 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
|
||||
}
|
193
clair/vulns.go
193
clair/vulns.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue