diff --git a/lib/backend.py b/lib/backend.py index 4e715460882b3d11dbab40e71f24f5cb14a70a6a..c38380da5fd203b57208e06641802c22a57df9e9 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -196,6 +196,8 @@ def _BuildUploadFileList(): constants.SSH_KNOWN_HOSTS_FILE, constants.VNC_PASSWORD_FILE, constants.RAPI_CERT_FILE, + constants.SPICE_CERT_FILE, + constants.SPICE_CACERT_FILE, constants.RAPI_USERS_FILE, constants.CONFD_HMAC_KEY, constants.CLUSTER_DOMAIN_SECRET_FILE, @@ -411,6 +413,8 @@ def LeaveCluster(modify_ssh_setup): try: utils.RemoveFile(constants.CONFD_HMAC_KEY) utils.RemoveFile(constants.RAPI_CERT_FILE) + utils.RemoveFile(constants.SPICE_CERT_FILE) + utils.RemoveFile(constants.SPICE_CACERT_FILE) utils.RemoveFile(constants.NODED_CERT_FILE) except: # pylint: disable=W0702 logging.exception("Error while removing cluster secrets") diff --git a/lib/bootstrap.py b/lib/bootstrap.py index 733a8ba98a27eab9b88e7062fc41e76629ded3c6..85c2251ba3fb4ffb798e61dce87bd1e655f91daf 100644 --- a/lib/bootstrap.py +++ b/lib/bootstrap.py @@ -88,10 +88,14 @@ def GenerateHmacKey(file_name): backup=True) -def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_confd_hmac_key, - new_cds, rapi_cert_pem=None, cds=None, +def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_spice_cert, + new_confd_hmac_key, new_cds, + rapi_cert_pem=None, spice_cert_pem=None, + spice_cacert_pem=None, cds=None, nodecert_file=constants.NODED_CERT_FILE, rapicert_file=constants.RAPI_CERT_FILE, + spicecert_file=constants.SPICE_CERT_FILE, + spicecacert_file=constants.SPICE_CACERT_FILE, hmackey_file=constants.CONFD_HMAC_KEY, cds_file=constants.CLUSTER_DOMAIN_SECRET_FILE): """Updates the cluster certificates, keys and secrets. @@ -100,18 +104,29 @@ def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_confd_hmac_key, @param new_cluster_cert: Whether to generate a new cluster certificate @type new_rapi_cert: bool @param new_rapi_cert: Whether to generate a new RAPI certificate + @type new_spice_cert: bool + @param new_spice_cert: Whether to generate a new SPICE certificate @type new_confd_hmac_key: bool @param new_confd_hmac_key: Whether to generate a new HMAC key @type new_cds: bool @param new_cds: Whether to generate a new cluster domain secret @type rapi_cert_pem: string @param rapi_cert_pem: New RAPI certificate in PEM format + @type spice_cert_pem: string + @param spice_cert_pem: New SPICE certificate in PEM format + @type spice_cacert_pem: string + @param spice_cacert_pem: Certificate of the CA that signed the SPICE + certificate, in PEM format @type cds: string @param cds: New cluster domain secret @type nodecert_file: string @param nodecert_file: optional override of the node cert file path @type rapicert_file: string @param rapicert_file: optional override of the rapi cert file path + @type spicecert_file: string + @param spicecert_file: optional override of the spice cert file path + @type spicecacert_file: string + @param spicecacert_file: optional override of the spice CA cert file path @type hmackey_file: string @param hmackey_file: optional override of the hmac key file path @@ -145,6 +160,31 @@ def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_confd_hmac_key, logging.debug("Generating new RAPI certificate at %s", rapicert_file) utils.GenerateSelfSignedSslCert(rapicert_file) + # SPICE + spice_cert_exists = os.path.exists(spicecert_file) + spice_cacert_exists = os.path.exists(spicecacert_file) + if spice_cert_pem: + # spice_cert_pem implies also spice_cacert_pem + logging.debug("Writing SPICE certificate at %s", spicecert_file) + utils.WriteFile(spicecert_file, data=spice_cert_pem, backup=True) + logging.debug("Writing SPICE CA certificate at %s", spicecacert_file) + utils.WriteFile(spicecacert_file, data=spice_cacert_pem, backup=True) + elif new_spice_cert or not spice_cert_exists: + if spice_cert_exists: + utils.CreateBackup(spicecert_file) + if spice_cacert_exists: + utils.CreateBackup(spicecacert_file) + + logging.debug("Generating new self-signed SPICE certificate at %s", + spicecert_file) + (_, cert_pem) = utils.GenerateSelfSignedSslCert(spicecert_file) + + # Self-signed certificate -> the public certificate is also the CA public + # certificate + logging.debug("Writing the public certificate to %s", + spicecert_file) + utils.io.WriteFile(spicecacert_file, mode=0400, data=cert_pem) + # Cluster domain secret if cds: logging.debug("Writing cluster domain secret to %s", cds_file) @@ -166,7 +206,7 @@ def _InitGanetiServerSetup(master_name): """ # Generate cluster secrets - GenerateClusterCrypto(True, False, False, False) + GenerateClusterCrypto(True, False, False, False, False) result = utils.RunCmd([constants.DAEMON_UTIL, "start", constants.NODED]) if result.failed: @@ -557,6 +597,8 @@ def SetupNodeDaemon(cluster_name, node, ssh_key_check): # either by being constants or by the checks above sshrunner.CopyFileToNode(node, constants.NODED_CERT_FILE) sshrunner.CopyFileToNode(node, constants.RAPI_CERT_FILE) + sshrunner.CopyFileToNode(node, constants.SPICE_CERT_FILE) + sshrunner.CopyFileToNode(node, constants.SPICE_CACERT_FILE) sshrunner.CopyFileToNode(node, constants.CONFD_HMAC_KEY) mycommand = ("%s stop-all; %s start %s -b %s" % (constants.DAEMON_UTIL, constants.DAEMON_UTIL, constants.NODED, diff --git a/lib/cli.py b/lib/cli.py index d7e497e58b8dee66035564b6201bb83c11be4600..3f8d4979d85a87e01fb4d5f0857b2cc42fc063ca 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -109,6 +109,7 @@ __all__ = [ "NEW_CONFD_HMAC_KEY_OPT", "NEW_RAPI_CERT_OPT", "NEW_SECONDARY_OPT", + "NEW_SPICE_CERT_OPT", "NIC_PARAMS_OPT", "NODE_FORCE_JOIN_OPT", "NODE_LIST_OPT", @@ -159,6 +160,8 @@ __all__ = [ "SHOWCMD_OPT", "SHUTDOWN_TIMEOUT_OPT", "SINGLE_NODE_OPT", + "SPICE_CACERT_OPT", + "SPICE_CERT_OPT", "SRC_DIR_OPT", "SRC_NODE_OPT", "SUBMIT_OPT", @@ -1083,6 +1086,21 @@ NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert", help=("Generate a new self-signed RAPI" " certificate")) +SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert", + default=None, + help="File containing new SPICE certificate") + +SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert", + default=None, + help="File containing the certificate of the CA" + " which signed the SPICE certificate") + +NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate", + dest="new_spice_cert", default=None, + action="store_true", + help=("Generate a new self-signed SPICE" + " certificate")) + NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key", dest="new_confd_hmac_key", default=False, action="store_true", diff --git a/lib/client/gnt_cluster.py b/lib/client/gnt_cluster.py index b61e8e3417968616aa28e1c875ebcdaa789eae8e..5637774f3531e8770e67e602937c8a6a205571e0 100644 --- a/lib/client/gnt_cluster.py +++ b/lib/client/gnt_cluster.py @@ -646,9 +646,45 @@ def SearchTags(opts, args): ToStdout("%s %s", path, tag) -def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, - new_confd_hmac_key, new_cds, cds_filename, - force): +def _ReadAndVerifyCert(cert_filename, verify_private_key=False): + """Reads and verifies an X509 certificate. + + @type cert_filename: string + @param cert_filename: the path of the file containing the certificate to + verify encoded in PEM format + @type verify_private_key: bool + @param verify_private_key: whether to verify the private key in addition to + the public certificate + @rtype: string + @return: a string containing the PEM-encoded certificate. + + """ + try: + pem = utils.ReadFile(cert_filename) + except IOError, err: + raise errors.X509CertError(cert_filename, + "Unable to read certificate: %s" % str(err)) + + try: + OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pem) + except Exception, err: + raise errors.X509CertError(cert_filename, + "Unable to load certificate: %s" % str(err)) + + if verify_private_key: + try: + OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, pem) + except Exception, err: + raise errors.X509CertError(cert_filename, + "Unable to load private key: %s" % str(err)) + + return pem + + +def _RenewCrypto(new_cluster_cert, new_rapi_cert, #pylint: disable=R0911 + rapi_cert_filename, new_spice_cert, spice_cert_filename, + spice_cacert_filename, new_confd_hmac_key, new_cds, + cds_filename, force): """Renews cluster certificates, keys and secrets. @type new_cluster_cert: bool @@ -657,6 +693,13 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, @param new_rapi_cert: Whether to generate a new RAPI certificate @type rapi_cert_filename: string @param rapi_cert_filename: Path to file containing new RAPI certificate + @type new_spice_cert: bool + @param new_spice_cert: Whether to generate a new SPICE certificate + @type spice_cert_filename: string + @param spice_cert_filename: Path to file containing new SPICE certificate + @type spice_cacert_filename: string + @param spice_cacert_filename: Path to file containing the certificate of the + CA that signed the SPICE certificate @type new_confd_hmac_key: bool @param new_confd_hmac_key: Whether to generate a new HMAC key @type new_cds: bool @@ -678,27 +721,26 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, " the same time.") return 1 - if rapi_cert_filename: - # Read and verify new certificate - try: - rapi_cert_pem = utils.ReadFile(rapi_cert_filename) - - OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, - rapi_cert_pem) - except Exception, err: # pylint: disable=W0703 - ToStderr("Can't load new RAPI certificate from %s: %s" % - (rapi_cert_filename, str(err))) - return 1 + if new_spice_cert and (spice_cert_filename or spice_cacert_filename): + ToStderr("When using --new-spice-certificate, the --spice-certificate" + " and --spice-ca-certificate must not be used.") + return 1 - try: - OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem) - except Exception, err: # pylint: disable=W0703 - ToStderr("Can't load new RAPI private key from %s: %s" % - (rapi_cert_filename, str(err))) - return 1 + if bool(spice_cacert_filename) ^ bool(spice_cert_filename): + ToStderr("Both --spice-certificate and --spice-ca-certificate must be" + " specified.") + return 1 - else: - rapi_cert_pem = None + rapi_cert_pem, spice_cert_pem, spice_cacert_pem = (None, None, None) + try: + if rapi_cert_filename: + rapi_cert_pem = _ReadAndVerifyCert(rapi_cert_filename, True) + if spice_cert_filename: + spice_cert_pem = _ReadAndVerifyCert(spice_cert_filename, True) + spice_cacert_pem = _ReadAndVerifyCert(spice_cacert_filename) + except errors.X509CertError, err: + ToStderr("Unable to load X509 certificate from %s: %s", err[0], err[1]) + return 1 if cds_filename: try: @@ -718,10 +760,14 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, def _RenewCryptoInner(ctx): ctx.feedback_fn("Updating certificates and keys") - bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, + bootstrap.GenerateClusterCrypto(new_cluster_cert, + new_rapi_cert, + new_spice_cert, new_confd_hmac_key, new_cds, rapi_cert_pem=rapi_cert_pem, + spice_cert_pem=spice_cert_pem, + spice_cacert_pem=spice_cacert_pem, cds=cds) files_to_copy = [] @@ -732,6 +778,10 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, if new_rapi_cert or rapi_cert_pem: files_to_copy.append(constants.RAPI_CERT_FILE) + if new_spice_cert or spice_cert_pem: + files_to_copy.append(constants.SPICE_CERT_FILE) + files_to_copy.append(constants.SPICE_CACERT_FILE) + if new_confd_hmac_key: files_to_copy.append(constants.CONFD_HMAC_KEY) @@ -760,6 +810,9 @@ def RenewCrypto(opts, args): return _RenewCrypto(opts.new_cluster_cert, opts.new_rapi_cert, opts.rapi_cert, + opts.new_spice_cert, + opts.spice_cert, + opts.spice_cacert, opts.new_confd_hmac_key, opts.new_cluster_domain_secret, opts.cluster_domain_secret, @@ -1348,7 +1401,8 @@ commands = { RenewCrypto, ARGS_NONE, [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT, NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT, - NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT], + NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT, + NEW_SPICE_CERT_OPT, SPICE_CERT_OPT, SPICE_CACERT_OPT], "[opts...]", "Renews cluster certificates, keys and secrets"), "epo": ( diff --git a/lib/cmdlib.py b/lib/cmdlib.py index 1dae379d82d813545090f15d4610468dccecf958..8c45f0111f79ad1edf089dfbb9b831124905a3fc 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -8998,8 +8998,9 @@ class LUInstanceCreate(LogicalUnit): feedback_fn("* running the instance OS create scripts...") # FIXME: pass debug option from opcode to backend - result = self.rpc.call_instance_os_add(pnode_name, iobj, False, - self.op.debug_level) + os_add_result = \ + self.rpc.call_instance_os_add(pnode_name, iobj, False, + self.op.debug_level) if pause_sync: feedback_fn("* resuming disk sync") result = self.rpc.call_blockdev_pause_resume_sync(pnode_name, @@ -9009,8 +9010,8 @@ class LUInstanceCreate(LogicalUnit): logging.warn("resume-sync of instance %s for disk %d failed", instance, idx) - result.Raise("Could not add os for instance %s" - " on node %s" % (instance, pnode_name)) + os_add_result.Raise("Could not add os for instance %s" + " on node %s" % (instance, pnode_name)) elif self.op.mode == constants.INSTANCE_IMPORT: feedback_fn("* running the instance OS import scripts...") diff --git a/lib/constants.py b/lib/constants.py index 524b698a82cb1cc7578f77b3a5634f4660ea3fc8..dd724545fe663918b3b4413dcb2021061c5f2d78 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -160,6 +160,8 @@ CLUSTER_CONF_FILE = DATA_DIR + "/config.data" NODED_CERT_FILE = DATA_DIR + "/server.pem" RAPI_CERT_FILE = DATA_DIR + "/rapi.pem" CONFD_HMAC_KEY = DATA_DIR + "/hmac.key" +SPICE_CERT_FILE = DATA_DIR + "/spice.pem" +SPICE_CACERT_FILE = DATA_DIR + "/spice-ca.pem" CLUSTER_DOMAIN_SECRET_FILE = DATA_DIR + "/cluster-domain-secret" INSTANCE_STATUS_FILE = RUN_GANETI_DIR + "/instance-status" SSH_KNOWN_HOSTS_FILE = DATA_DIR + "/known_hosts" @@ -193,7 +195,12 @@ WATCHER_GROUP_INSTANCE_STATUS_FILE = DATA_DIR + "/watcher.%s.instance-status" #: File containing Unix timestamp until which watcher should be paused WATCHER_PAUSEFILE = DATA_DIR + "/watcher.pause" -ALL_CERT_FILES = frozenset([NODED_CERT_FILE, RAPI_CERT_FILE]) +ALL_CERT_FILES = frozenset([ + NODED_CERT_FILE, + RAPI_CERT_FILE, + SPICE_CERT_FILE, + SPICE_CACERT_FILE, + ]) MASTER_SOCKET = SOCKET_DIR + "/ganeti-master" @@ -700,6 +707,9 @@ HV_KVM_SPICE_JPEG_IMG_COMPR = "spice_jpeg_wan_compression" HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR = "spice_zlib_glz_wan_compression" HV_KVM_SPICE_STREAMING_VIDEO_DETECTION = "spice_streaming_video" HV_KVM_SPICE_AUDIO_COMPR = "spice_playback_compression" +HV_KVM_SPICE_USE_TLS = "spice_use_tls" +HV_KVM_SPICE_TLS_CIPHERS = "spice_tls_ciphers" +HV_KVM_SPICE_USE_VDAGENT = "spice_use_vdagent" HV_ACPI = "acpi" HV_PAE = "pae" HV_USE_BOOTLOADER = "use_bootloader" @@ -751,6 +761,9 @@ HVS_PARAMETER_TYPES = { HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: VTYPE_STRING, HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: VTYPE_STRING, HV_KVM_SPICE_AUDIO_COMPR: VTYPE_BOOL, + HV_KVM_SPICE_USE_TLS: VTYPE_BOOL, + HV_KVM_SPICE_TLS_CIPHERS: VTYPE_STRING, + HV_KVM_SPICE_USE_VDAGENT: VTYPE_BOOL, HV_ACPI: VTYPE_BOOL, HV_PAE: VTYPE_BOOL, HV_USE_BOOTLOADER: VTYPE_BOOL, @@ -1378,6 +1391,9 @@ HVC_DEFAULTS = { HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: "", HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: "", HV_KVM_SPICE_AUDIO_COMPR: True, + HV_KVM_SPICE_USE_TLS: False, + HV_KVM_SPICE_TLS_CIPHERS: OPENSSL_CIPHERS, + HV_KVM_SPICE_USE_VDAGENT: True, HV_KVM_FLOPPY_IMAGE_PATH: "", HV_CDROM_IMAGE_PATH: "", HV_KVM_CDROM2_IMAGE_PATH: "", diff --git a/lib/errors.py b/lib/errors.py index 181288618d25bea24fd5c08435029bd49ad9ac06..ff7cbf85126ae7fec90e0b6634241485bafd7281 100644 --- a/lib/errors.py +++ b/lib/errors.py @@ -277,6 +277,14 @@ class SshKeyError(GenericError): """ +class X509CertError(GenericError): + """Invalid X509 certificate. + + This error has two arguments: the certificate filename and the error cause. + + """ + + class TagError(GenericError): """Generic tag error. diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py index 6efb1713ddb48c2921f015f067d079f56c03b279..3fd2b4db235a388f399a48bf55ca4decf95d4e09 100644 --- a/lib/hypervisor/hv_kvm.py +++ b/lib/hypervisor/hv_kvm.py @@ -437,6 +437,9 @@ class KVMHypervisor(hv_base.BaseHypervisor): hv_base.ParamInSet(False, constants.HT_KVM_SPICE_VALID_VIDEO_STREAM_DETECTION_OPTIONS), constants.HV_KVM_SPICE_AUDIO_COMPR: hv_base.NO_CHECK, + constants.HV_KVM_SPICE_USE_TLS: hv_base.NO_CHECK, + constants.HV_KVM_SPICE_TLS_CIPHERS: hv_base.NO_CHECK, + constants.HV_KVM_SPICE_USE_VDAGENT: hv_base.NO_CHECK, constants.HV_KVM_FLOPPY_IMAGE_PATH: hv_base.OPT_FILE_CHECK, constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK, constants.HV_KVM_CDROM2_IMAGE_PATH: hv_base.OPT_FILE_CHECK, @@ -916,7 +919,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): """Generate KVM information to start an instance. """ - # pylint: disable=R0914 + # pylint: disable=R0914,R0915 _, v_major, v_min, _ = self._GetKVMVersion() pidfile = self._InstancePidFile(instance.name) @@ -1154,7 +1157,18 @@ class KVMHypervisor(hv_base.BaseHypervisor): # ValidateParameters checked it. spice_address = spice_bind - spice_arg = "addr=%s,port=%s" % (spice_address, instance.network_port) + spice_arg = "addr=%s" % spice_address + if hvp[constants.HV_KVM_SPICE_USE_TLS]: + spice_arg = "%s,tls-port=%s,x509-cacert-file=%s" % (spice_arg, + instance.network_port, constants.SPICE_CACERT_FILE) + spice_arg = "%s,x509-key-file=%s,x509-cert-file=%s" % (spice_arg, + constants.SPICE_CERT_FILE, constants.SPICE_CERT_FILE) + tls_ciphers = hvp[constants.HV_KVM_SPICE_TLS_CIPHERS] + if tls_ciphers: + spice_arg = "%s,tls-ciphers=%s" % (spice_arg, tls_ciphers) + else: + spice_arg = "%s,port=%s" % (spice_arg, instance.network_port) + if not hvp[constants.HV_KVM_SPICE_PASSWORD_FILE]: spice_arg = "%s,disable-ticketing" % spice_arg @@ -1180,6 +1194,8 @@ class KVMHypervisor(hv_base.BaseHypervisor): # Audio compression, by default in qemu-kvm it is on if not hvp[constants.HV_KVM_SPICE_AUDIO_COMPR]: spice_arg = "%s,playback-compression=off" % spice_arg + if not hvp[constants.HV_KVM_SPICE_USE_VDAGENT]: + spice_arg = "%s,agent-mouse=off" % spice_arg logging.info("KVM: SPICE will listen on port %s", instance.network_port) kvm_cmd.extend(["-spice", spice_arg]) @@ -1794,6 +1810,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): constants.HV_KVM_SPICE_JPEG_IMG_COMPR, constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR, constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION, + constants.HV_KVM_SPICE_USE_TLS, ]) for param in spice_additional_params: if hvparams[param]: diff --git a/lib/tools/ensure_dirs.py b/lib/tools/ensure_dirs.py index 7abcce2a2bcf556326fd9b8df9663bac52a015b1..3d65c8c699a10d199151a35a60751e51c262ba9d 100644 --- a/lib/tools/ensure_dirs.py +++ b/lib/tools/ensure_dirs.py @@ -209,6 +209,10 @@ def GetPaths(): getent.masterd_gid, False), (constants.RAPI_CERT_FILE, FILE, 0440, getent.rapi_uid, getent.masterd_gid, False), + (constants.SPICE_CERT_FILE, FILE, 0440, getent.noded_uid, + getent.masterd_gid, False), + (constants.SPICE_CACERT_FILE, FILE, 0440, getent.noded_uid, + getent.masterd_gid, False), (constants.NODED_CERT_FILE, FILE, 0440, getent.masterd_uid, getent.masterd_gid, False), ] diff --git a/lib/utils/x509.py b/lib/utils/x509.py index 71ba25dc34d6e0a91739fa9cde779d9851f1d51d..b0d9f904c4a3ce63f69a943129103ac6d863f62e 100644 --- a/lib/utils/x509.py +++ b/lib/utils/x509.py @@ -259,6 +259,8 @@ def GenerateSelfSignedX509Cert(common_name, validity): @param common_name: commonName value @type validity: int @param validity: Validity for certificate in seconds + @return: a tuple of strings containing the PEM-encoded private key and + certificate """ # Create private and public key @@ -292,6 +294,8 @@ def GenerateSelfSignedSslCert(filename, common_name=constants.X509_CERT_CN, @param common_name: commonName value @type validity: int @param validity: validity of certificate in number of days + @return: a tuple of strings containing the PEM-encoded private key and + certificate """ # TODO: Investigate using the cluster name instead of X505_CERT_CN for @@ -301,3 +305,4 @@ def GenerateSelfSignedSslCert(filename, common_name=constants.X509_CERT_CN, validity * 24 * 60 * 60) utils_io.WriteFile(filename, mode=0400, data=key_pem + cert_pem) + return (key_pem, cert_pem) diff --git a/man/gnt-cluster.rst b/man/gnt-cluster.rst index c821cd76f255e1a731cd6e43134605766bb39af1..88511197a84365a178af0087aaaea1ee66df23c3 100644 --- a/man/gnt-cluster.rst +++ b/man/gnt-cluster.rst @@ -520,6 +520,8 @@ RENEW-CRYPTO | **renew-crypto** [-f] | [--new-cluster-certificate] [--new-confd-hmac-key] | [--new-rapi-certificate] [--rapi-certificate *rapi-cert*] +| [--new-spice-certificate | --spice-certificate *spice-cert* +| -- spice-ca-certificate *spice-ca-cert*] | [--new-cluster-domain-secret] [--cluster-domain-secret *filename*] This command will stop all Ganeti daemons in the cluster and start @@ -533,6 +535,12 @@ ganeti-rapi(8)) specify ``--new-rapi-certificate``. If you want to use your own certificate, e.g. one signed by a certificate authority (CA), pass its filename to ``--rapi-certificate``. +To generate a new self-signed SPICE certificate, used by SPICE +connections to the KVM hypervisor, specify the +``--new-spice-certificate`` option. If you want to provide a +certificate, pass its filename to ``--spice-certificate`` and pass the +signing CA certificate to ``--spice-ca-certificate``. + ``--new-cluster-domain-secret`` generates a new, random cluster domain secret. ``--cluster-domain-secret`` reads the secret from a file. The cluster domain secret is used to sign information diff --git a/man/gnt-instance.rst b/man/gnt-instance.rst index 9ae83c21b568c2239c5bc982cf3d8ba2668c8f63..9d88938d025346389c45a5543a2647a129cab3fe 100644 --- a/man/gnt-instance.rst +++ b/man/gnt-instance.rst @@ -353,6 +353,23 @@ spice\_playback\_compression Configures whether SPICE should compress audio streams or not. +spice\_use\_tls + Valid for the KVM hypervisor. + + Specifies that the SPICE server must use TLS to encrypt all the + traffic with the client. + +spice\_tls\_ciphers + Valid for the KVM hypervisor. + + Specifies a list of comma-separated ciphers that SPICE should use + for TLS connections. For the format, see man cipher(1). + +spice\_use\_vdagent + Valid for the KVM hypervisor. + + Enables or disables passing mouse events via SPICE vdagent. + acpi Valid for the Xen HVM and KVM hypervisors. diff --git a/tools/cfgupgrade b/tools/cfgupgrade index b44ea6c1fbc5845bb762faf68bf5ad03b42e095d..882dbfbdc204e1a2cca2d148fcbb479ebe10dd3e 100755 --- a/tools/cfgupgrade +++ b/tools/cfgupgrade @@ -122,6 +122,8 @@ def main(): options.SERVER_PEM_PATH = options.data_dir + "/server.pem" options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts" options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem" + options.SPICE_CERT_FILE = options.data_dir + "/spice.pem" + options.SPICE_CACERT_FILE = options.data_dir + "/spice-ca.pem" options.RAPI_USERS_FILE = options.data_dir + "/rapi/users" options.RAPI_USERS_FILE_PRE24 = options.data_dir + "/rapi_users" options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key" @@ -222,11 +224,13 @@ def main(): backup=True) if not options.dry_run: - bootstrap.GenerateClusterCrypto(False, False, False, False, - nodecert_file=options.SERVER_PEM_PATH, - rapicert_file=options.RAPI_CERT_FILE, - hmackey_file=options.CONFD_HMAC_KEY, - cds_file=options.CDS_FILE) + bootstrap.GenerateClusterCrypto(False, False, False, False, False, + nodecert_file=options.SERVER_PEM_PATH, + rapicert_file=options.RAPI_CERT_FILE, + spicecert_file=options.SPICE_CERT_FILE, + spicecacert_file=options.SPICE_CACERT_FILE, + hmackey_file=options.CONFD_HMAC_KEY, + cds_file=options.CDS_FILE) except Exception: logging.critical("Writing configuration failed. It is probably in an"