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"