From 760d12bb3a81d2b9a3671359f8e712bc2ea92eb1 Mon Sep 17 00:00:00 2001 From: Paul Weil Date: Mon, 11 Apr 2016 14:38:42 -0400 Subject: [PATCH] factor out image serving --- pkg/imageserver/types.go | 37 ++++++++++++++ pkg/imageserver/webdav.go | 83 ++++++++++++++++++++++++++++++++ pkg/inspector/image-inspector.go | 80 +++++++++--------------------- 3 files changed, 144 insertions(+), 56 deletions(-) create mode 100644 pkg/imageserver/types.go create mode 100644 pkg/imageserver/webdav.go diff --git a/pkg/imageserver/types.go b/pkg/imageserver/types.go new file mode 100644 index 0000000..ebbe747 --- /dev/null +++ b/pkg/imageserver/types.go @@ -0,0 +1,37 @@ +package imageserver + +import ( + docker "github.com/fsouza/go-dockerclient" +) + +// ImageServer abstracts the serving of image information. +type ImageServer interface { + // ServeImage Serves the image + ServeImage(imageMetadata *docker.Image) error +} + +// APIVersions holds a slice of supported API versions. +type APIVersions struct { + // Versions is the supported API versions + Versions []string `json:"versions"` +} + +// ImageServerOptions is used to configure an image server. +type ImageServerOptions struct { + // ServePath is the root path/port of serving. ex 0.0.0.0:8080 + ServePath string + // HealthzURL is the relative url of the health check. ex /healthz + HealthzURL string + // APIURL is the relative url where the api will be served. ex /api + APIURL string + // APIVersions are the supported API versions. + APIVersions APIVersions + // MetadataURL is the relative url of the metadata content. ex /api/v1/metadata + MetadataURL string + // ContentURL is the relative url of the content. ex /api/v1/content/ + ContentURL string + // ImageServeURL is the location that the image is being served from. + // NOTE: if the image server supports a chroot the server implementation will perform + // the chroot based on this URL. + ImageServeURL string +} diff --git a/pkg/imageserver/webdav.go b/pkg/imageserver/webdav.go new file mode 100644 index 0000000..3dc7130 --- /dev/null +++ b/pkg/imageserver/webdav.go @@ -0,0 +1,83 @@ +package imageserver + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "syscall" + + "golang.org/x/net/webdav" + + docker "github.com/fsouza/go-dockerclient" +) + +const ( + // CHROOT_SERVE_PATH is the path to server if we are performing a chroot + // this probably does not belong here. + CHROOT_SERVE_PATH = "/" +) + +// webdavImageServer implements ImageServer. +type webdavImageServer struct { + opts ImageServerOptions + chroot bool +} + +// ensures this always implements the interface or fail compilation. +var _ ImageServer = &webdavImageServer{} + +// NewWebdavImageServer creates a new webdav image server. +func NewWebdavImageServer(opts ImageServerOptions, chroot bool) ImageServer { + return &webdavImageServer{ + opts: opts, + chroot: chroot, + } +} + +// ServeImage Serves the image. +func (s *webdavImageServer) ServeImage(imageMetadata *docker.Image) error { + servePath := s.opts.ImageServeURL + if s.chroot { + if err := syscall.Chroot(s.opts.ImageServeURL); err != nil { + return fmt.Errorf("Unable to chroot into %s: %v\n", s.opts.ImageServeURL, err) + } + servePath = CHROOT_SERVE_PATH + } else { + log.Printf("!!!WARNING!!! It is insecure to serve the image content without changing") + log.Printf("root (--chroot). Absolute-path symlinks in the image can lead to disclose") + log.Printf("information of the hosting system.") + } + + log.Printf("Serving image content %s on webdav://%s%s", s.opts.ImageServeURL, s.opts.ServePath, s.opts.ContentURL) + + http.HandleFunc(s.opts.HealthzURL, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("ok\n")) + }) + + http.HandleFunc(s.opts.APIURL, func(w http.ResponseWriter, r *http.Request) { + body, err := json.MarshalIndent(s.opts.APIVersions, "", " ") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Write(body) + }) + + http.HandleFunc(s.opts.MetadataURL, func(w http.ResponseWriter, r *http.Request) { + body, err := json.MarshalIndent(imageMetadata, "", " ") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Write(body) + }) + + http.Handle(s.opts.ContentURL, &webdav.Handler{ + Prefix: s.opts.ContentURL, + FileSystem: webdav.Dir(servePath), + LockSystem: webdav.NewMemLS(), + }) + + return http.ListenAndServe(s.opts.ServePath, nil) +} diff --git a/pkg/inspector/image-inspector.go b/pkg/inspector/image-inspector.go index a3e5bf7..999c372 100644 --- a/pkg/inspector/image-inspector.go +++ b/pkg/inspector/image-inspector.go @@ -1,32 +1,25 @@ package inspector import ( - "encoding/json" "fmt" "io" "io/ioutil" "log" "math" "math/big" - "net/http" "os" "path" "strings" - "syscall" "archive/tar" "crypto/rand" docker "github.com/fsouza/go-dockerclient" - "golang.org/x/net/webdav" iicmd "github.com/simon3z/image-inspector/pkg/cmd" + apiserver "github.com/simon3z/image-inspector/pkg/imageserver" ) -type APIVersions struct { - Versions []string `json:"versions"` -} - const ( VERSION_TAG = "v1" DOCKER_TAR_PREFIX = "rootfs/" @@ -47,11 +40,30 @@ type ImageInspector interface { // defaultImageInspector is the default implementation of ImageInspector. type defaultImageInspector struct { opts iicmd.ImageInspectorOptions + // an optional image server that will server content for inspection. + imageServer apiserver.ImageServer } // NewDefaultImageInspector provides a new default inspector. func NewDefaultImageInspector(opts iicmd.ImageInspectorOptions) ImageInspector { - return &defaultImageInspector{opts} + inspector := &defaultImageInspector{ + opts: opts, + } + + // if serving then set up an image server + if len(opts.Serve) > 0 { + imageServerOpts := apiserver.ImageServerOptions{ + ServePath: opts.Serve, + HealthzURL: HEALTHZ_URL_PATH, + APIURL: API_URL_PREFIX, + APIVersions: apiserver.APIVersions{Versions: []string{VERSION_TAG}}, + MetadataURL: METADATA_URL_PATH, + ContentURL: CONTENT_URL_PREFIX, + ImageServeURL: opts.DstPath, + } + inspector.imageServer = apiserver.NewWebdavImageServer(imageServerOpts, opts.Chroot) + } + return inspector } // Inspect inspects and serves the image based on the ImageInspectorOptions. @@ -93,7 +105,7 @@ func (i *defaultImageInspector) Inspect() error { container, err := client.CreateContainer(docker.CreateContainerOptions{ Name: randomName, Config: &docker.Config{ - Image: i.opts.Image, + Image: i.opts.Image, // For security purpose we don't define any entrypoint and command Entrypoint: []string{""}, Cmd: []string{""}, @@ -145,52 +157,8 @@ func (i *defaultImageInspector) Inspect() error { ID: container.ID, }) - supportedVersions := APIVersions{Versions: []string{VERSION_TAG}} - - if len(i.opts.Serve) > 0 { - servePath := i.opts.DstPath - if i.opts.Chroot { - if err := syscall.Chroot(i.opts.DstPath); err != nil { - return fmt.Errorf("Unable to chroot into %s: %v\n", i.opts.DstPath, err) - } - servePath = CHROOT_SERVE_PATH - } else { - log.Printf("!!!WARNING!!! It is insecure to serve the image content without changing") - log.Printf("root (--chroot). Absolute-path symlinks in the image can lead to disclose") - log.Printf("information of the hosting system.") - } - - log.Printf("Serving image content %s on webdav://%s%s", i.opts.DstPath, i.opts.Serve, CONTENT_URL_PREFIX) - - http.HandleFunc(HEALTHZ_URL_PATH, func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("ok\n")) - }) - - http.HandleFunc(API_URL_PREFIX, func(w http.ResponseWriter, r *http.Request) { - body, err := json.MarshalIndent(supportedVersions, "", " ") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Write(body) - }) - - http.HandleFunc(METADATA_URL_PATH, func(w http.ResponseWriter, r *http.Request) { - body, err := json.MarshalIndent(imageMetadata, "", " ") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Write(body) - }) - - http.Handle(CONTENT_URL_PREFIX, &webdav.Handler{ - Prefix: CONTENT_URL_PREFIX, - FileSystem: webdav.Dir(servePath), - LockSystem: webdav.NewMemLS(), - }) - - return http.ListenAndServe(i.opts.Serve, nil) + if i.imageServer != nil { + return i.imageServer.ServeImage(imageMetadata) } return nil }