qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH v4] block: Add support for Secure Shell (ssh) bl


From: Stefan Hajnoczi
Subject: Re: [Qemu-devel] [PATCH v4] block: Add support for Secure Shell (ssh) block device.
Date: Thu, 28 Mar 2013 11:47:32 +0100
User-agent: Mutt/1.5.21 (2010-09-15)

On Wed, Mar 27, 2013 at 03:57:29PM +0000, Richard W.M. Jones wrote:
> From: "Richard W.M. Jones" <address@hidden>
> 
>   qemu-system-x86_64 -drive file=ssh://hostname/some/image
> 
> QEMU will ssh into 'hostname' and open '/some/image' which is made
> available as a standard block device.
> 
> You can specify a username (ssh://address@hidden/...) and/or a port number
> (ssh://host:port/...).  You can also use an alternate syntax using
> properties (file.user, file.host, file.port, file.path).
> 
> Current limitations:
> 
> - Authentication must be done without passwords or passphrases, using
>   ssh-agent.  Other authentication methods are not supported.
> 
> - Uses a single connection, instead of concurrent AIO with multiple
>   SSH connections.
> 
> This is implemented using libssh2 on the client side.  The server just
> requires a regular ssh daemon with sftp-server support.  Most ssh
> daemons on Unix/Linux systems will work out of the box.
> 
> Signed-off-by: Richard W.M. Jones <address@hidden>
> Cc: Stefan Hajnoczi <address@hidden>
> Cc: Kevin Wolf <address@hidden>
> ---
>  block/Makefile.objs |   1 +
>  block/ssh.c         | 878 
> ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  configure           |  47 +++
>  qemu-doc.texi       |  40 +++
>  qemu-options.hx     |  12 +
>  5 files changed, 978 insertions(+)
>  create mode 100644 block/ssh.c

Please run qemu-iotests, see tests/qemu-iotests/check.  For example,
with NBD:

  $ cd tests/qemu-iotests
  $ QEMU_PROG=$HOME/qemu/x86_64-softmmu/qemu-system-x86_64 
PATH=$HOME/qemu:$PATH \
    ./check -nbd

A patch will be required to add -ssh support to ./check.

> +/* Wrappers around error_report which make sure to dump as much
> + * information from libssh2 as possible.
> + */
> +static void
> +session_error_report(BDRVSSHState *s, const char *fs, ...)
> +{
> +    va_list args;
> +
> +    va_start(args, fs);
> +
> +    if ((s)->session) {
> +        char *ssh_err;
> +        int ssh_err_len = sizeof ssh_err;

This variable is unused and initialization to sizeof ssh_err seems
wrong.  Please drop it and pass NULL to libssh2_session_last_error(3).

> +        int ssh_err_code;
> +
> +        libssh2_session_last_error((s)->session,
> +                                   &ssh_err, &ssh_err_len, 0);
> +        /* This is not an errno.  See <libssh2.h>. */
> +        ssh_err_code = libssh2_session_last_errno((s)->session);
> +
> +        error_vprintf(fs, args);
> +        error_printf(": %s (libssh2 error code: %d)", ssh_err, ssh_err_code);
> +    }
> +    else {

QEMU tends to use } else { on a single line.

> +        error_vprintf(fs, args);
> +    }

In fact this else statement can be avoided:

error_vprintf(fs, args);
if (s->session) {
    ...
    error_printf(": %s (libssh2 error code: %d)", ssh_err, ssh_err_code);
}

> +
> +    va_end(args);
> +    error_printf("\n");
> +}
> +
> +static void
> +sftp_error_report(BDRVSSHState *s, const char *fs, ...)
> +{
> +    va_list args;
> +
> +    va_start(args, fs);
> +
> +    if ((s)->sftp) {
> +        char *ssh_err;
> +        int ssh_err_len = sizeof ssh_err;
> +        int ssh_err_code;
> +        unsigned long sftp_err_code;
> +
> +        libssh2_session_last_error((s)->session,
> +                                   &ssh_err, &ssh_err_len, 0);
> +        /* This is not an errno.  See <libssh2.h>. */
> +        ssh_err_code = libssh2_session_last_errno((s)->session);
> +        /* See <libssh2_sftp.h>. */
> +        sftp_err_code = libssh2_sftp_last_error((s)->sftp);
> +
> +        error_vprintf(fs, args);
> +        error_printf(": %s (libssh2 error code: %d, sftp error code: %lu)",
> +                     ssh_err, ssh_err_code, sftp_err_code);
> +    }
> +    else {
> +        error_vprintf(fs, args);
> +    }

Same comments apply to sftp_error_report().

> +
> +    va_end(args);
> +    error_printf("\n");
> +}
> +
> +#ifndef _WIN32
> +static const char *get_username(void)
> +{
> +    struct passwd *pwd;
> +
> +    pwd = getpwuid(geteuid ());
> +    if (!pwd) {
> +        error_report("failed to get current user name");
> +        return NULL;
> +    }
> +    return pwd->pw_name;
> +}
> +#else
> +static const char *get_username(void)
> +{
> +    error_report("on Windows, specify a remote user name in the URI");
> +    return NULL;
> +}
> +#endif

This is ugly since it's platform-specific and uses getpwuid(3) which is
not reentrant.

Does ssh(1) even use getpwuid(geteuid()) or does it check .ssh/config
and then getenv("USER")?  Perhaps we can just getenv("USER")?

> +
> +static int parse_uri(const char *filename, QDict *options)

Use Error **errp argument instead of error_report()?

> +{
> +    URI *uri;
> +    char *host = NULL;

Unused variable.

> +static int check_host_key(BDRVSSHState *s, const char *host, int port)
> +{
> +    int ret;
> +    const char *home;
> +    char *knh_file = NULL;
> +    LIBSSH2_KNOWNHOSTS *knh = NULL;
> +    const char *fingerprint;
> +    size_t len;
> +    int type;
> +
> +    knh = libssh2_knownhost_init(s->session);
> +    if (!knh) {
> +        ret = -EINVAL;
> +        session_error_report(s, "failed to initialize known hosts support");
> +        goto out;
> +    }
> +
> +    home = getenv("HOME");
> +    if (home) {
> +        knh_file = g_strdup_printf("%s/.ssh/known_hosts", home);
> +    } else {
> +        knh_file = g_strdup_printf("/root/.ssh/known_hosts");
> +    }

Windows support?

> +
> +    /* Read all known hosts from OpenSSH-style known_hosts file. */
> +    libssh2_knownhost_readfile(knh, knh_file, 
> LIBSSH2_KNOWNHOST_FILE_OPENSSH);
> +
> +    fingerprint = libssh2_session_hostkey(s->session, &len, &type);
> +    if (fingerprint) {
> +        struct libssh2_knownhost *found;
> +        int r;
> +
> +        r = libssh2_knownhost_checkp(knh, host, port, fingerprint, len,
> +                                     LIBSSH2_KNOWNHOST_TYPE_PLAIN|
> +                                     LIBSSH2_KNOWNHOST_KEYENC_RAW,
> +                                     &found);
> +        switch (r) {
> +        case LIBSSH2_KNOWNHOST_CHECK_MATCH:
> +            /* OK */
> +            DPRINTF("host key OK: %s", found->key);
> +            break;
> +        case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
> +            ret = -EINVAL;
> +            session_error_report(s, "host key does not match the one in 
> known_hosts (found key %s)",
> +                                 found->key);

Does the user know the offending known_hosts line?  ssh(1) normally says
something like "Mismatch with line ~/.ssh/known_hosts:35" so you know
which hostkey to drop if you wish to proceed.

> +            goto out;
> +        case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
> +            ret = -EINVAL;
> +            session_error_report(s, "no host key was found in known_hosts");
> +            goto out;

ssh(1) prompts the user to accept the hostkey.  When QEMU fails like
this the user needs to run ssh(1) first to populate known_hosts?

> @@ -1166,6 +1171,7 @@ echo "  --disable-glusterfs      disable GlusterFS 
> backend"
>  echo "  --enable-gcov            enable test coverage analysis with gcov"
>  echo "  --gcov=GCOV              use specified gcov [$gcov_tool]"
>  echo "  --enable-tpm             enable TPM support"
> +echo "  --enable-libssh2         enable ssh block device support"

Most options seem to also document --disable-FOO.  Please do that.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]