diff --git a/server/handlers.go b/server/handlers.go
new file mode 100644
index 00000000..7473c34c
--- /dev/null
+++ b/server/handlers.go
@@ -0,0 +1,303 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ log "github.com/Sirupsen/logrus"
+ "github.com/gorilla/mux"
+
+ "time"
+
+ "github.com/jessfraz/reg/clair"
+ "github.com/jessfraz/reg/registry"
+)
+
+type registryController struct {
+ reg *registry.Registry
+ cl *clair.Clair
+}
+
+// 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"`
+}
+
+func (rc *registryController) repositoriesHandler(w http.ResponseWriter, r *http.Request) {
+ log.WithFields(log.Fields{
+ "func": "repositories",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Debug("fetching repositories")
+
+ result := AnalysisResult{}
+ result.RegistryDomain = rc.reg.Domain
+
+ repoList, err := rc.reg.Catalog("")
+ if err != nil {
+ log.WithFields(log.Fields{
+ "func": "repositories",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Errorf("getting catalog failed: %v", err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ for _, repo := range repoList {
+ repoURI := fmt.Sprintf("%s/%s", rc.reg.Domain, repo)
+ r := Repository{
+ Name: repo,
+ URI: repoURI,
+ }
+
+ result.Repositories = append(result.Repositories, r)
+ }
+
+ if err := tmpl.ExecuteTemplate(w, "repositories", result); err != nil {
+ log.WithFields(log.Fields{
+ "func": "repositories",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Errorf("template rendering failed: %v", err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+}
+
+func (rc *registryController) tagHandler(w http.ResponseWriter, r *http.Request) {
+ log.WithFields(log.Fields{
+ "func": "tag",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Debug("fetching tag")
+
+ vars := mux.Vars(r)
+ repo := vars["repo"]
+ tag := vars["tag"]
+
+ if repo == "" {
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprint(w, "Empty repo")
+ return
+ }
+
+ if tag == "" {
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprint(w, "Empty tag")
+ return
+ }
+
+ fmt.Fprintf(w, "Repo: %s Tag: %s ", repo, tag)
+ return
+}
+
+func (rc *registryController) tagsHandler(w http.ResponseWriter, r *http.Request) {
+ log.WithFields(log.Fields{
+ "func": "tags",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Debug("fetching tags")
+
+ vars := mux.Vars(r)
+ repo := vars["repo"]
+ if repo == "" {
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprint(w, "Empty repo")
+ return
+ }
+
+ tags, err := rc.reg.Tags(repo)
+ if err != nil {
+ log.WithFields(log.Fields{
+ "func": "tags",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Errorf("getting tags for %s failed: %v", repo, err)
+
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprint(w, "No tags found")
+ return
+ }
+
+ result := AnalysisResult{}
+ result.RegistryDomain = rc.reg.Domain
+ result.Name = repo
+ for _, tag := range tags {
+ // get the manifest
+ m1, err := rc.reg.ManifestV1(repo, tag)
+ if err != nil {
+ log.WithFields(log.Fields{
+ "func": "tags",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Errorf("getting v1 manifest for %s:%s failed: %v", repo, tag, err)
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprint(w, "Manifest not found")
+ return
+ }
+
+ var createdDate time.Time
+ for _, h := range m1.History {
+ var comp v1Compatibility
+
+ if err := json.Unmarshal([]byte(h.V1Compatibility), &comp); err != nil {
+ log.WithFields(log.Fields{
+ "func": "tags",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Errorf("unmarshal v1 manifest for %s:%s failed: %v", repo, tag, err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ createdDate = comp.Created
+ break
+ }
+
+ repoURI := fmt.Sprintf("%s/%s", rc.reg.Domain, repo)
+ if tag != "latest" {
+ repoURI += ":" + tag
+ }
+ rp := 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 {
+ log.WithFields(log.Fields{
+ "func": "tags",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Errorf("vulnerability scanning for %s:%s failed: %v", repo, tag, err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ rp.VulnerabilityReport = vuln
+ }
+
+ result.Repositories = append(result.Repositories, rp)
+ }
+
+ if err := tmpl.ExecuteTemplate(w, "tags", result); err != nil {
+ log.WithFields(log.Fields{
+ "func": "tags",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Errorf("template rendering failed: %v", err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ return
+}
+
+func (rc *registryController) vulnerabilitiesHandler(w http.ResponseWriter, r *http.Request) {
+ log.WithFields(log.Fields{
+ "func": "vulnerabilities",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Debug("fetching vulnerabilities")
+
+ vars := mux.Vars(r)
+ repo := vars["repo"]
+ tag := vars["tag"]
+
+ if repo == "" {
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprint(w, "Empty repo")
+ return
+ }
+
+ if tag == "" {
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprint(w, "Empty tag")
+ return
+ }
+
+ m1, err := rc.reg.ManifestV1(repo, tag)
+ if err != nil {
+ log.WithFields(log.Fields{
+ "func": "vulnerabilities",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Errorf("getting v1 manifest for %s:%s failed: %v", repo, tag, err)
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprint(w, "Manifest not found")
+ return
+ }
+
+ for _, h := range m1.History {
+ var comp v1Compatibility
+
+ if err := json.Unmarshal([]byte(h.V1Compatibility), &comp); err != nil {
+ log.WithFields(log.Fields{
+ "func": "vulnerabilities",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Errorf("unmarshal v1 manifest for %s:%s failed: %v", repo, tag, err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ 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{
+ "func": "vulnerabilities",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Errorf("vulnerability scanning for %s:%s failed: %v", repo, tag, err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ }
+
+ if r.Header.Get("Accept-Encoding") == "application/json" {
+ js, err := json.Marshal(result)
+ if err != nil {
+ log.WithFields(log.Fields{
+ "func": "vulnerabilities",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Errorf("json marshal failed: %v", err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(js)
+ return
+ }
+
+ if err := tmpl.ExecuteTemplate(w, "vulns", result); err != nil {
+ log.WithFields(log.Fields{
+ "func": "vulnerabilities",
+ "URL": r.URL,
+ "method": r.Method,
+ }).Errorf("template rendering failed: %v", err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ return
+}
diff --git a/server/server.go b/server/server.go
index c04258b6..0b2c819a 100644
--- a/server/server.go
+++ b/server/server.go
@@ -2,15 +2,21 @@ package main
import (
"fmt"
+ "html/template"
+ "net/http"
"os"
+ "path/filepath"
+ "strings"
"sync"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/manifest/schema1"
+ "github.com/gorilla/mux"
"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"
)
@@ -26,6 +32,7 @@ var (
wg sync.WaitGroup
r *registry.Registry
cl *clair.Clair
+ tmpl *template.Template
)
// preload initializes any global options and configuration
@@ -114,12 +121,6 @@ func main() {
}
}
- // 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"))
@@ -127,6 +128,19 @@ func main() {
logrus.Warnf("creation of clair failed: %v", err)
}
}
+
+ // create the initial index
+ logrus.Info("creating initial static index")
+ if err := analyseRepositories(r, cl, 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)
+ }
+
ticker := time.NewTicker(dur)
go func() {
@@ -149,11 +163,87 @@ func main() {
}
}()
- port := c.String("port")
- keyfile := c.String("key")
- certfile := c.String("cert")
+ // 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")
+
+ // make sure all the templates exist
+ 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, "repositories.html")
+ if _, err := os.Stat(layout); os.IsNotExist(err) {
+ logrus.Fatalf("Template %s not found", layout)
+ }
+ tags := filepath.Join(templateDir, "tags.html")
+ if _, err := os.Stat(tags); os.IsNotExist(err) {
+ logrus.Fatalf("Template %s not found", tags)
+ }
+
+ 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"
+ }
+ },
+ }
+
+ tmpl = template.Must(template.New("").Funcs(funcMap).ParseGlob(templateDir + "/*.html"))
+
+ rc := registryController{
+ reg: r,
+ cl: cl,
+ }
+
+ // create mux server
+ mux := mux.NewRouter()
+
+ // static files handler
+ staticHandler := http.FileServer(http.Dir(staticDir))
+ mux.HandleFunc("/", rc.tagsHandler)
+ mux.Handle("/static", staticHandler)
+ mux.HandleFunc("/repo/{repo}", rc.tagsHandler)
+ mux.HandleFunc("/repo/{repo}/{tag}", rc.tagHandler)
+ mux.HandleFunc("/repo/{repo}/{tag}/vulns", rc.vulnerabilitiesHandler)
+
+ // 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())
+ }
- logrus.Fatal(listenAndServe(port, keyfile, certfile, r, cl))
return nil
}
diff --git a/server/templates/echo/repositories.html b/server/templates/repositories.html
similarity index 91%
rename from server/templates/echo/repositories.html
rename to server/templates/repositories.html
index c0657d4f..e9fa4684 100644
--- a/server/templates/echo/repositories.html
+++ b/server/templates/repositories.html
@@ -9,8 +9,8 @@