From a6ba091b4aa47d839da04503cdf9e4a5289f52b3 Mon Sep 17 00:00:00 2001 From: Jess Frazelle Date: Mon, 24 Apr 2017 13:52:31 -0400 Subject: [PATCH] cleanup and remove echo dependency Signed-off-by: Jess Frazelle --- server/handlers.go | 303 ++++++++++++++++ server/server.go | 110 +++++- server/templates/{echo => }/repositories.html | 8 +- server/templates/{echo => }/tags.html | 6 +- server/templates/{echo => }/vulns.html | 4 +- server/web.go | 337 ------------------ 6 files changed, 412 insertions(+), 356 deletions(-) create mode 100644 server/handlers.go rename server/templates/{echo => }/repositories.html (91%) rename server/templates/{echo => }/tags.html (93%) rename server/templates/{echo => }/vulns.html (95%) delete mode 100644 server/web.go 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 @@ {{ .RegistryDomain }} - - + +

{{ .RegistryDomain }}

@@ -45,7 +45,7 @@ - + -{{end}} \ No newline at end of file +{{end}} diff --git a/server/templates/echo/tags.html b/server/templates/tags.html similarity index 93% rename from server/templates/echo/tags.html rename to server/templates/tags.html index 6e60250c..faeb3b51 100644 --- a/server/templates/echo/tags.html +++ b/server/templates/tags.html @@ -9,8 +9,8 @@ {{ .RegistryDomain }}/{{ .Name }} - - + +

{{ .RegistryDomain }}/{{ .Name }}

@@ -50,7 +50,7 @@ - +