Revision 6d4a1656

b/lib/cli.py
79 79
  "MASTER_NETDEV_OPT",
80 80
  "MC_OPT",
81 81
  "NET_OPT",
82
  "NEW_CLUSTER_CERT_OPT",
83
  "NEW_HMAC_KEY_OPT",
84
  "NEW_RAPI_CERT_OPT",
82 85
  "NEW_SECONDARY_OPT",
83 86
  "NIC_PARAMS_OPT",
84 87
  "NODE_LIST_OPT",
......
102 105
  "OFFLINE_OPT",
103 106
  "OS_OPT",
104 107
  "OS_SIZE_OPT",
108
  "RAPI_CERT_OPT",
105 109
  "READD_OPT",
106 110
  "REBOOT_TYPE_OPT",
107 111
  "SECONDARY_IP_OPT",
......
860 864
                               help="Release the locks on the secondary"
861 865
                               " node(s) early")
862 866

  
867
NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
868
                                  dest="new_cluster_cert",
869
                                  default=False, action="store_true",
870
                                  help="Generate a new cluster certificate")
871

  
872
RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
873
                           default=None,
874
                           help="File containing new RAPI certificate")
875

  
876
NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
877
                               default=None, action="store_true",
878
                               help=("Generate a new self-signed RAPI"
879
                                     " certificate"))
880

  
881
NEW_HMAC_KEY_OPT = cli_option("--new-hmac-key", dest="new_hmac_key",
882
                              default=False, action="store_true",
883
                              help="Create a new HMAC key")
884

  
863 885

  
864 886
def _ParseArgs(argv, commands, aliases):
865 887
  """Parser for the command line arguments.
b/man/gnt-cluster.sgml
540 540
        <sbr>
541 541
        <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>
542 542
        <sbr>
543
        <arg>-C <replaceable>candidate_pool_size</replaceable></arg>
543
        <arg choice="opt">-C <replaceable>candidate_pool_size</replaceable></arg>
544 544

  
545 545
      </cmdsynopsis>
546 546

  
......
558 558
        </para>
559 559

  
560 560
      <para>
561
        The <option>-C</option> options specifies the
561
        The <option>-C</option> option specifies the
562 562
        <varname>candidate_pool_size</varname> cluster parameter. This
563 563
        is the number of nodes that the master will try to keep as
564 564
        <literal>master_candidates</literal>. For more details about
......
704 704
    </refsect2>
705 705

  
706 706
    <refsect2>
707
      <title>RENEW-CRYPTO</title>
708

  
709
      <cmdsynopsis>
710
        <command>renew-crypto</command>
711
        <arg>-f</arg>
712
        <sbr>
713
        <arg choice="opt">--new-cluster-certificate</arg>
714
        <arg choice="opt">--new-hmac-key</arg>
715
        <sbr>
716
        <arg choice="opt">--new-rapi-certificate</arg>
717
        <arg choice="opt">--rapi-certificate <replaceable>rapi-cert</replaceable></arg>
718
      </cmdsynopsis>
719

  
720
      <para>
721
        This command will stop all
722
        Ganeti daemons in the cluster and start them again once the new
723
        certificates and keys are replicated. The options
724
        <option>--new-cluster-certificate</option> and
725
        <option>--new-hmac-key</option> can be used to regenerate the
726
        cluster-internal SSL certificate respective the HMAC key used by
727
        <citerefentry>
728
        <refentrytitle>ganeti-confd</refentrytitle><manvolnum>8</manvolnum>
729
        </citerefentry>. To generate a new self-signed RAPI certificate (used
730
        by <citerefentry>
731
        <refentrytitle>ganeti-rapi</refentrytitle><manvolnum>8</manvolnum>
732
        </citerefentry>) specify <option>--new-rapi-certificate</option>. If
733
        you want to use your own certificate, e.g. one signed by a certificate
734
        authority (CA), pass its filename to
735
        <option>--rapi-certificate</option>.
736
      </para>
737
    </refsect2>
738

  
739
    <refsect2>
707 740
      <title>REPAIR-DISK-SIZES</title>
708 741

  
709 742
      <cmdsynopsis>
b/qa/ganeti-qa.py
88 88
  """Runs tests related to gnt-cluster.
