cleanup and remove echo dependency

Signed-off-by: Jess Frazelle <acidburn@google.com>
This commit is contained in:
Jess Frazelle 2017-04-24 13:52:31 -04:00
parent 9fdbf69688
commit a6ba091b4a
No known key found for this signature in database
GPG key ID: 18F3685C0022BFF3
6 changed files with 412 additions and 356 deletions

303
server/handlers.go Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -9,8 +9,8 @@
<base href="/" >
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{ .RegistryDomain }}</title>
<link rel="icon" type="image/ico" href="/favicon.ico">
<link rel="stylesheet" href="/css/styles.css" />
<link rel="icon" type="image/ico" href="/static/favicon.ico">
<link rel="stylesheet" href="/static/css/styles.css" />
</head>
<body>
<h1>{{ .RegistryDomain }}</h1>
@ -45,7 +45,7 @@
<div class="footer">
<a href="https://twitter.com/jessfraz">@jessfraz</a>
</div><!--/.footer-->
<script src="/js/scripts.js"></script>
<script src="/static/js/scripts.js"></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),
@ -56,4 +56,4 @@ ga('send', 'pageview');
</script>
</body>
</html>
{{end}}
{{end}}

View file

@ -9,8 +9,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" />
<link rel="icon" type="image/ico" href="/static/favicon.ico">
<link rel="stylesheet" href="/static/css/styles.css" />
</head>
<body>
<h1>{{ .RegistryDomain }}/{{ .Name }}</h1>
@ -50,7 +50,7 @@
<div class="footer">
<a href="https://twitter.com/jessfraz">@jessfraz</a>
</div><!--/.footer-->
<script src="/js/scripts.js"></script>
<script src="/static/js/scripts.js"></script>
<script type="text/javascript">
var ajaxCalls = [
{{ range $key, $value := .Repositories }}

View file

@ -9,8 +9,8 @@
<base href="/" >
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{ .RegistryURL }}/{{ .Repo }}:{{ .Tag }} Vulnerability Report</title>
<link rel="icon" type="image/ico" href="/favicon.ico">
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<link rel="icon" type="image/ico" href="/static/favicon.ico">
<link rel="stylesheet" href="/static/css/styles.css" />
</head>
<body>
<div class="container">

View file

@ -1,337 +0,0 @@
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
}