go.uber.org/yarpc@v1.72.1/transport/grpc/config.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package grpc 22 23 import ( 24 "errors" 25 "fmt" 26 "net" 27 "time" 28 29 "go.uber.org/yarpc/api/peer" 30 "go.uber.org/yarpc/api/transport" 31 yarpctls "go.uber.org/yarpc/api/transport/tls" 32 peerchooser "go.uber.org/yarpc/peer" 33 "go.uber.org/yarpc/peer/hostport" 34 "go.uber.org/yarpc/yarpcconfig" 35 "google.golang.org/grpc/credentials" 36 "google.golang.org/grpc/keepalive" 37 ) 38 39 // TransportSpec returns a TransportSpec for the gRPC transport. 40 // 41 // See TransportConfig, InboundConfig, and OutboundConfig for details on the 42 // different configuration parameters supported by this Transport. 43 // 44 // Any TransportOption, InboundOption, or OutboundOption may be passed to this function. 45 // These options will be applied BEFORE configuration parameters are 46 // interpreted. This allows configuration parameters to override Options 47 // provided to TransportSpec. 48 func TransportSpec(opts ...Option) yarpcconfig.TransportSpec { 49 transportSpec, err := newTransportSpec(opts...) 50 if err != nil { 51 panic(err.Error()) 52 } 53 return yarpcconfig.TransportSpec{ 54 Name: TransportName, 55 BuildTransport: transportSpec.buildTransport, 56 BuildInbound: transportSpec.buildInbound, 57 BuildUnaryOutbound: transportSpec.buildUnaryOutbound, 58 BuildStreamOutbound: transportSpec.buildStreamOutbound, 59 } 60 } 61 62 // TransportConfig configures a gRPC Transport. This is shared 63 // between all gRPC inbounds and outbounds of a Dispatcher. 64 // 65 // transports: 66 // grpc: 67 // backoff: 68 // exponential: 69 // first: 10ms 70 // max: 30s 71 // clientMaxHeaderListSize: 1024 72 // serverMaxHeaderListSize: 2048 73 // 74 // All parameters of TransportConfig are optional. This section 75 // may be omitted in the transports section. 76 type TransportConfig struct { 77 ServerMaxRecvMsgSize int `config:"serverMaxRecvMsgSize"` 78 ServerMaxSendMsgSize int `config:"serverMaxSendMsgSize"` 79 ClientMaxRecvMsgSize int `config:"clientMaxRecvMsgSize"` 80 ClientMaxSendMsgSize int `config:"clientMaxSendMsgSize"` 81 // GRPC header lise size options accept uint32 param. 82 // see: https://pkg.go.dev/google.golang.org/grpc#WithMaxHeaderListSize 83 ServerMaxHeaderListSize uint32 `config:"serverMaxHeaderListSize"` 84 ClientMaxHeaderListSize uint32 `config:"clientMaxHeaderListSize"` 85 Backoff yarpcconfig.Backoff `config:"backoff"` 86 } 87 88 // InboundConfig configures a gRPC Inbound. 89 // 90 // inbounds: 91 // grpc: 92 // address: ":80" 93 // 94 // A gRPC inbound can also enable TLS from key and cert files. 95 // 96 // inbounds: 97 // grpc: 98 // address: ":443" 99 // tls: 100 // enabled: true 101 // keyFile: "/path/to/key" 102 // certFile: "/path/to/cert" 103 type InboundConfig struct { 104 // Address to listen on. This field is required. 105 Address string `config:"address,interpolate"` 106 TLS InboundTLSConfig `config:"tls"` 107 } 108 109 func (c InboundConfig) inboundOptions() ([]InboundOption, error) { 110 return c.TLS.inboundOptions() 111 } 112 113 // InboundTLSConfig specifies the TLS configuration for the gRPC inbound. 114 type InboundTLSConfig struct { 115 Enabled bool `config:"enabled"` // disabled by default 116 CertFile string `config:"certFile,interpolate"` 117 KeyFile string `config:"keyFile,interpolate"` 118 119 // Mode when set to Permissive or Enforced enables TLS inbound and 120 // TLS configuration must be passed as an inbound option. 121 // Note: enable, cert and key fields are ignored when mode is set. 122 Mode yarpctls.Mode `config:"mode,interpolate"` 123 } 124 125 func (c InboundTLSConfig) inboundOptions() ([]InboundOption, error) { 126 if c.Mode != yarpctls.Disabled { 127 return []InboundOption{InboundTLSMode(c.Mode)}, nil 128 } 129 130 if !c.Enabled { 131 return nil, nil 132 } 133 creds, err := c.newInboundCredentials() 134 if err != nil { 135 return nil, err 136 } 137 return []InboundOption{InboundCredentials(creds)}, nil 138 } 139 140 func (c InboundTLSConfig) newInboundCredentials() (credentials.TransportCredentials, error) { 141 if c.CertFile != "" && c.KeyFile != "" { 142 return credentials.NewServerTLSFromFile(c.CertFile, c.KeyFile) 143 } 144 return nil, fmt.Errorf("both certFile and keyFile are necessary to construct gRPC transport credentials, got certFile=%q and keyFile=%q", c.CertFile, c.KeyFile) 145 } 146 147 // OutboundConfig configures a gRPC Outbound. 148 // 149 // outbounds: 150 // myservice: 151 // grpc: 152 // address: ":80" 153 // 154 // A gRPC outbound can also configure a peer list. 155 // 156 // outbounds: 157 // myservice: 158 // grpc: 159 // round-robin: 160 // peers: 161 // - 127.0.0.1:8080 162 // - 127.0.0.1:8081 163 // 164 // A gRPC outbound can enable TLS using the system cert.Pool. 165 // 166 // outbounds: 167 // theirsecureservice: 168 // grpc: 169 // address: ":443" 170 // tls: 171 // enabled: true 172 // compressor: gzip 173 // grpc-keepalive: 174 // enabled: true 175 // time: 10s 176 // timeout: 30s 177 // permit-without-stream: true 178 // 179 type OutboundConfig struct { 180 yarpcconfig.PeerChooser 181 182 // Address to connect to if no peer options set. 183 Address string `config:"address,interpolate"` 184 TLS OutboundTLSConfig `config:"tls"` 185 // Compressor to use by default if the server side supports it 186 Compressor string `config:"compressor"` 187 Keepalive OutboundKeepaliveConfig `config:"grpc-keepalive"` 188 } 189 190 func (c OutboundConfig) dialOptions(kit *yarpcconfig.Kit, tlsConfigProvider yarpctls.OutboundTLSConfigProvider) ([]DialOption, error) { 191 opts, err := c.TLS.dialOptions(tlsConfigProvider) 192 if err != nil { 193 return nil, err 194 } 195 196 keepaliveOpts, err := c.Keepalive.dialOptions() 197 if err != nil { 198 return nil, err 199 } 200 201 opts = append(opts, keepaliveOpts...) 202 return opts, nil 203 } 204 205 // OutboundTLSConfig configures TLS for a gRPC outbound. 206 type OutboundTLSConfig struct { 207 // Enabled field is deprecated, use Mode and SpiffeIDs fields instead. 208 Enabled bool `config:"enabled"` 209 // Mode when set to Enforced enables TLS outbound and 210 // outbound TLS configuration providered option will be used for fetching 211 // outbound tls.Config. 212 // Note: enable field is ignored when mode is set. 213 Mode yarpctls.Mode `config:"mode,interpolate"` 214 // SpiffeIDs is a list of the accepted server spiffe IDs. 215 SpiffeIDs []string `config:"spiffe-ids"` 216 } 217 218 func (c OutboundTLSConfig) dialOptions(tlsConfigProvider yarpctls.OutboundTLSConfigProvider) ([]DialOption, error) { 219 if c.Mode != yarpctls.Disabled { 220 if c.Mode == yarpctls.Permissive { 221 return nil, errors.New("outbound does not support permissive TLS mode") 222 } 223 224 if tlsConfigProvider == nil { 225 return nil, errors.New("outbound TLS enforced but outbound TLS config provider is nil") 226 } 227 228 config, err := tlsConfigProvider.ClientTLSConfig(c.SpiffeIDs) 229 if err != nil { 230 return nil, err 231 } 232 return []DialOption{DialerTLSConfig(config)}, nil 233 } 234 235 if !c.Enabled { 236 return nil, nil 237 } 238 creds := credentials.NewClientTLSFromCert(nil, "") 239 option := DialerCredentials(creds) 240 return []DialOption{option}, nil 241 } 242 243 // OutboundKeepaliveConfig configures gRPC keepalive for a gRPC outbound. 244 type OutboundKeepaliveConfig struct { 245 Enabled bool `config:"enabled"` 246 Time string `config:"time"` 247 Timeout string `config:"timeout"` 248 PermitWithoutStream bool `config:"permit-without-stream"` 249 } 250 251 func (c OutboundKeepaliveConfig) dialOptions() ([]DialOption, error) { 252 if !c.Enabled { 253 return nil, nil 254 } 255 256 var err error 257 258 // gRPC keepalive expects time to be minimum 10s. 259 // read more: https://pkg.go.dev/google.golang.org/grpc/keepalive#ClientParameters 260 keepaliveTime := time.Second * 10 261 if c.Time != "" { 262 keepaliveTime, err = time.ParseDuration(c.Time) 263 if err != nil { 264 return nil, fmt.Errorf("could not parse gRPC keepalive time: %v", err) 265 } 266 } 267 268 // gRPC keepalive defaults timeout to 20s. 269 // read more: https://pkg.go.dev/google.golang.org/grpc/keepalive#ClientParameters 270 keepaliveTimeout := time.Second * 20 271 if c.Timeout != "" { 272 keepaliveTimeout, err = time.ParseDuration(c.Timeout) 273 if err != nil { 274 return nil, fmt.Errorf("could not parse gRPC keepalive timeout: %v", err) 275 } 276 } 277 278 option := KeepaliveParams(keepalive.ClientParameters{ 279 Time: keepaliveTime, 280 Timeout: keepaliveTimeout, 281 PermitWithoutStream: c.PermitWithoutStream, 282 }) 283 return []DialOption{option}, nil 284 } 285 286 type transportSpec struct { 287 TransportOptions []TransportOption 288 InboundOptions []InboundOption 289 OutboundOptions []OutboundOption 290 DialOptions []DialOption 291 } 292 293 func newTransportSpec(opts ...Option) (*transportSpec, error) { 294 transportSpec := &transportSpec{} 295 for _, o := range opts { 296 switch opt := o.(type) { 297 case TransportOption: 298 transportSpec.TransportOptions = append(transportSpec.TransportOptions, opt) 299 case InboundOption: 300 transportSpec.InboundOptions = append(transportSpec.InboundOptions, opt) 301 case OutboundOption: 302 transportSpec.OutboundOptions = append(transportSpec.OutboundOptions, opt) 303 case DialOption: 304 transportSpec.DialOptions = append(transportSpec.DialOptions, opt) 305 default: 306 return nil, fmt.Errorf("unknown option of type %T: %v", o, o) 307 } 308 } 309 return transportSpec, nil 310 } 311 312 func (t *transportSpec) buildTransport(transportConfig *TransportConfig, kit *yarpcconfig.Kit) (transport.Transport, error) { 313 options := t.TransportOptions 314 if transportConfig.ServerMaxRecvMsgSize > 0 { 315 options = append(options, ServerMaxRecvMsgSize(transportConfig.ServerMaxRecvMsgSize)) 316 } 317 if transportConfig.ServerMaxSendMsgSize > 0 { 318 options = append(options, ServerMaxSendMsgSize(transportConfig.ServerMaxSendMsgSize)) 319 } 320 if transportConfig.ClientMaxRecvMsgSize > 0 { 321 options = append(options, ClientMaxRecvMsgSize(transportConfig.ClientMaxRecvMsgSize)) 322 } 323 if transportConfig.ClientMaxSendMsgSize > 0 { 324 options = append(options, ClientMaxSendMsgSize(transportConfig.ClientMaxSendMsgSize)) 325 } 326 if transportConfig.ServerMaxHeaderListSize > 0 { 327 options = append(options, ServerMaxHeaderListSize(transportConfig.ServerMaxHeaderListSize)) 328 } 329 if transportConfig.ClientMaxHeaderListSize > 0 { 330 options = append(options, ClientMaxHeaderListSize(transportConfig.ClientMaxHeaderListSize)) 331 } 332 backoffStrategy, err := transportConfig.Backoff.Strategy() 333 if err != nil { 334 return nil, err 335 } 336 options = append(options, BackoffStrategy(backoffStrategy), ServiceName(kit.ServiceName())) 337 return newTransport(newTransportOptions(options)), nil 338 } 339 340 func (t *transportSpec) buildInbound(inboundConfig *InboundConfig, tr transport.Transport, _ *yarpcconfig.Kit) (transport.Inbound, error) { 341 trans, ok := tr.(*Transport) 342 if !ok { 343 return nil, newTransportCastError(tr) 344 } 345 if inboundConfig.Address == "" { 346 return nil, newRequiredFieldMissingError("address") 347 } 348 listener, err := net.Listen("tcp", inboundConfig.Address) 349 if err != nil { 350 return nil, err 351 } 352 inboundOptions, err := inboundConfig.inboundOptions() 353 if err != nil { 354 return nil, fmt.Errorf("cannot build gRPC inbound from given configuration: %v", err) 355 } 356 return trans.NewInbound(listener, append(t.InboundOptions, inboundOptions...)...), nil 357 } 358 359 func (t *transportSpec) buildUnaryOutbound(outboundConfig *OutboundConfig, tr transport.Transport, kit *yarpcconfig.Kit) (transport.UnaryOutbound, error) { 360 return t.buildOutbound(outboundConfig, tr, kit) 361 } 362 363 func (t *transportSpec) buildStreamOutbound(outboundConfig *OutboundConfig, tr transport.Transport, kit *yarpcconfig.Kit) (transport.StreamOutbound, error) { 364 return t.buildOutbound(outboundConfig, tr, kit) 365 } 366 367 func (t *transportSpec) buildOutbound(outboundConfig *OutboundConfig, tr transport.Transport, kit *yarpcconfig.Kit) (*Outbound, error) { 368 trans, ok := tr.(*Transport) 369 if !ok { 370 return nil, newTransportCastError(tr) 371 } 372 373 dialOpts, err := outboundConfig.dialOptions(kit, newOutboundOptions(t.OutboundOptions).tlsConfigProvider) 374 if err != nil { 375 return nil, err 376 } 377 378 opts := append(dialOpts, t.DialOptions...) 379 dialer := trans.NewDialer(append([]DialOption{DialerDestinationServiceName(kit.OutboundServiceName())}, opts...)...) 380 var chooser peer.Chooser 381 if outboundConfig.Empty() { 382 if outboundConfig.Address == "" { 383 return nil, newRequiredFieldMissingError("address") 384 } 385 chooser = peerchooser.NewSingle(hostport.PeerIdentifier(outboundConfig.Address), dialer) 386 } else { 387 var err error 388 chooser, err = outboundConfig.BuildPeerChooser(dialer, hostport.Identify, kit) 389 if err != nil { 390 return nil, err 391 } 392 } 393 394 outboundOpts := []OutboundOption{OutboundCompressor(kit.Compressor(outboundConfig.Compressor))} 395 outboundOpts = append(outboundOpts, t.OutboundOptions...) 396 return trans.NewOutbound(chooser, outboundOpts...), nil 397 } 398 399 func newTransportCastError(tr transport.Transport) error { 400 return fmt.Errorf("could not cast %T to a *grpc.Transport", tr) 401 } 402 403 func newRequiredFieldMissingError(field string) error { 404 return fmt.Errorf("required field missing: %v", field) 405 }