diff --git a/lib/cli.py b/lib/cli.py
index 558feacf6473abf7c93b869250103e40412ea6a8..5d0a66a9b204e463870a070f8046a9aa8e05448d 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -79,6 +79,9 @@ __all__ = [
   "MASTER_NETDEV_OPT",
   "MC_OPT",
   "NET_OPT",
+  "NEW_CLUSTER_CERT_OPT",
+  "NEW_HMAC_KEY_OPT",
+  "NEW_RAPI_CERT_OPT",
   "NEW_SECONDARY_OPT",
   "NIC_PARAMS_OPT",
   "NODE_LIST_OPT",
@@ -102,6 +105,7 @@ __all__ = [
   "OFFLINE_OPT",
   "OS_OPT",
   "OS_SIZE_OPT",
+  "RAPI_CERT_OPT",
   "READD_OPT",
   "REBOOT_TYPE_OPT",
   "SECONDARY_IP_OPT",
@@ -860,6 +864,24 @@ EARLY_RELEASE_OPT = cli_option("--early-release",
                                help="Release the locks on the secondary"
                                " node(s) early")
 
+NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
+                                  dest="new_cluster_cert",
+                                  default=False, action="store_true",
+                                  help="Generate a new cluster certificate")
+
+RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
+                           default=None,
+                           help="File containing new RAPI certificate")
+
+NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
+                               default=None, action="store_true",
+                               help=("Generate a new self-signed RAPI"
+                                     " certificate"))
+
+NEW_HMAC_KEY_OPT = cli_option("--new-hmac-key", dest="new_hmac_key",
+                              default=False, action="store_true",
+                              help="Create a new HMAC key")
+
 
 def _ParseArgs(argv, commands, aliases):
   """Parser for the command line arguments.
diff --git a/man/gnt-cluster.sgml b/man/gnt-cluster.sgml
index 4b3b8223fcf333514d4deacb1170aa59d03c8819..e7947d049ed3870f8256576b2158c2f11aaaf738 100644
--- a/man/gnt-cluster.sgml
+++ b/man/gnt-cluster.sgml
@@ -540,7 +540,7 @@
         <sbr>
         <arg choice="opt">--nic-parameters <replaceable>nic-param</replaceable>=<replaceable>value</replaceable><arg rep="repeat" choice="opt">,<replaceable>nic-param</replaceable>=<replaceable>value</replaceable></arg></arg>
         <sbr>
-        <arg>-C <replaceable>candidate_pool_size</replaceable></arg>
+        <arg choice="opt">-C <replaceable>candidate_pool_size</replaceable></arg>
 
       </cmdsynopsis>
 
@@ -558,7 +558,7 @@
         </para>
 
       <para>
-        The <option>-C</option> options specifies the
+        The <option>-C</option> option specifies the
         <varname>candidate_pool_size</varname> cluster parameter. This
         is the number of nodes that the master will try to keep as
         <literal>master_candidates</literal>. For more details about
@@ -703,6 +703,39 @@
       </para>
     </refsect2>
 
+    <refsect2>
+      <title>RENEW-CRYPTO</title>
+
+      <cmdsynopsis>
+        <command>renew-crypto</command>
+        <arg>-f</arg>
+        <sbr>
+        <arg choice="opt">--new-cluster-certificate</arg>
+        <arg choice="opt">--new-hmac-key</arg>
+        <sbr>
+        <arg choice="opt">--new-rapi-certificate</arg>
+        <arg choice="opt">--rapi-certificate <replaceable>rapi-cert</replaceable></arg>
+      </cmdsynopsis>
+
+      <para>
+        This command will stop all
+        Ganeti daemons in the cluster and start them again once the new
+        certificates and keys are replicated. The options
+        <option>--new-cluster-certificate</option> and
+        <option>--new-hmac-key</option> can be used to regenerate the
+        cluster-internal SSL certificate respective the HMAC key used by
+        <citerefentry>
+        <refentrytitle>ganeti-confd</refentrytitle><manvolnum>8</manvolnum>
+        </citerefentry>. To generate a new self-signed RAPI certificate (used
+        by <citerefentry>
+        <refentrytitle>ganeti-rapi</refentrytitle><manvolnum>8</manvolnum>
+        </citerefentry>) specify <option>--new-rapi-certificate</option>. If
+        you want to use your own certificate, e.g. one signed by a certificate
+        authority (CA), pass its filename to
+        <option>--rapi-certificate</option>.
+      </para>
+    </refsect2>
+
     <refsect2>
       <title>REPAIR-DISK-SIZES</title>
 
diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py
index 73c01aee46dcebb7929c32cd3414ce3f1dc9fa88..d90ff6b5b98880b9302d2c9ec203586071840c67 100755
--- a/qa/ganeti-qa.py
+++ b/qa/ganeti-qa.py
@@ -88,6 +88,9 @@ def RunClusterTests():
   """Runs tests related to gnt-cluster.
 
   """
+  if qa_config.TestEnabled("cluster-renew-crypto"):
+    RunTest(qa_cluster.TestClusterRenewCrypto)
+
   if qa_config.TestEnabled('cluster-verify'):
     RunTest(qa_cluster.TestClusterVerify)
 
@@ -115,6 +118,7 @@ def RunClusterTests():
     RunTest(qa_rapi.TestVersion)
     RunTest(qa_rapi.TestEmptyCluster)
 
+
 def RunOsTests():
   """Runs all tests related to gnt-os.
 
