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

Support TLS config as base64 values #93

Merged
merged 1 commit into from
Jan 26, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config/development.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ tls:
caFile:
certFile:
keyFile:
caData:
certData:
keyData:
enableHostVerification: false
serverName:
5 changes: 4 additions & 1 deletion docker/config_template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ cors:
# override framework's default that allows all origins "*"
- {{ default .Env.TEMPORAL_CORS_ORIGINS "http://localhost:8080" }}
tls:
сaFile: {{ default .Env.TEMPORAL_TLS_CA "" }}
caFile: {{ default .Env.TEMPORAL_TLS_CA "" }}
certFile: {{ default .Env.TEMPORAL_TLS_CERT "" }}
keyFile: {{ default .Env.TEMPORAL_TLS_KEY "" }}
caData: {{ default .Env.TEMPORAL_TLS_CA_DATA "" }}
certData: {{ default .Env.TEMPORAL_TLS_CERT_DATA "" }}
keyData: {{ default .Env.TEMPORAL_TLS_KEY_DATA "" }}
enableHostVerification: {{ default .Env.TEMPORAL_TLS_ENABLE_HOST_VERIFICATION "false" }}
serverName: {{ default .Env.TEMPORAL_TLS_SERVER_NAME "" }}
auth:
Expand Down
5 changes: 4 additions & 1 deletion server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@ type (
}

TLS struct {
CaFile string `yaml:"сaFile"`
CaFile string `yaml:"caFile"`
CertFile string `yaml:"certFile"`
KeyFile string `yaml:"keyFile"`
CaData string `yaml:"caData"`
CertData string `yaml:"certData"`
KeyData string `yaml:"keyData"`
EnableHostVerification bool `yaml:"enableHostVerification"`
ServerName string `yaml:"serverName"`
}
Expand Down
106 changes: 92 additions & 14 deletions server/rpc/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,17 @@ package rpc
import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"strings"
"time"

"github.com/labstack/echo/v4"
"github.com/temporalio/ui-server/server/config"
)

Expand All @@ -49,26 +51,32 @@ type HttpGetter interface {
Get(url string) (resp *http.Response, err error)
}

