Skip to content
159 changes: 137 additions & 22 deletions common/rpc/encryption/localStoreCertProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ package encryption
import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
Expand All @@ -52,22 +53,36 @@ func (s *localStoreCertProvider) GetSettings() *config.GroupTLS {
}

func (s *localStoreCertProvider) FetchServerCertificate() (*tls.Certificate, error) {
if s.tlsSettings.Server.CertFile == "" {
if !s.tlsSettings.Server.InlineData && s.tlsSettings.Server.CertFile == "" {
return nil, nil
}

if s.tlsSettings.Server.InlineData && s.tlsSettings.Server.CertData == "" {
return nil, nil
}

// Check under a read lock first
s.RLock()
if s.serverCert != nil {
defer s.RUnlock()
return s.serverCert, nil
}
// Not found, manually unlock read lock and move to write lock

s.RUnlock()
s.Lock()
defer s.Unlock()

// Get serverCert from disk
if s.serverCert != nil {
return s.serverCert, nil
}

if s.tlsSettings.Server.InlineData {
return s.fetchServerCertificateFromInline()
}

return s.fetchServerCertificateFromFile()
}

func (s *localStoreCertProvider) fetchServerCertificateFromFile() (*tls.Certificate, error) {
serverCert, err := tls.LoadX509KeyPair(s.tlsSettings.Server.CertFile, s.tlsSettings.Server.KeyFile)
if err != nil {
return nil, fmt.Errorf("loading server tls certificate failed: %v", err)
Expand All @@ -77,57 +92,142 @@ func (s *localStoreCertProvider) FetchServerCertificate() (*tls.Certificate, err
return s.serverCert, nil
}

func (s *localStoreCertProvider) fetchServerCertificateFromInline() (*tls.Certificate, error) {
if s.tlsSettings.Server.CertData == "" {
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we return an error here?

return nil, nil
}

certBytes, err := base64.StdEncoding.DecodeString(s.tlsSettings.Server.CertData)
if err != nil {
return nil, fmt.Errorf("TLS public certificate could not be decoded: %w", err)
}

keyBytes, err := base64.StdEncoding.DecodeString(s.tlsSettings.Server.KeyData)
if err != nil {
return nil, fmt.Errorf("TLS certificate private key could not be decoded: %w", err)
}

serverCert, err := tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
return nil, fmt.Errorf("loading server tls certificate failed: %v", err)
}

s.serverCert = &serverCert
return s.serverCert, nil
}

func (s *localStoreCertProvider) FetchClientCAs() (*x509.CertPool, error) {
if s.tlsSettings.Server.ClientCAFiles == nil {
if !s.tlsSettings.Server.InlineData && len(s.tlsSettings.Server.ClientCAFiles) == 0 {
return nil, nil
}
if s.tlsSettings.Server.InlineData && len(s.tlsSettings.Server.ClientCaData) == 0 {
return nil, nil
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we return an error here?

Copy link
Member Author

Choose a reason for hiding this comment

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

we were not returning an error before my change, but am not opposed to returnong one here given that a user who requires mutual authentication and doesn't set this has a broken environment

Copy link
Member

Choose a reason for hiding this comment

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

Disregard. My mistake, sorry.

}

// Check under a read lock first
s.RLock()
if s.clientCAs != nil {
defer s.RUnlock()
return s.clientCAs, nil
}
// Not found, manually unlock read lock and move to write lock

s.RUnlock()
s.Lock()
defer s.Unlock()

if s.clientCAs != nil {
return s.clientCAs, nil
}

if s.tlsSettings.Server.InlineData {
return s.fetchClientCAsFromInline()
}

return s.fetchClientCAsFromFiles()
}

func (s *localStoreCertProvider) fetchClientCAsFromFiles() (*x509.CertPool, error) {
if len(s.tlsSettings.Server.ClientCAFiles) == 0 {
return nil, nil
}

var clientCaPool *x509.CertPool
if len(s.tlsSettings.Server.ClientCAFiles) > 0 {
var err error
clientCaPool, err = buildCAPool(s.tlsSettings.Server.ClientCAFiles)
if err != nil {
return nil, err
}
var err error
clientCaPool, err = buildCAPool(s.tlsSettings.Server.ClientCAFiles)
if err != nil {
return nil, err
}

s.clientCAs = clientCaPool
return s.clientCAs, nil
}

func (s *localStoreCertProvider) fetchClientCAsFromInline() (*x509.CertPool, error) {
if len(s.tlsSettings.Server.ClientCaData) == 0 {
return nil, nil
}

var clientCaPool *x509.CertPool
var err error
clientCaPool, err = buildCAPoolFromInline(s.tlsSettings.Server.ClientCaData)
if err != nil {
return nil, err
}

s.clientCAs = clientCaPool
return s.clientCAs, nil
}

func (s *localStoreCertProvider) FetchServerRootCAsForClient() (*x509.CertPool, error) {
if s.tlsSettings.Client.RootCAFiles == nil {
if !s.tlsSettings.Client.InlineData && len(s.tlsSettings.Client.RootCAFiles) == 0 {
return nil, nil
}
if s.tlsSettings.Client.InlineData && len(s.tlsSettings.Client.RootCAData) == 0 {
return nil, nil
}
// Check under a read lock first

s.RLock()
if s.serverCAs != nil {
defer s.RUnlock()
return s.clientCAs, nil
}
// Not found, manually unlock read lock and move to write lock

s.RUnlock()
s.Lock()
defer s.Unlock()

if s.tlsSettings.Client.InlineData {
return s.fetchServerRootCAsForClientFromInline()
}

return s.fetchServerRootCAsForClientFromFiles()
}

func (s *localStoreCertProvider) fetchServerRootCAsForClientFromFiles() (*x509.CertPool, error) {
if len(s.tlsSettings.Client.RootCAFiles) == 0 {
return nil, nil
}

var serverCAPool *x509.CertPool
if len(s.tlsSettings.Client.RootCAFiles) > 0 {
var err error
serverCAPool, err = buildCAPool(s.tlsSettings.Client.RootCAFiles)
if err != nil {
return nil, err
}
var err error
serverCAPool, err = buildCAPool(s.tlsSettings.Client.RootCAFiles)
if err != nil {
return nil, err
}

s.serverCAs = serverCAPool
return s.serverCAs, nil
}

func (s *localStoreCertProvider) fetchServerRootCAsForClientFromInline() (*x509.CertPool, error) {
if len(s.tlsSettings.Client.RootCAData) == 0 {
return nil, nil
}

var serverCAPool *x509.CertPool
var err error
serverCAPool, err = buildCAPoolFromInline(s.tlsSettings.Client.RootCAData)
if err != nil {
return nil, err
}

s.serverCAs = serverCAPool
Expand All @@ -148,3 +248,18 @@ func buildCAPool(caFiles []string) (*x509.CertPool, error) {
}
return caPool, nil
}

func buildCAPoolFromInline(caData []string) (*x509.CertPool, error) {
caPool := x509.NewCertPool()
for _, ca := range caData {
caBytes, err := base64.StdEncoding.DecodeString(ca)
if err != nil {
return nil, fmt.Errorf("failed reading client ca cert: %v", err)
}

if !caPool.AppendCertsFromPEM(caBytes) {
return nil, errors.New("unknown failure constructing cert pool for ca")
}
}
return caPool, nil
}
15 changes: 14 additions & 1 deletion common/service/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ type (
// A list of paths to files containing the PEM-encoded public key of the Certificate Authorities you wish to trust for client authentication.
// This value is ignored if `requireClientAuth` is not enabled.
ClientCAFiles []string `yaml:"clientCaFiles"`

// Optional inline base64 encoded versions of the above file paths
// Either specify base64 versions or file paths for all settings.
// Do not mix/match both. Set InlineData to true to use base64
InlineData bool `yaml:"inlineData"`
CertData string `yaml:"certData"`
KeyData string `yaml:"keyData"`
ClientCaData []string `yaml:"clientCaData"`

// Requires clients to authenticate with a certificate when connecting, otherwise known as mutual TLS.
RequireClientAuth bool `yaml:"requireClientAuth"`
}
Expand All @@ -148,6 +157,10 @@ type (

// Optional - A list of paths to files containing the PEM-encoded public key of the Certificate Authorities you wish to trust.
RootCAFiles []string `yaml:"rootCaFiles"`

// InlineData - indicates whether to read certificates from RootCAFiles or via RootCAData
InlineData bool `yaml:"inlineData"`
RootCAData []string `yaml:"rootCaData"`
}

// Membership contains config items related to the membership layer of temporal
Expand Down Expand Up @@ -497,5 +510,5 @@ func (c *Config) String() string {
}

func (r *GroupTLS) IsEnabled() bool {
return r.Server.KeyFile != ""
return r.Server.KeyFile != "" || r.Server.KeyData != ""
}
43 changes: 41 additions & 2 deletions docker/config_template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,29 @@ persistence:
cassandra:
hosts: {{ default .Env.CASSANDRA_SEEDS "" }}
keyspace: {{ default .Env.KEYSPACE "temporal" }}
user: {{ default .Env.CASSANDRA_USER "" }}
password: {{ default .Env.CASSANDRA_PASSWORD "" }}
port: {{ default .Env.CASSANDRA_PORT "9042" }}
tls:
enabled: {{ default .Env.CASSANDRA_TLS_ENABLED "false" }}
caData: {{ default .Env.CASSANDRA_CA_DATA "" }}
certData: {{ default .Env.CASSANDRA_CERT_DATA "" }}
keyData: {{ default .Env.CASSANDRA_CERT_KEY_DATA "" }}
enableHostVerification: {{ default .Env.CASSANDRA_HOST_VERIFICATION "false"}}

visibility:
cassandra:
hosts: {{ default .Env.CASSANDRA_SEEDS "" }}
keyspace: {{ default .Env.VISIBILITY_KEYSPACE "temporal_visibility" }}
user: {{ default .Env.CASSANDRA_USER "" }}
password: {{ default .Env.CASSANDRA_PASSWORD "" }}
port: {{ default .Env.CASSANDRA_PORT "9042" }}
tls:
enabled: {{ default .Env.CASSANDRA_TLS_ENABLED "false" }}
caData: {{ default .Env.CASSANDRA_CA_DATA "" }}
certData: {{ default .Env.CASSANDRA_CERT_DATA "" }}
keyData: {{ default .Env.CASSANDRA_CERT_KEY_DATA "" }}
enableHostVerification: {{ default .Env.CASSANDRA_HOST_VERIFICATION "false"}}
{{- else if eq $db "mysql" }}
default:
sql:
Expand Down Expand Up @@ -93,25 +112,45 @@ global:
tls:
internode:
server:
requireClientAuth: {{ default .Env.TEMPORAL_TLS_REQUIRE_CLIENT_AUTH "false" }}

certFile: {{ default .Env.TEMPORAL_TLS_SERVER_CERT "" }}
keyFile: {{ default .Env.TEMPORAL_TLS_SERVER_KEY "" }}
requireClientAuth: {{ default .Env.TEMPORAL_TLS_REQUIRE_CLIENT_AUTH "false" }}
clientCaFiles:
- {{ default .Env.TEMPORAL_TLS_SERVER_CA_CERT "" }}

inlineData: {{ default .Env.TEMPORAL_TLS_INLINE "false" }}
certData: {{ default .Env.TEMPORAL_TLS_SERVER_CERT_DATA "" }}
keyData: {{ default .Env.TEMPORAL_TLS_SERVER_KEY_DATA "" }}
clientCaData:
- {{ default .Env.TEMPORAL_TLS_SERVER_CA_CERT_DATA "" }}
client:
rootCaFiles:
- {{ default .Env.TEMPORAL_TLS_SERVER_CA_CERT "" }}
rootCaData:
- {{ default .Env.TEMPORAL_TLS_SERVER_CA_CERT_DATA "" }}
inlineData: {{ default .Env.TEMPORAL_TLS_INLINE "false" }}
frontend:
server:
requireClientAuth: {{ default .Env.TEMPORAL_TLS_REQUIRE_CLIENT_AUTH "false" }}
certFile: {{ default .Env.TEMPORAL_TLS_FRONTEND_CERT "" }}
keyFile: {{ default .Env.TEMPORAL_TLS_FRONTEND_KEY "" }}
requireClientAuth: {{ default .Env.TEMPORAL_TLS_REQUIRE_CLIENT_AUTH "false" }}
clientCaFiles:
- {{ default .Env.TEMPORAL_TLS_CLIENT1_CA_CERT "" }}
- {{ default .Env.TEMPORAL_TLS_CLIENT2_CA_CERT "" }}

inlineData: {{ default .Env.TEMPORAL_TLS_INLINE "false" }}
certData: {{ default .Env.TEMPORAL_TLS_FRONTEND_CERT_DATA "" }}
keyData: {{ default .Env.TEMPORAL_TLS_FRONTEND_KEY_DATA "" }}
clientCaData:
- {{ default .Env.TEMPORAL_TLS_CLIENT1_CA_CERT_DATA "" }}
- {{ default .Env.TEMPORAL_TLS_CLIENT2_CA_CERT_DATA "" }}
client:
rootCaFiles:
- {{ default .Env.TEMPORAL_TLS_SERVER_CA_CERT "" }}
rootCaData:
- {{ default .Env.TEMPORAL_TLS_SERVER_CA_CERT_DATA "" }}
inlineData: {{ default .Env.TEMPORAL_TLS_INLINE "false" }}

services:
frontend:
Expand Down