Skip to content

Commit

Permalink
server: added initial cert utilities for automatic certificate genera…
Browse files Browse the repository at this point in the history
…tion

server: added utility function for bundling init certs
server: added init function that uses a recieved bundle to provision a node
security: added helper functions to support automatic certificate generation
security: added ClientCAKeyPath helper to align with ClientCACertPath

This is part of cockroachdb#60632 and provides functions for cockroachdb#60636.

Release note: None
  • Loading branch information
Aaron Blum committed Feb 21, 2021
1 parent be115b9 commit e212b6d
Show file tree
Hide file tree
Showing 5 changed files with 780 additions and 0 deletions.
1 change: 1 addition & 0 deletions pkg/security/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go_library(
name = "security",
srcs = [
"auth.go",
"auto_tls_init.go",
"certificate_loader.go",
"certificate_manager.go",
"certs.go",
Expand Down
216 changes: 216 additions & 0 deletions pkg/security/auto_tls_init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// Copyright 2021 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package security

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"time"

"github.com/cockroachdb/cockroach/pkg/util/timeutil"
"github.com/cockroachdb/errors"
)

// TODO(aaron-crl): This shared a name and purpose with the value in
// pkg/security and should be consolidated.
const defaultKeySize = 4096

// notBeforeMargin provides a window to compensate for potential clock skew.
const notBeforeMargin = time.Second * 30

// createCertificateSerialNumber is a helper function that generates a
// random value between 1 and 2^130-1.
func createCertificateSerialNumber() (serialNumber *big.Int, err error) {
max := new(big.Int)
max.Exp(big.NewInt(2), big.NewInt(130), nil).Sub(max, big.NewInt(1))
serialNumber, err = rand.Int(rand.Reader, max)
if err != nil {
err = errors.Wrap(err, "failed to create new serial number")
}
return
}

// CreateCACertAndKey will create a CA with a validity beginning
// now() and expiring after `lifespan`. This is a utility function to help
// with cluster auto certificate generation.
func CreateCACertAndKey(lifespan time.Duration, service string) (certPEM []byte, keyPEM []byte, err error) {

notBefore := timeutil.Now().Add(-notBeforeMargin)
notAfter := timeutil.Now().Add(lifespan)

// Create random serial number for CA.
serialNumber, err := createCertificateSerialNumber()
if err != nil {
return nil, nil, err
}

// Create short lived initial CA template.
ca := &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Cockroach Labs"},
OrganizationalUnit: []string{service},
Country: []string{"US"},
},
NotBefore: notBefore,
NotAfter: notAfter,
IsCA: true,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
MaxPathLenZero: true,
}

// Create private and public key for CA.
caPrivKey, err := rsa.GenerateKey(rand.Reader, defaultKeySize)
if err != nil {
return nil, nil, err
}

caPrivKeyPEM := new(bytes.Buffer)
caPrivKeyPEMBytes, err := x509.MarshalPKCS8PrivateKey(caPrivKey)
if err != nil {
return nil, nil, err
}

err = pem.Encode(caPrivKeyPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: caPrivKeyPEMBytes,
})
if err != nil {
return nil, nil, err
}

// Create CA certificate then PEM encode it.
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
if err != nil {
return nil, nil, err
}

caPEM := new(bytes.Buffer)
err = pem.Encode(caPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
})
if err != nil {
return nil, nil, err
}

certPEM = caPEM.Bytes()
keyPEM = caPrivKeyPEM.Bytes()

return certPEM, keyPEM, nil
}

