From 403e6c73c2e42f67b9f76500071109d811854f5e Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Mon, 5 Aug 2024 18:06:13 +0100 Subject: [PATCH 1/5] lxd-agent: Add type for devlxd handler func and rename field. Single letter or abbreviated variable names should only be used in small scopes and not in struct fields (see https://google.github.io/styleguide/go/decisions.html#single-letter-variable-names). Signed-off-by: Mark Laing --- lxd-agent/devlxd.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lxd-agent/devlxd.go b/lxd-agent/devlxd.go index c22910de0e1a..60d48d8934b8 100644 --- a/lxd-agent/devlxd.go +++ b/lxd-agent/devlxd.go @@ -20,6 +20,8 @@ import ( "github.com/canonical/lxd/shared/logger" ) +type devLXDHandlerFunc func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse + // DevLxdServer creates an http.Server capable of handling requests against the // /dev/lxd Unix socket endpoint created inside VMs. func devLxdServer(d *Daemon) *http.Server { @@ -37,7 +39,7 @@ type devLxdHandler struct { * server side right now either, I went the simple route to avoid * needless noise. */ - f func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse + handlerFunc devLXDHandlerFunc } func getVsockClient(d *Daemon) (lxd.InstanceServer, error) { @@ -249,7 +251,7 @@ func devLxdAPI(d *Daemon) http.Handler { m.UseEncodedPath() // Allow encoded values in path segments. for _, handler := range handlers { - m.HandleFunc(handler.path, hoistReq(handler.f, d)) + m.HandleFunc(handler.path, hoistReq(handler.handlerFunc, d)) } return m From fd9b8be87d4738420f729b22694237e659fc018c Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Mon, 5 Aug 2024 19:38:37 +0100 Subject: [PATCH 2/5] lxd-agent: Define devlxd handlers by name. Additionally, specify field names in hander structs. Signed-off-by: Mark Laing --- lxd-agent/devlxd.go | 63 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/lxd-agent/devlxd.go b/lxd-agent/devlxd.go index 60d48d8934b8..95207e5f8a33 100644 --- a/lxd-agent/devlxd.go +++ b/lxd-agent/devlxd.go @@ -57,7 +57,12 @@ func getVsockClient(d *Daemon) (lxd.InstanceServer, error) { return server, nil } -var devlxdConfigGet = devLxdHandler{"/1.0/config", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { +var devlxdConfigGet = devLxdHandler{ + path: "/1.0/config", + handlerFunc: devlxdConfigGetHandler, +} + +func devlxdConfigGetHandler(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { client, err := getVsockClient(d) if err != nil { return smartResponse(fmt.Errorf("Failed connecting to LXD over vsock: %w", err)) @@ -84,9 +89,14 @@ var devlxdConfigGet = devLxdHandler{"/1.0/config", func(d *Daemon, w http.Respon } } return okResponse(filtered, "json") -}} +} + +var devlxdConfigKeyGet = devLxdHandler{ + path: "/1.0/config/{key}", + handlerFunc: devlxdConfigKeyGetHandler, +} -var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { +func devlxdConfigKeyGetHandler(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { key, err := url.PathUnescape(mux.Vars(r)["key"]) if err != nil { return &devLxdResponse{"bad request", http.StatusBadRequest, "raw"} @@ -116,9 +126,14 @@ var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", func(d *Daemon, w ht } return okResponse(value, "raw") -}} +} + +var devlxdMetadataGet = devLxdHandler{ + path: "/1.0/meta-data", + handlerFunc: devlxdMetadataGetHandler, +} -var devlxdMetadataGet = devLxdHandler{"/1.0/meta-data", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { +func devlxdMetadataGetHandler(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { var client lxd.InstanceServer var err error @@ -150,18 +165,28 @@ var devlxdMetadataGet = devLxdHandler{"/1.0/meta-data", func(d *Daemon, w http.R } return okResponse(metaData, "raw") -}} +} -var devLxdEventsGet = devLxdHandler{"/1.0/events", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { +var devLxdEventsGet = devLxdHandler{ + path: "/1.0/events", + handlerFunc: devlxdEventsGetHandler, +} + +func devlxdEventsGetHandler(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { err := eventsGet(d, r).Render(w) if err != nil { return smartResponse(err) } return okResponse("", "raw") -}} +} + +var devlxdAPIGet = devLxdHandler{ + path: "/1.0", + handlerFunc: devlxdAPIGetHandler, +} -var devlxdAPIGet = devLxdHandler{"/1.0", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { +func devlxdAPIGetHandler(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { client, err := getVsockClient(d) if err != nil { return smartResponse(fmt.Errorf("Failed connecting to LXD over vsock: %w", err)) @@ -193,9 +218,14 @@ var devlxdAPIGet = devLxdHandler{"/1.0", func(d *Daemon, w http.ResponseWriter, } return &devLxdResponse{fmt.Sprintf("method %q not allowed", r.Method), http.StatusBadRequest, "raw"} -}} +} + +var devlxdDevicesGet = devLxdHandler{ + path: "/1.0/devices", + handlerFunc: devlxdDevicesGetHandler, +} -var devlxdDevicesGet = devLxdHandler{"/1.0/devices", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { +func devlxdDevicesGetHandler(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { client, err := getVsockClient(d) if err != nil { return smartResponse(fmt.Errorf("Failed connecting to LXD over vsock: %w", err)) @@ -216,12 +246,15 @@ var devlxdDevicesGet = devLxdHandler{"/1.0/devices", func(d *Daemon, w http.Resp } return okResponse(devices, "json") -}} +} var handlers = []devLxdHandler{ - {"/", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { - return okResponse([]string{"/1.0"}, "json") - }}, + { + path: "/", + handlerFunc: func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { + return okResponse([]string{"/1.0"}, "json") + }, + }, devlxdAPIGet, devlxdConfigGet, devlxdConfigKeyGet, From 687125d69dbf4914b58c8504e6ec6e7b7df095b2 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Mon, 5 Aug 2024 20:33:54 +0100 Subject: [PATCH 3/5] lxd-agent: Add an image export handler to lxd-agent. Signed-off-by: Mark Laing --- lxd-agent/devlxd.go | 55 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/lxd-agent/devlxd.go b/lxd-agent/devlxd.go index 95207e5f8a33..6922e8903ddd 100644 --- a/lxd-agent/devlxd.go +++ b/lxd-agent/devlxd.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "io" "net" "net/http" "net/url" @@ -18,6 +19,7 @@ import ( "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" "github.com/canonical/lxd/shared/logger" + "github.com/canonical/lxd/shared/version" ) type devLXDHandlerFunc func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse @@ -248,6 +250,53 @@ func devlxdDevicesGetHandler(d *Daemon, w http.ResponseWriter, r *http.Request) return okResponse(devices, "json") } +var devlxdImageExport = devLxdHandler{ + path: "/1.0/images/{fingerprint}/export", + handlerFunc: devlxdImageExportHandler, +} + +func devlxdImageExportHandler(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse { + // Extract the fingerprint. + fingerprint, err := url.PathUnescape(mux.Vars(r)["fingerprint"]) + if err != nil { + return smartResponse(err) + } + + // Get a http.Client. + client, err := getClient(d.serverCID, int(d.serverPort), d.serverCertificate) + if err != nil { + return smartResponse(fmt.Errorf("Failed connecting to LXD over vsock: %w", err)) + } + + // Remove the request URI, this cannot be set on requests. + r.RequestURI = "" + + // Set up the request URL with the correct host. + r.URL = &api.NewURL().Scheme("https").Host("custom.socket").Path(version.APIVersion, "images", fingerprint, "export").URL + + // Proxy the request. + resp, err := client.Do(r) + if err != nil { + return errorResponse(http.StatusInternalServerError, err.Error()) + } + + // Set headers from the host LXD. + for k, vv := range resp.Header { + for _, v := range vv { + w.Header().Set(k, v) + } + } + + // Copy headers and response body. + w.WriteHeader(resp.StatusCode) + _, err = io.Copy(w, resp.Body) + if err != nil { + return smartResponse(err) + } + + return nil +} + var handlers = []devLxdHandler{ { path: "/", @@ -261,11 +310,17 @@ var handlers = []devLxdHandler{ devlxdMetadataGet, devLxdEventsGet, devlxdDevicesGet, + devlxdImageExport, } func hoistReq(f func(*Daemon, http.ResponseWriter, *http.Request) *devLxdResponse, d *Daemon) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { resp := f(d, w, r) + if resp == nil { + // The handler has already written the response. + return + } + if resp.code != http.StatusOK { http.Error(w, fmt.Sprintf("%s", resp.content), resp.code) } else if resp.ctype == "json" { From ed41bb1f88b95dcc99b1b1362db97dbcc5e36593 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Mon, 5 Aug 2024 20:34:18 +0100 Subject: [PATCH 4/5] lxd/instance/instancetype: Allow `security.devlxd.images` config key for VMs. Signed-off-by: Mark Laing --- lxd/instance/instancetype/instance.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lxd/instance/instancetype/instance.go b/lxd/instance/instancetype/instance.go index a63ced508a77..c57075f5a398 100644 --- a/lxd/instance/instancetype/instance.go +++ b/lxd/instance/instancetype/instance.go @@ -302,6 +302,15 @@ var InstanceConfigKeysAny = map[string]func(value string) error{ // shortdesc: Whether `/dev/lxd` is present in the instance "security.devlxd": validate.Optional(validate.IsBool), + // lxdmeta:generate(entities=instance; group=security; key=security.devlxd.images) + // + // --- + // type: bool + // defaultdesc: `false` + // liveupdate: no + // shortdesc: Controls the availability of the `/1.0/images` API over `devlxd` + "security.devlxd.images": validate.Optional(validate.IsBool), + // lxdmeta:generate(entities=instance; group=security; key=security.protection.delete) // // --- @@ -676,16 +685,6 @@ var InstanceConfigKeysContainer = map[string]func(value string) error{ // shortdesc: Raw Seccomp configuration "raw.seccomp": validate.IsAny, - // lxdmeta:generate(entities=instance; group=security; key=security.devlxd.images) - // - // --- - // type: bool - // defaultdesc: `false` - // liveupdate: no - // condition: container - // shortdesc: Controls the availability of the `/1.0/images` API over `devlxd` - "security.devlxd.images": validate.Optional(validate.IsBool), - // lxdmeta:generate(entities=instance; group=security; key=security.idmap.base) // Setting this option overrides auto-detection. // --- From 6e8512f8f53a81f7617c394fa5846e39e678c521 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Mon, 5 Aug 2024 20:39:47 +0100 Subject: [PATCH 5/5] {doc,lxd/metadata}: Runs make update-metadata. Signed-off-by: Mark Laing --- doc/metadata.txt | 1 - lxd/metadata/configuration.json | 1 - 2 files changed, 2 deletions(-) diff --git a/doc/metadata.txt b/doc/metadata.txt index cc0167094c00..ff12f8a61978 100644 --- a/doc/metadata.txt +++ b/doc/metadata.txt @@ -2112,7 +2112,6 @@ See {ref}`dev-lxd` for more information. ``` ```{config:option} security.devlxd.images instance-security -:condition: "container" :defaultdesc: "`false`" :liveupdate: "no" :shortdesc: "Controls the availability of the `/1.0/images` API over `devlxd`" diff --git a/lxd/metadata/configuration.json b/lxd/metadata/configuration.json index ec38ceab98fe..2f948ed3eefc 100644 --- a/lxd/metadata/configuration.json +++ b/lxd/metadata/configuration.json @@ -2392,7 +2392,6 @@ }, { "security.devlxd.images": { - "condition": "container", "defaultdesc": "`false`", "liveupdate": "no", "longdesc": "",