Skip to content

Commit

Permalink
factor out image serving
Browse files Browse the repository at this point in the history
  • Loading branch information
pweil- committed Apr 11, 2016
1 parent e24e4ce commit 760d12b
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 56 deletions.
37 changes: 37 additions & 0 deletions pkg/imageserver/types.go
Original file line number Diff line number Diff line change
@@ -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
}
83 changes: 83 additions & 0 deletions pkg/imageserver/webdav.go
Original file line number Diff line number Diff line change
@@ -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)
}
80 changes: 24 additions & 56 deletions pkg/inspector/image-inspector.go
Original file line number Diff line number Diff line change
@@ -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/"
Expand All @@ -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.
Expand Down Expand Up @@ -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{""},
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 760d12b

Please sign in to comment.