func CreateTLSConfig(address string, cfg config.TLS, logger echo.Logger) (*tls.Config, error) {
func CreateTLSConfig(address string, cfg *config.TLS) (*tls.Config, error) {
var host string
var cert *tls.Certificate
var caPool *x509.CertPool

if cfg.CaFile != "" {
caCertPool, err := fetchCACert(cfg.CaFile)
err := validateTLS(cfg)
if err != nil {
return nil, err
}

if cfg.CaFile != "" || cfg.CaData != "" {
caCertPool, err := loadCACert(cfg)
if err != nil {
logger.Fatalf("Unable to load server CA certificate")
log.Fatalf("Unable to load server CA certificate")
return nil, err
}

caPool = caCertPool
}
if cfg.CertFile != "" {
myCert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile)
if cfg.CertFile != "" || cfg.CertData != "" {
keyPair, err := loadKeyPair(cfg)
if err != nil {
logger.Fatalf("Unable to load client certificate")
log.Fatalf("Unable to load client certificate")
return nil, err
}
cert = &myCert
cert = &keyPair
}
// If we are given arguments to verify either server or client, configure TLS
if caPool != nil || cert != nil {
Expand Down Expand Up @@ -102,7 +110,10 @@ func CreateTLSConfig(address string, cfg config.TLS, logger echo.Logger) (*tls.C
return nil, nil
}

func fetchCACert(pathOrUrl string) (caPool *x509.CertPool, err error) {
func loadCACert(cfg *config.TLS) (caPool *x509.CertPool, err error) {
pathOrUrl := cfg.CaFile
caData := cfg.CaData

caPool = x509.NewCertPool()
var caBytes []byte

Expand All @@ -113,18 +124,27 @@ func fetchCACert(pathOrUrl string) (caPool *x509.CertPool, err error) {
if strings.HasPrefix(pathOrUrl, "https://") {
resp, err := netClient.Get(pathOrUrl)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to load CA cert from URL: %v", err)
}
defer resp.Body.Close()
caBytes, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to load CA cert from URL: %v", err)
}
} else {

log.Printf("Loaded TLS CA cert from URL: %v", pathOrUrl)
} else if pathOrUrl != "" {
caBytes, err = os.ReadFile(pathOrUrl)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to load CA cert from file: %v", err)
}
log.Printf("Loaded TLS CA cert from file: %v", pathOrUrl)
} else if caData != "" {
caBytes, err = base64.StdEncoding.DecodeString(caData)
if err != nil {
return nil, fmt.Errorf("unable to decode CA cert from base64: %v", err)
}
log.Printf("Loaded CA cert from base64")
}

if !caPool.AppendCertsFromPEM(caBytes) {
Expand All @@ -133,6 +153,47 @@ func fetchCACert(pathOrUrl string) (caPool *x509.CertPool, err error) {
return caPool, nil
}

func loadKeyPair(cfg *config.TLS) (tls.Certificate, error) {
var certBytes []byte
var keyBytes []byte
var err error

if cfg.KeyFile != "" {
keyBytes, err = os.ReadFile(cfg.KeyFile)
if err != nil {
return tls.Certificate{}, fmt.Errorf("unable to load TLS key from file: %v", err)
}
log.Printf("Loaded TLS key from file %v", cfg.KeyFile)
} else if cfg.KeyData != "" {
keyBytes, err = base64.StdEncoding.DecodeString(cfg.KeyData)
if err != nil {
return tls.Certificate{}, fmt.Errorf("unable to decode TLS key from base64: %w", err)
}
log.Printf("Loaded TLS key from base64")
}

if cfg.CertFile != "" {
certBytes, err = os.ReadFile(cfg.CertFile)
if err != nil {
return tls.Certificate{}, fmt.Errorf("unable to load TLS cert from file: %v", err)
}
log.Printf("Loaded TLS cert from file %v", cfg.CertFile)
} else if cfg.CertData != "" {
certBytes, err = base64.StdEncoding.DecodeString(cfg.CertData)
if err != nil {
return tls.Certificate{}, fmt.Errorf("unable to decode TLS cert from base64: %w", err)
}
log.Printf("Loaded TLS cert from base64")
}

keyPair, err := tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
return tls.Certificate{}, fmt.Errorf("unable to generate x509 key pair: %w", err)
}

return keyPair, err
}

func NewEmptyTLSConfig() *tls.Config {
return &tls.Config{
MinVersion: tls.VersionTLS12,
Expand All @@ -151,3 +212,20 @@ func NewTLSConfigForServer(
c.InsecureSkipVerify = !enableHostVerification
return c
}

func validateTLS(cfg *config.TLS) error {
if cfg.CertFile != "" && cfg.CertData != "" {
return fmt.Errorf("cannot specify TLS cert file and cert data at the same time")
}
if cfg.KeyFile != "" && cfg.KeyData != "" {
return fmt.Errorf("cannot specify TLS key file and key data at the same time")
}
if cfg.CaFile != "" && cfg.CaData != "" {
return fmt.Errorf("cannot specify TLS CA file and CA data at the same time")
}

if strings.TrimSpace(cfg.CaFile) == "" && strings.TrimSpace(cfg.CaData) == "" {
return fmt.Errorf("CA cannot be empty string")
}
return nil
}
7 changes: 6 additions & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ package server
import (
"embed"
"fmt"
"log"

"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
Expand Down Expand Up @@ -101,7 +102,11 @@ func NewServer(opts ...server_options.ServerOption) *Server {
securecookie.GenerateRandomKey(32),
)))

tlsCfg, _ := rpc.CreateTLSConfig(serverOpts.Config.TemporalGRPCAddress, serverOpts.Config.TLS, e.Logger)
tlsCfg, err := rpc.CreateTLSConfig(serverOpts.Config.TemporalGRPCAddress, &serverOpts.Config.TLS)
if err != nil {
log.Printf("Unable to create TLS config: %v\n", err)
log.Printf("Establishing insecure connection to Temporal")
}
conn := rpc.CreateFrontendGRPCConnection(serverOpts.Config.TemporalGRPCAddress, tlsCfg)
routes.SetAPIRoutes(e, serverOpts.Config, conn)

Expand Down