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.
@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
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)
"""
# 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:
"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",
"SHOWCMD_OPT",
"SHUTDOWN_TIMEOUT_OPT",
"SINGLE_NODE_OPT",
+ "SPICE_CACERT_OPT",
+ "SPICE_CERT_OPT",
"SRC_DIR_OPT",
"SRC_NODE_OPT",
"SUBMIT_OPT",
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",
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
@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
" 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:
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 = []
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)
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,
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": (
"""
+class X509CertError(GenericError):
+ """Invalid X509 certificate.
+
+ This error has two arguments: the certificate filename and the error cause.
+
+ """
+
+
class TagError(GenericError):
"""Generic tag error.
"""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)
@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
@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
validity * 24 * 60 * 60)
utils_io.WriteFile(filename, mode=0400, data=key_pem + cert_pem)
+ return (key_pem, cert_pem)
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"
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"