This adds a customize option:
virt-customize --ssh-inject USER[=KEY]
virt-builder --ssh-inject USER[=KEY]
virt-sysprep --ssh-inject USER[=KEY]
In each case this either injects the current (host) user's ssh pubkey
into the guest user USER (adding it to ~USER/.ssh/authorized_keys in
the guest), or you can specify a particular key.
For example:
virt-builder fedora-20 --ssh-inject root
will add the local user's ssh pubkey into the root account of the
newly created guest. Or:
virt-customize -a disk.img \
--ssh-inject 'mary=ssh-rsa AAAA.... mary@localhost'
adds the given ssh pubkey to mary's account in the guest.
This doesn't set the SELinux labels correctly on newly created files
and directories, so you have to use --selinux-relabel (probably we
should fix this as part of the general effort to fix SELinux
relabelling). However it should preserve the labels if the
~/.ssh/authorized_keys file already exists.
---
builder/cmdline.ml | 4 +--
customize/customize_run.ml | 81 ++++++++++++++++++++++++++++++++++++++++++++++
generator/customize.ml | 18 +++++++++++
3 files changed, 101 insertions(+), 2 deletions(-)
diff --git a/builder/cmdline.ml b/builder/cmdline.ml
index c0584f7..e21d5bb 100644
--- a/builder/cmdline.ml
+++ b/builder/cmdline.ml
@@ -306,8 +306,8 @@ read the man page virt-builder(1).
| `Command _ | `InstallPackages _ | `Script _ | `Update -> true
| `Delete _ | `Edit _ | `FirstbootCommand _ | `FirstbootPackages _
| `FirstbootScript _ | `Hostname _ | `Link _ | `Mkdir _
- | `Password _ | `RootPassword _ | `Scrub _ | `Timezone _ | `Upload _
- | `Write _ | `Chmod _ -> false
+ | `Password _ | `RootPassword _ | `Scrub _ | `SSHInject _
+ | `Timezone _ | `Upload _ | `Write _ | `Chmod _ -> false
) ops.ops in
if requires_execute_on_guest then
error (f_"sorry, cannot run commands on a guest with a different
architecture");
diff --git a/customize/customize_run.ml b/customize/customize_run.ml
index 51b218a..099d611 100644
--- a/customize/customize_run.ml
+++ b/customize/customize_run.ml
@@ -135,6 +135,81 @@ exec >>%s 2>&1
error (f_"sorry, don't know how to use --update with the '%s'
package manager") pm
in
+ (* Find the local [on the host] user's SSH public key. See
+ * ssh-copy-id(1) default_ID_file for rationale.
+ *)
+ let pubkey_re = Str.regexp "^id.*\\.pub$" in
+ let pubkey_ignore_re = Str.regexp ".*-cert\\.pub$" in
+
+ let local_user_ssh_pubkey () =
+ let home_dir =
+ try getenv "HOME"
+ with Not_found ->
+ error (f_"ssh-inject: $HOME environment variable is not set") in
+ let ssh_dir = home_dir // ".ssh" in
+ let files = Sys.readdir ssh_dir in
+ let files = Array.to_list files in
+ let files = List.filter (
+ fun file ->
+ Str.string_match pubkey_re file 0 &&
+ not (Str.string_match pubkey_ignore_re file 0)
+ ) files in
+ if files = [] then
+ error (f_"ssh-inject: no public key file found in %s") ssh_dir;
+
+ (* Newest file. *)
+ let files = List.map (
+ fun file ->
+ let file = ssh_dir // file in
+ let stat = stat file in
+ (file, stat.st_mtime)
+ ) files in
+ let files = List.sort (fun (_,m1) (_,m2) -> compare m2 m1) files in
+ let newest_file = fst (List.hd files) in
+
+ (* Read and return the public key. *)
+ let key = read_whole_file newest_file in
+ if key = "" then
+ error (f_"ssh-inject: public key file (%s) is empty") newest_file;
+
+ key
+
+ (* Inject SSH key, where possible. *)
+ and do_ssh_inject username key =
+ match g#inspect_get_type root with
+ | "linux" | "freebsd" | "netbsd" | "openbsd"
| "hurd" ->
+ (* If the key doesn't have \n at the end, add it. *)
+ let len = String.length key in
+ if len < 1 then
+ error (f_"ssh-inject: key is an empty string");
+ let key = if key.[len-1] = '\n' then key else key ^ "\n" in
+
+ (* Get user's home directory. *)
+ g#aug_init "/" 0;
+ let expr = sprintf "/files/etc/passwd/%s/home" username in
+ let home_dir = g#aug_get expr in
+ g#aug_close ();
+
+ (* Create ~user/.ssh if it doesn't exist. *)
+ let ssh_dir = sprintf "%s/.ssh" home_dir in
+ if not (g#exists ssh_dir) then (
+ g#mkdir ssh_dir;
+ g#chmod 0o755 ssh_dir
+ );
+
+ (* Create ~user/.ssh/authorized_keys if it doesn't exist. *)
+ let auth_keys = sprintf "%s/authorized_keys" ssh_dir in
+ if not (g#exists auth_keys) then (
+ g#touch auth_keys;
+ g#chmod 0o644 auth_keys
+ );
+
+ (* Append the key. *)
+ g#write_append auth_keys key
+ | typ ->
+ warning (f_"don't know how to inject SSH keys into %s guests") typ
+ in
+
(* Set the random seed. *)
msg (f_"Setting a random seed");
if not (Random_seed.set_random_seed g root) then
@@ -232,6 +307,12 @@ exec >>%s 2>&1
msg (f_"Scrubbing: %s") path;
g#scrub_file path
+ | `SSHInject user_key ->
+ let user, key = string_split "=" user_key in
+ let key = if key = "" then local_user_ssh_pubkey () else key in
+ msg (f_"SSH key inject: %s") user;
+ do_ssh_inject user key
+
| `Timezone tz ->
msg (f_"Setting the timezone: %s") tz;
if not (Timezone.set_timezone g root tz) then
diff --git a/generator/customize.ml b/generator/customize.ml
index 8642a54..ef91b62 100644
--- a/generator/customize.ml
+++ b/generator/customize.ml
@@ -260,6 +260,24 @@ It cannot delete directories, only regular files.
=back";
};
+ { op_name = "ssh-inject";
+ op_type = String "USER[=KEY]";
+ op_discrim = "`SSHInject";
+ op_shortdesc = "Inject a public key into the guest";
+ op_pod_longdesc = "\
+Inject an ssh key so the given C<USER> will be able to log in over
+ssh without supplying a password.
+
+If just I<--ssh-inject> C<USER> is given then we look in the
I<current>
+user's C<~/.ssh> directory to find the default public ID file. That
+key is uploaded.
+
+You can also upload a specific key using I<--ssh-inject>
C<\"USER=KEY\">
+
+In either case, this will create the C<~USER/.ssh> directory if required,
+and create or append the key to C<~USER/.ssh/authorized_keys>"
+ };
+
{ op_name = "timezone";
op_type = String "TIMEZONE";
op_discrim = "`Timezone";
--
2.0.4