Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from May 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 41 additions & 12 deletions physical/raft/raft.go
Expand Up @@ -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"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comments on these new fields have the wrong field name (missing File at the end) and should mention that this is a path to a file on disk.


// Retry indicates if the join process should automatically be retried
Retry bool `json:"-"`

Expand Down Expand Up @@ -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 {
Expand Down
51 changes: 51 additions & 0 deletions sdk/helper/tlsutil/tlsutil.go
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
51 changes: 51 additions & 0 deletions vendor/github.com/hashicorp/vault/sdk/helper/tlsutil/tlsutil.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 29 additions & 13 deletions website/pages/docs/configuration/storage/raft.mdx
Expand Up @@ -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:
```
Expand All @@ -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"
}
}
```
Expand Down