2016-08-13 01:23:13 -04:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2016-09-05 22:44:19 -04:00
|
|
|
"encoding/json"
|
2016-09-20 21:16:34 -04:00
|
|
|
"errors"
|
2016-08-13 01:23:13 -04:00
|
|
|
"fmt"
|
2016-12-20 00:13:34 -05:00
|
|
|
"io/ioutil"
|
2016-08-13 01:23:13 -04:00
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"text/tabwriter"
|
|
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
2017-08-14 06:34:56 -04:00
|
|
|
"github.com/docker/distribution"
|
|
|
|
"github.com/docker/distribution/manifest/schema2"
|
2017-03-05 01:10:04 -05:00
|
|
|
"github.com/docker/docker/api/types"
|
2017-02-12 21:45:47 -05:00
|
|
|
"github.com/jessfraz/reg/clair"
|
2016-09-30 14:09:10 -04:00
|
|
|
"github.com/jessfraz/reg/registry"
|
2017-03-05 01:10:04 -05:00
|
|
|
"github.com/jessfraz/reg/utils"
|
2017-06-05 17:46:12 -04:00
|
|
|
"github.com/jessfraz/reg/version"
|
2017-03-05 01:10:04 -05:00
|
|
|
digest "github.com/opencontainers/go-digest"
|
2016-09-05 22:44:19 -04:00
|
|
|
"github.com/urfave/cli"
|
2016-08-13 01:23:13 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
dockerConfigPath = ".docker/config.json"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2016-09-05 22:44:19 -04:00
|
|
|
auth types.AuthConfig
|
|
|
|
r *registry.Registry
|
2016-08-13 01:23:13 -04:00
|
|
|
)
|
|
|
|
|
2016-09-05 22:44:19 -04:00
|
|
|
// preload initializes any global options and configuration
|
|
|
|
// before the main or sub commands are run.
|
|
|
|
func preload(c *cli.Context) (err error) {
|
|
|
|
if c.GlobalBool("debug") {
|
|
|
|
logrus.SetLevel(logrus.DebugLevel)
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
|
|
|
|
2016-09-05 22:44:19 -04:00
|
|
|
if len(c.Args()) > 0 {
|
2016-09-20 21:16:34 -04:00
|
|
|
if c.Args()[0] != "help" {
|
2017-03-05 01:10:04 -05:00
|
|
|
auth, err = utils.GetAuthConfig(c)
|
2016-09-20 21:16:34 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-04-04 10:12:36 -04:00
|
|
|
// Prevent non-ssl unless explicitly forced
|
|
|
|
if !c.GlobalBool("force-non-ssl") && strings.HasPrefix(auth.ServerAddress, "http:") {
|
|
|
|
return fmt.Errorf("Attempt to use insecure protocol! Use non-ssl option to force")
|
|
|
|
}
|
|
|
|
|
2016-09-20 21:16:34 -04:00
|
|
|
// create the registry client
|
2017-02-13 14:42:33 -05:00
|
|
|
if c.GlobalBool("insecure") {
|
|
|
|
r, err = registry.NewInsecure(auth, c.GlobalBool("debug"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
r, err = registry.New(auth, c.GlobalBool("debug"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-09-20 21:16:34 -04:00
|
|
|
}
|
2016-09-05 22:44:19 -04:00
|
|
|
}
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
|
|
|
|
2016-09-05 22:44:19 -04:00
|
|
|
return nil
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
2016-09-05 22:44:19 -04:00
|
|
|
app := cli.NewApp()
|
|
|
|
app.Name = "reg"
|
2017-06-05 17:46:12 -04:00
|
|
|
app.Version = fmt.Sprintf("version %s, build %s", version.VERSION, version.GITCOMMIT)
|
2016-09-30 14:09:10 -04:00
|
|
|
app.Author = "@jessfraz"
|
2016-09-05 22:44:19 -04:00
|
|
|
app.Email = "no-reply@butts.com"
|
|
|
|
app.Usage = "Docker registry v2 client."
|
|
|
|
app.Before = preload
|
|
|
|
app.Flags = []cli.Flag{
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "debug, d",
|
|
|
|
Usage: "run in debug mode",
|
|
|
|
},
|
2017-02-13 04:32:36 -05:00
|
|
|
cli.BoolFlag{
|
2017-02-13 14:42:33 -05:00
|
|
|
Name: "insecure, k",
|
2017-02-13 04:32:36 -05:00
|
|
|
Usage: "do not verify tls certificates",
|
|
|
|
},
|
2017-04-04 10:12:36 -04:00
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "force-non-ssl, f",
|
|
|
|
Usage: "force allow use of non-ssl",
|
|
|
|
},
|
2016-09-05 22:44:19 -04:00
|
|
|
cli.StringFlag{
|
|
|
|
Name: "username, u",
|
|
|
|
Usage: "username for the registry",
|
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "password, p",
|
|
|
|
Usage: "password for the registry",
|
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "registry, r",
|
2017-03-17 12:37:24 -04:00
|
|
|
Usage: "URL to the private registry (ex. r.j3ss.co)",
|
2016-09-05 22:44:19 -04:00
|
|
|
},
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
2016-09-05 22:44:19 -04:00
|
|
|
app.Commands = []cli.Command{
|
2016-09-20 21:16:34 -04:00
|
|
|
{
|
2016-10-13 11:32:00 -04:00
|
|
|
Name: "delete",
|
|
|
|
Aliases: []string{"rm"},
|
|
|
|
Usage: "delete a specific reference of a repository",
|
2016-09-20 21:16:34 -04:00
|
|
|
Action: func(c *cli.Context) error {
|
2017-03-05 01:10:04 -05:00
|
|
|
repo, ref, err := utils.GetRepoAndRef(c)
|
2016-09-20 21:16:34 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := r.Delete(repo, ref); err != nil {
|
2016-10-13 11:32:00 -04:00
|
|
|
return fmt.Errorf("Delete %s@%s failed: %v", repo, ref, err)
|
2016-09-20 21:16:34 -04:00
|
|
|
}
|
2016-09-20 21:59:56 -04:00
|
|
|
fmt.Printf("Deleted %s@%s\n", repo, ref)
|
2016-09-20 21:16:34 -04:00
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
2016-09-05 22:44:19 -04:00
|
|
|
{
|
|
|
|
Name: "list",
|
|
|
|
Aliases: []string{"ls"},
|
|
|
|
Usage: "list all repositories",
|
|
|
|
Action: func(c *cli.Context) error {
|
|
|
|
// get the repositories via catalog
|
2016-12-20 01:36:05 -05:00
|
|
|
repos, err := r.Catalog("")
|
2016-09-05 22:44:19 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("Repositories for %s\n", auth.ServerAddress)
|
|
|
|
|
|
|
|
// setup the tab writer
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
|
|
|
|
|
|
|
|
// print header
|
|
|
|
fmt.Fprintln(w, "REPO\tTAGS")
|
|
|
|
|
|
|
|
for _, repo := range repos {
|
|
|
|
// get the tags and print to stdout
|
|
|
|
tags, err := r.Tags(repo)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "%s\t%s\n", repo, strings.Join(tags, ", "))
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Flush()
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2016-09-20 21:16:34 -04:00
|
|
|
Name: "manifest",
|
|
|
|
Usage: "get the json manifest for the specific reference of a repository",
|
2017-03-05 02:39:09 -05:00
|
|
|
Flags: []cli.Flag{
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "v1",
|
|
|
|
Usage: "force get v1 manifest",
|
|
|
|
},
|
|
|
|
},
|
2016-09-05 22:44:19 -04:00
|
|
|
Action: func(c *cli.Context) error {
|
2017-03-05 01:10:04 -05:00
|
|
|
repo, ref, err := utils.GetRepoAndRef(c)
|
2016-09-20 21:16:34 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2016-09-05 22:44:19 -04:00
|
|
|
}
|
|
|
|
|
2017-03-05 02:39:09 -05:00
|
|
|
var manifest interface{}
|
|
|
|
if c.Bool("v1") {
|
|
|
|
manifest, err = r.ManifestV1(repo, ref)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
manifest, err = r.Manifest(repo, ref)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-09-20 21:16:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
b, err := json.MarshalIndent(manifest, " ", " ")
|
2016-09-05 22:44:19 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-09-20 21:16:34 -04:00
|
|
|
fmt.Println(string(b))
|
2016-09-05 22:44:19 -04:00
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
2017-02-12 22:31:34 -05:00
|
|
|
{
|
|
|
|
Name: "tags",
|
|
|
|
Usage: "get the tags for a repository",
|
|
|
|
Action: func(c *cli.Context) error {
|
|
|
|
if len(c.Args()) < 1 {
|
|
|
|
return fmt.Errorf("pass the name of the repository")
|
|
|
|
}
|
|
|
|
|
|
|
|
tags, err := r.Tags(c.Args()[0])
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// print the tags
|
|
|
|
fmt.Println(strings.Join(tags, "\n"))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "download",
|
|
|
|
Aliases: []string{"layer"},
|
|
|
|
Usage: "download a layer for the specific reference of a repository",
|
|
|
|
Flags: []cli.Flag{
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "output, o",
|
|
|
|
Usage: "output file, default to stdout",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Action: func(c *cli.Context) error {
|
2017-03-05 01:10:04 -05:00
|
|
|
repo, ref, err := utils.GetRepoAndRef(c)
|
2017-02-12 22:31:34 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
layer, err := r.DownloadLayer(repo, digest.Digest(ref))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer layer.Close()
|
|
|
|
|
|
|
|
b, err := ioutil.ReadAll(layer)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.String("output") != "" {
|
|
|
|
return ioutil.WriteFile(c.String("output"), b, 0644)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprint(os.Stdout, string(b))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
2017-02-12 21:45:47 -05:00
|
|
|
{
|
|
|
|
Name: "vulns",
|
|
|
|
Usage: "get a vulnerability report for the image from CoreOS Clair",
|
|
|
|
Flags: []cli.Flag{
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "clair",
|
|
|
|
Usage: "url to clair instance",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Action: func(c *cli.Context) error {
|
|
|
|
if c.String("clair") == "" {
|
|
|
|
return errors.New("clair url cannot be empty, pass --clair")
|
|
|
|
}
|
|
|
|
|
2017-03-05 01:10:04 -05:00
|
|
|
repo, ref, err := utils.GetRepoAndRef(c)
|
2017-02-12 21:45:47 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-04-06 06:41:43 -04:00
|
|
|
// FIXME use clair.Vulnerabilities
|
|
|
|
|
2017-02-12 21:45:47 -05:00
|
|
|
// get the manifest
|
2017-08-14 06:34:56 -04:00
|
|
|
m2, err := r.Manifest(repo, ref)
|
|
|
|
|
2017-02-12 21:45:47 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-08-14 06:34:56 -04:00
|
|
|
mf, ok := m2.(schema2.Manifest)
|
|
|
|
|
|
|
|
var filteredLayers []distribution.Descriptor
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
|
|
for _, layer := range mf.Layers {
|
|
|
|
if !clair.IsEmptyLayer(layer.Digest) {
|
|
|
|
filteredLayers = append(filteredLayers, layer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
fmt.Println("Couldn't retrieve manifest V2, fallback to v1")
|
|
|
|
m, err := r.ManifestV1(repo, ref)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, layer := range m.FSLayers {
|
|
|
|
if !clair.IsEmptyLayer(layer.BlobSum) {
|
|
|
|
|
|
|
|
newLayer := distribution.Descriptor{
|
|
|
|
Digest: layer.BlobSum,
|
|
|
|
}
|
|
|
|
|
|
|
|
filteredLayers = append(filteredLayers, newLayer)
|
|
|
|
}
|
2017-02-12 21:45:47 -05:00
|
|
|
}
|
2017-08-14 06:34:56 -04:00
|
|
|
|
2017-02-12 21:45:47 -05:00
|
|
|
}
|
2017-08-14 06:34:56 -04:00
|
|
|
|
|
|
|
if len(filteredLayers) == 0 {
|
2017-02-12 21:45:47 -05:00
|
|
|
fmt.Printf("No need to analyse image %s:%s as there is no non-emtpy layer", repo, ref)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// initialize clair
|
|
|
|
cr, err := clair.New(c.String("clair"), c.GlobalBool("debug"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-08-14 06:34:56 -04:00
|
|
|
for i := len(filteredLayers) - 1; i >= 0; i-- {
|
2017-02-12 21:45:47 -05:00
|
|
|
// form the clair layer
|
2017-08-14 06:34:56 -04:00
|
|
|
l, err := cr.NewClairLayerV2(r, repo, filteredLayers, i)
|
2017-02-12 21:45:47 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// post the layer
|
|
|
|
if _, err := cr.PostLayer(l); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-14 06:34:56 -04:00
|
|
|
vl, err := cr.GetLayer(filteredLayers[0].Digest.String(), false, true)
|
2017-02-12 21:45:47 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the vulns
|
|
|
|
var vulns []clair.Vulnerability
|
|
|
|
for _, f := range vl.Features {
|
|
|
|
for _, v := range f.Vulnerabilities {
|
|
|
|
vulns = append(vulns, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Printf("Found %d vulnerabilities \n", len(vulns))
|
|
|
|
|
|
|
|
vulnsBy := func(sev string, store map[string][]clair.Vulnerability) []clair.Vulnerability {
|
|
|
|
items, found := store[sev]
|
|
|
|
if !found {
|
|
|
|
items = make([]clair.Vulnerability, 0)
|
|
|
|
store[sev] = items
|
|
|
|
}
|
|
|
|
return items
|
|
|
|
}
|
|
|
|
|
|
|
|
// group by severity
|
|
|
|
store := make(map[string][]clair.Vulnerability)
|
|
|
|
for _, v := range vulns {
|
|
|
|
sevRow := vulnsBy(v.Severity, store)
|
|
|
|
store[v.Severity] = append(sevRow, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// iterate over the priorities list
|
|
|
|
iteratePriorities := func(f func(sev string)) {
|
|
|
|
for _, sev := range clair.Priorities {
|
|
|
|
if len(store[sev]) != 0 {
|
|
|
|
f(sev)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
iteratePriorities(func(sev string) {
|
|
|
|
for _, v := range store[sev] {
|
|
|
|
fmt.Printf("%s: [%s] \n%s\n%s\n", v.Name, v.Severity, v.Description, v.Link)
|
|
|
|
fmt.Println("-----------------------------------------")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
iteratePriorities(func(sev string) {
|
|
|
|
fmt.Printf("%s: %d\n", sev, len(store[sev]))
|
|
|
|
})
|
|
|
|
|
|
|
|
// return an error if there are more than 10 bad vulns
|
|
|
|
lenBadVulns := len(store["High"]) + len(store["Critical"]) + len(store["Defcon1"])
|
|
|
|
if lenBadVulns > 10 {
|
|
|
|
logrus.Fatalf("%d bad vunerabilities found", lenBadVulns)
|
|
|
|
}
|
|
|
|
|
2016-09-05 22:44:19 -04:00
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
|
|
|
|
2017-06-05 14:20:42 -04:00
|
|
|
if err := app.Run(os.Args); err != nil {
|
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
2016-09-05 22:44:19 -04:00
|
|
|
}
|