Skip to content

Commit cd1e466

Browse files
authored
Add support for TLS and mTLS to tctl (#812)
* Add command line options for TLS to tctl * Add support for mTLS to tctl
1 parent 7370293 commit cd1e466

File tree

2 files changed

+84
-7
lines changed

2 files changed

+84
-7
lines changed

tools/cli/app.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,24 @@ func NewCliApp() *cli.App {
6565
Usage: "automatically confirm all prompts",
6666
Hidden: true,
6767
},
68+
cli.StringFlag{
69+
Name: FlagTLSCertPath,
70+
Value: "",
71+
Usage: "path to x509 certificate",
72+
EnvVar: "TEMPORAL_CLI_TLS_CERT",
73+
},
74+
cli.StringFlag{
75+
Name: FlagTLSKeyPath,
76+
Value: "",
77+
Usage: "path to private key",
78+
EnvVar: "TEMPORAL_CLI_TLS_KEY",
79+
},
80+
cli.StringFlag{
81+
Name: FlagTLSCaPath,
82+
Value: "",
83+
Usage: "path to server CA certificate",
84+
EnvVar: "TEMPORAL_CLI_TLS_CA",
85+
},
6886
}
6987
app.Commands = []cli.Command{
7088
{

tools/cli/factory.go

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,21 @@
2525
package cli
2626

2727
import (
28+
"crypto/tls"
29+
"crypto/x509"
30+
"errors"
31+
"io/ioutil"
32+
"net"
33+
2834
"github.com/urfave/cli"
2935
"go.temporal.io/api/workflowservice/v1"
3036
sdkclient "go.temporal.io/sdk/client"
3137
"go.uber.org/zap"
3238
"google.golang.org/grpc"
39+
"google.golang.org/grpc/credentials"
3340

3441
"go.temporal.io/server/api/adminservice/v1"
3542
"go.temporal.io/server/common/log"
36-
"go.temporal.io/server/common/rpc"
3743
)
3844

3945
// ClientFactory is used to construct rpc clients
@@ -61,14 +67,14 @@ func NewClientFactory() ClientFactory {
6167

6268
// FrontendClient builds a frontend client
6369
func (b *clientFactory) FrontendClient(c *cli.Context) workflowservice.WorkflowServiceClient {
64-
connection := b.createGRPCConnection(c.GlobalString(FlagAddress))
70+
connection, _ := b.createGRPCConnection(c)
6571

6672
return workflowservice.NewWorkflowServiceClient(connection)
6773
}
6874

6975
// AdminClient builds an admin client (based on server side thrift interface)
7076
func (b *clientFactory) AdminClient(c *cli.Context) adminservice.AdminServiceClient {
71-
connection := b.createGRPCConnection(c.GlobalString(FlagAddress))
77+
connection, _ := b.createGRPCConnection(c)
7278

7379
return adminservice.NewAdminServiceClient(connection)
7480
}
@@ -95,16 +101,69 @@ func (b *clientFactory) SDKClient(c *cli.Context, namespace string) sdkclient.Cl
95101
return sdkClient
96102
}
97103

98-
func (b *clientFactory) createGRPCConnection(hostPort string) *grpc.ClientConn {
104+
func (b *clientFactory) createGRPCConnection(c *cli.Context) (*grpc.ClientConn, error) {
105+
hostPort := c.GlobalString(FlagAddress)
99106
if hostPort == "" {
100107
hostPort = localHostPort
101108
}
109+
// Ignoring error as we'll fail to dial anyway, and that will produce a meaningful error
110+
host, _, _ := net.SplitHostPort(hostPort)
111+
112+
certPath := c.GlobalString(FlagTLSCertPath)
113+
keyPath := c.GlobalString(FlagTLSKeyPath)
114+
caPath := c.GlobalString(FlagTLSCaPath)
115+
116+
grpcSecurityOptions := grpc.WithInsecure()
117+
var cert *tls.Certificate
118+
var caPool *x509.CertPool
119+
120+
if caPath != "" {
121+
caCertPool, err := fetchCACert(caPath)
122+
if err != nil {
123+
b.logger.Fatal("Failed to load server CA certificate", zap.Error(err))
124+
return nil, err
125+
}
126+
caPool = caCertPool
127+
}
128+
if certPath != "" {
129+
myCert, err := tls.LoadX509KeyPair(certPath, keyPath)
130+
if err != nil {
131+
b.logger.Fatal("Failed to load client certificate", zap.Error(err))
132+
return nil, err
133+
}
134+
cert = &myCert
135+
}
136+
// If we are given arguments to verify either server or client, configure TLS
137+
if caPool != nil || cert != nil {
138+
tlsConfig := &tls.Config{
139+
ServerName: host,
140+
}
141+
if caPool != nil {
142+
tlsConfig.RootCAs = caPool
143+
}
144+
if cert != nil {
145+
tlsConfig.Certificates = []tls.Certificate{*cert}
146+
}
147+
grpcSecurityOptions = grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))
148+
}
102149

103-
connection, err := rpc.Dial(hostPort, nil)
150+
connection, err := grpc.Dial(hostPort, grpcSecurityOptions)
104151
if err != nil {
105152
b.logger.Fatal("Failed to create connection", zap.Error(err))
106-
return nil
153+
return nil, err
107154
}
155+
return connection, nil
156+
}
108157

109-
return connection
158+
func fetchCACert(path string) (*x509.CertPool, error) {
159+
caPool := x509.NewCertPool()
160+
caBytes, err := ioutil.ReadFile(path)
161+
if err != nil {
162+
return nil, err
163+
}
164+
165+
if !caPool.AppendCertsFromPEM(caBytes) {
166+
return nil, errors.New("unknown failure constructing cert pool for ca")
167+
}
168+
return caPool, nil
110169
}

0 commit comments

Comments
 (0)