[lxc-devel] [lxd/master] Allow for cached/public image downloads through /dev/lxd

stgraber on Github lxc-bot at linuxcontainers.org
Thu May 24 15:23:36 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180524/408df6bb/attachment.bin>
-------------- next part --------------
From f05e80f7b43df13fbb4c8386c3cbc9d48fbcac4e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 5 May 2018 16:00:38 +0200
Subject: [PATCH 1/3] doc: Fix typo in api-extensions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/api-extensions.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 2fde1fd22..23f9441be 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -444,7 +444,7 @@ The following existing endpoints have been modified:
  * `POST /1.0/networks` accepts a new target query parameter
  * `GET /1.0/networks/<name>` accepts a new target query parameter
 
-## event_lifecycle
+## event\_lifecycle
 This adds a new `lifecycle` message type to the events API.
 
 ## storage\_api\_remote\_volume\_handling

From d9c9fdd0fd31c365f018f1c7d35d2293e5ec1f2d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 5 May 2018 15:45:29 +0200
Subject: [PATCH 2/3] devlxd: Allow download of public images
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go |  5 +++++
 lxd/devlxd.go | 29 ++++++++++++++++++++---------
 2 files changed, 25 insertions(+), 9 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index c672fceb4..a0342dcb4 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -143,6 +143,11 @@ func (d *Daemon) checkTrustedClient(r *http.Request) error {
 		return nil
 	}
 
+	if r.RemoteAddr == "@devlxd" {
+		// Devlxd unix socket
+		return fmt.Errorf("devlxd query")
+	}
+
 	if r.TLS == nil {
 		return fmt.Errorf("no TLS")
 	}
diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index 503ae9489..628ab00bc 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -54,10 +54,10 @@ type devLxdHandler struct {
 	 * server side right now either, I went the simple route to avoid
 	 * needless noise.
 	 */
-	f func(c container, w http.ResponseWriter, r *http.Request) *devLxdResponse
+	f func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse
 }
 
