diff --git a/docs/manpages/virt-pki-validate.rst b/docs/manpages/virt-pki-validate.rst index 53d5fbe269..cf17bad790 100644 --- a/docs/manpages/virt-pki-validate.rst +++ b/docs/manpages/virt-pki-validate.rst @@ -48,7 +48,7 @@ failure a non-zero status will be set. AUTHOR ====== -Daniel Veillard +Daniel Veillard, Daniel P. Berrangé BUGS @@ -70,7 +70,7 @@ Alternatively, you may report bugs to your software distributor / vendor. COPYRIGHT ========= -Copyright (C) 2006-2012 by Red Hat, Inc. +Copyright (C) 2006-2024 by Red Hat, Inc. LICENSE diff --git a/libvirt.spec.in b/libvirt.spec.in index 244e5e824c..9bff6ef6db 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -1032,8 +1032,6 @@ capabilities of VirtualBox %package client Summary: Client side utilities of the libvirt library Requires: libvirt-libs = %{version}-%{release} -# Needed by virt-pki-validate script. -Requires: gnutls-utils # Ensure smooth upgrades Obsoletes: libvirt-bash-completion < 7.3.0 @@ -2513,7 +2511,7 @@ exit 0 %{mingw32_bindir}/virt-admin.exe %{mingw32_bindir}/virt-xml-validate %{mingw32_bindir}/virt-pki-query-dn.exe -%{mingw32_bindir}/virt-pki-validate +%{mingw32_bindir}/virt-pki-validate.exe %{mingw32_bindir}/libvirt-lxc-0.dll %{mingw32_bindir}/libvirt-qemu-0.dll %{mingw32_bindir}/libvirt-admin-0.dll @@ -2572,7 +2570,7 @@ exit 0 %{mingw64_bindir}/virt-admin.exe %{mingw64_bindir}/virt-xml-validate %{mingw64_bindir}/virt-pki-query-dn.exe -%{mingw64_bindir}/virt-pki-validate +%{mingw64_bindir}/virt-pki-validate.exe %{mingw64_bindir}/libvirt-lxc-0.dll %{mingw64_bindir}/libvirt-qemu-0.dll %{mingw64_bindir}/libvirt-admin-0.dll diff --git a/po/POTFILES b/po/POTFILES index cb73d07904..4bfbb91164 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -391,6 +391,7 @@ tools/virt-host-validate-qemu.c tools/virt-host-validate.c tools/virt-login-shell-helper.c tools/virt-pki-query-dn.c +tools/virt-pki-validate.c tools/virt-validate-common.c tools/vsh-table.c tools/vsh.c diff --git a/tools/meson.build b/tools/meson.build index feb6b7c3ad..a58f5d4175 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -255,13 +255,33 @@ configure_file( install_mode: 'rwxr-xr-x', ) -configure_file( - input: 'virt-pki-validate.in', - output: '@BASENAME@', - configuration: tools_conf, +executable( + 'virt-pki-validate', + tlsconfig_sources + [ + 'virt-validate-common.c', + 'virt-pki-validate.c', + ], + dependencies: [ + glib_dep, + gnutls_dep, + ], + include_directories: [ + libvirt_inc, + src_inc_dir, + top_inc_dir, + util_inc_dir, + rpc_inc_dir, + ], + link_args: ( + libvirt_relro + + libvirt_no_indirect + + libvirt_no_undefined + ), + link_with: [ + libvirt_lib + ], install: true, install_dir: bindir, - install_mode: 'rwxr-xr-x', ) executable( diff --git a/tools/virt-pki-validate.c b/tools/virt-pki-validate.c new file mode 100644 index 0000000000..556664dd29 --- /dev/null +++ b/tools/virt-pki-validate.c @@ -0,0 +1,309 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include "internal.h" + +#include +#include +#include + +#include +#include + +#include "virgettext.h" +#include "virfile.h" +#include "virnettlsconfig.h" +#include "virnettlscert.h" +#include "virutil.h" +#include "virt-validate-common.h" + +static bool +virPKIValidateFile(const char *file, + uid_t owner, + gid_t group, + mode_t mode) +{ + struct stat sb; + if (stat(file, &sb) < 0) + return false; + + if (sb.st_uid != owner || + sb.st_gid != group) + return false; + + return (sb.st_mode & 0777) == mode; +} + +#define FILE_REQUIRE_EXISTS(scope, path, message, hint, ...) \ + do { \ + virValidateCheck(scope, "%s", message); \ + if (!virFileExists(path)) { \ + virValidateFail(VIR_VALIDATE_FAIL, hint, __VA_ARGS__); \ + ok = false; \ + goto done; \ + } else { \ + virValidatePass(); \ + } \ + } while (0) + +#define FILE_REQUIRE_ACCESS(scope, path, message, uid, gid, mode, hint, ...) \ + do { \ + virValidateCheck(scope, "%s", message); \ + if (!virPKIValidateFile(path, uid, gid, mode)) { \ + virValidateFail(VIR_VALIDATE_FAIL, hint, __VA_ARGS__); \ + ok = false; \ + } else { \ + virValidatePass(); \ + } \ + } while (0) + +static bool +virPKIValidateTrust(void) +{ + g_autofree char *cacert = NULL, *cacrl = NULL; + bool ok = true; + + virNetTLSConfigSystemTrust(&cacert, + &cacrl); + + FILE_REQUIRE_EXISTS("TRUST", + LIBVIRT_PKI_DIR, + _("Checking if system PKI dir exists"), + _("The system PKI dir %1$s is usually installed as part of the base filesystem or openssl packages"), + LIBVIRT_PKI_DIR); + + FILE_REQUIRE_ACCESS("TRUST", + LIBVIRT_PKI_DIR, + _("Checking system PKI dir access"), + 0, 0, 0755, + _("The system PKI dir %1$s must be accessible to all users. As root, run: chown root.root; chmod 0755 %2$s"), + LIBVIRT_PKI_DIR, LIBVIRT_PKI_DIR); + + + FILE_REQUIRE_EXISTS("TRUST", + LIBVIRT_CACERT_DIR, + _("Checking if system CA dir exists"), + _("The system CA dir %1$s is usually installed as part of the base filesystem or openssl packages"), + LIBVIRT_CACERT_DIR); + + FILE_REQUIRE_ACCESS("TRUST", + LIBVIRT_CACERT_DIR, + _("Checking system CA dir access"), + 0, 0, 0755, + _("The system CA dir %1$s must be accessible to all users. As root, run: chown root.root; chmod 0755 %2$s"), + LIBVIRT_CACERT_DIR, LIBVIRT_CACERT_DIR); + + FILE_REQUIRE_EXISTS("TRUST", + cacert, + _("Checking if CA cert exists"), + _("The machine cannot act as a client or server. See https://libvirt.org/kbase/tlscerts.html#setting-up-a-certificate-authority-ca on how to install %1$s"), + cacert); + + FILE_REQUIRE_ACCESS("TRUST", + cacert, + _("Checking CA cert access"), + 0, 0, 0644, + _("The CA certificate %1$s must be accessible to all users. As root run: chown root.root %2$s; chmod 0644 %3$s"), + cacert, cacert, cacert); + + done: + return ok; +} + +static bool +virPKIValidateIdentity(bool isServer) +{ + g_autofree char *cacert = NULL, *cacrl = NULL; + g_autofree char *cert = NULL, *key = NULL; + bool ok = true; + const char *scope = isServer ? "SERVER" : "CLIENT"; + + virNetTLSConfigSystemTrust(&cacert, + &cacrl); + virNetTLSConfigSystemIdentity(isServer, + &cert, + &key); + + FILE_REQUIRE_EXISTS(scope, + LIBVIRT_CERT_DIR, + _("Checking if system cert dir exists"), + _("The system cert dir %1$s is usually installed as part of the libvirt package"), + LIBVIRT_CERT_DIR); + + FILE_REQUIRE_ACCESS(scope, + LIBVIRT_CERT_DIR, + _("Checking system cert dir access"), + 0, 0, 0755, + _("The system cert dir %1$s must be accessible to all users. As root, run: chown root.root; chmod 0755 %2$s"), + LIBVIRT_PKI_DIR, LIBVIRT_PKI_DIR); + + FILE_REQUIRE_EXISTS(scope, + LIBVIRT_KEY_DIR, + _("Checking if system key dir exists"), + _("The system key dir %1$s is usually installed as part of the libvirt package"), + LIBVIRT_KEY_DIR); + + FILE_REQUIRE_ACCESS(scope, + LIBVIRT_KEY_DIR, + _("Checking system key dir access"), + 0, 0, 0755, + _("The system key dir %1$s must be accessible to all users. As root, run: chown root.root; chmod 0755 %2$s"), + LIBVIRT_KEY_DIR, LIBVIRT_PKI_DIR); + + FILE_REQUIRE_EXISTS(scope, + key, + _("Checking if key exists"), + isServer ? + _("The machine cannot act as a server. See https://libvirt.org/kbase/tlscerts.html#issuing-server-certificates on how to regenerate %1$s") : + _("The machine cannot act as a client. See https://libvirt.org/kbase/tlscerts.html#issuing-client-certificates on how to regenerate %1$s"), + key); + + FILE_REQUIRE_ACCESS(scope, + key, + _("Checking key access"), + 0, 0, isServer ? 0600 : 0644, + isServer ? + _("The server key %1$s must not be accessible to unprivileged users. As root run: chown root.root %2$s; chmod 0600 %3$s") : + _("The client key %1$s must be accessible to all users. As root run: chown root.root %2$s; chmod 0644 %3$s"), + key, key, key); + + FILE_REQUIRE_EXISTS(scope, + cert, + _("Checking if cert exists"), + isServer ? + _("The machine cannot act as a server. See https://libvirt.org/kbase/tlscerts.html#issuing-server-certificates on how to regenerate %1$s") : + _("The machine cannot act as a client. See https://libvirt.org/kbase/tlscerts.html#issuing-client-certificates on how to regenerate %1$s"), + cert); + + FILE_REQUIRE_ACCESS(scope, + cert, + _("Checking cert access"), + 0, 0, 0644, + isServer ? + _("The server cert %1$s must be accessible to all users. As root run: chown root.root %2$s; chmod 0644 %3$s") : + _("The client cert %1$s must be accessible to all users. As root run: chown root.root %2$s; chmod 0644 %3$s"), + cert, cert, cert); + + virValidateCheck(scope, "%s", _("Checking cert properties")); + + if (virNetTLSCertSanityCheck(isServer, + cacert, + cert) < 0) { + virValidateFail(VIR_VALIDATE_FAIL, "%s", + virGetLastErrorMessage()); + ok = false; + } else { + virValidatePass(); + } + + if (isServer) { + gnutls_x509_crt_t crt; + + virValidateCheck(scope, "%s", _("Checking cert hostname match")); + + if (!(crt = virNetTLSCertLoadFromFile(cert, true))) { + virValidateFail(VIR_VALIDATE_FAIL, + _("Unable to load %1$s: %2$s"), + cert, virGetLastErrorMessage()); + } else { + g_autofree char *hostname = virGetHostname(); + int ret = gnutls_x509_crt_check_hostname(crt, hostname); + gnutls_x509_crt_deinit(crt); + if (!ret) { + /* Only warning, since there can be valid reasons for mis-match */ + virValidateFail(VIR_VALIDATE_WARN, + _("Certificate %1$s owner does not match the hostname %2$s"), + cert, hostname); + ok = false; + } else { + virValidatePass(); + } + } + } + + done: + return ok; +} + + +static void +print_usage(const char *progname, + FILE *out) +{ + fprintf(out, + _("Usage:\n" + " %1$s { -v | -h } [TRUST|SERVER|CLIENT]\n" + "\n" + "Validate TLS certificate configuration\n" + "\n" + "options:\n" + " -h | --help display this help and exit\n" + " -v | --version output version information and exit\n"), + progname); +} + +int main(int argc, char **argv) +{ + const char *scope = NULL; + bool quiet = false; + int arg = 0; + bool ok = true; + const char *progname = argv[0]; + struct option opt[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 }, + }; + + if (virGettextInitialize() < 0) + return EXIT_FAILURE; + + while ((arg = getopt_long(argc, argv, "hvsup:", opt, NULL)) != -1) { + switch (arg) { + case 'v': + printf("%s\n", PACKAGE_VERSION); + return EXIT_SUCCESS; + + case 'h': + print_usage(progname, stdout); + return EXIT_SUCCESS; + + case 'q': + quiet = true; + break; + + case '?': + default: + print_usage(progname, stderr); + return EXIT_FAILURE; + } + } + + if ((argc - optind) > 2) { + fprintf(stderr, _("%1$s: too many command line arguments\n"), argv[0]); + print_usage(progname, stderr); + return EXIT_FAILURE; + } + + if (argc > 1) + scope = argv[optind]; + + virValidateSetQuiet(quiet); + + if ((!scope || g_str_equal(scope, "trust")) && + !virPKIValidateTrust()) + ok = false; + if ((!scope || g_str_equal(scope, "server")) && + !virPKIValidateIdentity(true)) + ok = false; + if ((!scope || g_str_equal(scope, "client")) && + !virPKIValidateIdentity(false)) + ok = false; + + if (!ok) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/tools/virt-pki-validate.in b/tools/virt-pki-validate.in deleted file mode 100644 index c77daa9862..0000000000 --- a/tools/virt-pki-validate.in +++ /dev/null @@ -1,295 +0,0 @@ -#!/bin/sh -# -# This shell script checks the TLS certificates and options needed -# for the secure client/server support of libvirt as documented at -# https://libvirt.org/kbase/tlscerts.html -# -# Copyright (C) 2009-2013 Red Hat, Inc. -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library. If not, see -# . -# -# Daniel Veillard -# - -case $1 in - -h | --h | --he | --hel | --help) - cat <&2 - exit 1 ;; -esac - -if test $# != 0; then - echo "$0: unrecognized argument '$1'" >&2 - exit 1 -fi - -USER=`who am i | awk '{ print $1 }'` -SERVER=1 -CLIENT=1 -PORT=16514 -# -# First get certtool -# -CERTOOL=`which certtool 2>/dev/null` -if [ ! -x "$CERTOOL" ] -then - echo "Could not locate the certtool program" - echo "make sure the gnutls-utils (or gnutls-bin) package is installed" - exit 1 -fi -echo Found "$CERTOOL" - -# -# Check the directory structure -# -SYSCONFDIR="@sysconfdir@" -PKI="$SYSCONFDIR/pki" -if [ ! -d "$PKI" ] -then - echo the $PKI directory is missing, it is usually - echo installed as part of the filesystem or openssl packages - exit 1 -fi - -if [ ! -r "$PKI" ] -then - echo the $PKI directory is not readable by $USER - echo "as root do: chmod a+rx $PKI" - exit 1 -fi -if [ ! -x "$PKI" ] -then - echo the $PKI directory is not listable by $USER - echo "as root do: chmod a+rx $PKI" - exit 1 -fi - -CA="$PKI/CA" -if [ ! -d "$CA" ] -then - echo the $CA directory is missing, it is usually - echo installed as part of the or openssl package - exit 1 -fi - -if [ ! -r "$CA" ] -then - echo the $CA directory is not readable by $USER - echo "as root do: chmod a+rx $CA" - exit 1 -fi -if [ ! -x "$CA" ] -then - echo the $CA directory is not listable by $USER - echo "as root do: chmod a+rx $CA" - exit 1 -fi - -LIBVIRT="$PKI/libvirt" -if [ ! -d "$LIBVIRT" ] -then - echo the $LIBVIRT directory is missing, it is usually - echo installed by the libvirt package - echo "as root do: mkdir -m 755 $LIBVIRT ; chown root:root $LIBVIRT" - exit 1 -fi - -if [ ! -r "$LIBVIRT" ] -then - echo the $LIBVIRT directory is not readable by $USER - echo "as root do: chown root:root $LIBVIRT ; chmod 755 $LIBVIRT" - exit 1 -fi -if [ ! -x "$LIBVIRT" ] -then - echo the $LIBVIRT directory is not listable by $USER - echo "as root do: chown root:root $LIBVIRT ; chmod 755 $LIBVIRT" - exit 1 -fi - -LIBVIRTP="$LIBVIRT/private" -if [ ! -d "$LIBVIRTP" ] -then - echo the $LIBVIRTP directory is missing, it is usually - echo installed by the libvirt package - echo "as root do: mkdir -m 755 $LIBVIRTP ; chown root:root $LIBVIRTP" - exit 1 -fi - -if [ ! -r "$LIBVIRTP" ] -then - echo the $LIBVIRTP directory is not readable by $USER - echo "as root do: chown root:root $LIBVIRTP ; chmod 755 $LIBVIRTP" - exit 1 -fi -if [ ! -x "$LIBVIRTP" ] -then - echo the $LIBVIRTP directory is not listable by $USER - echo "as root do: chown root:root $LIBVIRTP ; chmod 755 $LIBVIRTP" - exit 1 -fi - -# -# Now check the certificates -# First the CA certificate -# -if [ ! -f "$CA/cacert.pem" ] -then - echo the CA certificate $CA/cacert.pem is missing while it - echo should be installed on both client and servers - echo "see https://libvirt.org/kbase/tlscerts.html#setting-up-a-certificate-authority-ca" - echo on how to install it - exit 1 -fi -if [ ! -r "$CA/cacert.pem" ] -then - echo the CA certificate $CA/cacert.pem is not readable by $USER - echo "as root do: chmod 644 $CA/cacert.pem" - exit 1 -fi -sed_get_org='/Issuer:/ { - s/.*Issuer:.*CN=// - s/,.*// - p -}' -ORG=`"$CERTOOL" -i --infile "$CA/cacert.pem" | sed -n "$sed_get_org"` -if [ "$ORG" = "" ] -then - echo the CA certificate $CA/cacert.pem does not define the organization - echo it should probably regenerated - echo "see https://libvirt.org/kbase/tlscerts.html#setting-up-a-certificate-authority-ca" - echo on how to regenerate it - exit 1 -fi -echo Found CA certificate $CA/cacert.pem for $ORG - -# Second the client certificates - -if [ -f "$LIBVIRT/clientcert.pem" ] -then - if [ ! -r "$LIBVIRT/clientcert.pem" ] - then - echo Client certificate $LIBVIRT/clientcert.pem should be world readable - echo "as root do: chown root:root $LIBVIRT/clientcert.pem ; chmod 644 $LIBVIRT/clientcert.pem" - else - C_ORG=`"$CERTOOL" -i --infile "$LIBVIRT/clientcert.pem" | grep Subject: | sed 's+.*O=\([^,]*\).*+\1+'` - if [ "$ORG" != "$C_ORG" ] - then - echo The CA certificate and the client certificate do not match - echo CA organization: $ORG - echo Client organization: $C_ORG - fi - CLIENT=`"$CERTOOL" -i --infile "$LIBVIRT/clientcert.pem" | grep Subject: | sed 's+.*CN=\(.[^,]*\).*+\1+'` - echo Found client certificate $LIBVIRT/clientcert.pem for $CLIENT - if [ ! -e "$LIBVIRTP/clientkey.pem" ] - then - echo Missing client private key $LIBVIRTP/clientkey.pem - else - echo Found client private key $LIBVIRTP/clientkey.pem - OWN=`ls -l "$LIBVIRTP/clientkey.pem" | awk '{ print $3 }'` - # The substr($1, 1, 10) gets rid of acl and xattr markers - MOD=`ls -l "$LIBVIRTP/clientkey.pem" | awk '{ print substr($1, 1, 10) }'` - if [ "$OWN" != "root" ] - then - echo The client private key should be owned by root - echo "as root do: chown root $LIBVIRTP/clientkey.pem" - fi - if [ "$MOD" != "-rw-r--r--" ] - then - echo The client private key need to be read by client tools - echo "as root do: chmod 644 $LIBVIRTP/clientkey.pem" - fi - fi - - fi -else - echo Did not find "$LIBVIRT/clientcert.pem" client certificate - echo The machine cannot act as a client - echo "see https://libvirt.org/kbase/tlscerts.html#issuing-client-certificates" - echo on how to regenerate it - CLIENT=0 -fi - -# Third the server certificates - -if [ -f "$LIBVIRT/servercert.pem" ] -then - if [ ! -r "$LIBVIRT/servercert.pem" ] - then - echo Server certificate $LIBVIRT/servercert.pem should be world readable - echo "as root do: chown root:root $LIBVIRT/servercert.pem ; chmod 644 $LIBVIRT/servercert.pem" - else - S_ORG=`"$CERTOOL" -i --infile "$LIBVIRT/servercert.pem" | grep Subject: | sed 's+.*O=\([^,]*\).*+\1+'` - if [ "$ORG" != "$S_ORG" ] - then - echo The CA certificate and the server certificate do not match - echo CA organization: $ORG - echo Server organization: $S_ORG - fi - S_HOST=`"$CERTOOL" -i --infile "$LIBVIRT/servercert.pem" | grep Subject: | sed 's+.*CN=\([^,]*\).*+\1+'` - if test "$S_HOST" != "`hostname -s`" && test "$S_HOST" != "`hostname`" - then - echo The server certificate does not seem to match the host name - echo hostname: '"'`hostname`'"' - echo Server certificate CN: '"'$S_HOST'"' - fi - echo Found server certificate $LIBVIRT/servercert.pem for $S_HOST - if [ ! -e "$LIBVIRTP/serverkey.pem" ] - then - echo Missing server private key $LIBVIRTP/serverkey.pem - else - echo Found server private key $LIBVIRTP/serverkey.pem - OWN=`ls -l "$LIBVIRTP/serverkey.pem" | awk '{ print $3 }'` - # The substr($1, 1, 10) gets rid of acl and xattr markers - MOD=`ls -l "$LIBVIRTP/serverkey.pem" | awk '{ print substr($1, 1, 10) }'` - if [ "$OWN" != "root" ] - then - echo The server private key should be owned by root - echo "as root do: chown root $LIBVIRTP/serverkey.pem" - fi - if [ "$MOD" != "-rw-------" ] - then - echo The server private key need to be read only by root - echo "as root do: chmod 600 $LIBVIRTP/serverkey.pem" - fi - fi - - fi -else - echo Did not find $LIBVIRT/servercert.pem server certificate - echo The machine cannot act as a server - echo "see https://libvirt.org/kbase/tlscerts.html#issuing-server-certificates" - echo on how to regenerate it - SERVER=0 -fi - -exit 0