@@ -176,6 +180,7 @@ def RunCommonInstanceTests(instance):
   if qa_rapi.Enabled():
     RunTest(qa_rapi.TestInstance, instance)
 
+
 def RunExportImportTests(instance, pnode):
   """Tries to export and import the instance.
 
diff --git a/qa/qa-sample.json b/qa/qa-sample.json
index d167e06e9831fcf58756d1730d4d8d6f5e5debe0..a150b37d783965ae37da1ee396dfda681a6ee261 100644
--- a/qa/qa-sample.json
+++ b/qa/qa-sample.json
@@ -45,6 +45,7 @@
     "cluster-command": true,
     "cluster-copyfile": true,
     "cluster-master-failover": true,
+    "cluster-renew-crypto": true,
     "cluster-destroy": true,
     "cluster-rename": true,
 
diff --git a/qa/qa_cluster.py b/qa/qa_cluster.py
index 9ca548337fab63b8d966c804ff12e7e92dc2bd09..d4efc50deb3b52713ac3412e8015648f2f4028de 100644
--- a/qa/qa_cluster.py
+++ b/qa/qa_cluster.py
@@ -25,13 +25,15 @@
 
 import tempfile
 
+from ganeti import constants
+from ganeti import bootstrap
 from ganeti import utils
 
 import qa_config
 import qa_utils
 import qa_error
 
-from qa_utils import AssertEqual, StartSSH
+from qa_utils import AssertEqual, AssertNotEqual, StartSSH
 
 
 def _RemoveFileFromAllNodes(filename):
@@ -144,6 +146,50 @@ def TestClusterVersion():
                        utils.ShellQuoteArgs(cmd)).wait(), 0)
 
 
+def TestClusterRenewCrypto():
+  """gnt-cluster renew-crypto"""
+  master = qa_config.GetMasterNode()
+
+  # Conflicting options
+  cmd = ["gnt-cluster", "renew-crypto", "--force",
+         "--new-cluster-certificate", "--new-hmac-key",
+         "--new-rapi-certificate", "--rapi-certificate=/dev/null"]
+  AssertNotEqual(StartSSH(master["primary"],
+                          utils.ShellQuoteArgs(cmd)).wait(), 0)
+
+  # Invalid RAPI certificate
+  cmd = ["gnt-cluster", "renew-crypto", "--force",
+         "--rapi-certificate=/dev/null"]
+  AssertNotEqual(StartSSH(master["primary"],
+                          utils.ShellQuoteArgs(cmd)).wait(), 0)
+
+  # Custom RAPI certificate
+  fh = tempfile.NamedTemporaryFile()
+
+  # Ensure certificate doesn't cause "gnt-cluster verify" to complain
+  validity = constants.SSL_CERT_EXPIRATION_WARN * 3
+
+  bootstrap.GenerateSelfSignedSslCert(fh.name, validity=validity)
+
+  tmpcert = qa_utils.UploadFile(master["primary"], fh.name)
+  try:
+    cmd = ["gnt-cluster", "renew-crypto", "--force",
+           "--rapi-certificate=%s" % tmpcert]
+    AssertEqual(StartSSH(master["primary"],
+                         utils.ShellQuoteArgs(cmd)).wait(), 0)
+  finally:
+    cmd = ["rm", "-f", tmpcert]
+    AssertEqual(StartSSH(master["primary"],
+                         utils.ShellQuoteArgs(cmd)).wait(), 0)
+
+  # Normal case
+  cmd = ["gnt-cluster", "renew-crypto", "--force",
+         "--new-cluster-certificate", "--new-hmac-key",
+         "--new-rapi-certificate"]
+  AssertEqual(StartSSH(master["primary"],
+                       utils.ShellQuoteArgs(cmd)).wait(), 0)
+
+
 def TestClusterBurnin():
   """Burnin"""
   master = qa_config.GetMasterNode()
diff --git a/scripts/gnt-cluster b/scripts/gnt-cluster
index 59b73a7b0bdf92d5d0b7bbaa3665381f9c04f642..01ecedbc9cf681a2a729a4f97deee8f9a2e2692c 100755
--- a/scripts/gnt-cluster
+++ b/scripts/gnt-cluster
@@ -29,6 +29,7 @@
 import sys
 import os.path
 import time
+import OpenSSL
 
 from ganeti.cli import *
 from ganeti import opcodes
@@ -493,6 +494,100 @@ def SearchTags(opts, args):
     ToStdout("%s %s", path, tag)
 
 
+def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
+                 new_hmac_key, force):
+  """Renews cluster certificates, keys and secrets.
+
+  @type new_cluster_cert: bool
+  @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 rapi_cert_filename: string
+  @param rapi_cert_filename: Path to file containing new RAPI certificate
+  @type new_hmac_key: bool
+  @param new_hmac_key: Whether to generate a new HMAC key
+  @type force: bool
+  @param force: Whether to ask user for confirmation
+
+  """
+  assert new_cluster_cert or new_rapi_cert or rapi_cert_filename or new_hmac_key
+
+  if new_rapi_cert and rapi_cert_filename:
+    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
+             " options can be specified at 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-msg=W0703
+      ToStderr("Can't load new RAPI certificate from %s: %s" %
+               (rapi_cert_filename, str(err)))
+      return 1
+
+    try:
+      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
+    except Exception, err: # pylint: disable-msg=W0703
+      ToStderr("Can't load new RAPI private key from %s: %s" %
+               (rapi_cert_filename, str(err)))
+      return 1
+
+  else:
+    rapi_cert_pem = None
+
+  if not force:
+    usertext = ("This requires all daemons on all nodes to be restarted and"
+                " may take some time. Continue?")
+    if not AskUser(usertext):
+      return 1
+
+  def _RenewCryptoInner(ctx):
+    ctx.feedback_fn("Updating certificates and keys")
+    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
+                                    new_hmac_key,
+                                    rapi_cert_pem=rapi_cert_pem)
+
+    files_to_copy = []
+
+    if new_cluster_cert:
+      files_to_copy.append(constants.SSL_CERT_FILE)
+
+    if new_rapi_cert or rapi_cert_pem:
+      files_to_copy.append(constants.RAPI_CERT_FILE)
+
+    if new_hmac_key:
+      files_to_copy.append(constants.HMAC_CLUSTER_KEY)
+
+    if files_to_copy:
+      for node_name in ctx.nonmaster_nodes:
+        ctx.feedback_fn("Copying %s to %s" %
+                        (", ".join(files_to_copy), node_name))
+        for file_name in files_to_copy:
+          ctx.ssh.CopyFileToNode(node_name, file_name)
+
+  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
+
+  ToStdout("All requested certificates and keys have been replaced."
+           " Running \"gnt-cluster verify\" now is recommended.")
+
+  return 0
+
+
+def RenewCrypto(opts, args):
+  """Renews cluster certificates, keys and secrets.
+
+  """
+  return _RenewCrypto(opts.new_cluster_cert,
+                      opts.new_rapi_cert,
+                      opts.rapi_cert,
+                      opts.new_hmac_key,
+                      opts.force)
+
+
 def SetClusterParams(opts, args):
   """Modify the cluster.
 
@@ -512,10 +607,11 @@ def SetClusterParams(opts, args):
 
   vg_name = opts.vg_name
   if not opts.lvm_storage and opts.vg_name:
-    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
+    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
     return 1
-  elif not opts.lvm_storage:
-    vg_name = ''
+
+  if not opts.lvm_storage:
+    vg_name = ""
 
   hvlist = opts.enabled_hypervisors
   if hvlist is not None:
@@ -692,7 +788,14 @@ commands = {
      NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT],
     "[opts...]",
     "Alters the parameters of the cluster"),
+  "renew-crypto": (
+    RenewCrypto, ARGS_NONE,
+    [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT, NEW_HMAC_KEY_OPT,
+     FORCE_OPT],
+    "[opts...]",
+    "Renews cluster certificates, keys and secrets"),
   }
 
+
 if __name__ == '__main__':
   sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))