go.temporal.io/server@v1.23.0/common/membership/ringpop/factory_test.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package ringpop 26 27 import ( 28 "context" 29 "crypto/tls" 30 "os" 31 "testing" 32 "time" 33 34 "github.com/golang/mock/gomock" 35 "github.com/stretchr/testify/require" 36 "github.com/stretchr/testify/suite" 37 "gopkg.in/yaml.v3" 38 39 "go.temporal.io/server/common/config" 40 "go.temporal.io/server/common/dynamicconfig" 41 "go.temporal.io/server/common/log" 42 "go.temporal.io/server/common/metrics" 43 "go.temporal.io/server/common/primitives" 44 "go.temporal.io/server/common/rpc/encryption" 45 "go.temporal.io/server/tests/testutils" 46 ) 47 48 type ( 49 RingpopSuite struct { 50 *require.Assertions 51 suite.Suite 52 53 controller *gomock.Controller 54 55 logger log.Logger 56 internodeCertDir string 57 internodeChain testutils.CertChain 58 59 membershipConfig config.Membership 60 internodeConfigMutualTLS config.GroupTLS 61 internodeConfigServerTLS config.GroupTLS 62 63 mutualTLSFactoryA *factory 64 mutualTLSFactoryB *factory 65 serverTLSFactoryA *factory 66 serverTLSFactoryB *factory 67 68 insecureFactory *factory 69 } 70 ) 71 72 const ( 73 localhostIPv4 = "127.0.0.1" 74 ) 75 76 var ( 77 rpcTestCfgDefault = &config.RPC{ 78 GRPCPort: 0, 79 MembershipPort: 7600, 80 BindOnIP: localhostIPv4, 81 } 82 serverCfgInsecure = &config.Global{ 83 Membership: config.Membership{ 84 MaxJoinDuration: 5, 85 BroadcastAddress: localhostIPv4, 86 }, 87 } 88 ) 89 90 func TestRingpopSuite(t *testing.T) { 91 suite.Run(t, new(RingpopSuite)) 92 } 93 94 func (s *RingpopSuite) SetupTest() { 95 s.Assertions = require.New(s.T()) 96 s.logger = log.NewTestLogger() 97 s.controller = gomock.NewController(s.T()) 98 99 var err error 100 s.internodeCertDir, err = os.MkdirTemp("", "RingpopSuiteInternode") 101 s.NoError(err) 102 s.internodeChain, err = testutils.GenerateTestChain(s.internodeCertDir, localhostIPv4) 103 s.NoError(err) 104 105 s.internodeConfigMutualTLS = config.GroupTLS{ 106 Server: config.ServerTLS{ 107 CertFile: s.internodeChain.CertPubFile, 108 KeyFile: s.internodeChain.CertKeyFile, 109 ClientCAFiles: []string{s.internodeChain.CaPubFile}, 110 RequireClientAuth: true, 111 }, 112 Client: config.ClientTLS{ 113 RootCAFiles: []string{s.internodeChain.CaPubFile}, 114 }, 115 } 116 117 s.internodeConfigServerTLS = config.GroupTLS{ 118 Server: config.ServerTLS{ 119 CertData: testutils.ConvertFileToBase64(s.internodeChain.CertPubFile), 120 KeyData: testutils.ConvertFileToBase64(s.internodeChain.CertKeyFile), 121 }, 122 Client: config.ClientTLS{ 123 RootCAData: []string{testutils.ConvertFileToBase64(s.internodeChain.CaPubFile)}, 124 }, 125 } 126 127 s.setupInternodeRingpop() 128 } 129 130 func (s *RingpopSuite) TearDownSuite() { 131 _ = os.RemoveAll(s.internodeCertDir) 132 } 133 134 func (s *RingpopSuite) TestHostsMode() { 135 var cfg config.Membership 136 err := yaml.Unmarshal([]byte(getHostsConfig()), &cfg) 137 s.Nil(err) 138 s.Equal("1.2.3.4", cfg.BroadcastAddress) 139 s.Equal(time.Second*30, cfg.MaxJoinDuration) 140 141 params := factoryParams{ 142 Config: &cfg, 143 ServiceName: "test", 144 Logger: log.Logger(log.NewNoopLogger()), 145 } 146 f, err := newFactory(params) 147 s.Nil(err) 148 s.NotNil(f) 149 } 150 151 func getHostsConfig() string { 152 return `name: "test" 153 broadcastAddress: "1.2.3.4" 154 maxJoinDuration: 30s` 155 } 156 157 func (s *RingpopSuite) TestInvalidBroadcastAddress() { 158 cfg := config.Membership{ 159 MaxJoinDuration: time.Minute, 160 BroadcastAddress: "oopsie", 161 } 162 logger := log.Logger(log.NewNoopLogger()) 163 params := factoryParams{ 164 Config: &cfg, 165 ServiceName: "test", 166 Logger: logger, 167 } 168 _, err := newFactory(params) 169 170 s.ErrorIs(err, errMalformedBroadcastAddress) 171 s.ErrorContains(err, "oopsie") 172 } 173 174 func newTestRingpopFactory( 175 serviceName primitives.ServiceName, 176 logger log.Logger, 177 rpcConfig *config.RPC, 178 tlsProvider encryption.TLSConfigProvider, 179 dc *dynamicconfig.Collection, 180 ) *factory { 181 return &factory{ 182 ServiceName: serviceName, 183 Logger: logger, 184 RPCConfig: rpcConfig, 185 TLSFactory: tlsProvider, 186 DC: dc, 187 } 188 } 189 190 func (s *RingpopSuite) TestRingpopMutualTLS() { 191 s.NoError(runRingpopTLSTest(&s.Suite, s.mutualTLSFactoryA, s.mutualTLSFactoryB)) 192 } 193 194 func (s *RingpopSuite) TestRingpopServerTLS() { 195 s.NoError(runRingpopTLSTest(&s.Suite, s.serverTLSFactoryA, s.serverTLSFactoryB)) 196 } 197 198 func (s *RingpopSuite) TestRingpopInvalidTLS() { 199 s.Error(runRingpopTLSTest(&s.Suite, s.insecureFactory, s.serverTLSFactoryB)) 200 } 201 202 func runRingpopTLSTest(s *suite.Suite, serverA *factory, serverB *factory) error { 203 // Start two ringpop nodes 204 chA := serverA.getTChannel() 205 chB := serverB.getTChannel() 206 defer chA.Close() 207 defer chB.Close() 208 209 // Ping A through B to make sure B's dialer uses TLS to communicate with A 210 hostPortA := chA.PeerInfo().HostPort 211 if err := chB.Ping(context.Background(), hostPortA); err != nil { 212 return err 213 } 214 215 // Confirm that A's listener is actually using TLS 216 clientTLSConfig, err := serverB.TLSFactory.GetInternodeClientConfig() 217 s.NoError(err) 218 219 conn, err := tls.Dial("tcp", hostPortA, clientTLSConfig) 220 if err != nil { 221 return err 222 } 223 if conn != nil { 224 _ = conn.Close() 225 } 226 return nil 227 } 228 229 func (s *RingpopSuite) setupInternodeRingpop() { 230 provider, err := encryption.NewTLSConfigProviderFromConfig(serverCfgInsecure.TLS, metrics.NoopMetricsHandler, s.logger, nil) 231 s.NoError(err) 232 s.insecureFactory = newTestRingpopFactory("tester", s.logger, rpcTestCfgDefault, provider, dynamicconfig.NewNoopCollection()) 233 s.NotNil(s.insecureFactory) 234 235 serverTLS := &config.Global{ 236 Membership: s.membershipConfig, 237 TLS: config.RootTLS{ 238 Internode: s.internodeConfigServerTLS, 239 }, 240 } 241 242 mutualTLS := &config.Global{ 243 Membership: s.membershipConfig, 244 TLS: config.RootTLS{ 245 Internode: s.internodeConfigMutualTLS, 246 }, 247 } 248 249 rpcCfgA := &config.RPC{GRPCPort: 0, MembershipPort: 7600, BindOnIP: localhostIPv4} 250 rpcCfgB := &config.RPC{GRPCPort: 0, MembershipPort: 7601, BindOnIP: localhostIPv4} 251 252 dc := dynamicconfig.NewCollection(dynamicconfig.StaticClient(map[dynamicconfig.Key]any{ 253 dynamicconfig.EnableRingpopTLS: true, 254 }), s.logger) 255 256 provider, err = encryption.NewTLSConfigProviderFromConfig(mutualTLS.TLS, metrics.NoopMetricsHandler, s.logger, nil) 257 s.NoError(err) 258 s.mutualTLSFactoryA = newTestRingpopFactory("tester-A", s.logger, rpcCfgA, provider, dc) 259 s.NotNil(s.mutualTLSFactoryA) 260 s.mutualTLSFactoryB = newTestRingpopFactory("tester-B", s.logger, rpcCfgB, provider, dc) 261 s.NotNil(s.mutualTLSFactoryB) 262 263 provider, err = encryption.NewTLSConfigProviderFromConfig(serverTLS.TLS, metrics.NoopMetricsHandler, s.logger, nil) 264 s.NoError(err) 265 s.serverTLSFactoryA = newTestRingpopFactory("tester-A", s.logger, rpcCfgA, provider, dc) 266 s.NotNil(s.serverTLSFactoryA) 267 s.serverTLSFactoryB = newTestRingpopFactory("tester-B", s.logger, rpcCfgB, provider, dc) 268 s.NotNil(s.serverTLSFactoryB) 269 }