From 0dfa96c043cee4ce82c0a45c3ad09b0a61798b79 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Mon, 22 Sep 2014 21:57:57 +0100 Subject: [PATCH] v2v: -o rhev: Write files and directories as user:group 36:36 (RHBZ#1143887). We need to write files and directories as user:group 36:36, else RHEV-M cannot import the VM. Doing this in the presence of NFS is difficult. See v2v/kvmuid.mli for how it is done in this commit. --- po/POTFILES | 1 + po/POTFILES-ml | 1 + v2v/Makefile.am | 7 ++-- v2v/kvmuid-c.c | 33 ++++++++++++++++++ v2v/kvmuid.ml | 86 ++++++++++++++++++++++++++++++++++++++++++++++ v2v/kvmuid.mli | 69 +++++++++++++++++++++++++++++++++++++ v2v/output_rhev.ml | 57 +++++++++++++++++++++++++----- 7 files changed, 243 insertions(+), 11 deletions(-) create mode 100644 v2v/kvmuid-c.c create mode 100644 v2v/kvmuid.ml create mode 100644 v2v/kvmuid.mli diff --git a/po/POTFILES b/po/POTFILES index aec8c62f8..be2de2839 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -337,5 +337,6 @@ src/tmpdirs.c src/utils.c test-tool/test-tool.c v2v/domainxml-c.c +v2v/kvmuid-c.c v2v/utils-c.c v2v/xml-c.c diff --git a/po/POTFILES-ml b/po/POTFILES-ml index 76fe681ac..de37bf7dd 100644 --- a/po/POTFILES-ml +++ b/po/POTFILES-ml @@ -92,6 +92,7 @@ v2v/input_disk.ml v2v/input_libvirt.ml v2v/input_libvirtxml.ml v2v/input_ova.ml +v2v/kvmuid.ml v2v/lib_esx.ml v2v/lib_linux.ml v2v/lib_ovf.ml diff --git a/v2v/Makefile.am b/v2v/Makefile.am index 03d744212..4b57acabd 100644 --- a/v2v/Makefile.am +++ b/v2v/Makefile.am @@ -37,6 +37,7 @@ SOURCES_MLI = \ input_libvirtxml.mli \ input_ova.mli \ JSON.mli \ + kvmuid.mli \ lib_esx.mli \ lib_linux.mli \ lib_ovf.mli \ @@ -60,6 +61,7 @@ SOURCES_ML = \ domainxml.ml \ DOM.ml \ JSON.ml \ + kvmuid.ml \ lib_esx.ml \ lib_xen.ml \ lib_ovf.ml \ @@ -88,9 +90,10 @@ SOURCES_C = \ $(top_builddir)/mllib/mkdtemp-c.c \ $(top_builddir)/customize/crypt-c.c \ $(top_builddir)/customize/perl_edit-c.c \ + domainxml-c.c \ + kvmuid-c.c \ utils-c.c \ - xml-c.c \ - domainxml-c.c + xml-c.c if HAVE_OCAML diff --git a/v2v/kvmuid-c.c b/v2v/kvmuid-c.c new file mode 100644 index 000000000..fb4171123 --- /dev/null +++ b/v2v/kvmuid-c.c @@ -0,0 +1,33 @@ +/* virt-v2v + * Copyright (C) 2009-2014 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include +#include + +#include + +extern int v2v_exit (value rv) __attribute__((noreturn)); + +int +v2v_exit (value rv) +{ + _exit (Int_val (rv)); +} diff --git a/v2v/kvmuid.ml b/v2v/kvmuid.ml new file mode 100644 index 000000000..93908d911 --- /dev/null +++ b/v2v/kvmuid.ml @@ -0,0 +1,86 @@ +(* virt-v2v + * Copyright (C) 2009-2014 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +(* Functions for making files and directories as another user. *) + +open Unix +open Printf + +open Common_gettext.Gettext + +open Utils + +type t = { + uid : int option; + gid : int option; +} + +let create ?uid ?gid () = { uid = uid; gid = gid } + +(* Call _exit directly, ie. do not run OCaml atexit handlers. *) +external _exit : int -> unit = "v2v_exit" "noalloc" + +let with_fork { uid = uid; gid = gid } f = + let pid = fork () in + if pid = 0 then ( (* child *) + (match gid with None -> () | Some gid -> setgid gid); + (match uid with None -> () | Some uid -> setuid uid); + (try f () + with exn -> + eprintf "%s: KVM uid wrapper: %s\n%!" prog (Printexc.to_string exn); + _exit 1 + ); + _exit 0 + ); + (* parent *) + let _, status = waitpid [] pid in + match status with + | WEXITED 0 -> () + | WEXITED i -> + error (f_"subprocess exited with non-zero error code %d") i + | WSIGNALED i | WSTOPPED i -> + error (f_"subprocess signalled or stopped by signal %d") i + +let mkdir t path perm = + with_fork t (fun () -> mkdir path perm) + +let rmdir t path = + with_fork t (fun () -> rmdir path) + +let output t path f = + with_fork t ( + fun () -> + let chan = open_out path in + f chan; + close_out chan + ) + +let make_file t path content = + output t path (fun chan -> output_string chan content) + +let unlink t path = + with_fork t (fun () -> unlink path) + +let func t f = with_fork t f + +let command t cmd = + with_fork t ( + fun () -> + let r = Sys.command cmd in + if r <> 0 then failwith "external command failed" + ) diff --git a/v2v/kvmuid.mli b/v2v/kvmuid.mli new file mode 100644 index 000000000..dfd31c277 --- /dev/null +++ b/v2v/kvmuid.mli @@ -0,0 +1,69 @@ +(* virt-v2v + * Copyright (C) 2009-2014 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +(** Functions for making files and directories as another user. + + [-o rhev] output mode has to write files as UID:GID 36:36, + otherwise RHEV cannot read them. Because the files are located on + NFS (and hence might be root-squashed) we also cannot chown the + files. We cannot setuid the whole process to 36:36 because it + needs to do other root things like mounting and unmounting the NFS + volume. + + The solution to this craziness is to fork a subprocess every time + we need to create a file, setuid in the subprocess, and write the + file. The subprocess then exits, leaving the main process still + running as root. + + This mini-library encapsulates this tomfoolery into something that + is slightly more sane to use. + + NB. We are {b not} dropping permissions for security reasons. + This file has nothing to do with security. *) + +type t +(** Abstract handle. *) + +val create : ?uid:int -> ?gid:int -> unit -> t +(** Create handle. The optional [?uid] and [?gid] parameters are the + user/group to run as. If omitted, then we don't change user + and/or group (but we still do the forking anyway). *) + +val mkdir : t -> string -> int -> unit +(** [mkdir t path perm] creates the directory [path] with mode [perm]. *) + +val rmdir : t -> string -> unit +(** [rmdir t path] removes the directory [path]. *) + +val make_file : t -> string -> string -> unit +(** [make_file t path content] creates the file [path] with content + [content]. The current umask controls file permissions. *) + +val output : t -> string -> (out_channel -> unit) -> unit +(** [output t path f] creates the file [path] with content from + function [f]. The current umask controls file permissions. *) + +val unlink : t -> string -> unit +(** [unlink t path] deletes the file [path]. *) + +val func : t -> (unit -> unit) -> unit +(** [func t f] runs the arbitrary function [f]. *) + +val command : t -> string -> unit +(** [command t cmd] runs [cmd] as the alternate user/group after + forking. *) diff --git a/v2v/output_rhev.ml b/v2v/output_rhev.ml index 59991bab4..bb777870b 100644 --- a/v2v/output_rhev.ml +++ b/v2v/output_rhev.ml @@ -38,8 +38,13 @@ let rec mount_and_check_storage_domain verbose domain_class os = | server, export -> let export = "/" ^ export in - (* Try mounting it. *) + (* Create a mountpoint. Default mode is too restrictive for us + * when we need to write into the directory as 36:36. + *) let mp = Mkdtemp.temp_dir "v2v." "" in + chmod mp 0o755; + + (* Try mounting it. *) let cmd = sprintf "mount %s:%s %s" (quote server) (quote export) (quote mp) in if verbose then printf "%s\n%!" cmd; @@ -111,7 +116,19 @@ and check_storage_domain verbose domain_class os mp = (* Looks good, so return the SD mountpoint and UUID. *) (mp, uuid) +(* UID:GID required for files and directories when writing to ESD. *) +let uid = 36 and gid = 36 + class output_rhev verbose os vmtype output_alloc = + (* Create a UID-switching handle. If we're not root, create a dummy + * one because we cannot switch UIDs. + *) + let running_as_root = geteuid () = 0 in + let kvmuid_t = + if running_as_root then + Kvmuid.create ~uid ~gid () + else + Kvmuid.create () in object inherit output verbose @@ -171,6 +188,23 @@ object eprintf "RHEV: ESD mountpoint: %s\nRHEV: ESD UUID: %s\n%!" esd_mp esd_uuid; + (* See if we can write files as UID:GID 36:36. *) + let () = + let testfile = esd_mp // esd_uuid // "v2v-uid-test" in + Kvmuid.make_file kvmuid_t testfile ""; + let stat = stat testfile in + Kvmuid.unlink kvmuid_t testfile; + let actual_uid = stat.st_uid and actual_gid = stat.st_gid in + if verbose then + eprintf "RHEV: actual UID:GID of new files is %d:%d\n" + actual_uid actual_gid; + if uid <> actual_uid || gid <> actual_gid then ( + if running_as_root then + warning ~prog (f_"cannot write files to the NFS server as %d:%d, even though we appear to be running as root. This probably means the NFS client or idmapd is not configured properly.\n\nYou will have to chown the files that virt-v2v creates after the run, otherwise RHEV-M will not be able to import the VM.") uid gid + else + warning ~prog (f_"cannot write files to the NFS server as %d:%d. You might want to stop virt-v2v (^C) and rerun it as root.") uid gid + ) in + (* Create unique UUIDs for everything *) image_uuid <- uuidgen ~prog (); vm_uuid <- uuidgen ~prog (); @@ -185,7 +219,7 @@ object * conversion fails for any reason then we delete this directory. *) image_dir <- esd_mp // esd_uuid // "images" // image_uuid; - mkdir image_dir 0o755; + Kvmuid.mkdir kvmuid_t image_dir 0o755; at_exit (fun () -> if delete_target_directory then ( let cmd = sprintf "rm -rf %s" (quote image_dir) in @@ -227,14 +261,21 @@ object List.iter ( fun ({ target_file = target_file }, meta) -> let meta_filename = target_file ^ ".meta" in - let chan = open_out meta_filename in - output_string chan meta; - close_out chan + Kvmuid.make_file kvmuid_t meta_filename meta ) (List.combine targets metas); (* Return the list of targets. *) targets + method disk_create ?backingfile ?backingformat ?preallocation ?compat + ?clustersize path format size = + Kvmuid.func kvmuid_t ( + fun () -> + let g = new Guestfs.guestfs () in + g#disk_create ?backingfile ?backingformat ?preallocation ?compat + ?clustersize path format size + ) + (* This is called after conversion to write the OVF metadata. *) method create_metadata source targets guestcaps inspect = (* Create the metadata. *) @@ -243,11 +284,9 @@ object (* Write it to the metadata file. *) let dir = esd_mp // esd_uuid // "master" // "vms" // vm_uuid in - mkdir dir 0o755; + Kvmuid.mkdir kvmuid_t dir 0o755; let file = dir // vm_uuid ^ ".ovf" in - let chan = open_out file in - doc_to_chan chan ovf; - close_out chan; + Kvmuid.output kvmuid_t file (fun chan -> doc_to_chan chan ovf); (* Finished, so don't delete the target directory on exit. *) delete_target_directory <- false