package main import ( "encoding/json" "fmt" "net/http" "net/url" "os" "path/filepath" "strings" "time" "github.com/Sirupsen/logrus" "github.com/gorilla/mux" "github.com/jessfraz/reg/clair" "github.com/jessfraz/reg/registry" ) type registryController struct { reg *registry.Registry cl *clair.Clair } type v1Compatibility struct { ID string `json:"id"` Created time.Time `json:"created"` } // 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"` LastUpdated string `json:"lastUpdated"` } func (rc *registryController) repositories(staticDir string) error { updating = true logrus.Info("fetching catalog") result := AnalysisResult{ RegistryDomain: rc.reg.Domain, LastUpdated: time.Now().Local().Format(time.RFC1123), } repoList, err := r.Catalog("") if err != nil { return fmt.Errorf("getting catalog failed: %v", err) } 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) } // parse & execute the template logrus.Info("executing the template repositories") path := filepath.Join(staticDir, "index.html") 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, "repositories", result); err != nil { f.Close() return fmt.Errorf("execute template repositories failed: %v", err) } updating = false return nil } func (rc *registryController) tagsHandler(w http.ResponseWriter, r *http.Request) { logrus.WithFields(logrus.Fields{ "func": "tags", "URL": r.URL, "method": r.Method, }).Info("fetching tags") vars := mux.Vars(r) repo, err := url.QueryUnescape(vars["repo"]) if err != nil || repo == "" { w.WriteHeader(http.StatusNotFound) fmt.Fprint(w, "Empty repo") return } tags, err := rc.reg.Tags(repo) if err != nil { logrus.WithFields(logrus.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 { logrus.WithFields(logrus.Fields{ "func": "tags", "URL": r.URL, "method": r.Method, "repo": repo, "tag": tag, }).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 { logrus.WithFields(logrus.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, } result.Repositories = append(result.Repositories, rp) } if err := tmpl.ExecuteTemplate(w, "tags", result); err != nil { logrus.WithFields(logrus.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) { logrus.WithFields(logrus.Fields{ "func": "vulnerabilities", "URL": r.URL, "method": r.Method, }).Info("fetching vulnerabilities") vars := mux.Vars(r) repo, err := url.QueryUnescape(vars["repo"]) tag := vars["tag"] if err != nil || 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 { logrus.WithFields(logrus.Fields{ "func": "vulnerabilities", "URL": r.URL, "method": r.Method, "repo": repo, "tag": tag, }).Errorf("getting v1 manifest for %s:%s failed: %v", repo, tag, err) w.WriteHeader(http.StatusNotFound) fmt.Fprint(w, "Manifest not found") return } result := clair.VulnerabilityReport{} if rc.cl != nil { result, err = rc.cl.Vulnerabilities(rc.reg, repo, tag, m1) if err != nil { logrus.WithFields(logrus.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 strings.HasSuffix(r.URL.String(), ".json") { js, err := json.Marshal(result) if err != nil { logrus.WithFields(logrus.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 { logrus.WithFields(logrus.Fields{ "func": "vulnerabilities", "URL": r.URL, "method": r.Method, }).Errorf("template rendering failed: %v", err) w.WriteHeader(http.StatusInternalServerError) return } return }