mirror of
https://github.com/genuinetools/reg.git
synced 2024-09-28 11:46:20 -04:00
Merge branch 'majst01-dynamic-frontend'
* majst01-dynamic-frontend: Remove code duplicates Add git to be able to do go get Make tests work again Make sample shell script more robust load BadVulns count with AJAX calls Satisfy golint Vendor all dependencies Always point to the vulnerabilities cleanup and reusage of error messages Remove now unneeded templates Show vulnerabilities Fix searching for new dynamic frontend Refactor NewClairLayer to a single place First draft of a dynamic frontend, display of vulnerabilities is still not implemented.
This commit is contained in:
commit
510b24edff
16 changed files with 696 additions and 304 deletions
|
@ -15,6 +15,7 @@
|
|||
install:
|
||||
- go get github.com/golang/lint/golint
|
||||
script:
|
||||
- go get ./...
|
||||
- go build -v
|
||||
- go vet $(go list ./... | grep -v vendor)
|
||||
- test -z "$(golint ./... | grep -v vendor | tee /dev/stderr)"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
FROM golang:alpine
|
||||
|
||||
RUN apk add --no-cache \
|
||||
build-base
|
||||
build-base \
|
||||
git
|
||||
|
|
2
Makefile
2
Makefile
|
@ -25,6 +25,8 @@ lint:
|
|||
|
||||
test:
|
||||
@echo "+ $@"
|
||||
@go get github.com/labstack/echo
|
||||
@go get github.com/labstack/echo/middleware
|
||||
@go test -v -tags "$(BUILDTAGS) cgo" $(shell go list ./... | grep -v vendor)
|
||||
|
||||
vet:
|
||||
|
|
|
@ -56,6 +56,16 @@ type Vulnerability struct {
|
|||
FixedIn []feature `json:"FixedIn,omitempty"`
|
||||
}
|
||||
|
||||
// VulnerabilityReport represents the result of a vulnerability scan of a repo.
|
||||
type VulnerabilityReport struct {
|
||||
RegistryURL string
|
||||
Repo string
|
||||
Tag string
|
||||
Date string
|
||||
Vulns []Vulnerability
|
||||
VulnsBySeverity map[string][]Vulnerability
|
||||
BadVulns int
|
||||
}
|
||||
type feature struct {
|
||||
Name string `json:"Name,omitempty"`
|
||||
NamespaceName string `json:"NamespaceName,omitempty"`
|
||||
|
|
111
clair/vulns.go
Normal file
111
clair/vulns.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package clair
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/jessfraz/reg/registry"
|
||||
)
|
||||
|
||||
// Vulnerabilities scans the given repo and tag
|
||||
func (c *Clair) Vulnerabilities(r *registry.Registry, repo, tag string, m schema1.SignedManifest) (VulnerabilityReport, error) {
|
||||
report := VulnerabilityReport{
|
||||
RegistryURL: r.Domain,
|
||||
Repo: repo,
|
||||
Tag: tag,
|
||||
Date: time.Now().Local().Format(time.RFC1123),
|
||||
VulnsBySeverity: make(map[string][]Vulnerability),
|
||||
}
|
||||
|
||||
// filter out the empty layers
|
||||
var filteredLayers []schema1.FSLayer
|
||||
for _, layer := range m.FSLayers {
|
||||
if layer.BlobSum != EmptyLayerBlobSum {
|
||||
filteredLayers = append(filteredLayers, layer)
|
||||
}
|
||||
}
|
||||
m.FSLayers = filteredLayers
|
||||
if len(m.FSLayers) == 0 {
|
||||
fmt.Printf("No need to analyse image %s:%s as there is no non-emtpy layer", repo, tag)
|
||||
return report, nil
|
||||
}
|
||||
|
||||
for i := len(m.FSLayers) - 1; i >= 0; i-- {
|
||||
// form the clair layer
|
||||
l, err := c.NewClairLayer(r, repo, m.FSLayers, i)
|
||||
if err != nil {
|
||||
return report, err
|
||||
}
|
||||
|
||||
// post the layer
|
||||
if _, err := c.PostLayer(l); err != nil {
|
||||
return report, err
|
||||
}
|
||||
}
|
||||
|
||||
vl, err := c.GetLayer(m.FSLayers[0].BlobSum.String(), false, 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, v)
|
||||
}
|
||||
}
|
||||
|
||||
vulnsBy := func(sev string, store map[string][]Vulnerability) []Vulnerability {
|
||||
items, found := store[sev]
|
||||
if !found {
|
||||
items = make([]Vulnerability, 0)
|
||||
store[sev] = items
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// NewClairLayer will form a layer struct required for a clar 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()
|
||||
}
|
||||
|
||||
// form the path
|
||||
p := strings.Join([]string{r.URL, "v2", image, "blobs", fsLayers[index].BlobSum.String()}, "/")
|
||||
|
||||
// get the token
|
||||
token, err := r.Token(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := make(map[string]string)
|
||||
if token != "" {
|
||||
h = map[string]string{
|
||||
"Authorization": fmt.Sprintf("Bearer %s", token),
|
||||
}
|
||||
}
|
||||
|
||||
return &Layer{
|
||||
Name: fsLayers[index].BlobSum.String(),
|
||||
Path: p,
|
||||
ParentName: parentName,
|
||||
Format: "Docker",
|
||||
Headers: h,
|
||||
}, nil
|
||||
}
|
4
main.go
4
main.go
|
@ -266,6 +266,8 @@ func main() {
|
|||
return err
|
||||
}
|
||||
|
||||
// FIXME use clair.Vulnerabilities
|
||||
|
||||
// get the manifest
|
||||
m, err := r.ManifestV1(repo, ref)
|
||||
if err != nil {
|
||||
|
@ -293,7 +295,7 @@ func main() {
|
|||
|
||||
for i := len(m.FSLayers) - 1; i >= 0; i-- {
|
||||
// form the clair layer
|
||||
l, err := utils.NewClairLayer(r, repo, m.FSLayers, i)
|
||||
l, err := cr.NewClairLayer(r, repo, m.FSLayers, i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
252
server/server.go
252
server/server.go
|
@ -1,29 +1,22 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/jessfraz/reg/clair"
|
||||
"github.com/jessfraz/reg/registry"
|
||||
"github.com/jessfraz/reg/utils"
|
||||
wordwrap "github.com/mitchellh/go-wordwrap"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
// VERSION is the binary version.
|
||||
VERSION = "v0.1.0"
|
||||
VERSION = "v0.2.0"
|
||||
|
||||
dockerConfigPath = ".docker/config.json"
|
||||
)
|
||||
|
@ -31,8 +24,8 @@ const (
|
|||
var (
|
||||
updating = false
|
||||
wg sync.WaitGroup
|
||||
tmpl *template.Template
|
||||
r *registry.Registry
|
||||
cl *clair.Clair
|
||||
)
|
||||
|
||||
// preload initializes any global options and configuration
|
||||
|
@ -121,127 +114,58 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
// get the path to the static directory
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
staticDir := filepath.Join(wd, "static")
|
||||
|
||||
// create the template
|
||||
templateDir := filepath.Join(staticDir, "../templates")
|
||||
funcMap := template.FuncMap{
|
||||
"trim": func(s string) string {
|
||||
return wordwrap.WrapString(s, 80)
|
||||
},
|
||||
"color": func(s string) string {
|
||||
switch s = strings.ToLower(s); s {
|
||||
case "high":
|
||||
return "danger"
|
||||
case "critical":
|
||||
return "danger"
|
||||
case "defcon1":
|
||||
return "danger"
|
||||
case "medium":
|
||||
return "warning"
|
||||
case "low":
|
||||
return "info"
|
||||
case "negligible":
|
||||
return "info"
|
||||
case "unknown":
|
||||
return "default"
|
||||
default:
|
||||
return "default"
|
||||
}
|
||||
},
|
||||
}
|
||||
vulns := filepath.Join(templateDir, "vulns.html")
|
||||
if _, err := os.Stat(vulns); os.IsNotExist(err) {
|
||||
logrus.Fatalf("Template %s not found", vulns)
|
||||
}
|
||||
layout := filepath.Join(templateDir, "layout.html")
|
||||
if _, err := os.Stat(layout); os.IsNotExist(err) {
|
||||
logrus.Fatalf("Template %s not found", layout)
|
||||
}
|
||||
tmpl = template.Must(template.New("").Funcs(funcMap).ParseFiles(vulns, layout))
|
||||
|
||||
// create the initial index
|
||||
logrus.Info("creating initial static index")
|
||||
if err := createStaticIndex(r, staticDir, "", c.GlobalBool("debug"), c.GlobalInt("workers")); err != nil {
|
||||
logrus.Fatalf("Error creating index: %v", err)
|
||||
}
|
||||
|
||||
// parse the duration
|
||||
dur, err := time.ParseDuration(c.String("interval"))
|
||||
if err != nil {
|
||||
logrus.Fatalf("parsing %s as duration failed: %v", c.String("interval"), err)
|
||||
}
|
||||
|
||||
// create a clair instance if needed
|
||||
if c.GlobalString("clair") != "" {
|
||||
cl, err = clair.New(c.GlobalString("clair"), c.GlobalBool("debug"))
|
||||
if err != nil {
|
||||
logrus.Warnf("creation of clair failed: %v", err)
|
||||
}
|
||||
}
|
||||
ticker := time.NewTicker(dur)
|
||||
|
||||
go func() {
|
||||
// create more indexes every X minutes based off interval
|
||||
// analyse repositories every X minutes based off interval
|
||||
for range ticker.C {
|
||||
if !updating {
|
||||
logrus.Info("creating timer based static index")
|
||||
if err := createStaticIndex(r, staticDir, c.GlobalString("clair"), c.GlobalBool("debug"), c.GlobalInt("workers")); err != nil {
|
||||
logrus.Warnf("creating static index failed: %v", err)
|
||||
logrus.Info("start repository analysis")
|
||||
start := time.Now()
|
||||
if err := analyseRepositories(r, cl, c.GlobalBool("debug"), c.GlobalInt("workers")); err != nil {
|
||||
logrus.Warnf("repository analysis failed: %v", err)
|
||||
wg.Wait()
|
||||
updating = false
|
||||
}
|
||||
wg.Wait()
|
||||
logrus.Info("finished waiting for vulns wait group")
|
||||
elapsed := time.Since(start)
|
||||
logrus.Infof("finished repository analysis in %s", elapsed)
|
||||
} else {
|
||||
logrus.Warnf("skipping timer based static index update for %s", c.String("interval"))
|
||||
logrus.Warnf("skipping timer based repository analysis for %s", c.String("interval"))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// create mux server
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// static files handler
|
||||
staticHandler := http.FileServer(http.Dir(staticDir))
|
||||
mux.Handle("/", staticHandler)
|
||||
|
||||
// set up the server
|
||||
port := c.String("port")
|
||||
server := &http.Server{
|
||||
Addr: ":" + port,
|
||||
Handler: mux,
|
||||
}
|
||||
logrus.Infof("Starting server on port %q", port)
|
||||
if c.String("cert") != "" && c.String("key") != "" {
|
||||
logrus.Fatal(server.ListenAndServeTLS(c.String("cert"), c.String("key")))
|
||||
} else {
|
||||
logrus.Fatal(server.ListenAndServe())
|
||||
}
|
||||
keyfile := c.String("key")
|
||||
certfile := c.String("cert")
|
||||
|
||||
logrus.Fatal(listenAndServe(port, keyfile, certfile, r, cl))
|
||||
return nil
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
}
|
||||
|
||||
type data struct {
|
||||
RegistryURL string
|
||||
LastUpdated string
|
||||
Repos []repository
|
||||
}
|
||||
|
||||
type repository struct {
|
||||
Name string
|
||||
Tag string
|
||||
RepoURI string
|
||||
CreatedDate string
|
||||
VulnURI string
|
||||
}
|
||||
|
||||
type v1Compatibility struct {
|
||||
ID string `json:"id"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
func createStaticIndex(r *registry.Registry, staticDir, clairURI string, debug bool, workers int) error {
|
||||
func analyseRepositories(r *registry.Registry, cl *clair.Clair, debug bool, workers int) error {
|
||||
updating = true
|
||||
logrus.Info("fetching catalog")
|
||||
repoList, err := r.Catalog("")
|
||||
|
@ -250,7 +174,6 @@ func createStaticIndex(r *registry.Registry, staticDir, clairURI string, debug b
|
|||
}
|
||||
|
||||
logrus.Info("fetching tags")
|
||||
var repos []repository
|
||||
sem := make(chan int, workers)
|
||||
for i, repo := range repoList {
|
||||
// get the tags
|
||||
|
@ -260,35 +183,12 @@ func createStaticIndex(r *registry.Registry, staticDir, clairURI string, debug b
|
|||
}
|
||||
for j, tag := range tags {
|
||||
// get the manifest
|
||||
|
||||
m1, err := r.ManifestV1(repo, tag)
|
||||
if err != nil {
|
||||
logrus.Warnf("getting v1 manifest for %s:%s failed: %v", repo, tag, err)
|
||||
}
|
||||
|
||||
var createdDate string
|
||||
for _, h := range m1.History {
|
||||
var comp v1Compatibility
|
||||
if err := json.Unmarshal([]byte(h.V1Compatibility), &comp); err != nil {
|
||||
return fmt.Errorf("unmarshal v1compatibility failed: %v", err)
|
||||
}
|
||||
createdDate = humanize.Time(comp.Created)
|
||||
break
|
||||
}
|
||||
|
||||
repoURI := fmt.Sprintf("%s/%s", r.Domain, repo)
|
||||
if tag != "latest" {
|
||||
repoURI += ":" + tag
|
||||
}
|
||||
|
||||
newrepo := repository{
|
||||
Name: repo,
|
||||
Tag: tag,
|
||||
RepoURI: repoURI,
|
||||
CreatedDate: createdDate,
|
||||
}
|
||||
|
||||
if clairURI != "" {
|
||||
if cl != nil {
|
||||
wg.Add(1)
|
||||
sem <- 1
|
||||
go func(repo, tag string, i, j int) {
|
||||
|
@ -297,52 +197,21 @@ func createStaticIndex(r *registry.Registry, staticDir, clairURI string, debug b
|
|||
<-sem
|
||||
}()
|
||||
|
||||
logrus.Infof("creating vuln static page for %s:%s", repo, tag)
|
||||
logrus.Infof("search vulnerabilities for %s:%s", repo, tag)
|
||||
|
||||
if err := createVulnStaticPage(r, staticDir, clairURI, repo, tag, m1, debug); err != nil {
|
||||
logrus.Warnf("creating vuln static page for %s:%s failed: %v", repo, tag, err)
|
||||
if err := searchVulnerabilities(r, cl, repo, tag, m1, debug); err != nil {
|
||||
logrus.Warnf("searching vulnerabilities for %s:%s failed: %v", repo, tag, err)
|
||||
}
|
||||
}(repo, tag, i, j)
|
||||
|
||||
newrepo.VulnURI = filepath.Join(repo, tag)
|
||||
}
|
||||
repos = append(repos, newrepo)
|
||||
}
|
||||
}
|
||||
|
||||
d := data{
|
||||
RegistryURL: r.Domain,
|
||||
Repos: repos,
|
||||
LastUpdated: time.Now().Local().Format(time.RFC1123),
|
||||
}
|
||||
|
||||
logrus.Info("rendering index template")
|
||||
if err := renderTemplate(staticDir, "index", "index.html", d); err != nil {
|
||||
return err
|
||||
}
|
||||
updating = false
|
||||
return nil
|
||||
}
|
||||
|
||||
type vulnsReport struct {
|
||||
RegistryURL string
|
||||
Repo string
|
||||
Tag string
|
||||
Date string
|
||||
Vulns []clair.Vulnerability
|
||||
VulnsBySeverity map[string][]clair.Vulnerability
|
||||
BadVulns int
|
||||
}
|
||||
|
||||
func createVulnStaticPage(r *registry.Registry, staticDir, clairURI, repo, tag string, m schema1.SignedManifest, debug bool) error {
|
||||
report := vulnsReport{
|
||||
RegistryURL: r.Domain,
|
||||
Repo: repo,
|
||||
Tag: tag,
|
||||
Date: time.Now().Local().Format(time.RFC1123),
|
||||
VulnsBySeverity: make(map[string][]clair.Vulnerability),
|
||||
}
|
||||
|
||||
func searchVulnerabilities(r *registry.Registry, cl *clair.Clair, repo, tag string, m schema1.SignedManifest, debug bool) error {
|
||||
// filter out the empty layers
|
||||
var filteredLayers []schema1.FSLayer
|
||||
for _, layer := range m.FSLayers {
|
||||
|
@ -356,81 +225,18 @@ func createVulnStaticPage(r *registry.Registry, staticDir, clairURI, repo, tag s
|
|||
return nil
|
||||
}
|
||||
|
||||
// initialize clair
|
||||
cr, err := clair.New(clairURI, debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := len(m.FSLayers) - 1; i >= 0; i-- {
|
||||
// form the clair layer
|
||||
l, err := utils.NewClairLayer(r, repo, m.FSLayers, i)
|
||||
l, err := cl.NewClairLayer(r, repo, m.FSLayers, i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// post the layer
|
||||
if _, err := cr.PostLayer(l); err != nil {
|
||||
if _, err := cl.PostLayer(l); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
vl, err := cr.GetLayer(m.FSLayers[0].BlobSum.String(), false, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the vulns
|
||||
for _, f := range vl.Features {
|
||||
for _, v := range f.Vulnerabilities {
|
||||
report.Vulns = append(report.Vulns, v)
|
||||
}
|
||||
}
|
||||
|
||||
vulnsBy := func(sev string, store map[string][]clair.Vulnerability) []clair.Vulnerability {
|
||||
items, found := store[sev]
|
||||
if !found {
|
||||
items = make([]clair.Vulnerability, 0)
|
||||
store[sev] = items
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// 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"])
|
||||
|
||||
path := filepath.Join(repo, tag, "index.html")
|
||||
if err := renderTemplate(staticDir, "vulns", path, report); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderTemplate(staticDir, templateName, dest string, data interface{}) error {
|
||||
// parse & execute the template
|
||||
logrus.Debugf("executing the template %s", templateName)
|
||||
|
||||
path := filepath.Join(staticDir, dest)
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debugf("creating/opening file %s", path)
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := tmpl.ExecuteTemplate(f, templateName, data); err != nil {
|
||||
f.Close()
|
||||
return fmt.Errorf("execute template %s failed: %v", templateName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -194,7 +194,7 @@ td {
|
|||
td:last-child,
|
||||
th:last-child {
|
||||
text-align: right;
|
||||
padding-right: 0px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
td a {
|
||||
display: block;
|
||||
|
@ -205,6 +205,38 @@ tr.parent a {
|
|||
.parent a:hover {
|
||||
color: #2a2a2a;
|
||||
}
|
||||
|
||||
/*------------------------------------*\
|
||||
Loading Indicator
|
||||
\*------------------------------------*/
|
||||
.signal {
|
||||
border: 2px solid #333;
|
||||
border-radius: 15px;
|
||||
height: 15px;
|
||||
left: 50%;
|
||||
margin: -8px 0 0 -8px;
|
||||
opacity: 0;
|
||||
top: 50%;
|
||||
width: 15px;
|
||||
float: right;
|
||||
animation: pulsate 1s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
@keyframes pulsate {
|
||||
0% {
|
||||
transform: scale(.1);
|
||||
opacity: 0.0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1.2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------*\
|
||||
Footer
|
||||
\*------------------------------------*/
|
||||
|
|
|
@ -23,7 +23,7 @@ function prettyDate(time){
|
|||
function search(search_val){
|
||||
var suche = search_val.toLowerCase();
|
||||
var table = document.getElementById("directory");
|
||||
var cellNr = 3;
|
||||
var cellNr = 1;
|
||||
var ele;
|
||||
for (var r = 1; r < table.rows.length; r++){
|
||||
ele = table.rows[r].cells[cellNr].innerHTML.replace(/<[^>]+>/g,"");
|
||||
|
@ -35,6 +35,25 @@ function search(search_val){
|
|||
}
|
||||
}
|
||||
|
||||
function loadVulnerabilityCount(url){
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url);
|
||||
xhr.setRequestHeader("Accept-Encoding", "text/json")
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
var report = JSON.parse(xhr.responseText);
|
||||
var id = report.Repo + ':' + report.Tag;
|
||||
var element = document.getElementById(id);
|
||||
|
||||
if (element) {
|
||||
element.innerHTML = report.BadVulns;
|
||||
} else {
|
||||
console.log("element not found for given id ", id);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
var el = document.querySelectorAll('tr:nth-child(2)')[0].querySelectorAll('td:nth-child(2)')[0];
|
||||
if (el.textContent == 'Parent Directory'){
|
||||
|
@ -72,22 +91,26 @@ our_table.setAttribute('id', 'directory');
|
|||
var search_input = document.querySelectorAll('input[name="filter"]')[0];
|
||||
var clear_button = document.querySelectorAll('a.clear')[0];
|
||||
|
||||
if (search_input.value !== ''){
|
||||
search(search_input.value);
|
||||
if (search_input) {
|
||||
if (search_input.value !== ''){
|
||||
search(search_input.value);
|
||||
}
|
||||
|
||||
search_input.addEventListener('keyup', function(e){
|
||||
e.preventDefault();
|
||||
search(search_input.value);
|
||||
});
|
||||
|
||||
search_input.addEventListener('keypress', function(e){
|
||||
if ( e.which == 13 ) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
search_input.addEventListener('keyup', function(e){
|
||||
e.preventDefault();
|
||||
search(search_input.value);
|
||||
});
|
||||
|
||||
search_input.addEventListener('keypress', function(e){
|
||||
if ( e.which == 13 ) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
clear_button.addEventListener('click', function(e){
|
||||
search_input.value = '';
|
||||
search('');
|
||||
});
|
||||
if (clear_button) {
|
||||
clear_button.addEventListener('click', function(e){
|
||||
search_input.value = '';
|
||||
search('');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{{define "index"}}
|
||||
{{define "repositories"}}
|
||||
<!DOCTYPE html>
|
||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
|
@ -8,12 +8,12 @@
|
|||
<meta charset="utf-8">
|
||||
<base href="/" >
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>{{ .RegistryURL }}</title>
|
||||
<title>{{ .RegistryDomain }}</title>
|
||||
<link rel="icon" type="image/ico" href="/favicon.ico">
|
||||
<link rel="stylesheet" href="/css/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ .RegistryURL }}</h1>
|
||||
<h1>{{ .RegistryDomain }}</h1>
|
||||
<form>
|
||||
<input name="filter" type="search"><a class="clear">clear</a>
|
||||
</form>
|
||||
|
@ -21,32 +21,21 @@
|
|||
<div class="wrapper">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Tag</th>
|
||||
<th>Created</th>
|
||||
<th>Repository Name</th>
|
||||
<th>Pull Command</th>
|
||||
</tr>
|
||||
{{ range $key, $value := .Repos }}
|
||||
{{ range $key, $value := .Repositories }}
|
||||
<tr>
|
||||
<td valign="top">
|
||||
{{ if $value.VulnURI }}<a href="{{ $value.VulnURI }}">{{ end }}
|
||||
{{ $value.Name }}
|
||||
{{ if $value.VulnURI }}</a>{{ end }}
|
||||
</td>
|
||||
<td>
|
||||
{{ if $value.VulnURI }}<a href="{{ $value.VulnURI }}">{{ end }}
|
||||
{{ $value.Tag }}
|
||||
{{ if $value.VulnURI }}</a>{{ end }}
|
||||
</td>
|
||||
<td>
|
||||
{{ if $value.VulnURI }}<a href="{{ $value.VulnURI }}">{{ end }}
|
||||
{{ $value.CreatedDate }}
|
||||
{{ if $value.VulnURI }}</a>{{ end }}
|
||||
<a href="/repo/{{ $value.Name | urlquery }}">
|
||||
{{ $value.Name }}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td align="right" nowrap>
|
||||
{{ if $value.VulnURI }}<a href="{{ $value.VulnURI }}">{{ end }}
|
||||
<code>docker pull {{ $value.RepoURI }}</code>
|
||||
{{ if $value.VulnURI }}</a>{{ end }}
|
||||
<a href="/repo/{{ $value.Name | urlquery }}">
|
||||
<code>docker pull {{ $value.URI }}</code>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
|
@ -55,7 +44,6 @@
|
|||
|
||||
<div class="footer">
|
||||
<a href="https://twitter.com/jessfraz">@jessfraz</a>
|
||||
<p>Last Updated: {{ .LastUpdated }}</p>
|
||||
</div><!--/.footer-->
|
||||
<script src="/js/scripts.js"></script>
|
||||
<script>
|
76
server/templates/echo/tags.html
Normal file
76
server/templates/echo/tags.html
Normal file
|
@ -0,0 +1,76 @@
|
|||
{{define "tags"}}
|
||||
<!DOCTYPE html>
|
||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
||||
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<base href="/" >
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>{{ .RegistryDomain }}/{{ .Name }}</title>
|
||||
<link rel="icon" type="image/ico" href="/favicon.ico">
|
||||
<link rel="stylesheet" href="/css/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ .RegistryDomain }}/{{ .Name }}</h1>
|
||||
<div class="wrapper">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Tag</th>
|
||||
<th>Created</th>
|
||||
<th>Vulnerabilities</th>
|
||||
</tr>
|
||||
{{ range $key, $value := .Repositories }}
|
||||
<tr>
|
||||
<td valign="left" nowrap>
|
||||
<a href="/repo/{{ $value.Name | urlquery }}/{{ $value.Tag }}/vulns">
|
||||
{{ $value.Name }}
|
||||
</a>
|
||||
</td>
|
||||
<td align="right" nowrap>
|
||||
<a href="/repo/{{ $value.Name | urlquery }}/{{ $value.Tag }}/vulns">
|
||||
{{ $value.Tag }}
|
||||
</a>
|
||||
</td>
|
||||
<td align="right" nowrap>
|
||||
{{ $value.Created.Format "02 Jan, 2006 15:04:05 UTC" }}
|
||||
</td>
|
||||
<td align="right" nowrap>
|
||||
<a href="/repo/{{ $value.Name | urlquery }}/{{ $value.Tag }}/vulns" id="{{ $value.Name }}:{{ $value.Tag }}">
|
||||
<div class="signal"></div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<a href="https://twitter.com/jessfraz">@jessfraz</a>
|
||||
</div><!--/.footer-->
|
||||
<script src="/js/scripts.js"></script>
|
||||
<script type="text/javascript">
|
||||
var ajaxCalls = [
|
||||
{{ range $key, $value := .Repositories }}
|
||||
'/repo/{{ $value.Name | urlquery }}/{{ $value.Tag }}/vulns',
|
||||
{{ end }}
|
||||
];
|
||||
window.onload = function() {
|
||||
Array.prototype.forEach.call(ajaxCalls, function(url, index){
|
||||
loadVulnerabilityCount(url);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-29404280-12', 'jessfraz.com');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
337
server/web.go
Normal file
337
server/web.go
Normal file
|
@ -0,0 +1,337 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
wordwrap "github.com/mitchellh/go-wordwrap"
|
||||
|
||||
"time"
|
||||
|
||||
"net/url"
|
||||
|
||||
"github.com/jessfraz/reg/clair"
|
||||
"github.com/jessfraz/reg/registry"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
type registryController struct {
|
||||
reg *registry.Registry
|
||||
cl *clair.Clair
|
||||
}
|
||||
|
||||
// A Template hold template data
|
||||
type Template struct {
|
||||
templates *template.Template
|
||||
}
|
||||
|
||||
// A Repository holds data after a vulnerability scan of a single repo
|
||||
type Repository struct {
|
||||
Name string `json:"name"`
|
||||
Tag string `json:"tag"`
|
||||
Created time.Time `json:"created"`
|
||||
URI string `json:"uri"`
|
||||
VulnerabilityReport clair.VulnerabilityReport `json:"vulnerability"`
|
||||
}
|
||||
|
||||
// A AnalysisResult holds all vulnerabilities of a scan
|
||||
type AnalysisResult struct {
|
||||
Repositories []Repository `json:"repositories"`
|
||||
RegistryDomain string `json:"registrydomain"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Render a template
|
||||
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||
return t.templates.ExecuteTemplate(w, name, data)
|
||||
}
|
||||
|
||||
func listenAndServe(port, keyfile, certfile string, r *registry.Registry, c *clair.Clair) error {
|
||||
e := echo.New()
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
e.Use(middleware.Static("static"))
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"trim": func(s string) string {
|
||||
return wordwrap.WrapString(s, 80)
|
||||
},
|
||||
"color": func(s string) string {
|
||||
switch s = strings.ToLower(s); s {
|
||||
case "high":
|
||||
return "danger"
|
||||
case "critical":
|
||||
return "danger"
|
||||
case "defcon1":
|
||||
return "danger"
|
||||
case "medium":
|
||||
return "warning"
|
||||
case "low":
|
||||
return "info"
|
||||
case "negligible":
|
||||
return "info"
|
||||
case "unknown":
|
||||
return "default"
|
||||
default:
|
||||
return "default"
|
||||
}
|
||||
},
|
||||
}
|
||||
// precompile templates
|
||||
t := &Template{
|
||||
templates: template.Must(template.New("").Funcs(funcMap).ParseGlob("templates/echo/*.html")),
|
||||
}
|
||||
e.Renderer = t
|
||||
|
||||
rc := registryController{
|
||||
reg: r,
|
||||
cl: c,
|
||||
}
|
||||
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.Redirect(http.StatusMovedPermanently, "/repo")
|
||||
})
|
||||
|
||||
e.GET("/repo", rc.repositories)
|
||||
e.GET("/repo/:repo", rc.tags)
|
||||
e.GET("/repo/:repo/:tag", rc.tag)
|
||||
e.GET("/repo/:repo/:tag/vulns", rc.vulnerabilities)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: ":" + port,
|
||||
}
|
||||
|
||||
if keyfile != "" && certfile != "" {
|
||||
cer, err := tls.LoadX509KeyPair(certfile, keyfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srv.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cer}}
|
||||
}
|
||||
|
||||
return e.StartServer(srv)
|
||||
}
|
||||
|
||||
func (rc *registryController) repositories(c echo.Context) error {
|
||||
log.WithFields(log.Fields{
|
||||
"method": "repositories",
|
||||
"context": c,
|
||||
}).Debug("fetching repositories")
|
||||
|
||||
result := AnalysisResult{}
|
||||
result.RegistryDomain = rc.reg.Domain
|
||||
|
||||
repoList, err := rc.reg.Catalog("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting catalog failed: %v", err)
|
||||
}
|
||||
for _, repo := range repoList {
|
||||
log.WithFields(log.Fields{
|
||||
"repo": repo,
|
||||
}).Debug("fetched repo")
|
||||
repoURI := fmt.Sprintf("%s/%s", rc.reg.Domain, repo)
|
||||
r := Repository{
|
||||
Name: repo,
|
||||
URI: repoURI,
|
||||
}
|
||||
|
||||
result.Repositories = append(result.Repositories, r)
|
||||
}
|
||||
err = c.Render(http.StatusOK, "repositories", result)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Error("error during template rendering")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// e.GET("/repo/:repo/:tag", rc.tag)
|
||||
func (rc *registryController) tag(c echo.Context) error {
|
||||
repo, err := url.QueryUnescape(c.Param("repo"))
|
||||
if err != nil {
|
||||
return c.String(http.StatusNotFound, "Given repo can not be unescaped.")
|
||||
}
|
||||
if repo == "" {
|
||||
return c.String(http.StatusNotFound, "No repo given")
|
||||
}
|
||||
tag := c.Param("tag")
|
||||
if tag == "" {
|
||||
return c.String(http.StatusNotFound, "No tag given")
|
||||
}
|
||||
|
||||
return c.String(http.StatusOK, fmt.Sprintf("Repo: %s Tag: %s ", repo, tag))
|
||||
}
|
||||
|
||||
func (rc *registryController) tags(c echo.Context) error {
|
||||
repo, err := url.QueryUnescape(c.Param("repo"))
|
||||
if err != nil {
|
||||
return c.String(http.StatusNotFound, "Given repo can not be unescaped.")
|
||||
}
|
||||
if repo == "" {
|
||||
return c.String(http.StatusNotFound, "No repo given")
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"method": "tags",
|
||||
"context": c,
|
||||
"repo": repo,
|
||||
}).Info("fetching tags")
|
||||
|
||||
tags, err := rc.reg.Tags(repo)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"repo": repo,
|
||||
}).Error("getting tags failed.", repo, err)
|
||||
return c.String(http.StatusNotFound, "No Tags found")
|
||||
}
|
||||
|
||||
result := AnalysisResult{}
|
||||
result.RegistryDomain = rc.reg.Domain
|
||||
result.Name = repo
|
||||
for _, tag := range tags {
|
||||
// get the manifest
|
||||
|
||||
m1, err := r.ManifestV1(repo, tag)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"repo": repo,
|
||||
"tag": tag,
|
||||
}).Warn("getting v1 manifest failed")
|
||||
}
|
||||
|
||||
var createdDate time.Time
|
||||
for _, h := range m1.History {
|
||||
var comp v1Compatibility
|
||||
if err := json.Unmarshal([]byte(h.V1Compatibility), &comp); err != nil {
|
||||
msg := "unmarshal v1compatibility failed"
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"repo": repo,
|
||||
"tag": tag,
|
||||
}).Warn(msg)
|
||||
return c.String(http.StatusInternalServerError, msg)
|
||||
}
|
||||
createdDate = comp.Created
|
||||
break
|
||||
}
|
||||
|
||||
repoURI := fmt.Sprintf("%s/%s", r.Domain, repo)
|
||||
if tag != "latest" {
|
||||
repoURI += ":" + tag
|
||||
}
|
||||
r := Repository{
|
||||
Name: repo,
|
||||
Tag: tag,
|
||||
URI: repoURI,
|
||||
Created: createdDate,
|
||||
}
|
||||
|
||||
if rc.cl != nil {
|
||||
vuln, err := rc.cl.Vulnerabilities(rc.reg, repo, tag, m1)
|
||||
if err != nil {
|
||||
msg := "error during vulnerability scanning."
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"repo": repo,
|
||||
"tag": tag,
|
||||
}).Error(msg)
|
||||
return c.String(http.StatusInternalServerError, msg)
|
||||
}
|
||||
r.VulnerabilityReport = vuln
|
||||
}
|
||||
|
||||
result.Repositories = append(result.Repositories, r)
|
||||
}
|
||||
err = c.Render(http.StatusOK, "tags", result)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Error("error during template rendering")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (rc *registryController) vulnerabilities(c echo.Context) error {
|
||||
repo, err := url.QueryUnescape(c.Param("repo"))
|
||||
if err != nil {
|
||||
return c.String(http.StatusNotFound, "Given repo can not be unescaped.")
|
||||
}
|
||||
if repo == "" {
|
||||
return c.String(http.StatusNotFound, "No repo given")
|
||||
}
|
||||
tag := c.Param("tag")
|
||||
if tag == "" {
|
||||
return c.String(http.StatusNotFound, "No tag given")
|
||||
}
|
||||
|
||||
m1, err := r.ManifestV1(repo, tag)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"repo": repo,
|
||||
"tag": tag,
|
||||
}).Warn("getting v1 manifest failed")
|
||||
}
|
||||
|
||||
for _, h := range m1.History {
|
||||
var comp v1Compatibility
|
||||
if err := json.Unmarshal([]byte(h.V1Compatibility), &comp); err != nil {
|
||||
msg := "unmarshal v1compatibility failed"
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"repo": repo,
|
||||
"tag": tag,
|
||||
}).Warn(msg)
|
||||
return c.String(http.StatusInternalServerError, msg)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
result := clair.VulnerabilityReport{}
|
||||
|
||||
if rc.cl != nil {
|
||||
result, err = rc.cl.Vulnerabilities(rc.reg, repo, tag, m1)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"repo": repo,
|
||||
"tag": tag,
|
||||
}).Error("error during vulnerability scanning.")
|
||||
}
|
||||
}
|
||||
|
||||
outputType := outputType(c.Request())
|
||||
if outputType == "json" {
|
||||
err = c.JSON(http.StatusOK, result)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Error("error creating json response")
|
||||
}
|
||||
} else {
|
||||
err = c.Render(http.StatusOK, "vulns", result)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Error("error during template rendering")
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func outputType(r *http.Request) string {
|
||||
outputtype := "json"
|
||||
if r.Header.Get("Accept-Encoding") == "text/html" {
|
||||
outputtype = "html"
|
||||
}
|
||||
return outputtype
|
||||
}
|
19
startClair.sh
Executable file
19
startClair.sh
Executable file
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
mkdir -p $PWD/clair_config
|
||||
|
||||
docker rm -f postgres.test || true
|
||||
docker rm -f clair.test || true
|
||||
|
||||
curl -sfL https://raw.githubusercontent.com/coreos/clair/master/config.example.yaml -o $PWD/clair_config/config.yaml
|
||||
sed -i -s "s/host=localhost/host=postgres/g" $PWD/clair_config/config.yaml
|
||||
docker run -d --name postgres.test -e POSTGRES_PASSWORD="" -p 5432:5432 postgres:9.6-alpine
|
||||
sleep 5
|
||||
docker run -it \
|
||||
--name clair.test \
|
||||
--link postgres.test:postgres \
|
||||
-p 6060-6061:6060-6061 \
|
||||
-v $PWD/clair_config:/config \
|
||||
quay.io/coreos/clair-git:latest -config=/config/config.yaml
|
19
startRegistryAndCreateImages.sh
Executable file
19
startRegistryAndCreateImages.sh
Executable file
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
docker rm -f registry.test || true
|
||||
docker run -it -d --name registry.test -p 5000:5000 registry:2.6.0
|
||||
|
||||
docker pull alpine:3.5 || true
|
||||
|
||||
for repo in `seq 1 20`;
|
||||
do
|
||||
for tag in `seq 1 10`;
|
||||
do
|
||||
docker tag alpine:3.5 127.0.0.1:5000/company/alpine-${repo}:${tag}
|
||||
docker push 127.0.0.1:5000/company/alpine-${repo}:${tag}
|
||||
done
|
||||
done
|
||||
|
||||
docker logs -f registry.test
|
|
@ -5,11 +5,8 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/cli/config"
|
||||
"github.com/jessfraz/reg/clair"
|
||||
"github.com/jessfraz/reg/registry"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
@ -78,35 +75,3 @@ func GetRepoAndRef(c *cli.Context) (repo, ref string, err error) {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// NewClairLayer creates a clair layer from a docker registry image and fsLayers.
|
||||
func NewClairLayer(r *registry.Registry, image string, fsLayers []schema1.FSLayer, index int) (*clair.Layer, error) {
|
||||
var parentName string
|
||||
if index < len(fsLayers)-1 {
|
||||
parentName = fsLayers[index+1].BlobSum.String()
|
||||
}
|
||||
|
||||
// form the path
|
||||
p := strings.Join([]string{r.URL, "v2", image, "blobs", fsLayers[index].BlobSum.String()}, "/")
|
||||
|
||||
// get the token
|
||||
token, err := r.Token(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := make(map[string]string)
|
||||
if token != "" {
|
||||
h = map[string]string{
|
||||
"Authorization": fmt.Sprintf("Bearer %s", token),
|
||||
}
|
||||
}
|
||||
|
||||
return &clair.Layer{
|
||||
Name: fsLayers[index].BlobSum.String(),
|
||||
Path: p,
|
||||
ParentName: parentName,
|
||||
Format: "Docker",
|
||||
Headers: h,
|
||||
}, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue