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 }