// CreateServiceCertAndKey creates a cert/key pair signed by the provided CA.
// This is a utility function to help with cluster auto certificate generation.
func CreateServiceCertAndKey(lifespan time.Duration, service, hostname string, caCertPEM []byte, caKeyPEM []byte) (certPEM []byte, keyPEM []byte, err error) {

notBefore := timeutil.Now().Add(-notBeforeMargin)
notAfter := timeutil.Now().Add(lifespan)

// Create random serial number for CA.
serialNumber, err := createCertificateSerialNumber()
if err != nil {
return nil, nil, err
}

caCertBlock, _ := pem.Decode(caCertPEM)
if caCertBlock == nil {
err = errors.New("failed to parse valid PEM from CaCertificate blob")
return nil, nil, err
}

caCert, err := x509.ParseCertificate(caCertBlock.Bytes)
if err != nil {
err = errors.Wrap(err, "failed to parse valid Certificate from PEM blob")
return nil, nil, err
}

caKeyBlock, _ := pem.Decode(caKeyPEM)
if caKeyBlock == nil {
err = errors.New("failed to parse valid PEM from CaKey blob")
return nil, nil, err
}

caKey, err := x509.ParsePKCS8PrivateKey(caKeyBlock.Bytes)
if err != nil {
err = errors.Wrap(err, "failed to parse valid Certificate from PEM blob")
return nil, nil, err
}

// Bulid service certificate template; template will be used for all
// autogenerated service certificates.
// TODO(aaron-crl): This should match the implementation in
// pkg/security/x509.go until we can consolidate them.
serviceCert := &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Cockroach Labs"},
OrganizationalUnit: []string{service},
Country: []string{"US"},
},
NotBefore: notBefore,
NotAfter: notAfter,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
}

// Attempt to parse hostname as IP, if successful add it as an IP
// otherwise presume it is a DNS name.
// TODO(aaron-crl): Pass these values via config object.
ip := net.ParseIP(hostname)
if ip != nil {
serviceCert.IPAddresses = []net.IP{ip}
} else {
serviceCert.DNSNames = []string{hostname}
}

servicePrivKey, err := rsa.GenerateKey(rand.Reader, defaultKeySize)
if err != nil {
return nil, nil, err
}

serviceCertBytes, err := x509.CreateCertificate(rand.Reader, serviceCert, caCert, &servicePrivKey.PublicKey, caKey)
if err != nil {
return nil, nil, err
}

serviceCertBlock := new(bytes.Buffer)
err = pem.Encode(serviceCertBlock, &pem.Block{
Type: "CERTIFICATE",
Bytes: serviceCertBytes,
})
if err != nil {
return nil, nil, err
}

servicePrivKeyPEM := new(bytes.Buffer)
certPrivKeyPEMBytes, err := x509.MarshalPKCS8PrivateKey(servicePrivKey)
if err != nil {
return nil, nil, err
}

err = pem.Encode(servicePrivKeyPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: certPrivKeyPEMBytes,
})
if err != nil {
return nil, nil, err
}

