raft: use file paths for TLS info in the retry_join block (#8894)

* raft: use file paths for TLS info in the retry_join stanza

* raft: maintain backward compat for existing tls params

* docs: update raft docs with new file-based TLS params

* Update godoc comment, fix docs
This commit is contained in:
Calvin Leung Huang 2020-05-06 18:26:08 -07:00 committed by GitHub
parent 69fa1965de
commit e7af25b969
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 172 additions and 25 deletions

View File

@ -118,14 +118,28 @@ type LeaderJoinInfo struct {
// LeaderCACert is the CA cert of the leader node
LeaderCACert string `json:"leader_ca_cert"`
// LeaderClientCert is the client certificate for the follower node to establish
// client authentication during TLS
// LeaderClientCert is the client certificate for the follower node to
// establish client authentication during TLS
LeaderClientCert string `json:"leader_client_cert"`
// LeaderClientKey is the client key for the follower node to establish client
// authentication during TLS
// LeaderClientKey is the client key for the follower node to establish
// client authentication during TLS.
LeaderClientKey string `json:"leader_client_key"`
// LeaderCACertFile is the path on disk to the the CA cert file of the
// leader node. This should only be provided via Vault's configuration file.
LeaderCACertFile string `json:"leader_ca_cert_file"`
// LeaderClientCertFile is the path on disk to the client certificate file
// for the follower node to establish client authentication during TLS. This
// should only be provided via Vault's configuration file.
LeaderClientCertFile string `json:"leader_client_cert_file"`
// LeaderClientKeyFile is the path on disk to the client key file for the
// follower node to establish client authentication during TLS. This should
// only be provided via Vault's configuration file.
LeaderClientKeyFile string `json:"leader_client_key_file"`
// Retry indicates if the join process should automatically be retried
Retry bool `json:"-"`
@ -153,20 +167,35 @@ func (b *RaftBackend) JoinConfig() ([]*LeaderJoinInfo, error) {
for _, info := range leaderInfos {
info.Retry = true
var tlsConfig *tls.Config
var err error
if len(info.LeaderCACert) != 0 || len(info.LeaderClientCert) != 0 || len(info.LeaderClientKey) != 0 {
tlsConfig, err = tlsutil.ClientTLSConfig([]byte(info.LeaderCACert), []byte(info.LeaderClientCert), []byte(info.LeaderClientKey))
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("failed to create tls config to communicate with leader node %q: {{err}}", info.LeaderAPIAddr), err)
}
info.TLSConfig, err = parseTLSInfo(info)
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("failed to create tls config to communicate with leader node %q: {{err}}", info.LeaderAPIAddr), err)
}
info.TLSConfig = tlsConfig
}
return leaderInfos, nil
}
// parseTLSInfo is a helper for parses the TLS information, preferring file
// paths over raw certificate content.
func parseTLSInfo(leaderInfo *LeaderJoinInfo) (*tls.Config, error) {
var tlsConfig *tls.Config
var err error
if len(leaderInfo.LeaderCACertFile) != 0 || len(leaderInfo.LeaderClientCertFile) != 0 || len(leaderInfo.LeaderClientKeyFile) != 0 {
tlsConfig, err = tlsutil.LoadClientTLSConfig(leaderInfo.LeaderCACertFile, leaderInfo.LeaderClientCertFile, leaderInfo.LeaderClientKeyFile)
if err != nil {
return nil, err
}
} else if len(leaderInfo.LeaderCACert) != 0 || len(leaderInfo.LeaderClientCert) != 0 || len(leaderInfo.LeaderClientKey) != 0 {
tlsConfig, err = tlsutil.ClientTLSConfig([]byte(leaderInfo.LeaderCACert), []byte(leaderInfo.LeaderClientCert), []byte(leaderInfo.LeaderClientKey))
if err != nil {
return nil, err
}
}
return tlsConfig, nil
}
// EnsurePath is used to make sure a path exists
func EnsurePath(path string, dir bool) error {
if !dir {

View File

@ -79,6 +79,8 @@ func GetCipherName(cipher uint16) (string, error) {
return "", fmt.Errorf("unsupported cipher %d", cipher)
}
// ClientTLSConfig parses the CA certificate, and optionally a public/private
// client certificate key pair. The certificates must be in PEM encoded format.
func ClientTLSConfig(caCert []byte, clientCert []byte, clientKey []byte) (*tls.Config, error) {
var tlsConfig *tls.Config
var pool *x509.CertPool
@ -117,6 +119,55 @@ func ClientTLSConfig(caCert []byte, clientCert []byte, clientKey []byte) (*tls.C
return tlsConfig, nil
}
// LoadClientTLSConfig loads and parse the CA certificate, and optionally a
// public/private client certificate key pair. The certificates must be in PEM
// encoded format.
func LoadClientTLSConfig(caCert, clientCert, clientKey string) (*tls.Config, error) {
var tlsConfig *tls.Config
var pool *x509.CertPool
switch {
case len(caCert) != 0:
// Valid
case len(clientCert) != 0 && len(clientKey) != 0:
// Valid
default:
return nil, ErrInvalidCertParams
}
if len(caCert) != 0 {
pool = x509.NewCertPool()
data, err := ioutil.ReadFile(caCert)
if err != nil {
return nil, errwrap.Wrapf("failed to read CA file: {{err}}", err)
}
if !pool.AppendCertsFromPEM(data) {
return nil, fmt.Errorf("failed to parse CA certificate")
}
}
tlsConfig = &tls.Config{
RootCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert,
MinVersion: tls.VersionTLS12,
}
var cert tls.Certificate
var err error
if len(clientCert) != 0 && len(clientKey) != 0 {
cert, err = tls.LoadX509KeyPair(clientCert, clientKey)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
tlsConfig.BuildNameToCertificate()
return tlsConfig, nil
}
func SetupTLSConfig(conf map[string]string, address string) (*tls.Config, error) {
serverName, _, err := net.SplitHostPort(address)
switch {

View File

@ -79,6 +79,8 @@ func GetCipherName(cipher uint16) (string, error) {
return "", fmt.Errorf("unsupported cipher %d", cipher)
}
// ClientTLSConfig parses the CA certificate, and optionally a public/private
// client certificate key pair. The certificates must be in PEM encoded format.
func ClientTLSConfig(caCert []byte, clientCert []byte, clientKey []byte) (*tls.Config, error) {
var tlsConfig *tls.Config
var pool *x509.CertPool
@ -117,6 +119,55 @@ func ClientTLSConfig(caCert []byte, clientCert []byte, clientKey []byte) (*tls.C
return tlsConfig, nil
}
// LoadClientTLSConfig loads and parse the CA certificate, and optionally a
// public/private client certificate key pair. The certificates must be in PEM
// encoded format.
func LoadClientTLSConfig(caCert, clientCert, clientKey string) (*tls.Config, error) {
var tlsConfig *tls.Config
var pool *x509.CertPool
switch {
case len(caCert) != 0:
// Valid
case len(clientCert) != 0 && len(clientKey) != 0:
// Valid
default:
return nil, ErrInvalidCertParams
}
if len(caCert) != 0 {
pool = x509.NewCertPool()
data, err := ioutil.ReadFile(caCert)
if err != nil {
return nil, errwrap.Wrapf("failed to read CA file: {{err}}", err)
}
if !pool.AppendCertsFromPEM(data) {
return nil, fmt.Errorf("failed to parse CA certificate")
}
}
tlsConfig = &tls.Config{
RootCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert,
MinVersion: tls.VersionTLS12,
}
var cert tls.Certificate
var err error
if len(clientCert) != 0 && len(clientKey) != 0 {
cert, err = tls.LoadX509KeyPair(clientCert, clientKey)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
tlsConfig.BuildNameToCertificate()
return tlsConfig, nil
}
func SetupTLSConfig(conf map[string]string, address string) (*tls.Config, error) {
serverName, _, err := net.SplitHostPort(address)
switch {

View File

@ -96,13 +96,29 @@ time. To use Raft for HA coordination users must also use Raft for storage.
### `retry_join` stanza
- `leader_api_addr` `(string: "")` - Address of a possible leader node
- `leader_api_addr` `(string: "")` - Address of a possible leader node.
- `leader_ca_cert` `(string: "")` - CA cert of the possible leader node
- `leader_ca_cert_file` `(string: "")` - File path to the CA cert of the
possible leader node.
- `leader_client_cert` `(string: "")` - Client certificate for the follower node to establish client authentication with the possible leader node
- `leader_client_cert_file` `(string: "")` - File path to the client certificate
for the follower node to establish client authentication with the possible
leader node.
- `leader_client_key` `(string: "")` - Client key for the follower node to establish client authentication with the possible leader node
- `leader_client_key_file` `(string: "")` - File path to the client key for the
follower node to establish client authentication with the possible leader node.
- `leader_ca_cert` `(string: "")` - CA cert of the possible leader node.
- `leader_client_cert` `(string: "")` - Client certificate for the follower node
to establish client authentication with the possible leader node.
- `leader_client_key` `(string: "")` - Client key for the follower node to
establish client authentication with the possible leader node.
Each `retry_join` block may provide TLS certificates via file paths or as a
single-line certificate string value with newlines delimited by `\n`, but not a
combination of both.
Example Configuration:
```
@ -111,21 +127,21 @@ storage "raft" {
node_id = "node1"
retry_join {
leader_api_addr = "http://127.0.0.2:8200"
leader_ca_cert = "/path/to/ca1"
leader_client_cert = "/path/to/client/cert1"
leader_client_key = "/path/to/client/key1"
leader_ca_cer_file = "/path/to/ca1"
leader_client_cert_file = "/path/to/client/cert1"
leader_client_key_file = "/path/to/client/key1"
}
retry_join {
leader_api_addr = "http://127.0.0.3:8200"
leader_ca_cert = "/path/to/ca2"
leader_client_cert = "/path/to/client/cert2"
leader_client_key = "/path/to/client/key2"
leader_ca_cert_file = "/path/to/ca2"
leader_client_cert_file = "/path/to/client/cert2"
leader_client_key_file = "/path/to/client/key2"
}
retry_join {
leader_api_addr = "http://127.0.0.4:8200"
leader_ca_cert = "/path/to/ca3"
leader_client_cert = "/path/to/client/cert3"
leader_client_key = "/path/to/client/key3"
leader_ca_cert_file = "/path/to/ca3"
leader_client_cert_file = "/path/to/client/cert3"
leader_client_key_file = "/path/to/client/key3"
}
}
```