github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/cert.go (about)

     1  // Copyright 2015 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package cli
    12  
    13  import (
    14  	"fmt"
    15  	"os"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/security"
    20  	"github.com/cockroachdb/cockroach/pkg/sql"
    21  	"github.com/cockroachdb/errors"
    22  	"github.com/spf13/cobra"
    23  )
    24  
    25  const defaultKeySize = 2048
    26  
    27  // We use 366 days on certificate lifetimes to at least match X years,
    28  // otherwise leap years risk putting us just under.
    29  const defaultCALifetime = 10 * 366 * 24 * time.Hour  // ten years
    30  const defaultCertLifetime = 5 * 366 * 24 * time.Hour // five years
    31  
    32  var keySize int
    33  var caCertificateLifetime time.Duration
    34  var certificateLifetime time.Duration
    35  var allowCAKeyReuse bool
    36  var overwriteFiles bool
    37  var generatePKCS8Key bool
    38  
    39  // A createCACert command generates a CA certificate and stores it
    40  // in the cert directory.
    41  var createCACertCmd = &cobra.Command{
    42  	Use:   "create-ca --certs-dir=<path to cockroach certs dir> --ca-key=<path-to-ca-key>",
    43  	Short: "create CA certificate and key",
    44  	Long: `
    45  Generate a CA certificate "<certs-dir>/ca.crt" and CA key "<ca-key>".
    46  The certs directory is created if it does not exist.
    47  
    48  If the CA key exists and --allow-ca-key-reuse is true, the key is used.
    49  If the CA certificate exists and --overwrite is true, the new CA certificate is prepended to it.
    50  `,
    51  	Args: cobra.NoArgs,
    52  	RunE: MaybeDecorateGRPCError(runCreateCACert),
    53  }
    54  
    55  // runCreateCACert generates a key and CA certificate and writes them
    56  // to their corresponding files.
    57  func runCreateCACert(cmd *cobra.Command, args []string) error {
    58  	return errors.Wrap(
    59  		security.CreateCAPair(
    60  			baseCfg.SSLCertsDir,
    61  			baseCfg.SSLCAKey,
    62  			keySize,
    63  			caCertificateLifetime,
    64  			allowCAKeyReuse,
    65  			overwriteFiles),
    66  		"failed to generate CA cert and key")
    67  }
    68  
    69  // A createClientCACert command generates a client CA certificate and stores it
    70  // in the cert directory.
    71  var createClientCACertCmd = &cobra.Command{
    72  	Use:   "create-client-ca --certs-dir=<path to cockroach certs dir> --ca-key=<path-to-client-ca-key>",
    73  	Short: "create client CA certificate and key",
    74  	Long: `
    75  Generate a client CA certificate "<certs-dir>/ca-client.crt" and CA key "<client-ca-key>".
    76  The certs directory is created if it does not exist.
    77  
    78  If the CA key exists and --allow-ca-key-reuse is true, the key is used.
    79  If the CA certificate exists and --overwrite is true, the new CA certificate is prepended to it.
    80  
    81  The client CA is optional and should only be used when separate CAs are desired for server certificates
    82  and client certificates.
    83  
    84  If the client CA exists, a client.node.crt client certificate must be created using:
    85    cockroach cert create-client node
    86  
    87  Once the client.node.crt exists, all client certificates will be verified using the client CA.
    88  `,
    89  	Args: cobra.NoArgs,
    90  	RunE: MaybeDecorateGRPCError(runCreateClientCACert),
    91  }
    92  
    93  // runCreateClientCACert generates a key and CA certificate and writes them
    94  // to their corresponding files.
    95  func runCreateClientCACert(cmd *cobra.Command, args []string) error {
    96  	return errors.Wrap(
    97  		security.CreateClientCAPair(
    98  			baseCfg.SSLCertsDir,
    99  			baseCfg.SSLCAKey,
   100  			keySize,
   101  			caCertificateLifetime,
   102  			allowCAKeyReuse,
   103  			overwriteFiles),
   104  		"failed to generate client CA cert and key")
   105  }
   106  
   107  // A createNodeCert command generates a node certificate and stores it
   108  // in the cert directory.
   109  var createNodeCertCmd = &cobra.Command{
   110  	Use:   "create-node --certs-dir=<path to cockroach certs dir> --ca-key=<path-to-ca-key> <host 1> <host 2> ... <host N>",
   111  	Short: "create node certificate and key",
   112  	Long: `
   113  Generate a node certificate "<certs-dir>/node.crt" and key "<certs-dir>/node.key".
   114  
   115  If --overwrite is true, any existing files are overwritten.
   116  
   117  At least one host should be passed in (either IP address or dns name).
   118  
   119  Requires a CA cert in "<certs-dir>/ca.crt" and matching key in "--ca-key".
   120  If "ca.crt" contains more than one certificate, the first is used.
   121  Creation fails if the CA expiration time is before the desired certificate expiration.
   122  `,
   123  	Args: func(cmd *cobra.Command, args []string) error {
   124  		if len(args) == 0 {
   125  			return errors.Errorf("create-node requires at least one host name or address, none was specified")
   126  		}
   127  		return nil
   128  	},
   129  	RunE: MaybeDecorateGRPCError(runCreateNodeCert),
   130  }
   131  
   132  // runCreateNodeCert generates key pair and CA certificate and writes them
   133  // to their corresponding files.
   134  // TODO(marc): there is currently no way to specify which CA cert to use if more
   135  // than one is present. We shoult try to load each certificate along with the key
   136  // and pick the one that works. That way, the key specifies the certificate.
   137  func runCreateNodeCert(cmd *cobra.Command, args []string) error {
   138  	return errors.Wrap(
   139  		security.CreateNodePair(
   140  			baseCfg.SSLCertsDir,
   141  			baseCfg.SSLCAKey,
   142  			keySize,
   143  			certificateLifetime,
   144  			overwriteFiles,
   145  			args),
   146  		"failed to generate node certificate and key")
   147  }
   148  
   149  // A createClientCert command generates a client certificate and stores it
   150  // in the cert directory under <username>.crt and key under <username>.key.
   151  var createClientCertCmd = &cobra.Command{
   152  	Use:   "create-client --certs-dir=<path to cockroach certs dir> --ca-key=<path-to-ca-key> <username>",
   153  	Short: "create client certificate and key",
   154  	Long: `
   155  Generate a client certificate "<certs-dir>/client.<username>.crt" and key
   156  "<certs-dir>/client.<username>.key".
   157  
   158  If --overwrite is true, any existing files are overwritten.
   159  
   160  Requires a CA cert in "<certs-dir>/ca.crt" and matching key in "--ca-key".
   161  If "ca.crt" contains more than one certificate, the first is used.
   162  Creation fails if the CA expiration time is before the desired certificate expiration.
   163  `,
   164  	Args: cobra.ExactArgs(1),
   165  	RunE: MaybeDecorateGRPCError(runCreateClientCert),
   166  }
   167  
   168  // runCreateClientCert generates key pair and CA certificate and writes them
   169  // to their corresponding files.
   170  // TODO(marc): there is currently no way to specify which CA cert to use if more
   171  // than one if present.
   172  func runCreateClientCert(cmd *cobra.Command, args []string) error {
   173  	var err error
   174  	var username string
   175  	// We intentionally allow the `node` user to have a cert.
   176  	if username, err = sql.NormalizeAndValidateUsernameNoBlacklist(args[0]); err != nil {
   177  		return errors.Wrap(err, "failed to generate client certificate and key")
   178  	}
   179  
   180  	return errors.Wrap(
   181  		security.CreateClientPair(
   182  			baseCfg.SSLCertsDir,
   183  			baseCfg.SSLCAKey,
   184  			keySize,
   185  			certificateLifetime,
   186  			overwriteFiles,
   187  			username,
   188  			generatePKCS8Key),
   189  		"failed to generate client certificate and key")
   190  }
   191  
   192  // A listCerts command generates a client certificate and stores it
   193  // in the cert directory under <username>.crt and key under <username>.key.
   194  var listCertsCmd = &cobra.Command{
   195  	Use:   "list",
   196  	Short: "list certs in --certs-dir",
   197  	Long: `
   198  List certificates and keys found in the certificate directory.
   199  `,
   200  	Args: cobra.NoArgs,
   201  	RunE: MaybeDecorateGRPCError(runListCerts),
   202  }
   203  
   204  // runListCerts loads and lists all certs.
   205  func runListCerts(cmd *cobra.Command, args []string) error {
   206  	cm, err := security.NewCertificateManager(baseCfg.SSLCertsDir)
   207  	if err != nil {
   208  		return errors.Wrap(err, "cannot load certificates")
   209  	}
   210  
   211  	fmt.Fprintf(os.Stdout, "Certificate directory: %s\n", baseCfg.SSLCertsDir)
   212  
   213  	certTableHeaders := []string{"Usage", "Certificate File", "Key File", "Expires", "Notes", "Error"}
   214  	alignment := "llllll"
   215  	var rows [][]string
   216  
   217  	addRow := func(ci *security.CertInfo, notes string) {
   218  		var errString string
   219  		if ci.Error != nil {
   220  			errString = ci.Error.Error()
   221  		}
   222  		rows = append(rows, []string{
   223  			ci.FileUsage.String(),
   224  			ci.Filename,
   225  			ci.KeyFilename,
   226  			ci.ExpirationTime.Format("2006/01/02"),
   227  			notes,
   228  			errString,
   229  		})
   230  	}
   231  
   232  	if cert := cm.CACert(); cert != nil {
   233  		var notes string
   234  		if cert.Error == nil && len(cert.ParsedCertificates) > 0 {
   235  			notes = fmt.Sprintf("num certs: %d", len(cert.ParsedCertificates))
   236  		}
   237  		addRow(cert, notes)
   238  	}
   239  
   240  	if cert := cm.ClientCACert(); cert != nil {
   241  		var notes string
   242  		if cert.Error == nil && len(cert.ParsedCertificates) > 0 {
   243  			notes = fmt.Sprintf("num certs: %d", len(cert.ParsedCertificates))
   244  		}
   245  		addRow(cert, notes)
   246  	}
   247  
   248  	if cert := cm.UICACert(); cert != nil {
   249  		var notes string
   250  		if cert.Error == nil && len(cert.ParsedCertificates) > 0 {
   251  			notes = fmt.Sprintf("num certs: %d", len(cert.ParsedCertificates))
   252  		}
   253  		addRow(cert, notes)
   254  	}
   255  
   256  	if cert := cm.NodeCert(); cert != nil {
   257  		var addresses []string
   258  		if cert.Error == nil && len(cert.ParsedCertificates) > 0 {
   259  			addresses = cert.ParsedCertificates[0].DNSNames
   260  			for _, ip := range cert.ParsedCertificates[0].IPAddresses {
   261  				addresses = append(addresses, ip.String())
   262  			}
   263  		} else {
   264  			addresses = append(addresses, "<unknown>")
   265  		}
   266  
   267  		addRow(cert, fmt.Sprintf("addresses: %s", strings.Join(addresses, ",")))
   268  	}
   269  
   270  	if cert := cm.UICert(); cert != nil {
   271  		var addresses []string
   272  		if cert.Error == nil && len(cert.ParsedCertificates) > 0 {
   273  			addresses = cert.ParsedCertificates[0].DNSNames
   274  			for _, ip := range cert.ParsedCertificates[0].IPAddresses {
   275  				addresses = append(addresses, ip.String())
   276  			}
   277  		} else {
   278  			addresses = append(addresses, "<unknown>")
   279  		}
   280  
   281  		addRow(cert, fmt.Sprintf("addresses: %s", strings.Join(addresses, ",")))
   282  	}
   283  
   284  	for _, cert := range cm.ClientCerts() {
   285  		var user string
   286  		if cert.Error == nil && len(cert.ParsedCertificates) > 0 {
   287  			user = cert.ParsedCertificates[0].Subject.CommonName
   288  		} else {
   289  			user = "<unknown>"
   290  		}
   291  
   292  		addRow(cert, fmt.Sprintf("user: %s", user))
   293  	}
   294  
   295  	return printQueryOutput(os.Stdout, certTableHeaders, newRowSliceIter(rows, alignment))
   296  }
   297  
   298  var certCmds = []*cobra.Command{
   299  	createCACertCmd,
   300  	createClientCACertCmd,
   301  	createNodeCertCmd,
   302  	createClientCertCmd,
   303  	listCertsCmd,
   304  }
   305  
   306  var certCmd = &cobra.Command{
   307  	Use:   "cert",
   308  	Short: "create ca, node, and client certs",
   309  	RunE:  usageAndErr,
   310  }
   311  
   312  func init() {
   313  	certCmd.AddCommand(certCmds...)
   314  }