return serviceCertBlock.Bytes(), servicePrivKeyPEM.Bytes(), err
}
112 changes: 112 additions & 0 deletions pkg/security/certificate_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,14 @@ func (cl CertsLocator) CACertPath() string {
// CACertFilename returns the expected file name for the CA certificate.
func CACertFilename() string { return "ca" + certExtension }

// CAKeyPath returns the expected file path for the CA certificate.
func (cl CertsLocator) CAKeyPath() string {
return filepath.Join(cl.certsDir, CACertFilename())
}

// CAKeyFilename returns the expected file name for the CA certificate.
func CAKeyFilename() string { return "ca" + keyExtension }

// TenantClientCACertPath returns the expected file path for the Tenant client CA
// certificate.
func (cl CertsLocator) TenantClientCACertPath() string {
Expand All @@ -283,12 +291,24 @@ func (cl CertsLocator) ClientCACertPath() string {
return filepath.Join(cl.certsDir, "ca-client"+certExtension)
}

// ClientCAKeyPath returns the expected file path for the CA key
// used to sign client certificates.
func (cl CertsLocator) ClientCAKeyPath() string {
return filepath.Join(cl.certsDir, "ca-client"+keyExtension)
}

// UICACertPath returns the expected file path for the CA certificate
// used to verify Admin UI certificates.
func (cl CertsLocator) UICACertPath() string {
return filepath.Join(cl.certsDir, "ca-ui"+certExtension)
}

// UICAKeyPath returns the expected file path for the CA certificate
// used to verify Admin UI certificates.
func (cl CertsLocator) UICAKeyPath() string {
return filepath.Join(cl.certsDir, "ca-ui"+keyExtension)
}

// NodeCertPath returns the expected file path for the node certificate.
func (cl CertsLocator) NodeCertPath() string {
return filepath.Join(cl.certsDir, NodeCertFilename())
Expand Down Expand Up @@ -359,6 +379,98 @@ func ClientKeyFilename(user SQLUsername) string {
return "client." + user.Normalized() + keyExtension
}

// SQLServiceCertPath returns the expected file path for the
// SQL service certificate
func (cl CertsLocator) SQLServiceCertPath() string {
return filepath.Join(cl.certsDir, SQLServiceCertFilename())
}

// SQLServiceCertFilename returns the expected file name for the SQL service
// certificate
func SQLServiceCertFilename() string {
return "service.sql" + certExtension
}

// SQLServiceKeyPath returns the expected file path for the SQL service key
func (cl CertsLocator) SQLServiceKeyPath() string {
return filepath.Join(cl.certsDir, SQLServiceKeyFilename())
}

// SQLServiceKeyFilename returns the expected file name for the SQL service
// certificate
func SQLServiceKeyFilename() string {
return "service.sql" + keyExtension
}

// SQLServiceCACertPath returns the expected file path for the
// SQL CA certificate
func (cl CertsLocator) SQLServiceCACertPath() string {
return filepath.Join(cl.certsDir, SQLServiceCACertFilename())
}

// SQLServiceCACertFilename returns the expected file name for the SQL CA
// certificate
func SQLServiceCACertFilename() string {
return "service.ca.sql" + certExtension
}

// SQLServiceCAKeyPath returns the expected file path for the SQL CA key
func (cl CertsLocator) SQLServiceCAKeyPath() string {
return filepath.Join(cl.certsDir, SQLServiceCAKeyFilename())
}

// SQLServiceCAKeyFilename returns the expected file name for the SQL CA
// certificate
func SQLServiceCAKeyFilename() string {
return "service.ca.sql" + keyExtension
}

// RPCServiceCertPath returns the expected file path for the
// RPC service certificate
func (cl CertsLocator) RPCServiceCertPath() string {
return filepath.Join(cl.certsDir, RPCServiceCertFilename())
}

// RPCServiceCertFilename returns the expected file name for the RPC service
// certificate
func RPCServiceCertFilename() string {
return "service.rpc" + certExtension
}

// RPCServiceKeyPath returns the expected file path for the RPC service key
func (cl CertsLocator) RPCServiceKeyPath() string {
return filepath.Join(cl.certsDir, RPCServiceKeyFilename())
}

// RPCServiceKeyFilename returns the expected file name for the RPC service
// certificate
func RPCServiceKeyFilename() string {
return "service.rpc" + keyExtension
}

// RPCServiceCACertPath returns the expected file path for the
// RPC service certificate
func (cl CertsLocator) RPCServiceCACertPath() string {
return filepath.Join(cl.certsDir, RPCServiceCACertFilename())
}

// RPCServiceCACertFilename returns the expected file name for the RPC service
// certificate
func RPCServiceCACertFilename() string {
return "service.rpc" + certExtension
}

// RPCServiceCAKeyPath returns the expected file path for the RPC service key
func (cl CertsLocator) RPCServiceCAKeyPath() string {
return filepath.Join(cl.certsDir, RPCServiceCAKeyFilename())
}

// RPCServiceCAKeyFilename returns the expected file name for the RPC service
// certificate
func RPCServiceCAKeyFilename() string {
return "service.rpc" + keyExtension
}

// CACert returns the CA cert. May be nil.
// Callers should check for an internal Error field.
func (cm *CertificateManager) CACert() *CertInfo {
Expand Down
1 change: 1 addition & 0 deletions pkg/server/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ go_library(
"api_auth.go",
"api_error.go",
"authentication.go",
"auto_tls_init.go",
"auto_upgrade.go",
"config.go",
"config_unix.go",
Expand Down
Loading

0 comments on commit e212b6d

Please sign in to comment.