Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

factor out image serving #11

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you call this file webdav.go and not imageserver.go? not everything here is served with webdav.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll rename.


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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pweil- we have a problem here: In the pkg/inspector/image-inspector.go code DstPath might change in line 138 i.opts.DstPath, err = ioutil.TempDir("/var/tmp", "image-inspector-") and this will happen after the creation the ImageServer. In the case where the user didn't specify a path for the image to be mounted and we are using a random one this will create an error.

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,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, this won't work here, need to set it later since we manipulate the DstPath later for tmpfs if it's empty. Will fix up

}
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