Skip to content

Commit 1a9695e

Browse files
authored
Add internal-frontend role (#3706)
1 parent c5254f3 commit 1a9695e

28 files changed

+312
-119
lines changed

common/authorization/default_authorizer.go

-8
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,6 @@ var resultDeny = Result{Decision: DecisionDeny}
4646

4747
func (a *defaultAuthorizer) Authorize(_ context.Context, claims *Claims, target *CallTarget) (Result, error) {
4848

49-
// TODO: This is a temporary workaround to allow calls to system namespace and
50-
// calls with no namespace to pass through. When handling of mTLS data is added,
51-
// we should remove "temporal-system" from here. Handling of call with
52-
// no namespace will need to be performed at the API level, so that data would
53-
// be filtered based of caller's permissions to namespaces and system.
54-
if target.Namespace == "temporal-system" || target.Namespace == "" {
55-
return resultAllow, nil
56-
}
5749
if claims == nil {
5850
return resultDeny, nil
5951
}

common/config/config.go

+46-10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ package config
2626

2727
import (
2828
"bytes"
29+
"fmt"
2930
"strings"
3031
"time"
3132

@@ -38,6 +39,7 @@ import (
3839
"go.temporal.io/server/common/masker"
3940
"go.temporal.io/server/common/metrics"
4041
"go.temporal.io/server/common/persistence/visibility/store/elasticsearch/client"
42+
"go.temporal.io/server/common/primitives"
4143
"go.temporal.io/server/common/telemetry"
4244
)
4345

@@ -111,9 +113,12 @@ type (
111113

112114
// RootTLS contains all TLS settings for the Temporal server
113115
RootTLS struct {
114-
// Internode controls backend service communication TLS settings.
116+
// Internode controls backend service (history, matching, internal-frontend)
117+
// communication TLS settings.
115118
Internode GroupTLS `yaml:"internode"`
116-
// Frontend controls SDK Client to Frontend communication TLS settings.
119+
// Frontend controls frontend server TLS settings. To control system worker -> frontend
120+
// TLS, use the SystemWorker field. (Frontend.Client is accepted for backwards
121+
// compatibility.)
117122
Frontend GroupTLS `yaml:"frontend"`
118123
// SystemWorker controls TLS setting for System Workers connecting to Frontend.
119124
SystemWorker WorkerTLS `yaml:"systemWorker"`
@@ -481,16 +486,30 @@ type (
481486
S3ForcePathStyle bool `yaml:"s3ForcePathStyle"`
482487
}
483488

484-
// PublicClient is config for internal nodes (history/matching/worker) connecting to
485-
// temporal frontend. There are two methods of connecting:
486-
// Explicit endpoint: Supply a host:port to connect to. This can resolve to multiple IPs,
487-
// or a single IP that is a load-balancer.
488-
// Membership resolver (new in 1.18): Leave this empty, and other nodes will use the
489-
// membership service resolver to find the frontend.
490-
// TODO: remove this and always use membership resolver
489+
// PublicClient is the config for internal nodes (history/matching/worker) connecting to
490+
// frontend. There are three methods of connecting:
491+
// 1. Use membership to locate "internal-frontend" and connect to them using the Internode
492+
// TLS config (which can be "no TLS"). This is recommended for deployments that use an
493+
// Authorizer and ClaimMapper. To use this, leave this section out of your config, and
494+
// make sure there is an "internal-frontend" section in Services.
495+
// 2. Use membership to locate "frontend" and connect to them using the Frontend TLS config
496+
// (which can be "no TLS"). This is recommended for deployments that don't use an
497+
// Authorizer or ClaimMapper, or have implemented a custom ClaimMapper that correctly
498+
// identifies the system worker using mTLS and assigns it an Admin-level claim.
499+
// To use this, leave this section out of your config and make sure there is _no_
500+
// "internal-frontend" section in Services.
501+
// 3. Connect to an explicit endpoint using the SystemWorker (falling back to Frontend) TLS
502+
// config (which can be "no TLS"). You can use this if you want to force frontend
503+
// connections to go through an external load balancer. If you use this with a
504+
// ClaimMapper+Authorizer, you need to ensure that your ClaimMapper assigns Admin
505+
// claims to worker nodes, and your Authorizer correctly handles those claims.
491506
PublicClient struct {
492-
// HostPort is the host port to connect on. Host can be DNS name
507+
// HostPort is the host port to connect on. Host can be DNS name. See the above
508+
// comment: in many situations you can leave this empty.
493509
HostPort string `yaml:"hostPort"`
510+
// Force selection of either the "internode" or "frontend" TLS configs for these
511+
// connections (only those two strings are valid).
512+
ForceTLSConfig string `yaml:"forceTLSConfig"`
494513
}
495514

496515
// NamespaceDefaults is the default config for each namespace
@@ -551,6 +570,12 @@ const (
551570
ClusterMDStoreName DataStoreName = "ClusterMDStore"
552571
)
553572

573+
const (
574+
ForceTLSConfigAuto = ""
575+
ForceTLSConfigInternode = "internode"
576+
ForceTLSConfigFrontend = "frontend"
577+
)
578+
554579
// Validate validates this config
555580
func (c *Config) Validate() error {
556581
if err := c.Persistence.Validate(); err != nil {
@@ -561,6 +586,17 @@ func (c *Config) Validate() error {
561586
return err
562587
}
563588

589+
_, hasIFE := c.Services[string(primitives.InternalFrontendService)]
590+
if hasIFE && (c.PublicClient.HostPort != "" || c.PublicClient.ForceTLSConfig != "") {
591+
return fmt.Errorf("when using internal-frontend, publicClient must be empty")
592+
}
593+
594+
switch c.PublicClient.ForceTLSConfig {
595+
case ForceTLSConfigAuto, ForceTLSConfigInternode, ForceTLSConfigFrontend:
596+
default:
597+
return fmt.Errorf("invalid value for publicClient.forceTLSConfig: %q", c.PublicClient.ForceTLSConfig)
598+
}
599+
564600
return nil
565601
}
566602

common/membership/rpMonitor.go

+2
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ func ServiceNameToServiceTypeEnum(name primitives.ServiceName) (persistence.Serv
160160
return persistence.All, nil
161161
case primitives.FrontendService:
162162
return persistence.Frontend, nil
163+
case primitives.InternalFrontendService:
164+
return persistence.InternalFrontend, nil
163165
case primitives.HistoryService:
164166
return persistence.History, nil
165167
case primitives.MatchingService:

common/persistence/dataInterfaces.go

+1
Original file line numberDiff line numberDiff line change
@@ -1306,4 +1306,5 @@ const (
13061306
History
13071307
Matching
13081308
Worker
1309+
InternalFrontend
13091310
)

common/primitives/role.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ type ServiceName string
2828

2929
// These constants represent service roles
3030
const (
31-
AllServices ServiceName = "all"
32-
FrontendService ServiceName = "frontend"
33-
HistoryService ServiceName = "history"
34-
MatchingService ServiceName = "matching"
35-
WorkerService ServiceName = "worker"
36-
ServerService ServiceName = "server"
37-
UnitTestService ServiceName = "unittest"
31+
AllServices ServiceName = "all"
32+
FrontendService ServiceName = "frontend"
33+
InternalFrontendService ServiceName = "internal-frontend"
34+
HistoryService ServiceName = "history"
35+
MatchingService ServiceName = "matching"
36+
WorkerService ServiceName = "worker"
37+
ServerService ServiceName = "server"
38+
UnitTestService ServiceName = "unittest"
3839
)

common/resource/fx.go

+60-20
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ package resource
2626

2727
import (
2828
"context"
29+
"crypto/tls"
2930
"fmt"
3031
"net"
3132
"os"
@@ -93,7 +94,6 @@ type (
9394
// See LifetimeHooksModule for detail
9495
var Module = fx.Options(
9596
persistenceClient.Module,
96-
fx.Provide(SnTaggedLoggerProvider),
9797
fx.Provide(HostNameProvider),
9898
fx.Provide(TimeSourceProvider),
9999
cluster.MetadataLifetimeHooksModule,
@@ -136,7 +136,7 @@ var DefaultOptions = fx.Options(
136136
fx.Provide(DCRedirectionPolicyProvider),
137137
)
138138

139-
func SnTaggedLoggerProvider(logger log.Logger, sn primitives.ServiceName) log.SnTaggedLogger {
139+
func DefaultSnTaggedLoggerProvider(logger log.Logger, sn primitives.ServiceName) log.SnTaggedLogger {
140140
return log.With(logger, tag.Service(sn))
141141
}
142142

@@ -395,19 +395,13 @@ func SdkClientFactoryProvider(
395395
logger log.SnTaggedLogger,
396396
resolver membership.GRPCResolver,
397397
) (sdk.ClientFactory, error) {
398-
tlsFrontendConfig, err := tlsConfigProvider.GetFrontendClientConfig()
398+
frontendURL, frontendTLSConfig, err := getFrontendConnectionDetails(cfg, tlsConfigProvider, resolver)
399399
if err != nil {
400-
return nil, fmt.Errorf("unable to load frontend TLS configuration: %w", err)
401-
}
402-
403-
hostPort := cfg.PublicClient.HostPort
404-
if hostPort == "" {
405-
hostPort = resolver.MakeURL(primitives.FrontendService)
400+
return nil, err
406401
}
407-
408402
return sdk.NewClientFactory(
409-
hostPort,
410-
tlsFrontendConfig,
403+
frontendURL,
404+
frontendTLSConfig,
411405
metricsHandler,
412406
logger,
413407
), nil
@@ -426,24 +420,70 @@ func RPCFactoryProvider(
426420
svcName primitives.ServiceName,
427421
logger log.Logger,
428422
tlsConfigProvider encryption.TLSConfigProvider,
429-
dc *dynamicconfig.Collection,
430423
resolver membership.GRPCResolver,
431424
traceInterceptor telemetry.ClientTraceInterceptor,
432-
) common.RPCFactory {
425+
) (common.RPCFactory, error) {
433426
svcCfg := cfg.Services[string(svcName)]
434-
hostPort := cfg.PublicClient.HostPort
435-
if hostPort == "" {
436-
hostPort = resolver.MakeURL(primitives.FrontendService)
427+
frontendURL, frontendTLSConfig, err := getFrontendConnectionDetails(cfg, tlsConfigProvider, resolver)
428+
if err != nil {
429+
return nil, err
437430
}
438431
return rpc.NewFactory(
439432
&svcCfg.RPC,
440433
svcName,
441434
logger,
442435
tlsConfigProvider,
443-
dc,
444-
hostPort,
436+
frontendURL,
437+
frontendTLSConfig,
445438
[]grpc.UnaryClientInterceptor{
446439
grpc.UnaryClientInterceptor(traceInterceptor),
447440
},
448-
)
441+
), nil
442+
}
443+
444+
func getFrontendConnectionDetails(
445+
cfg *config.Config,
446+
tlsConfigProvider encryption.TLSConfigProvider,
447+
resolver membership.GRPCResolver,
448+
) (string, *tls.Config, error) {
449+
// To simplify the static config, we switch default values based on whether the config
450+
// defines an "internal-frontend" service. The default for TLS config can be overridden
451+
// with publicClient.forceTLSConfig, and the default for hostPort can be overridden by
452+
// explicitly setting hostPort to "membership://internal-frontend" or
453+
// "membership://frontend".
454+
_, hasIFE := cfg.Services[string(primitives.InternalFrontendService)]
455+
456+
forceTLS := cfg.PublicClient.ForceTLSConfig
457+
if forceTLS == config.ForceTLSConfigAuto {
458+
if hasIFE {
459+
forceTLS = config.ForceTLSConfigInternode
460+
} else {
461+
forceTLS = config.ForceTLSConfigFrontend
462+
}
463+
}
464+
465+
var frontendTLSConfig *tls.Config
466+
var err error
467+
switch forceTLS {
468+
case config.ForceTLSConfigInternode:
469+
frontendTLSConfig, err = tlsConfigProvider.GetInternodeClientConfig()
470+
case config.ForceTLSConfigFrontend:
471+
frontendTLSConfig, err = tlsConfigProvider.GetFrontendClientConfig()
472+
default:
473+
err = fmt.Errorf("invalid forceTLSConfig")
474+
}
475+
if err != nil {
476+
return "", nil, fmt.Errorf("unable to load TLS configuration: %w", err)
477+
}
478+
479+
frontendURL := cfg.PublicClient.HostPort
480+
if frontendURL == "" {
481+
if hasIFE {
482+
frontendURL = resolver.MakeURL(primitives.InternalFrontendService)
483+
} else {
484+
frontendURL = resolver.MakeURL(primitives.FrontendService)
485+
}
486+
}
487+
488+
return frontendURL, frontendTLSConfig, nil
449489
}

common/resourcetest/resourceTest.go

+1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ func NewTest(
160160
historyServiceResolver := membership.NewMockServiceResolver(controller)
161161
workerServiceResolver := membership.NewMockServiceResolver(controller)
162162
membershipMonitor.EXPECT().GetResolver(primitives.FrontendService).Return(frontendServiceResolver, nil).AnyTimes()
163+
membershipMonitor.EXPECT().GetResolver(primitives.InternalFrontendService).Return(nil, membership.ErrUnknownService).AnyTimes()
163164
membershipMonitor.EXPECT().GetResolver(primitives.MatchingService).Return(matchingServiceResolver, nil).AnyTimes()
164165
membershipMonitor.EXPECT().GetResolver(primitives.HistoryService).Return(historyServiceResolver, nil).AnyTimes()
165166
membershipMonitor.EXPECT().GetResolver(primitives.WorkerService).Return(workerServiceResolver, nil).AnyTimes()

common/rpc/rpc.go

+7-17
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import (
3535
"go.temporal.io/server/common"
3636
"go.temporal.io/server/common/config"
3737
"go.temporal.io/server/common/convert"
38-
"go.temporal.io/server/common/dynamicconfig"
3938
"go.temporal.io/server/common/log"
4039
"go.temporal.io/server/common/log/tag"
4140
"go.temporal.io/server/common/primitives"
@@ -49,8 +48,9 @@ type RPCFactory struct {
4948
config *config.RPC
5049
serviceName primitives.ServiceName
5150
logger log.Logger
52-
dc *dynamicconfig.Collection
53-
frontendURL string
51+
52+
frontendURL string
53+
frontendTLSConfig *tls.Config
5454

5555
initListener sync.Once
5656
grpcListener net.Listener
@@ -65,16 +65,16 @@ func NewFactory(
6565
sName primitives.ServiceName,
6666
logger log.Logger,
6767
tlsProvider encryption.TLSConfigProvider,
68-
dc *dynamicconfig.Collection,
6968
frontendURL string,
69+
frontendTLSConfig *tls.Config,
7070
clientInterceptors []grpc.UnaryClientInterceptor,
7171
) *RPCFactory {
7272
return &RPCFactory{
7373
config: cfg,
7474
serviceName: sName,
7575
logger: logger,
76-
dc: dc,
7776
frontendURL: frontendURL,
77+
frontendTLSConfig: frontendTLSConfig,
7878
tlsFactory: tlsProvider,
7979
clientInterceptors: clientInterceptors,
8080
}
@@ -201,19 +201,9 @@ func (d *RPCFactory) CreateRemoteFrontendGRPCConnection(rpcAddress string) *grpc
201201
return d.dial(rpcAddress, tlsClientConfig)
202202
}
203203

204-
// CreateLocalFrontendGRPCConnection creates connection for internal calls
204+
// CreateLocalFrontendGRPCConnection creates connection for internal frontend calls
205205
func (d *RPCFactory) CreateLocalFrontendGRPCConnection() *grpc.ClientConn {
206-
var tlsClientConfig *tls.Config
207-
var err error
208-
if d.tlsFactory != nil {
209-
tlsClientConfig, err = d.tlsFactory.GetFrontendClientConfig()
210-
if err != nil {
211-
d.logger.Fatal("Failed to create tls config for gRPC connection", tag.Error(err))
212-
return nil
213-
}
214-
}
215-
216-
return d.dial(d.frontendURL, tlsClientConfig)
206+
return d.dial(d.frontendURL, d.frontendTLSConfig)
217207
}
218208

219209
// CreateInternodeGRPCConnection creates connection for gRPC calls

0 commit comments

Comments
 (0)