-var devlxdConfigGet = devLxdHandler{"/1.0/config", func(c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
+var devlxdConfigGet = devLxdHandler{"/1.0/config", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
 	filtered := []string{}
 	for k := range c.ExpandedConfig() {
 		if strings.HasPrefix(k, "user.") {
@@ -67,7 +67,7 @@ var devlxdConfigGet = devLxdHandler{"/1.0/config", func(c container, w http.Resp
 	return okResponse(filtered, "json")
 }}
 
-var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", func(c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
+var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
 	key := mux.Vars(r)["key"]
 	if !strings.HasPrefix(key, "user.") {
 		return &devLxdResponse{"not authorized", http.StatusForbidden, "raw"}
@@ -81,7 +81,17 @@ var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", func(c container, w
 	return okResponse(value, "raw")
 }}
 
-var devlxdMetadataGet = devLxdHandler{"/1.0/meta-data", func(c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
+var devlxdImageExport = devLxdHandler{"/1.0/images/{fingerprint}/export", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
+	resp := imageExport(d, r)
+	err := resp.Render(w)
+	if err != nil {
+		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
+	}
+
+	return &devLxdResponse{"", http.StatusOK, "raw"}
+}}
+
+var devlxdMetadataGet = devLxdHandler{"/1.0/meta-data", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
 	value := c.ExpandedConfig()["user.meta-data"]
 	return okResponse(fmt.Sprintf("#cloud-config\ninstance-id: %s\nlocal-hostname: %s\n%s", c.Name(), c.Name(), value), "raw")
 }}
@@ -89,7 +99,7 @@ var devlxdMetadataGet = devLxdHandler{"/1.0/meta-data", func(c container, w http
 var devlxdEventsLock sync.Mutex
 var devlxdEventListeners map[int]map[string]*eventListener = make(map[int]map[string]*eventListener)
 
-var devlxdEventsGet = devLxdHandler{"/1.0/events", func(c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
+var devlxdEventsGet = devLxdHandler{"/1.0/events", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
 	typeStr := r.FormValue("type")
 	if typeStr == "" {
 		typeStr = "config,device"
@@ -183,19 +193,20 @@ func devlxdEventSend(c container, eventType string, eventMessage interface{}) er
 }
 
 var handlers = []devLxdHandler{
-	{"/", func(c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
+	{"/", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
 		return okResponse([]string{"/1.0"}, "json")
 	}},
-	{"/1.0", func(c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
+	{"/1.0", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
 		return okResponse(shared.Jmap{"api_version": version.APIVersion}, "json")
 	}},
 	devlxdConfigGet,
 	devlxdConfigKeyGet,
 	devlxdMetadataGet,
 	devlxdEventsGet,
+	devlxdImageExport,
 }
 
-func hoistReq(f func(container, http.ResponseWriter, *http.Request) *devLxdResponse, d *Daemon) func(http.ResponseWriter, *http.Request) {
+func hoistReq(f func(*Daemon, container, http.ResponseWriter, *http.Request) *devLxdResponse, d *Daemon) func(http.ResponseWriter, *http.Request) {
 	return func(w http.ResponseWriter, r *http.Request) {
 		conn := extractUnderlyingConn(w)
 		cred, ok := pidMapper.m[conn]
@@ -224,7 +235,7 @@ func hoistReq(f func(container, http.ResponseWriter, *http.Request) *devLxdRespo
 			return
 		}
 
-		resp := f(c, w, r)
+		resp := f(d, c, w, r)
 		if resp.code != http.StatusOK {
 			http.Error(w, fmt.Sprintf("%s", resp.content), resp.code)
 		} else if resp.ctype == "json" {

From 7f4f959d5c86a7e87cfb2fe27d71d021c6c024df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 5 May 2018 16:12:51 +0200
Subject: [PATCH 3/3] devlxd: Add image download API
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This introduces a new API to devlxd which allows downloading of images
stored on the host. It requires the images on the host to either be
public or be marked as cached and requires the full image fingerprint.

This API only gives access to the image blob, not any of its metadata as
that data may not be relevant for a container.

The intended use for this is to minimize network usage for those using
nested LXDs when the image used is already in the host's image store.
The nested LXD will still hit the image store to grab the current image
metadata, resolve aliases, ... but the image blob itself will be
downloaded from the host over devlxd.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/api-extensions.md   |  6 ++++++
 doc/containers.md       |  1 +
 doc/dev-lxd.md          | 11 +++++++++++
 lxd/devlxd.go           |  7 +++++++
 lxd/images.go           | 26 ++++++++++++++++++++------
 scripts/bash/lxd-client |  3 ++-
 shared/container.go     |  7 ++++---
 shared/version/api.go   |  1 +
 8 files changed, 52 insertions(+), 10 deletions(-)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 23f9441be..d2ed82c79 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -476,3 +476,9 @@ This includes the following new endpoints (see [RESTful API](rest-api.md) for de
 The following existing endpoint has been modified:
 
  * `POST /1.0/containers` accepts the new source type `backup`
+
+## devlxd\_images
+Adds a `security.devlxd.images` config option for containers which
+controls the availability of a `/1.0/images/FINGERPRINT/export` API over
+devlxd. This can be used by a container running nested LXD to access raw
+images from the host.
diff --git a/doc/containers.md b/doc/containers.md
index c94ada5b2..7d059f606 100644
--- a/doc/containers.md
+++ b/doc/containers.md
@@ -52,6 +52,7 @@ raw.idmap                               | blob      | -             | no
 raw.lxc                                 | blob      | -             | no            | -                                    | Raw LXC configuration to be appended to the generated one
 raw.seccomp                             | blob      | -             | no            | container\_syscall\_filtering        | Raw Seccomp configuration
 security.devlxd                         | boolean   | true          | no            | restrict\_devlxd                     | Controls the presence of /dev/lxd in the container
+security.devlxd.images                  | boolean   | false         | no            | devlxd\_images                       | Controls the availability of the /1.0/images API over devlxd
 security.idmap.base                     | integer   | -             | no            | id\_map\_base                        | The base host ID to use for the allocation (overrides auto-detection)
 security.idmap.isolated                 | boolean   | false         | no            | id\_map                              | Use an idmap for this container that is unique among containers with isolated set.
 security.idmap.size                     | integer   | -             | no            | id\_map                              | The size of the idmap to use
diff --git a/doc/dev-lxd.md b/doc/dev-lxd.md
index f8b6ef05b..0bf997d38 100644
--- a/doc/dev-lxd.md
+++ b/doc/dev-lxd.md
@@ -40,6 +40,7 @@ authentication support in the `/dev/lxd/sock` API.
      * /1.0/config
        * /1.0/config/{key}
      * /1.0/events
+     * /1.0/images/{fingerprint}/export
      * /1.0/meta-data
 
 ## API details
@@ -135,6 +136,16 @@ This never returns. Each notification is sent as a separate JSON dict:
         }
     }
 
+### `/1.0/images/<FINGERPRINT>/export`
+#### GET
+ * Description: Download a public/cached image from the host
+ * Return: raw image or error
+ * Access: Requires security.devlxd.images set to true
+
+Return value:
+
+    See /1.0/images/<FINGERPRINT>/export in the daemon API.
+
 
 ### `/1.0/meta-data`
 #### GET
diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index 628ab00bc..2a05bdaa1 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -82,6 +82,13 @@ var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", func(d *Daemon, c co
 }}
 
 var devlxdImageExport = devLxdHandler{"/1.0/images/{fingerprint}/export", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
+	if !shared.IsTrue(c.ExpandedConfig()["security.devlxd.images"]) {
+		return &devLxdResponse{"not authorized", http.StatusForbidden, "raw"}
+	}
+
+	// Use by security checks to distinguish devlxd vs lxd APIs
+	r.RemoteAddr = "@devlxd"
+
 	resp := imageExport(d, r)
 	err := resp.Render(w)
 	if err != nil {
diff --git a/lxd/images.go b/lxd/images.go
index ddcb64c83..dc55b6003 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -1501,13 +1501,27 @@ func imageExport(d *Daemon, r *http.Request) Response {
 	public := d.checkTrustedClient(r) != nil
 	secret := r.FormValue("secret")
 
-	_, imgInfo, err := d.cluster.ImageGet(fingerprint, false, false)
-	if err != nil {
-		return SmartError(err)
-	}
+	var imgInfo *api.Image
+	var err error
+	if r.RemoteAddr == "@devlxd" {
+		// /dev/lxd API requires exact match
+		_, imgInfo, err = d.cluster.ImageGet(fingerprint, false, true)
+		if err != nil {
+			return SmartError(err)
+		}
 
-	if !imgInfo.Public && public && !imageValidSecret(imgInfo.Fingerprint, secret) {
-		return NotFound
+		if !imgInfo.Public && !imgInfo.Cached {
+			return NotFound
+		}
+	} else {
+		_, imgInfo, err = d.cluster.ImageGet(fingerprint, false, false)
+		if err != nil {
+			return SmartError(err)
+		}
+
+		if !imgInfo.Public && public && !imageValidSecret(imgInfo.Fingerprint, secret) {
+			return NotFound
+		}
 	}
 
 	// Check if the image is only available on another node.
diff --git a/scripts/bash/lxd-client b/scripts/bash/lxd-client
index bc4d4a8cb..54d8d7eb8 100644
--- a/scripts/bash/lxd-client
+++ b/scripts/bash/lxd-client
@@ -83,7 +83,8 @@ _have lxc && {
       migration.incremental.memory.goal nvidia.runtime \
       migration.incremental.memory.iterations raw.apparmor raw.idmap raw.lxc \
       raw.seccomp security.idmap.base security.idmap.isolated \
-      security.idmap.size security.devlxd security.nesting security.privileged \
+      security.idmap.size security.devlxd security.devlxd.images \
+      security.nesting security.privileged \
       security.syscalls.blacklist security.syscalls.blacklist_compat \
       security.syscalls.blacklist_default \
       volatile.apply_quota volatile.apply_template volatile.base_image \
diff --git a/shared/container.go b/shared/container.go
index b6cfc7ada..b4ce32974 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -208,9 +208,10 @@ var KnownContainerConfigKeys = map[string]func(value string) error{
 
 	"nvidia.runtime": IsBool,
 
-	"security.nesting":    IsBool,
-	"security.privileged": IsBool,
-	"security.devlxd":     IsBool,
+	"security.nesting":       IsBool,
+	"security.privileged":    IsBool,
+	"security.devlxd":        IsBool,
+	"security.devlxd.images": IsBool,
 
 	"security.idmap.base":     IsUint32,
 	"security.idmap.isolated": IsBool,
diff --git a/shared/version/api.go b/shared/version/api.go
index 1928747e8..2957abaf2 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -104,6 +104,7 @@ var APIExtensions = []string{
 	"nvidia_runtime",
 	"container_mount_propagation",
 	"container_backup",
+	"devlxd_images",
 }
 
 // APIExtensionsCount returns the number of available API extensions.


More information about the lxc-devel mailing list