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.
This commit is contained in:
Richard W.M. Jones
2014-09-22 21:57:57 +01:00
parent c9c5353223
commit 0dfa96c043
7 changed files with 243 additions and 11 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

33
v2v/kvmuid-c.c Normal file
View File

@@ -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 <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <caml/mlvalues.h>
extern int v2v_exit (value rv) __attribute__((noreturn));
int
v2v_exit (value rv)
{
_exit (Int_val (rv));
}

86
v2v/kvmuid.ml Normal file
View File

@@ -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"
)

69
v2v/kvmuid.mli Normal file
View File

@@ -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. *)

View File

@@ -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