reg/server/web.go

279 lines
6.6 KiB
Go

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
}
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"`
}
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
// e.Use(middleware.Logger())
rc := registryController{
reg: r,
cl: c,
}
// e.GET("/repo", nil)
// e.GET("/repo/:repo", nil)
// e.GET("/repo/:repo/:tag", nil)
// e.GET("/repo/:repo/:tag/vulns", nil)
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 {
log.WithFields(log.Fields{
"error": err,
"repo": repo,
"tag": tag,
}).Warn("unmarshal v1compatibility failed")
return c.String(http.StatusInternalServerError, "unmarshal v1compatibility failed")
}
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)
// vuln, err := getVulnerabilities(rc.reg, rc.clairUrl, repo, tag, m1, true) // FIXME debug must be configurable
if err != nil {
log.WithFields(log.Fields{
"error": err,
"repo": repo,
"tag": tag,
}).Error("error during vulnerability scanning.")
}
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")
}
return nil
}