diff --git a/doc/content/toolstack/high-level/daemons.md b/doc/content/toolstack/high-level/daemons.md index e00c2fc287a..103798bb0d5 100644 --- a/doc/content/toolstack/high-level/daemons.md +++ b/doc/content/toolstack/high-level/daemons.md @@ -34,6 +34,9 @@ xapi-storage-script message-switch : exchanges messages between the daemons on a host +xapi-guard +: forwards uefi and vtpm persistence calls from domains to xapi + v6d : controls which features are enabled. @@ -52,4 +55,4 @@ mpathalert if paths fail and need repair wsproxy -: handles access to VM consoles \ No newline at end of file +: handles access to VM consoles diff --git a/doc/content/xapi-guard/_index.md b/doc/content/xapi-guard/_index.md new file mode 100644 index 00000000000..bcafb968b07 --- /dev/null +++ b/doc/content/xapi-guard/_index.md @@ -0,0 +1,28 @@ ++++ +title = "Xapi-guard" +weight = 50 ++++ + +The `xapi-guard` daemon is the component in the xapi toolstack that is responsible for handling persistence requests from VMs (domains). +Currently these are UEFI vars and vTPM updates. + +The code is in `ocaml/xapi-guard`. +When the daemon managed only with UEFI updates it was called `varstored-guard`. +Some files and package names still use the previous name. + +Principles +---------- +1. Calls from domains must be limited in privilege to do certain API calls, and + to read and write from their corresponding VM in xapi's database only. +2. Xenopsd is able to control xapi-guard through message switch, this access is + not limited. +3. Listening to domain socket is restored whenever the daemon restarts to minimize disruption of running domains. + + +Overview +-------- + +Xapi-guard forwards calls from domains to xapi to persist UEFI variables, and update vTPMs. +To do this, it listens to 1 socket per service (varstored, or swtpm) per domain. +To create these sockets before the domains are running, it listens to a message-switch socket. +This socket listens to calls from xenopsd, which orchestrates the domain creation. diff --git a/ocaml/xapi-guard/lib/dorpc.ml b/ocaml/xapi-guard/lib/dorpc.ml index 2074f35e529..bbb1f11bb74 100644 --- a/ocaml/xapi-guard/lib/dorpc.ml +++ b/ocaml/xapi-guard/lib/dorpc.ml @@ -13,7 +13,7 @@ *) open Idl -module D = Debug.Make (struct let name = "varstored-guard rpc" end) +module D = Debug.Make (struct let name = "xapi-guard rpc" end) let wrap_rpc error f = let on_error e = diff --git a/ocaml/xapi-guard/lib/dune b/ocaml/xapi-guard/lib/dune index bfb4841ab3d..87d10e7766e 100644 --- a/ocaml/xapi-guard/lib/dune +++ b/ocaml/xapi-guard/lib/dune @@ -20,7 +20,7 @@ ) (library (name xapi_guard) - (modules dorpc) + (modules dorpc types) (libraries rpclib.core lwt diff --git a/ocaml/xapi-guard/lib/server_interface.ml b/ocaml/xapi-guard/lib/server_interface.ml index 5b2acd7c27c..4bcaae8a387 100644 --- a/ocaml/xapi-guard/lib/server_interface.ml +++ b/ocaml/xapi-guard/lib/server_interface.ml @@ -15,7 +15,7 @@ open Rpc open Lwt.Syntax -module D = Debug.Make (struct let name = "varstored_interface" end) +module D = Debug.Make (struct let name = __MODULE__ end) open D @@ -25,7 +25,7 @@ let err = Xenops_interface.err type nvram = (string * string) list [@@deriving rpcty] -let originator = "varstored-guard" +let originator = "xapi-guard" type session = [`session] Ref.t @@ -152,7 +152,26 @@ let update_key key state t = let empty = "" -let serve_forever_lwt_callback_vtpm ~cache mutex vtpm _path _ req body = +let serve_forever_lwt_callback_vtpm ~cache mutex vm_uuid _ req body = + let get_vtpm_ref () = + let* vm = + with_xapi ~cache @@ Xen_api_lwt_unix.VM.get_by_uuid ~uuid:vm_uuid + in + let* vTPMs = with_xapi ~cache @@ Xen_api_lwt_unix.VM.get_VTPMs ~self:vm in + match vTPMs with + | [] -> + D.warn + "%s: received a request from a VM that has no VTPM associated, \ + ignoring request" + __FUNCTION__ ; + let msg = + Printf.sprintf "No VTPM associated with VM %s, nothing to do" vm_uuid + in + raise (Failure msg) + | self :: _ -> + let* uuid = with_xapi ~cache @@ Xen_api_lwt_unix.VTPM.get_uuid ~self in + with_xapi ~cache @@ VTPM.get_by_uuid ~uuid + in let uri = Cohttp.Request.uri req in (* in case the connection is interrupted/etc. we may still have pending operations, so use a per vTPM mutex to ensure we really only have 1 pending operation at a time for a vTPM @@ -161,7 +180,8 @@ let serve_forever_lwt_callback_vtpm ~cache mutex vtpm _path _ req body = (* TODO: some logging *) match (Cohttp.Request.meth req, Uri.path uri) with | `GET, key when key <> "/" -> - let* contents = with_xapi ~cache @@ VTPM.get_contents ~self:vtpm in + let* self = get_vtpm_ref () in + let* contents = with_xapi ~cache @@ VTPM.get_contents ~self in let body = contents |> deserialize |> lookup_key key in let headers = Cohttp.Header.of_list [("Content-Type", "application/octet-stream")] @@ -169,19 +189,21 @@ let serve_forever_lwt_callback_vtpm ~cache mutex vtpm _path _ req body = Cohttp_lwt_unix.Server.respond_string ~headers ~status:`OK ~body () | `PUT, key when key <> "/" -> let* body = Cohttp_lwt.Body.to_string body in - let* contents = with_xapi ~cache @@ VTPM.get_contents ~self:vtpm in + let* self = get_vtpm_ref () in + let* contents = with_xapi ~cache @@ VTPM.get_contents ~self in let contents = contents |> deserialize |> update_key key body |> serialize in - let* () = with_xapi ~cache @@ VTPM.set_contents ~self:vtpm ~contents in + let* () = with_xapi ~cache @@ VTPM.set_contents ~self ~contents in Cohttp_lwt_unix.Server.respond ~status:`No_content ~body:Cohttp_lwt.Body.empty () | `DELETE, key when key <> "/" -> - let* contents = with_xapi ~cache @@ VTPM.get_contents ~self:vtpm in + let* self = get_vtpm_ref () in + let* contents = with_xapi ~cache @@ VTPM.get_contents ~self in let contents = contents |> deserialize |> update_key key empty |> serialize in - let* () = with_xapi ~cache @@ VTPM.set_contents ~self:vtpm ~contents in + let* () = with_xapi ~cache @@ VTPM.set_contents ~self ~contents in Cohttp_lwt_unix.Server.respond ~status:`No_content ~body:Cohttp_lwt.Body.empty () | _, _ -> @@ -192,14 +214,22 @@ let serve_forever_lwt_callback_vtpm ~cache mutex vtpm _path _ req body = let make_server_varstored ~cache path vm_uuid = let module Server = Xapi_idl_guard_varstored.Interface.RPC_API (Rpc_lwt.GenServer ()) in - let* vm = with_xapi ~cache @@ VM.get_by_uuid ~uuid:vm_uuid in + let get_vm_ref () = with_xapi ~cache @@ VM.get_by_uuid ~uuid:vm_uuid in let ret v = (* TODO: maybe map XAPI exceptions *) Lwt.bind v Lwt.return_ok |> Rpc_lwt.T.put in - let get_nvram _ _ = ret @@ with_xapi ~cache @@ VM.get_NVRAM ~self:vm in + let get_nvram _ _ = + (let* self = get_vm_ref () in + with_xapi ~cache @@ VM.get_NVRAM ~self + ) + |> ret + in let set_nvram _ _ nvram = - ret @@ with_xapi ~cache @@ VM.set_NVRAM_EFI_variables ~self:vm ~value:nvram + (let* self = get_vm_ref () in + with_xapi ~cache @@ VM.set_NVRAM_EFI_variables ~self ~value:nvram + ) + |> ret in let message_create _ _name priority _cls _uuid body = ret @@ -224,20 +254,6 @@ let make_server_varstored ~cache path vm_uuid = |> serve_forever_lwt path let make_server_vtpm_rest ~cache path vm_uuid = - let vtpm_server uuid = - let* vtpm = with_xapi ~cache @@ VTPM.get_by_uuid ~uuid in - let mutex = Lwt_mutex.create () in - serve_forever_lwt_callback_vtpm ~cache mutex vtpm path - |> serve_forever_lwt path - in - let* vm = with_xapi ~cache @@ Xen_api_lwt_unix.VM.get_by_uuid ~uuid:vm_uuid in - let* vTPMs = with_xapi ~cache @@ Xen_api_lwt_unix.VM.get_VTPMs ~self:vm in - match vTPMs with - | [] -> - D.warn - "%s: asked to start swtpm server in socket, but no vtpms associated!" - __FUNCTION__ ; - Lwt.return Lwt.return - | self :: _ -> - let* uuid = with_xapi ~cache @@ Xen_api_lwt_unix.VTPM.get_uuid ~self in - vtpm_server uuid + let mutex = Lwt_mutex.create () in + let callback = serve_forever_lwt_callback_vtpm ~cache mutex vm_uuid in + serve_forever_lwt path callback diff --git a/ocaml/xapi-guard/lib/types.ml b/ocaml/xapi-guard/lib/types.ml new file mode 100644 index 00000000000..7b705c89a01 --- /dev/null +++ b/ocaml/xapi-guard/lib/types.ml @@ -0,0 +1,5 @@ +module Service = struct + type t = Varstored | Swtpm [@@deriving rpcty] + + let to_string = function Varstored -> "Varstored" | Swtpm -> "Swtpm" +end diff --git a/ocaml/xapi-guard/lib/types.mli b/ocaml/xapi-guard/lib/types.mli new file mode 100644 index 00000000000..6bb036826d7 --- /dev/null +++ b/ocaml/xapi-guard/lib/types.mli @@ -0,0 +1,7 @@ +module Service : sig + type t = Varstored | Swtpm + + val typ_of : t Rpc.Types.typ + + val to_string : t -> string +end diff --git a/ocaml/xapi-guard/src/main.ml b/ocaml/xapi-guard/src/main.ml index 551a60372d4..b80bf354516 100644 --- a/ocaml/xapi-guard/src/main.ml +++ b/ocaml/xapi-guard/src/main.ml @@ -13,18 +13,16 @@ * GNU Lesser General Public License for more details. *) -open Xapi_guard open Lwt.Syntax open Xapi_guard_server +module Types = Xapi_guard.Types module SessionCache = Xen_api_lwt_unix.SessionCache -module D = Debug.Make (struct let name = "varstored-guard" end) +let daemon_name = "xapi-guard" -let ret v = Lwt.bind v Lwt.return_ok |> Rpc_lwt.T.put - -type ty = Varstored | Swtpm [@@deriving rpcty] +module D = Debug.Make (struct let name = daemon_name end) -let ty_to_string = function Varstored -> "Varstored" | Swtpm -> "Swtpm" +let ret v = Lwt.bind v Lwt.return_ok |> Rpc_lwt.T.put let log_fds () = let count stream = Lwt_stream.fold (fun _ n -> n + 1) stream 0 in @@ -41,7 +39,7 @@ module Persistent = struct vm_uuid: Xapi_idl_guard_privileged.Interface.Uuidm.t ; path: string ; gid: int - ; typ: ty + ; typ: Types.Service.t } [@@deriving rpcty] @@ -109,7 +107,8 @@ let listen_for_vm {Persistent.vm_uuid; path; gid; typ} = in let vm_uuid_str = Uuidm.to_string vm_uuid in D.debug "%s: listening for %s on socket %s for VM %s" __FUNCTION__ - (ty_to_string typ) path vm_uuid_str ; + (Types.Service.to_string typ) + path vm_uuid_str ; let* () = safe_unlink path in let* stop_server = make_server ~cache path vm_uuid_str in let* () = log_fds () in @@ -230,7 +229,8 @@ let rpc_fn = let process body = let+ response = - Dorpc.wrap_rpc Xapi_idl_guard_privileged.Interface.E.error (fun () -> + Xapi_guard.Dorpc.wrap_rpc Xapi_idl_guard_privileged.Interface.E.error + (fun () -> let call = Jsonrpc.call_of_string body in D.debug "Received request from message-switch, method %s" call.Rpc.name ; rpc_fn call @@ -278,12 +278,12 @@ let main log_level = old_hook exn ) ; let () = Lwt_main.run @@ make_message_switch_server () in - D.debug "Exiting varstored-guard" + D.debug "Exiting %s" daemon_name open! Cmdliner let cmd = - let info = Cmd.info "varstored-guard" in + let info = Cmd.info daemon_name in let log_level = let doc = "Syslog level. E.g. debug, info etc." in let level_conv =