libvirt/tools/virt-pki-validate.c

425 lines
16 KiB
C
Raw Normal View History

/*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config.h>
#include "internal.h"
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#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(bool system, const char *path)
{
g_autofree char *cacert = NULL, *cacrl = NULL;
bool ok = true;
if (system) {
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);
} else if (path) {
virNetTLSConfigCustomTrust(path,
&cacert,
&cacrl);
FILE_REQUIRE_EXISTS("TRUST",
path,
_("Checking if custom PKI base dir exists"),
_("Create the dir %1$s"),
path);
FILE_REQUIRE_ACCESS("TRUST",
path,
_("Checking custom PKI base dir access"),
getuid(), getgid(), 0700,
_("The PKI base dir %1$s must not be accessible to other users. Run: chown %2$d.%3$d %4$s; chmod 0700 %5$s"),
path, getuid(), getgid(), path, path);
} else {
g_autofree char *pkipath = virNetTLSConfigUserPKIBaseDir();
virNetTLSConfigUserTrust(&cacert,
&cacrl);
FILE_REQUIRE_EXISTS("TRUST",
pkipath,
_("Checking if user PKI base dir exists"),
_("Create the dir %1$s"),
pkipath);
FILE_REQUIRE_ACCESS("TRUST",
pkipath,
_("Checking user PKI base dir access"),
getuid(), getgid(), 0700,
_("The PKI base dir %1$s must not be accessible to other users. Run: chown %2$d.%3$d %4$s; chmod 0700 %5$s"),
pkipath, getuid(), getgid(), pkipath, pkipath);
}
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);
if (system) {
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);
} else {
FILE_REQUIRE_ACCESS("TRUST",
cacert,
_("Checking CA cert access"),
getuid(), getgid(), 0600,
_("The CA certificate %1$s must not be accessible to other users. As this user, run: chown %2$d.%3$d %4$s; chmod 0600 %5$s"),
cacert, getuid(), getgid(), cacert, cacert);
}
done:
return ok;
}
static bool
virPKIValidateIdentity(bool isServer, bool system, const char *path)
{
g_autofree char *cacert = NULL, *cacrl = NULL;
g_autofree char *cert = NULL, *key = NULL;
bool ok = true;
const char *scope = isServer ? "SERVER" : "CLIENT";
if (system) {
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_CERT_DIR, LIBVIRT_CERT_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_KEY_DIR);
} else if (path) {
virNetTLSConfigCustomTrust(path,
&cacert,
&cacrl);
virNetTLSConfigCustomIdentity(path,
isServer,
&cert,
&key);
} else {
virNetTLSConfigUserTrust(&cacert,
&cacrl);
virNetTLSConfigUserIdentity(isServer,
&cert,
&key);
}
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);
if (system) {
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);
} else {
FILE_REQUIRE_ACCESS(scope,
key,
_("Checking key access"),
getuid(), getgid(), 0600,
isServer ?
_("The server key %1$s must be not be accessible to other users. As this user, run: chown %2$d.%3$d %4$s; chmod 0600 %5$s") :
_("The client key %1$s must be not be accessible to other users. As this user, run: chown %2$d.%3$d %4$s; chmod 0600 %5$s"),
key, getuid(), getgid(), 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);
if (system) {
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);
} else {
FILE_REQUIRE_ACCESS(scope,
cert,
_("Checking cert access"),
getuid(), getgid(), 0600,
isServer ?
_("The server cert %1$s must be restricted to this user. As this user, run: chown %2$d.%3$d %4$s; chmod 0600 %5$s") :
_("The client cert %1$s must be restricted to this user. As this user, run: chown %2$d.%3$d %4$s; chmod 0600 %5$s"),
cert, getuid(), getgid(), 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"
" -s | --system validate system certificates (default)\n"
" -u | --user validate user certificates\n"
" -p DIR | --path DIR validate custom certificate path\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 system = false;
bool user = false;
const char *path = 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' },
{ "system", no_argument, NULL, 's' },
{ "user", no_argument, NULL, 'u' },
{ "path", required_argument, NULL, 'p' },
{ NULL, 0, NULL, 0 },
};
if (virGettextInitialize() < 0)
return EXIT_FAILURE;
while ((arg = getopt_long(argc, argv, "hvsup:", opt, NULL)) != -1) {
switch (arg) {
case 's':
system = true;
break;
case 'u':
user = true;
break;
case 'p':
path = optarg;
break;
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 ((system && user) ||
(system && path) ||
(user && path)) {
g_printerr("--system, --user & --path are mutually exclusive\n");
return EXIT_FAILURE;
}
if (!system && !user && !path)
system = true;
if ((!scope || g_str_equal(scope, "trust")) &&
!virPKIValidateTrust(system, path))
ok = false;
if ((!scope || g_str_equal(scope, "server")) &&
!virPKIValidateIdentity(true, system, path))
ok = false;
if ((!scope || g_str_equal(scope, "client")) &&
!virPKIValidateIdentity(false, system, path))
ok = false;
if (!ok)
return EXIT_FAILURE;
return EXIT_SUCCESS;
}