89 89

  
90 90
  """
91
  if qa_config.TestEnabled("cluster-renew-crypto"):
92
    RunTest(qa_cluster.TestClusterRenewCrypto)
93

  
91 94
  if qa_config.TestEnabled('cluster-verify'):
92 95
    RunTest(qa_cluster.TestClusterVerify)
93 96

  
......
115 118
    RunTest(qa_rapi.TestVersion)
116 119
    RunTest(qa_rapi.TestEmptyCluster)
117 120

  
121

  
118 122
def RunOsTests():
119 123
  """Runs all tests related to gnt-os.
120 124

  
......
176 180
  if qa_rapi.Enabled():
177 181
    RunTest(qa_rapi.TestInstance, instance)
178 182

  
183

  
179 184
def RunExportImportTests(instance, pnode):
180 185
  """Tries to export and import the instance.
181 186

  
b/qa/qa-sample.json
45 45
    "cluster-command": true,
46 46
    "cluster-copyfile": true,
47 47
    "cluster-master-failover": true,
48
    "cluster-renew-crypto": true,
48 49
    "cluster-destroy": true,
49 50
    "cluster-rename": true,
50 51

  
b/qa/qa_cluster.py
25 25

  
26 26
import tempfile
27 27

  
28
from ganeti import constants
29
from ganeti import bootstrap
28 30
from ganeti import utils
29 31

  
30 32
import qa_config
31 33
import qa_utils
32 34
import qa_error
33 35

  
34
from qa_utils import AssertEqual, StartSSH
36
from qa_utils import AssertEqual, AssertNotEqual, StartSSH
35 37

  
36 38

  
37 39
def _RemoveFileFromAllNodes(filename):
......
144 146
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
145 147

  
146 148

  
149
def TestClusterRenewCrypto():
150
  """gnt-cluster renew-crypto"""
151
  master = qa_config.GetMasterNode()
152

  
153
  # Conflicting options
154
  cmd = ["gnt-cluster", "renew-crypto", "--force",
155
         "--new-cluster-certificate", "--new-hmac-key",
156
         "--new-rapi-certificate", "--rapi-certificate=/dev/null"]
157
  AssertNotEqual(StartSSH(master["primary"],
158
                          utils.ShellQuoteArgs(cmd)).wait(), 0)
159

  
160
  # Invalid RAPI certificate
161
  cmd = ["gnt-cluster", "renew-crypto", "--force",
162
         "--rapi-certificate=/dev/null"]
163
  AssertNotEqual(StartSSH(master["primary"],
164
                          utils.ShellQuoteArgs(cmd)).wait(), 0)
165

  
166
  # Custom RAPI certificate
167
  fh = tempfile.NamedTemporaryFile()
168

  
169
  # Ensure certificate doesn't cause "gnt-cluster verify" to complain
170
  validity = constants.SSL_CERT_EXPIRATION_WARN * 3
171

  
172
  bootstrap.GenerateSelfSignedSslCert(fh.name, validity=validity)
173

  
174
  tmpcert = qa_utils.UploadFile(master["primary"], fh.name)
175
  try:
176
    cmd = ["gnt-cluster", "renew-crypto", "--force",
177
           "--rapi-certificate=%s" % tmpcert]
178
    AssertEqual(StartSSH(master["primary"],
179
                         utils.ShellQuoteArgs(cmd)).wait(), 0)
180
  finally:
181
    cmd = ["rm", "-f", tmpcert]
182
    AssertEqual(StartSSH(master["primary"],
183
                         utils.ShellQuoteArgs(cmd)).wait(), 0)
184

  
185
  # Normal case
186
  cmd = ["gnt-cluster", "renew-crypto", "--force",
187
         "--new-cluster-certificate", "--new-hmac-key",
188
         "--new-rapi-certificate"]
189
  AssertEqual(StartSSH(master["primary"],
190
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
191

  
192

  
147 193
def TestClusterBurnin():
148 194
  """Burnin"""
149 195
  master = qa_config.GetMasterNode()
b/scripts/gnt-cluster
29 29
import sys
30 30
import os.path
31 31
import time
32
import OpenSSL
32 33

  
33 34
from ganeti.cli import *
34 35
from ganeti import opcodes
......
493 494
    ToStdout("%s %s", path, tag)
494 495

  
495 496

  
497
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
498
                 new_hmac_key, force):
499
  """Renews cluster certificates, keys and secrets.
500

  
501
  @type new_cluster_cert: bool
502
  @param new_cluster_cert: Whether to generate a new cluster certificate
503
  @type new_rapi_cert: bool
504
  @param new_rapi_cert: Whether to generate a new RAPI certificate
505
  @type rapi_cert_filename: string
506
  @param rapi_cert_filename: Path to file containing new RAPI certificate
507
  @type new_hmac_key: bool
508
  @param new_hmac_key: Whether to generate a new HMAC key
509
  @type force: bool
510
  @param force: Whether to ask user for confirmation
511

  
512
  """
513
  assert new_cluster_cert or new_rapi_cert or rapi_cert_filename or new_hmac_key
514

  
515
  if new_rapi_cert and rapi_cert_filename:
516
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
517
             " options can be specified at the same time.")
518
    return 1
519

  
520
  if rapi_cert_filename:
521
    # Read and verify new certificate
522
    try:
523
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
524

  
525
      OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
526
                                      rapi_cert_pem)
527
    except Exception, err: # pylint: disable-msg=W0703
528
      ToStderr("Can't load new RAPI certificate from %s: %s" %
529
               (rapi_cert_filename, str(err)))
530
      return 1
531

  
532
    try:
533
      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
534
    except Exception, err: # pylint: disable-msg=W0703
535
      ToStderr("Can't load new RAPI private key from %s: %s" %
536
               (rapi_cert_filename, str(err)))
537
      return 1
538

  
539
  else:
540
    rapi_cert_pem = None
541

  
542
  if not force:
543
    usertext = ("This requires all daemons on all nodes to be restarted and"
544
                " may take some time. Continue?")
545
    if not AskUser(usertext):
546
      return 1
547

  
548
  def _RenewCryptoInner(ctx):
549
    ctx.feedback_fn("Updating certificates and keys")
550
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
551
                                    new_hmac_key,
552
                                    rapi_cert_pem=rapi_cert_pem)
553

  
554
    files_to_copy = []
555

  
556
    if new_cluster_cert:
557
      files_to_copy.append(constants.SSL_CERT_FILE)
558

  
559
    if new_rapi_cert or rapi_cert_pem:
560
      files_to_copy.append(constants.RAPI_CERT_FILE)
561

  
562
    if new_hmac_key:
563
      files_to_copy.append(constants.HMAC_CLUSTER_KEY)
564

  
565
    if files_to_copy:
566
      for node_name in ctx.nonmaster_nodes:
567
        ctx.feedback_fn("Copying %s to %s" %
568
                        (", ".join(files_to_copy), node_name))
569
        for file_name in files_to_copy:
570
          ctx.ssh.CopyFileToNode(node_name, file_name)
571

  
572
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
573

  
574
  ToStdout("All requested certificates and keys have been replaced."
575
           " Running \"gnt-cluster verify\" now is recommended.")
576

  
577
  return 0
578

  
579

  
580
def RenewCrypto(opts, args):
581
  """Renews cluster certificates, keys and secrets.
582

  
583
  """
584
  return _RenewCrypto(opts.new_cluster_cert,
585
                      opts.new_rapi_cert,
586
                      opts.rapi_cert,
587
                      opts.new_hmac_key,
588
                      opts.force)
589

  
590

  
496 591
def SetClusterParams(opts, args):
497 592
  """Modify the cluster.
498 593

  
......
512 607

  
513 608
  vg_name = opts.vg_name
514 609
  if not opts.lvm_storage and opts.vg_name:
515
    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
610
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
516 611
    return 1
517
  elif not opts.lvm_storage:
518
    vg_name = ''
612

  
613
  if not opts.lvm_storage:
614
    vg_name = ""
519 615

  
520 616
  hvlist = opts.enabled_hypervisors
521 617
  if hvlist is not None:
......
692 788
     NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT],
693 789
    "[opts...]",
694 790
    "Alters the parameters of the cluster"),
791
  "renew-crypto": (
792
    RenewCrypto, ARGS_NONE,
793
    [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT, NEW_HMAC_KEY_OPT,
794
     FORCE_OPT],
795
    "[opts...]",
796
    "Renews cluster certificates, keys and secrets"),
695 797
  }
696 798

  
799

  
697 800
if __name__ == '__main__':
698 801
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))

Also available in: Unified diff