github.com/nginxinc/kubernetes-ingress@v1.12.5/internal/configs/virtualserver.go (about) 1 package configs 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 8 "github.com/golang/glog" 9 "github.com/nginxinc/kubernetes-ingress/internal/k8s/secrets" 10 "github.com/nginxinc/kubernetes-ingress/internal/nginx" 11 conf_v1alpha1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1alpha1" 12 api_v1 "k8s.io/api/core/v1" 13 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 14 "k8s.io/apimachinery/pkg/labels" 15 "k8s.io/apimachinery/pkg/runtime" 16 17 "github.com/nginxinc/kubernetes-ingress/internal/configs/version2" 18 conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" 19 ) 20 21 const ( 22 nginx502Server = "unix:/var/lib/nginx/nginx-502-server.sock" 23 internalLocationPrefix = "internal_location_" 24 nginx418Server = "unix:/var/lib/nginx/nginx-418-server.sock" 25 specContext = "spec" 26 routeContext = "route" 27 subRouteContext = "subroute" 28 ) 29 30 var incompatibleLBMethodsForSlowStart = map[string]bool{ 31 "random": true, 32 "ip_hash": true, 33 "random two": true, 34 "random two least_conn": true, 35 "random two least_time=header": true, 36 "random two least_time=last_byte": true, 37 } 38 39 // MeshPodOwner contains the type and name of the K8s resource that owns the pod. 40 // This owner information is needed for NGINX Service Mesh metrics. 41 type MeshPodOwner struct { 42 // OwnerType is one of the following: statefulset, daemonset, deployment. 43 OwnerType string 44 // OwnerName is the name of the statefulset, daemonset, or deployment. 45 OwnerName string 46 } 47 48 // PodInfo contains the name of the Pod and the MeshPodOwner information 49 // which is used for NGINX Service Mesh metrics. 50 type PodInfo struct { 51 Name string 52 MeshPodOwner 53 } 54 55 // VirtualServerEx holds a VirtualServer along with the resources that are referenced in this VirtualServer. 56 type VirtualServerEx struct { 57 VirtualServer *conf_v1.VirtualServer 58 Endpoints map[string][]string 59 VirtualServerRoutes []*conf_v1.VirtualServerRoute 60 ExternalNameSvcs map[string]bool 61 Policies map[string]*conf_v1.Policy 62 PodsByIP map[string]PodInfo 63 SecretRefs map[string]*secrets.SecretReference 64 ApPolRefs map[string]*unstructured.Unstructured 65 LogConfRefs map[string]*unstructured.Unstructured 66 } 67 68 func (vsx *VirtualServerEx) String() string { 69 if vsx == nil { 70 return "<nil>" 71 } 72 73 if vsx.VirtualServer == nil { 74 return "VirtualServerEx has no VirtualServer" 75 } 76 77 return fmt.Sprintf("%s/%s", vsx.VirtualServer.Namespace, vsx.VirtualServer.Name) 78 } 79 80 // GenerateEndpointsKey generates a key for the Endpoints map in VirtualServerEx. 81 func GenerateEndpointsKey( 82 serviceNamespace string, 83 serviceName string, 84 subselector map[string]string, 85 port uint16, 86 ) string { 87 if len(subselector) > 0 { 88 return fmt.Sprintf("%s/%s_%s:%d", serviceNamespace, serviceName, labels.Set(subselector).String(), port) 89 } 90 return fmt.Sprintf("%s/%s:%d", serviceNamespace, serviceName, port) 91 } 92 93 type upstreamNamer struct { 94 prefix string 95 namespace string 96 } 97 98 func newUpstreamNamerForVirtualServer(virtualServer *conf_v1.VirtualServer) *upstreamNamer { 99 return &upstreamNamer{ 100 prefix: fmt.Sprintf("vs_%s_%s", virtualServer.Namespace, virtualServer.Name), 101 namespace: virtualServer.Namespace, 102 } 103 } 104 105 func newUpstreamNamerForVirtualServerRoute( 106 virtualServer *conf_v1.VirtualServer, 107 virtualServerRoute *conf_v1.VirtualServerRoute, 108 ) *upstreamNamer { 109 return &upstreamNamer{ 110 prefix: fmt.Sprintf( 111 "vs_%s_%s_vsr_%s_%s", 112 virtualServer.Namespace, 113 virtualServer.Name, 114 virtualServerRoute.Namespace, 115 virtualServerRoute.Name, 116 ), 117 namespace: virtualServerRoute.Namespace, 118 } 119 } 120 121 func newUpstreamNamerForTransportServer(transportServer *conf_v1alpha1.TransportServer) *upstreamNamer { 122 return &upstreamNamer{ 123 prefix: fmt.Sprintf("ts_%s_%s", transportServer.Namespace, transportServer.Name), 124 } 125 } 126 127 func (namer *upstreamNamer) GetNameForUpstreamFromAction(action *conf_v1.Action) string { 128 var upstream string 129 if action.Proxy != nil && action.Proxy.Upstream != "" { 130 upstream = action.Proxy.Upstream 131 } else { 132 upstream = action.Pass 133 } 134 135 return fmt.Sprintf("%s_%s", namer.prefix, upstream) 136 } 137 138 func (namer *upstreamNamer) GetNameForUpstream(upstream string) string { 139 return fmt.Sprintf("%s_%s", namer.prefix, upstream) 140 } 141 142 type variableNamer struct { 143 safeNsName string 144 } 145 146 func newVariableNamer(virtualServer *conf_v1.VirtualServer) *variableNamer { 147 safeNsName := strings.ReplaceAll(fmt.Sprintf("%s_%s", virtualServer.Namespace, virtualServer.Name), "-", "_") 148 return &variableNamer{ 149 safeNsName: safeNsName, 150 } 151 } 152 153 func (namer *variableNamer) GetNameForSplitClientVariable(index int) string { 154 return fmt.Sprintf("$vs_%s_splits_%d", namer.safeNsName, index) 155 } 156 157 func (namer *variableNamer) GetNameForVariableForMatchesRouteMap( 158 matchesIndex int, 159 matchIndex int, 160 conditionIndex int, 161 ) string { 162 return fmt.Sprintf("$vs_%s_matches_%d_match_%d_cond_%d", namer.safeNsName, matchesIndex, matchIndex, conditionIndex) 163 } 164 165 func (namer *variableNamer) GetNameForVariableForMatchesRouteMainMap(matchesIndex int) string { 166 return fmt.Sprintf("$vs_%s_matches_%d", namer.safeNsName, matchesIndex) 167 } 168 169 func newHealthCheckWithDefaults( 170 upstream conf_v1.Upstream, 171 upstreamName string, 172 cfgParams *ConfigParams, 173 ) *version2.HealthCheck { 174 return &version2.HealthCheck{ 175 Name: upstreamName, 176 URI: "/", 177 Interval: "5s", 178 Jitter: "0s", 179 Fails: 1, 180 Passes: 1, 181 Port: int(upstream.Port), 182 ProxyPass: fmt.Sprintf("%v://%v", generateProxyPassProtocol(upstream.TLS.Enable), upstreamName), 183 ProxyConnectTimeout: generateTimeWithDefault(upstream.ProxyConnectTimeout, cfgParams.ProxyConnectTimeout), 184 ProxyReadTimeout: generateTimeWithDefault(upstream.ProxyReadTimeout, cfgParams.ProxyReadTimeout), 185 ProxySendTimeout: generateTimeWithDefault(upstream.ProxySendTimeout, cfgParams.ProxySendTimeout), 186 Headers: make(map[string]string), 187 } 188 } 189 190 // VirtualServerConfigurator generates a VirtualServer configuration 191 type virtualServerConfigurator struct { 192 cfgParams *ConfigParams 193 isPlus bool 194 isResolverConfigured bool 195 isTLSPassthrough bool 196 enableSnippets bool 197 warnings Warnings 198 spiffeCerts bool 199 oidcPolCfg *oidcPolicyCfg 200 } 201 202 type oidcPolicyCfg struct { 203 oidc *version2.OIDC 204 key string 205 } 206 207 func (vsc *virtualServerConfigurator) addWarningf(obj runtime.Object, msgFmt string, args ...interface{}) { 208 vsc.warnings.AddWarningf(obj, msgFmt, args...) 209 } 210 211 func (vsc *virtualServerConfigurator) addWarnings(obj runtime.Object, msgs []string) { 212 for _, msg := range msgs { 213 vsc.warnings.AddWarning(obj, msg) 214 } 215 } 216 217 func (vsc *virtualServerConfigurator) clearWarnings() { 218 vsc.warnings = make(map[runtime.Object][]string) 219 } 220 221 // newVirtualServerConfigurator creates a new VirtualServerConfigurator 222 func newVirtualServerConfigurator( 223 cfgParams *ConfigParams, 224 isPlus bool, 225 isResolverConfigured bool, 226 staticParams *StaticConfigParams, 227 ) *virtualServerConfigurator { 228 return &virtualServerConfigurator{ 229 cfgParams: cfgParams, 230 isPlus: isPlus, 231 isResolverConfigured: isResolverConfigured, 232 isTLSPassthrough: staticParams.TLSPassthrough, 233 enableSnippets: staticParams.EnableSnippets, 234 warnings: make(map[runtime.Object][]string), 235 spiffeCerts: staticParams.NginxServiceMesh, 236 oidcPolCfg: &oidcPolicyCfg{}, 237 } 238 } 239 240 func (vsc *virtualServerConfigurator) generateEndpointsForUpstream( 241 owner runtime.Object, 242 namespace string, 243 upstream conf_v1.Upstream, 244 virtualServerEx *VirtualServerEx, 245 ) []string { 246 endpointsKey := GenerateEndpointsKey(namespace, upstream.Service, upstream.Subselector, upstream.Port) 247 externalNameSvcKey := GenerateExternalNameSvcKey(namespace, upstream.Service) 248 endpoints := virtualServerEx.Endpoints[endpointsKey] 249 if !vsc.isPlus && len(endpoints) == 0 { 250 return []string{nginx502Server} 251 } 252 253 _, isExternalNameSvc := virtualServerEx.ExternalNameSvcs[externalNameSvcKey] 254 if isExternalNameSvc && !vsc.isResolverConfigured { 255 msgFmt := "Type ExternalName service %v in upstream %v will be ignored. To use ExternaName services, a resolver must be configured in the ConfigMap" 256 vsc.addWarningf(owner, msgFmt, upstream.Service, upstream.Name) 257 endpoints = []string{} 258 } 259 260 return endpoints 261 } 262 263 // GenerateVirtualServerConfig generates a full configuration for a VirtualServer 264 func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(vsEx *VirtualServerEx, apResources map[string]string) (version2.VirtualServerConfig, Warnings) { 265 vsc.clearWarnings() 266 267 sslConfig := vsc.generateSSLConfig(vsEx.VirtualServer, vsEx.VirtualServer.Spec.TLS, vsEx.VirtualServer.Namespace, vsEx.SecretRefs, vsc.cfgParams) 268 tlsRedirectConfig := generateTLSRedirectConfig(vsEx.VirtualServer.Spec.TLS) 269 270 policyOpts := policyOptions{ 271 tls: sslConfig != nil, 272 secretRefs: vsEx.SecretRefs, 273 apResources: apResources, 274 } 275 276 ownerDetails := policyOwnerDetails{ 277 owner: vsEx.VirtualServer, 278 ownerNamespace: vsEx.VirtualServer.Namespace, 279 vsNamespace: vsEx.VirtualServer.Namespace, 280 vsName: vsEx.VirtualServer.Name, 281 } 282 policiesCfg := vsc.generatePolicies(ownerDetails, vsEx.VirtualServer.Spec.Policies, vsEx.Policies, specContext, policyOpts) 283 284 // crUpstreams maps an UpstreamName to its conf_v1.Upstream as they are generated 285 // necessary for generateLocation to know what Upstream each Location references 286 crUpstreams := make(map[string]conf_v1.Upstream) 287 288 virtualServerUpstreamNamer := newUpstreamNamerForVirtualServer(vsEx.VirtualServer) 289 var upstreams []version2.Upstream 290 var statusMatches []version2.StatusMatch 291 var healthChecks []version2.HealthCheck 292 var limitReqZones []version2.LimitReqZone 293 294 limitReqZones = append(limitReqZones, policiesCfg.LimitReqZones...) 295 296 // generate upstreams for VirtualServer 297 for _, u := range vsEx.VirtualServer.Spec.Upstreams { 298 upstreamName := virtualServerUpstreamNamer.GetNameForUpstream(u.Name) 299 upstreamNamespace := vsEx.VirtualServer.Namespace 300 endpoints := vsc.generateEndpointsForUpstream(vsEx.VirtualServer, upstreamNamespace, u, vsEx) 301 302 // isExternalNameSvc is always false for OSS 303 _, isExternalNameSvc := vsEx.ExternalNameSvcs[GenerateExternalNameSvcKey(upstreamNamespace, u.Service)] 304 ups := vsc.generateUpstream(vsEx.VirtualServer, upstreamName, u, isExternalNameSvc, endpoints) 305 upstreams = append(upstreams, ups) 306 307 u.TLS.Enable = isTLSEnabled(u, vsc.spiffeCerts) 308 crUpstreams[upstreamName] = u 309 310 if hc := generateHealthCheck(u, upstreamName, vsc.cfgParams); hc != nil { 311 healthChecks = append(healthChecks, *hc) 312 if u.HealthCheck.StatusMatch != "" { 313 statusMatches = append( 314 statusMatches, 315 generateUpstreamStatusMatch(upstreamName, u.HealthCheck.StatusMatch), 316 ) 317 } 318 } 319 } 320 // generate upstreams for each VirtualServerRoute 321 for _, vsr := range vsEx.VirtualServerRoutes { 322 upstreamNamer := newUpstreamNamerForVirtualServerRoute(vsEx.VirtualServer, vsr) 323 for _, u := range vsr.Spec.Upstreams { 324 upstreamName := upstreamNamer.GetNameForUpstream(u.Name) 325 upstreamNamespace := vsr.Namespace 326 endpoints := vsc.generateEndpointsForUpstream(vsr, upstreamNamespace, u, vsEx) 327 328 // isExternalNameSvc is always false for OSS 329 _, isExternalNameSvc := vsEx.ExternalNameSvcs[GenerateExternalNameSvcKey(upstreamNamespace, u.Service)] 330 ups := vsc.generateUpstream(vsr, upstreamName, u, isExternalNameSvc, endpoints) 331 upstreams = append(upstreams, ups) 332 u.TLS.Enable = isTLSEnabled(u, vsc.spiffeCerts) 333 crUpstreams[upstreamName] = u 334 335 if hc := generateHealthCheck(u, upstreamName, vsc.cfgParams); hc != nil { 336 healthChecks = append(healthChecks, *hc) 337 if u.HealthCheck.StatusMatch != "" { 338 statusMatches = append( 339 statusMatches, 340 generateUpstreamStatusMatch(upstreamName, u.HealthCheck.StatusMatch), 341 ) 342 } 343 } 344 } 345 } 346 347 var locations []version2.Location 348 var internalRedirectLocations []version2.InternalRedirectLocation 349 var returnLocations []version2.ReturnLocation 350 var splitClients []version2.SplitClient 351 var maps []version2.Map 352 var errorPageLocations []version2.ErrorPageLocation 353 vsrErrorPagesFromVs := make(map[string][]conf_v1.ErrorPage) 354 vsrErrorPagesRouteIndex := make(map[string]int) 355 vsrLocationSnippetsFromVs := make(map[string]string) 356 vsrPoliciesFromVs := make(map[string][]conf_v1.PolicyReference) 357 isVSR := false 358 matchesRoutes := 0 359 360 variableNamer := newVariableNamer(vsEx.VirtualServer) 361 362 // generates config for VirtualServer routes 363 for _, r := range vsEx.VirtualServer.Spec.Routes { 364 errorPageIndex := len(errorPageLocations) 365 errorPageLocations = append(errorPageLocations, generateErrorPageLocations(errorPageIndex, r.ErrorPages)...) 366 367 // ignore routes that reference VirtualServerRoute 368 if r.Route != "" { 369 name := r.Route 370 if !strings.Contains(name, "/") { 371 name = fmt.Sprintf("%v/%v", vsEx.VirtualServer.Namespace, r.Route) 372 } 373 374 // store route location snippet for the referenced VirtualServerRoute in case they don't define their own 375 if r.LocationSnippets != "" { 376 vsrLocationSnippetsFromVs[name] = r.LocationSnippets 377 } 378 379 // store route error pages and route index for the referenced VirtualServerRoute in case they don't define their own 380 if len(r.ErrorPages) > 0 { 381 vsrErrorPagesFromVs[name] = r.ErrorPages 382 vsrErrorPagesRouteIndex[name] = errorPageIndex 383 } 384 385 // store route policies for the referenced VirtualServerRoute in case they don't define their own 386 if len(r.Policies) > 0 { 387 vsrPoliciesFromVs[name] = r.Policies 388 } 389 390 continue 391 } 392 393 vsLocSnippets := r.LocationSnippets 394 ownerDetails := policyOwnerDetails{ 395 owner: vsEx.VirtualServer, 396 ownerNamespace: vsEx.VirtualServer.Namespace, 397 vsNamespace: vsEx.VirtualServer.Namespace, 398 vsName: vsEx.VirtualServer.Name, 399 } 400 routePoliciesCfg := vsc.generatePolicies(ownerDetails, r.Policies, vsEx.Policies, routeContext, policyOpts) 401 if policiesCfg.OIDC { 402 routePoliciesCfg.OIDC = policiesCfg.OIDC 403 } 404 limitReqZones = append(limitReqZones, routePoliciesCfg.LimitReqZones...) 405 406 if len(r.Matches) > 0 { 407 cfg := generateMatchesConfig( 408 r, 409 virtualServerUpstreamNamer, 410 crUpstreams, 411 variableNamer, 412 matchesRoutes, 413 len(splitClients), 414 vsc.cfgParams, 415 r.ErrorPages, 416 errorPageIndex, 417 vsLocSnippets, 418 vsc.enableSnippets, 419 len(returnLocations), 420 isVSR, 421 "", "", 422 ) 423 addPoliciesCfgToLocations(routePoliciesCfg, cfg.Locations) 424 425 maps = append(maps, cfg.Maps...) 426 locations = append(locations, cfg.Locations...) 427 internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation) 428 returnLocations = append(returnLocations, cfg.ReturnLocations...) 429 splitClients = append(splitClients, cfg.SplitClients...) 430 matchesRoutes++ 431 } else if len(r.Splits) > 0 { 432 cfg := generateDefaultSplitsConfig(r, virtualServerUpstreamNamer, crUpstreams, variableNamer, len(splitClients), 433 vsc.cfgParams, r.ErrorPages, errorPageIndex, r.Path, vsLocSnippets, vsc.enableSnippets, len(returnLocations), isVSR, "", "") 434 addPoliciesCfgToLocations(routePoliciesCfg, cfg.Locations) 435 436 splitClients = append(splitClients, cfg.SplitClients...) 437 locations = append(locations, cfg.Locations...) 438 internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation) 439 returnLocations = append(returnLocations, cfg.ReturnLocations...) 440 } else { 441 upstreamName := virtualServerUpstreamNamer.GetNameForUpstreamFromAction(r.Action) 442 upstream := crUpstreams[upstreamName] 443 444 proxySSLName := generateProxySSLName(upstream.Service, vsEx.VirtualServer.Namespace) 445 446 loc, returnLoc := generateLocation(r.Path, upstreamName, upstream, r.Action, vsc.cfgParams, r.ErrorPages, false, 447 errorPageIndex, proxySSLName, r.Path, vsLocSnippets, vsc.enableSnippets, len(returnLocations), isVSR, "", "") 448 addPoliciesCfgToLocation(routePoliciesCfg, &loc) 449 450 locations = append(locations, loc) 451 if returnLoc != nil { 452 returnLocations = append(returnLocations, *returnLoc) 453 } 454 } 455 } 456 457 // generate config for subroutes of each VirtualServerRoute 458 for _, vsr := range vsEx.VirtualServerRoutes { 459 isVSR := true 460 upstreamNamer := newUpstreamNamerForVirtualServerRoute(vsEx.VirtualServer, vsr) 461 for _, r := range vsr.Spec.Subroutes { 462 errorPageIndex := len(errorPageLocations) 463 errorPageLocations = append(errorPageLocations, generateErrorPageLocations(errorPageIndex, r.ErrorPages)...) 464 errorPages := r.ErrorPages 465 vsrNamespaceName := fmt.Sprintf("%v/%v", vsr.Namespace, vsr.Name) 466 // use the VirtualServer error pages if the route does not define any 467 if r.ErrorPages == nil { 468 if vsErrorPages, ok := vsrErrorPagesFromVs[vsrNamespaceName]; ok { 469 errorPages = vsErrorPages 470 errorPageIndex = vsrErrorPagesRouteIndex[vsrNamespaceName] 471 } 472 } 473 474 locSnippets := r.LocationSnippets 475 // use the VirtualServer location snippet if the route does not define any 476 if r.LocationSnippets == "" { 477 locSnippets = vsrLocationSnippetsFromVs[vsrNamespaceName] 478 } 479 480 var ownerDetails policyOwnerDetails 481 var policyRefs []conf_v1.PolicyReference 482 var context string 483 if len(r.Policies) == 0 { 484 // use the VirtualServer route policies if the route does not define any 485 ownerDetails = policyOwnerDetails{ 486 owner: vsEx.VirtualServer, 487 ownerNamespace: vsEx.VirtualServer.Namespace, 488 vsNamespace: vsEx.VirtualServer.Namespace, 489 vsName: vsEx.VirtualServer.Name, 490 } 491 policyRefs = vsrPoliciesFromVs[vsrNamespaceName] 492 context = routeContext 493 } else { 494 ownerDetails = policyOwnerDetails{ 495 owner: vsr, 496 ownerNamespace: vsr.Namespace, 497 vsNamespace: vsEx.VirtualServer.Namespace, 498 vsName: vsEx.VirtualServer.Name, 499 } 500 policyRefs = r.Policies 501 context = subRouteContext 502 } 503 routePoliciesCfg := vsc.generatePolicies(ownerDetails, policyRefs, vsEx.Policies, context, policyOpts) 504 if policiesCfg.OIDC { 505 routePoliciesCfg.OIDC = policiesCfg.OIDC 506 } 507 limitReqZones = append(limitReqZones, routePoliciesCfg.LimitReqZones...) 508 if len(r.Matches) > 0 { 509 cfg := generateMatchesConfig( 510 r, 511 upstreamNamer, 512 crUpstreams, 513 variableNamer, 514 matchesRoutes, 515 len(splitClients), 516 vsc.cfgParams, 517 errorPages, 518 errorPageIndex, 519 locSnippets, 520 vsc.enableSnippets, 521 len(returnLocations), 522 isVSR, 523 vsr.Name, 524 vsr.Namespace, 525 ) 526 addPoliciesCfgToLocations(routePoliciesCfg, cfg.Locations) 527 528 maps = append(maps, cfg.Maps...) 529 locations = append(locations, cfg.Locations...) 530 internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation) 531 returnLocations = append(returnLocations, cfg.ReturnLocations...) 532 splitClients = append(splitClients, cfg.SplitClients...) 533 matchesRoutes++ 534 } else if len(r.Splits) > 0 { 535 cfg := generateDefaultSplitsConfig(r, upstreamNamer, crUpstreams, variableNamer, len(splitClients), vsc.cfgParams, 536 errorPages, errorPageIndex, r.Path, locSnippets, vsc.enableSnippets, len(returnLocations), isVSR, vsr.Name, vsr.Namespace) 537 addPoliciesCfgToLocations(routePoliciesCfg, cfg.Locations) 538 539 splitClients = append(splitClients, cfg.SplitClients...) 540 locations = append(locations, cfg.Locations...) 541 internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation) 542 returnLocations = append(returnLocations, cfg.ReturnLocations...) 543 } else { 544 upstreamName := upstreamNamer.GetNameForUpstreamFromAction(r.Action) 545 upstream := crUpstreams[upstreamName] 546 proxySSLName := generateProxySSLName(upstream.Service, vsr.Namespace) 547 548 loc, returnLoc := generateLocation(r.Path, upstreamName, upstream, r.Action, vsc.cfgParams, errorPages, false, 549 errorPageIndex, proxySSLName, r.Path, locSnippets, vsc.enableSnippets, len(returnLocations), isVSR, vsr.Name, vsr.Namespace) 550 addPoliciesCfgToLocation(routePoliciesCfg, &loc) 551 552 locations = append(locations, loc) 553 if returnLoc != nil { 554 returnLocations = append(returnLocations, *returnLoc) 555 } 556 } 557 } 558 } 559 560 httpSnippets := generateSnippets(vsc.enableSnippets, vsEx.VirtualServer.Spec.HTTPSnippets, []string{}) 561 serverSnippets := generateSnippets( 562 vsc.enableSnippets, 563 vsEx.VirtualServer.Spec.ServerSnippets, 564 vsc.cfgParams.ServerSnippets, 565 ) 566 567 vsCfg := version2.VirtualServerConfig{ 568 Upstreams: upstreams, 569 SplitClients: splitClients, 570 Maps: maps, 571 StatusMatches: statusMatches, 572 LimitReqZones: removeDuplicateLimitReqZones(limitReqZones), 573 HTTPSnippets: httpSnippets, 574 Server: version2.Server{ 575 ServerName: vsEx.VirtualServer.Spec.Host, 576 StatusZone: vsEx.VirtualServer.Spec.Host, 577 ProxyProtocol: vsc.cfgParams.ProxyProtocol, 578 SSL: sslConfig, 579 ServerTokens: vsc.cfgParams.ServerTokens, 580 SetRealIPFrom: vsc.cfgParams.SetRealIPFrom, 581 RealIPHeader: vsc.cfgParams.RealIPHeader, 582 RealIPRecursive: vsc.cfgParams.RealIPRecursive, 583 Snippets: serverSnippets, 584 InternalRedirectLocations: internalRedirectLocations, 585 Locations: locations, 586 ReturnLocations: returnLocations, 587 HealthChecks: healthChecks, 588 TLSRedirect: tlsRedirectConfig, 589 ErrorPageLocations: errorPageLocations, 590 TLSPassthrough: vsc.isTLSPassthrough, 591 Allow: policiesCfg.Allow, 592 Deny: policiesCfg.Deny, 593 LimitReqOptions: policiesCfg.LimitReqOptions, 594 LimitReqs: policiesCfg.LimitReqs, 595 JWTAuth: policiesCfg.JWTAuth, 596 IngressMTLS: policiesCfg.IngressMTLS, 597 EgressMTLS: policiesCfg.EgressMTLS, 598 OIDC: vsc.oidcPolCfg.oidc, 599 WAF: policiesCfg.WAF, 600 PoliciesErrorReturn: policiesCfg.ErrorReturn, 601 VSNamespace: vsEx.VirtualServer.Namespace, 602 VSName: vsEx.VirtualServer.Name, 603 }, 604 SpiffeCerts: vsc.spiffeCerts, 605 } 606 607 return vsCfg, vsc.warnings 608 } 609 610 type policiesCfg struct { 611 Allow []string 612 Deny []string 613 LimitReqOptions version2.LimitReqOptions 614 LimitReqZones []version2.LimitReqZone 615 LimitReqs []version2.LimitReq 616 JWTAuth *version2.JWTAuth 617 IngressMTLS *version2.IngressMTLS 618 EgressMTLS *version2.EgressMTLS 619 OIDC bool 620 WAF *version2.WAF 621 ErrorReturn *version2.Return 622 } 623 624 func newPoliciesConfig() *policiesCfg { 625 return &policiesCfg{} 626 } 627 628 type policyOwnerDetails struct { 629 owner runtime.Object 630 ownerNamespace string 631 vsNamespace string 632 vsName string 633 } 634 635 type policyOptions struct { 636 tls bool 637 secretRefs map[string]*secrets.SecretReference 638 apResources map[string]string 639 } 640 641 type validationResults struct { 642 isError bool 643 warnings []string 644 } 645 646 func newValidationResults() *validationResults { 647 return &validationResults{} 648 } 649 650 func (v *validationResults) addWarningf(msgFmt string, args ...interface{}) { 651 v.warnings = append(v.warnings, fmt.Sprintf(msgFmt, args...)) 652 } 653 654 func (p *policiesCfg) addAccessControlConfig(accessControl *conf_v1.AccessControl) *validationResults { 655 res := newValidationResults() 656 p.Allow = append(p.Allow, accessControl.Allow...) 657 p.Deny = append(p.Deny, accessControl.Deny...) 658 if len(p.Allow) > 0 && len(p.Deny) > 0 { 659 res.addWarningf( 660 "AccessControl policy (or policies) with deny rules is overridden by policy (or policies) with allow rules", 661 ) 662 } 663 return res 664 } 665 666 func (p *policiesCfg) addRateLimitConfig( 667 rateLimit *conf_v1.RateLimit, 668 polKey string, 669 polNamespace string, 670 polName string, 671 vsNamespace string, 672 vsName string, 673 ) *validationResults { 674 res := newValidationResults() 675 rlZoneName := fmt.Sprintf("pol_rl_%v_%v_%v_%v", polNamespace, polName, vsNamespace, vsName) 676 p.LimitReqs = append(p.LimitReqs, generateLimitReq(rlZoneName, rateLimit)) 677 p.LimitReqZones = append(p.LimitReqZones, generateLimitReqZone(rlZoneName, rateLimit)) 678 if len(p.LimitReqs) == 1 { 679 p.LimitReqOptions = generateLimitReqOptions(rateLimit) 680 } else { 681 curOptions := generateLimitReqOptions(rateLimit) 682 if curOptions.DryRun != p.LimitReqOptions.DryRun { 683 res.addWarningf("RateLimit policy %s with limit request option dryRun='%v' is overridden to dryRun='%v' by the first policy reference in this context", polKey, curOptions.DryRun, p.LimitReqOptions.DryRun) 684 } 685 if curOptions.LogLevel != p.LimitReqOptions.LogLevel { 686 res.addWarningf("RateLimit policy %s with limit request option logLevel='%v' is overridden to logLevel='%v' by the first policy reference in this context", polKey, curOptions.LogLevel, p.LimitReqOptions.LogLevel) 687 } 688 if curOptions.RejectCode != p.LimitReqOptions.RejectCode { 689 res.addWarningf("RateLimit policy %s with limit request option rejectCode='%v' is overridden to rejectCode='%v' by the first policy reference in this context", polKey, curOptions.RejectCode, p.LimitReqOptions.RejectCode) 690 } 691 } 692 return res 693 } 694 695 func (p *policiesCfg) addJWTAuthConfig( 696 jwtAuth *conf_v1.JWTAuth, 697 polKey string, 698 polNamespace string, 699 secretRefs map[string]*secrets.SecretReference, 700 ) *validationResults { 701 res := newValidationResults() 702 if p.JWTAuth != nil { 703 res.addWarningf("Multiple jwt policies in the same context is not valid. JWT policy %s will be ignored", polKey) 704 return res 705 } 706 707 jwtSecretKey := fmt.Sprintf("%v/%v", polNamespace, jwtAuth.Secret) 708 secretRef := secretRefs[jwtSecretKey] 709 var secretType api_v1.SecretType 710 if secretRef.Secret != nil { 711 secretType = secretRef.Secret.Type 712 } 713 if secretType != "" && secretType != secrets.SecretTypeJWK { 714 res.addWarningf("JWT policy %s references a secret %s of a wrong type '%s', must be '%s'", polKey, jwtSecretKey, secretType, secrets.SecretTypeJWK) 715 res.isError = true 716 return res 717 } else if secretRef.Error != nil { 718 res.addWarningf("JWT policy %s references an invalid secret %s: %v", polKey, jwtSecretKey, secretRef.Error) 719 res.isError = true 720 return res 721 } 722 723 p.JWTAuth = &version2.JWTAuth{ 724 Secret: secretRef.Path, 725 Realm: jwtAuth.Realm, 726 Token: jwtAuth.Token, 727 } 728 return res 729 } 730 731 func (p *policiesCfg) addIngressMTLSConfig( 732 ingressMTLS *conf_v1.IngressMTLS, 733 polKey string, 734 polNamespace string, 735 context string, 736 tls bool, 737 secretRefs map[string]*secrets.SecretReference, 738 ) *validationResults { 739 res := newValidationResults() 740 if !tls { 741 res.addWarningf("TLS must be enabled in VirtualServer for IngressMTLS policy %s", polKey) 742 res.isError = true 743 return res 744 } 745 if context != specContext { 746 res.addWarningf("IngressMTLS policy %s is not allowed in the %v context", polKey, context) 747 res.isError = true 748 return res 749 } 750 if p.IngressMTLS != nil { 751 res.addWarningf("Multiple ingressMTLS policies are not allowed. IngressMTLS policy %s will be ignored", polKey) 752 return res 753 } 754 755 secretKey := fmt.Sprintf("%v/%v", polNamespace, ingressMTLS.ClientCertSecret) 756 secretRef := secretRefs[secretKey] 757 var secretType api_v1.SecretType 758 if secretRef.Secret != nil { 759 secretType = secretRef.Secret.Type 760 } 761 if secretType != "" && secretType != secrets.SecretTypeCA { 762 res.addWarningf("IngressMTLS policy %s references a secret %s of a wrong type '%s', must be '%s'", polKey, secretKey, secretType, secrets.SecretTypeCA) 763 res.isError = true 764 return res 765 } else if secretRef.Error != nil { 766 res.addWarningf("IngressMTLS policy %q references an invalid secret %s: %v", polKey, secretKey, secretRef.Error) 767 res.isError = true 768 return res 769 } 770 771 verifyDepth := 1 772 verifyClient := "on" 773 if ingressMTLS.VerifyDepth != nil { 774 verifyDepth = *ingressMTLS.VerifyDepth 775 } 776 if ingressMTLS.VerifyClient != "" { 777 verifyClient = ingressMTLS.VerifyClient 778 } 779 780 p.IngressMTLS = &version2.IngressMTLS{ 781 ClientCert: secretRef.Path, 782 VerifyClient: verifyClient, 783 VerifyDepth: verifyDepth, 784 } 785 return res 786 } 787 788 func (p *policiesCfg) addEgressMTLSConfig( 789 egressMTLS *conf_v1.EgressMTLS, 790 polKey string, 791 polNamespace string, 792 secretRefs map[string]*secrets.SecretReference, 793 ) *validationResults { 794 res := newValidationResults() 795 if p.EgressMTLS != nil { 796 res.addWarningf( 797 "Multiple egressMTLS policies in the same context is not valid. EgressMTLS policy %s will be ignored", 798 polKey, 799 ) 800 return res 801 } 802 803 var tlsSecretPath string 804 805 if egressMTLS.TLSSecret != "" { 806 egressTLSSecret := fmt.Sprintf("%v/%v", polNamespace, egressMTLS.TLSSecret) 807 808 secretRef := secretRefs[egressTLSSecret] 809 var secretType api_v1.SecretType 810 if secretRef.Secret != nil { 811 secretType = secretRef.Secret.Type 812 } 813 if secretType != "" && secretType != api_v1.SecretTypeTLS { 814 res.addWarningf("EgressMTLS policy %s references a secret %s of a wrong type '%s', must be '%s'", polKey, egressTLSSecret, secretType, api_v1.SecretTypeTLS) 815 res.isError = true 816 return res 817 } else if secretRef.Error != nil { 818 res.addWarningf("EgressMTLS policy %s references an invalid secret %s: %v", polKey, egressTLSSecret, secretRef.Error) 819 res.isError = true 820 return res 821 } 822 823 tlsSecretPath = secretRef.Path 824 } 825 826 var trustedSecretPath string 827 828 if egressMTLS.TrustedCertSecret != "" { 829 trustedCertSecret := fmt.Sprintf("%v/%v", polNamespace, egressMTLS.TrustedCertSecret) 830 831 secretRef := secretRefs[trustedCertSecret] 832 var secretType api_v1.SecretType 833 if secretRef.Secret != nil { 834 secretType = secretRef.Secret.Type 835 } 836 if secretType != "" && secretType != secrets.SecretTypeCA { 837 res.addWarningf("EgressMTLS policy %s references a secret %s of a wrong type '%s', must be '%s'", polKey, trustedCertSecret, secretType, secrets.SecretTypeCA) 838 res.isError = true 839 return res 840 } else if secretRef.Error != nil { 841 res.addWarningf("EgressMTLS policy %s references an invalid secret %s: %v", polKey, trustedCertSecret, secretRef.Error) 842 res.isError = true 843 return res 844 } 845 846 trustedSecretPath = secretRef.Path 847 } 848 849 p.EgressMTLS = &version2.EgressMTLS{ 850 Certificate: tlsSecretPath, 851 CertificateKey: tlsSecretPath, 852 Ciphers: generateString(egressMTLS.Ciphers, "DEFAULT"), 853 Protocols: generateString(egressMTLS.Protocols, "TLSv1 TLSv1.1 TLSv1.2"), 854 VerifyServer: egressMTLS.VerifyServer, 855 VerifyDepth: generateIntFromPointer(egressMTLS.VerifyDepth, 1), 856 SessionReuse: generateBool(egressMTLS.SessionReuse, true), 857 ServerName: egressMTLS.ServerName, 858 TrustedCert: trustedSecretPath, 859 SSLName: generateString(egressMTLS.SSLName, "$proxy_host"), 860 } 861 return res 862 } 863 864 func (p *policiesCfg) addOIDCConfig( 865 oidc *conf_v1.OIDC, 866 polKey string, 867 polNamespace string, 868 secretRefs map[string]*secrets.SecretReference, 869 oidcPolCfg *oidcPolicyCfg, 870 ) *validationResults { 871 res := newValidationResults() 872 if p.OIDC { 873 res.addWarningf( 874 "Multiple oidc policies in the same context is not valid. OIDC policy %s will be ignored", 875 polKey, 876 ) 877 return res 878 } 879 880 if oidcPolCfg.oidc != nil { 881 if oidcPolCfg.key != polKey { 882 res.addWarningf( 883 "Only one oidc policy is allowed in a VirtualServer and its VirtualServerRoutes. Can't use %s. Use %s", 884 polKey, 885 oidcPolCfg.key, 886 ) 887 res.isError = true 888 return res 889 } 890 } else { 891 secretKey := fmt.Sprintf("%v/%v", polNamespace, oidc.ClientSecret) 892 secretRef := secretRefs[secretKey] 893 894 var secretType api_v1.SecretType 895 if secretRef.Secret != nil { 896 secretType = secretRef.Secret.Type 897 } 898 if secretType != "" && secretType != secrets.SecretTypeOIDC { 899 res.addWarningf("OIDC policy %s references a secret %s of a wrong type '%s', must be '%s'", polKey, secretKey, secretType, secrets.SecretTypeOIDC) 900 res.isError = true 901 return res 902 } else if secretRef.Error != nil { 903 res.addWarningf("OIDC policy %s references an invalid secret %s: %v", polKey, secretKey, secretRef.Error) 904 res.isError = true 905 return res 906 } 907 908 clientSecret := secretRef.Secret.Data[ClientSecretKey] 909 910 redirectURI := oidc.RedirectURI 911 if redirectURI == "" { 912 redirectURI = "/_codexch" 913 } 914 scope := oidc.Scope 915 if scope == "" { 916 scope = "openid" 917 } 918 919 oidcPolCfg.oidc = &version2.OIDC{ 920 AuthEndpoint: oidc.AuthEndpoint, 921 TokenEndpoint: oidc.TokenEndpoint, 922 JwksURI: oidc.JWKSURI, 923 ClientID: oidc.ClientID, 924 ClientSecret: string(clientSecret), 925 Scope: scope, 926 RedirectURI: redirectURI, 927 } 928 oidcPolCfg.key = polKey 929 } 930 931 p.OIDC = true 932 933 return res 934 } 935 936 func (p *policiesCfg) addWAFConfig( 937 waf *conf_v1.WAF, 938 polKey string, 939 polNamespace string, 940 apResources map[string]string, 941 ) *validationResults { 942 res := newValidationResults() 943 if p.WAF != nil { 944 res.addWarningf("Multiple WAF policies in the same context is not valid. WAF policy %s will be ignored", polKey) 945 return res 946 } 947 948 if waf.Enable { 949 p.WAF = &version2.WAF{Enable: "on"} 950 } else { 951 p.WAF = &version2.WAF{Enable: "off"} 952 } 953 954 if waf.ApPolicy != "" { 955 apPolKey := waf.ApPolicy 956 hasNamepace := strings.Contains(apPolKey, "/") 957 if !hasNamepace { 958 apPolKey = fmt.Sprintf("%v/%v", polNamespace, apPolKey) 959 } 960 961 if apPolPath, exists := apResources[apPolKey]; exists { 962 p.WAF.ApPolicy = apPolPath 963 } else { 964 res.addWarningf("WAF policy %s references an invalid or non-existing App Protect policy %s", polKey, apPolKey) 965 res.isError = true 966 return res 967 } 968 } 969 970 if waf.SecurityLog != nil { 971 p.WAF.ApSecurityLogEnable = true 972 973 logConfKey := waf.SecurityLog.ApLogConf 974 hasNamepace := strings.Contains(logConfKey, "/") 975 if !hasNamepace { 976 logConfKey = fmt.Sprintf("%v/%v", polNamespace, logConfKey) 977 } 978 979 if logConfPath, ok := apResources[logConfKey]; ok { 980 logDest := generateString(waf.SecurityLog.LogDest, "syslog:server=localhost:514") 981 p.WAF.ApLogConf = fmt.Sprintf("%s %s", logConfPath, logDest) 982 } else { 983 res.addWarningf("WAF policy %s references an invalid or non-existing log config %s", polKey, logConfKey) 984 res.isError = true 985 } 986 } 987 988 return res 989 } 990 991 func (vsc *virtualServerConfigurator) generatePolicies( 992 ownerDetails policyOwnerDetails, 993 policyRefs []conf_v1.PolicyReference, 994 policies map[string]*conf_v1.Policy, 995 context string, 996 policyOpts policyOptions, 997 ) policiesCfg { 998 config := newPoliciesConfig() 999 1000 for _, p := range policyRefs { 1001 polNamespace := p.Namespace 1002 if polNamespace == "" { 1003 polNamespace = ownerDetails.ownerNamespace 1004 } 1005 1006 key := fmt.Sprintf("%s/%s", polNamespace, p.Name) 1007 1008 if pol, exists := policies[key]; exists { 1009 var res *validationResults 1010 switch { 1011 case pol.Spec.AccessControl != nil: 1012 res = config.addAccessControlConfig(pol.Spec.AccessControl) 1013 case pol.Spec.RateLimit != nil: 1014 res = config.addRateLimitConfig( 1015 pol.Spec.RateLimit, 1016 key, 1017 polNamespace, 1018 p.Name, 1019 ownerDetails.vsNamespace, 1020 ownerDetails.vsName, 1021 ) 1022 case pol.Spec.JWTAuth != nil: 1023 res = config.addJWTAuthConfig(pol.Spec.JWTAuth, key, polNamespace, policyOpts.secretRefs) 1024 case pol.Spec.IngressMTLS != nil: 1025 res = config.addIngressMTLSConfig( 1026 pol.Spec.IngressMTLS, 1027 key, 1028 polNamespace, 1029 context, 1030 policyOpts.tls, 1031 policyOpts.secretRefs, 1032 ) 1033 case pol.Spec.EgressMTLS != nil: 1034 res = config.addEgressMTLSConfig(pol.Spec.EgressMTLS, key, polNamespace, policyOpts.secretRefs) 1035 case pol.Spec.OIDC != nil: 1036 res = config.addOIDCConfig(pol.Spec.OIDC, key, polNamespace, policyOpts.secretRefs, vsc.oidcPolCfg) 1037 case pol.Spec.WAF != nil: 1038 res = config.addWAFConfig(pol.Spec.WAF, key, polNamespace, policyOpts.apResources) 1039 default: 1040 res = newValidationResults() 1041 } 1042 vsc.addWarnings(ownerDetails.owner, res.warnings) 1043 if res.isError { 1044 return policiesCfg{ 1045 ErrorReturn: &version2.Return{Code: 500}, 1046 } 1047 } 1048 } else { 1049 vsc.addWarningf(ownerDetails.owner, "Policy %s is missing or invalid", key) 1050 return policiesCfg{ 1051 ErrorReturn: &version2.Return{Code: 500}, 1052 } 1053 } 1054 } 1055 1056 return *config 1057 } 1058 1059 func generateLimitReq(zoneName string, rateLimitPol *conf_v1.RateLimit) version2.LimitReq { 1060 var limitReq version2.LimitReq 1061 1062 limitReq.ZoneName = zoneName 1063 1064 if rateLimitPol.Burst != nil { 1065 limitReq.Burst = *rateLimitPol.Burst 1066 } 1067 if rateLimitPol.Delay != nil { 1068 limitReq.Delay = *rateLimitPol.Delay 1069 } 1070 1071 limitReq.NoDelay = generateBool(rateLimitPol.NoDelay, false) 1072 if limitReq.NoDelay { 1073 limitReq.Delay = 0 1074 } 1075 1076 return limitReq 1077 } 1078 1079 func generateLimitReqZone(zoneName string, rateLimitPol *conf_v1.RateLimit) version2.LimitReqZone { 1080 return version2.LimitReqZone{ 1081 ZoneName: zoneName, 1082 Key: rateLimitPol.Key, 1083 ZoneSize: rateLimitPol.ZoneSize, 1084 Rate: rateLimitPol.Rate, 1085 } 1086 } 1087 1088 func generateLimitReqOptions(rateLimitPol *conf_v1.RateLimit) version2.LimitReqOptions { 1089 return version2.LimitReqOptions{ 1090 DryRun: generateBool(rateLimitPol.DryRun, false), 1091 LogLevel: generateString(rateLimitPol.LogLevel, "error"), 1092 RejectCode: generateIntFromPointer(rateLimitPol.RejectCode, 503), 1093 } 1094 } 1095 1096 func removeDuplicateLimitReqZones(rlz []version2.LimitReqZone) []version2.LimitReqZone { 1097 encountered := make(map[string]bool) 1098 result := []version2.LimitReqZone{} 1099 1100 for _, v := range rlz { 1101 if !encountered[v.ZoneName] { 1102 encountered[v.ZoneName] = true 1103 result = append(result, v) 1104 } 1105 } 1106 1107 return result 1108 } 1109 1110 func addPoliciesCfgToLocation(cfg policiesCfg, location *version2.Location) { 1111 location.Allow = cfg.Allow 1112 location.Deny = cfg.Deny 1113 location.LimitReqOptions = cfg.LimitReqOptions 1114 location.LimitReqs = cfg.LimitReqs 1115 location.JWTAuth = cfg.JWTAuth 1116 location.EgressMTLS = cfg.EgressMTLS 1117 location.OIDC = cfg.OIDC 1118 location.WAF = cfg.WAF 1119 location.PoliciesErrorReturn = cfg.ErrorReturn 1120 } 1121 1122 func addPoliciesCfgToLocations(cfg policiesCfg, locations []version2.Location) { 1123 for i := range locations { 1124 addPoliciesCfgToLocation(cfg, &locations[i]) 1125 } 1126 } 1127 1128 func getUpstreamResourceLabels(owner runtime.Object) version2.UpstreamLabels { 1129 var resourceType, resourceName, resourceNamespace string 1130 1131 switch owner := owner.(type) { 1132 case *conf_v1.VirtualServer: 1133 resourceType = "virtualserver" 1134 resourceName = owner.Name 1135 resourceNamespace = owner.Namespace 1136 case *conf_v1.VirtualServerRoute: 1137 resourceType = "virtualserverroute" 1138 resourceName = owner.Name 1139 resourceNamespace = owner.Namespace 1140 } 1141 1142 return version2.UpstreamLabels{ 1143 ResourceType: resourceType, 1144 ResourceName: resourceName, 1145 ResourceNamespace: resourceNamespace, 1146 } 1147 } 1148 1149 func (vsc *virtualServerConfigurator) generateUpstream( 1150 owner runtime.Object, 1151 upstreamName string, 1152 upstream conf_v1.Upstream, 1153 isExternalNameSvc bool, 1154 endpoints []string, 1155 ) version2.Upstream { 1156 var upsServers []version2.UpstreamServer 1157 for _, e := range endpoints { 1158 s := version2.UpstreamServer{ 1159 Address: e, 1160 } 1161 upsServers = append(upsServers, s) 1162 } 1163 1164 lbMethod := generateLBMethod(upstream.LBMethod, vsc.cfgParams.LBMethod) 1165 1166 upstreamLabels := getUpstreamResourceLabels(owner) 1167 upstreamLabels.Service = upstream.Service 1168 1169 ups := version2.Upstream{ 1170 Name: upstreamName, 1171 UpstreamLabels: upstreamLabels, 1172 Servers: upsServers, 1173 Resolve: isExternalNameSvc, 1174 LBMethod: lbMethod, 1175 Keepalive: generateIntFromPointer(upstream.Keepalive, vsc.cfgParams.Keepalive), 1176 MaxFails: generateIntFromPointer(upstream.MaxFails, vsc.cfgParams.MaxFails), 1177 FailTimeout: generateTimeWithDefault(upstream.FailTimeout, vsc.cfgParams.FailTimeout), 1178 MaxConns: generateIntFromPointer(upstream.MaxConns, vsc.cfgParams.MaxConns), 1179 UpstreamZoneSize: vsc.cfgParams.UpstreamZoneSize, 1180 } 1181 1182 if vsc.isPlus { 1183 ups.SlowStart = vsc.generateSlowStartForPlus(owner, upstream, lbMethod) 1184 ups.Queue = generateQueueForPlus(upstream.Queue, "60s") 1185 ups.SessionCookie = generateSessionCookie(upstream.SessionCookie) 1186 } 1187 1188 return ups 1189 } 1190 1191 func (vsc *virtualServerConfigurator) generateSlowStartForPlus( 1192 owner runtime.Object, 1193 upstream conf_v1.Upstream, 1194 lbMethod string, 1195 ) string { 1196 if upstream.SlowStart == "" { 1197 return "" 1198 } 1199 1200 _, isIncompatible := incompatibleLBMethodsForSlowStart[lbMethod] 1201 isHash := strings.HasPrefix(lbMethod, "hash") 1202 if isIncompatible || isHash { 1203 msgFmt := "Slow start will be disabled for upstream %v because lb method '%v' is incompatible with slow start" 1204 vsc.addWarningf(owner, msgFmt, upstream.Name, lbMethod) 1205 return "" 1206 } 1207 1208 return generateTime(upstream.SlowStart) 1209 } 1210 1211 func generateHealthCheck( 1212 upstream conf_v1.Upstream, 1213 upstreamName string, 1214 cfgParams *ConfigParams, 1215 ) *version2.HealthCheck { 1216 if upstream.HealthCheck == nil || !upstream.HealthCheck.Enable { 1217 return nil 1218 } 1219 1220 hc := newHealthCheckWithDefaults(upstream, upstreamName, cfgParams) 1221 1222 if upstream.HealthCheck.Path != "" { 1223 hc.URI = upstream.HealthCheck.Path 1224 } 1225 1226 if upstream.HealthCheck.Interval != "" { 1227 hc.Interval = generateTime(upstream.HealthCheck.Interval) 1228 } 1229 1230 if upstream.HealthCheck.Jitter != "" { 1231 hc.Jitter = generateTime(upstream.HealthCheck.Jitter) 1232 } 1233 1234 if upstream.HealthCheck.Fails > 0 { 1235 hc.Fails = upstream.HealthCheck.Fails 1236 } 1237 1238 if upstream.HealthCheck.Passes > 0 { 1239 hc.Passes = upstream.HealthCheck.Passes 1240 } 1241 1242 if upstream.HealthCheck.Port > 0 { 1243 hc.Port = upstream.HealthCheck.Port 1244 } 1245 1246 if upstream.HealthCheck.ConnectTimeout != "" { 1247 hc.ProxyConnectTimeout = generateTime(upstream.HealthCheck.ConnectTimeout) 1248 } 1249 1250 if upstream.HealthCheck.ReadTimeout != "" { 1251 hc.ProxyReadTimeout = generateTime(upstream.HealthCheck.ReadTimeout) 1252 } 1253 1254 if upstream.HealthCheck.SendTimeout != "" { 1255 hc.ProxySendTimeout = generateTime(upstream.HealthCheck.SendTimeout) 1256 } 1257 1258 for _, h := range upstream.HealthCheck.Headers { 1259 hc.Headers[h.Name] = h.Value 1260 } 1261 1262 if upstream.HealthCheck.TLS != nil { 1263 hc.ProxyPass = fmt.Sprintf("%v://%v", generateProxyPassProtocol(upstream.HealthCheck.TLS.Enable), upstreamName) 1264 } 1265 1266 if upstream.HealthCheck.StatusMatch != "" { 1267 hc.Match = generateStatusMatchName(upstreamName) 1268 } 1269 1270 return hc 1271 } 1272 1273 func generateSessionCookie(sc *conf_v1.SessionCookie) *version2.SessionCookie { 1274 if sc == nil || !sc.Enable { 1275 return nil 1276 } 1277 1278 return &version2.SessionCookie{ 1279 Enable: true, 1280 Name: sc.Name, 1281 Path: sc.Path, 1282 Expires: sc.Expires, 1283 Domain: sc.Domain, 1284 HTTPOnly: sc.HTTPOnly, 1285 Secure: sc.Secure, 1286 } 1287 } 1288 1289 func generateStatusMatchName(upstreamName string) string { 1290 return fmt.Sprintf("%s_match", upstreamName) 1291 } 1292 1293 func generateUpstreamStatusMatch(upstreamName string, status string) version2.StatusMatch { 1294 return version2.StatusMatch{ 1295 Name: generateStatusMatchName(upstreamName), 1296 Code: status, 1297 } 1298 } 1299 1300 // GenerateExternalNameSvcKey returns the key to identify an ExternalName service. 1301 func GenerateExternalNameSvcKey(namespace string, service string) string { 1302 return fmt.Sprintf("%v/%v", namespace, service) 1303 } 1304 1305 func generateLBMethod(method string, defaultMethod string) string { 1306 if method == "" { 1307 return defaultMethod 1308 } else if method == "round_robin" { 1309 return "" 1310 } 1311 return method 1312 } 1313 1314 func generateIntFromPointer(n *int, defaultN int) int { 1315 if n == nil { 1316 return defaultN 1317 } 1318 return *n 1319 } 1320 1321 func upstreamHasKeepalive(upstream conf_v1.Upstream, cfgParams *ConfigParams) bool { 1322 if upstream.Keepalive != nil { 1323 return *upstream.Keepalive != 0 1324 } 1325 return cfgParams.Keepalive != 0 1326 } 1327 1328 func generateRewrites(path string, proxy *conf_v1.ActionProxy, internal bool, originalPath string) []string { 1329 if proxy == nil || proxy.RewritePath == "" { 1330 return nil 1331 } 1332 1333 if originalPath != "" { 1334 path = originalPath 1335 } 1336 1337 isRegex := false 1338 if strings.HasPrefix(path, "~") { 1339 isRegex = true 1340 } 1341 1342 trimmedPath := strings.TrimPrefix(strings.TrimPrefix(path, "~"), "*") 1343 trimmedPath = strings.TrimSpace(trimmedPath) 1344 1345 var rewrites []string 1346 1347 if internal { 1348 // For internal locations (splits locations) only, recover the original request_uri. 1349 rewrites = append(rewrites, "^ $request_uri") 1350 } 1351 1352 if isRegex { 1353 rewrites = append(rewrites, fmt.Sprintf(`"^%v" "%v" break`, trimmedPath, proxy.RewritePath)) 1354 } else if internal { 1355 rewrites = append(rewrites, fmt.Sprintf(`"^%v(.*)$" "%v$1" break`, trimmedPath, proxy.RewritePath)) 1356 } 1357 1358 return rewrites 1359 } 1360 1361 func generateProxyPassRewrite(path string, proxy *conf_v1.ActionProxy, internal bool) string { 1362 if proxy == nil || internal { 1363 return "" 1364 } 1365 1366 if strings.HasPrefix(path, "/") || strings.HasPrefix(path, "=") { 1367 return proxy.RewritePath 1368 } 1369 1370 return "" 1371 } 1372 1373 func generateProxyPass(tlsEnabled bool, upstreamName string, internal bool, proxy *conf_v1.ActionProxy) string { 1374 proxyPass := fmt.Sprintf("%v://%v", generateProxyPassProtocol(tlsEnabled), upstreamName) 1375 1376 if internal && (proxy == nil || proxy.RewritePath == "") { 1377 return fmt.Sprintf("%v$request_uri", proxyPass) 1378 } 1379 1380 return proxyPass 1381 } 1382 1383 func generateProxyPassProtocol(enableTLS bool) string { 1384 if enableTLS { 1385 return "https" 1386 } 1387 return "http" 1388 } 1389 1390 func generateString(s string, defaultS string) string { 1391 if s == "" { 1392 return defaultS 1393 } 1394 return s 1395 } 1396 1397 func generateTime(value string) string { 1398 // it is expected that the value has been validated prior to call generateTime 1399 parsed, _ := ParseTime(value) 1400 return parsed 1401 } 1402 1403 func generateTimeWithDefault(value string, defaultValue string) string { 1404 if value == "" { 1405 // we don't transform the default value yet 1406 // this is done for backward compatibility, as the time values in the ConfigMap are not validated yet 1407 return defaultValue 1408 } 1409 1410 return generateTime(value) 1411 } 1412 1413 func generateSnippets(enableSnippets bool, snippet string, defaultSnippets []string) []string { 1414 if !enableSnippets || snippet == "" { 1415 return defaultSnippets 1416 } 1417 return strings.Split(snippet, "\n") 1418 } 1419 1420 func generateBuffers(s *conf_v1.UpstreamBuffers, defaultS string) string { 1421 if s == nil { 1422 return defaultS 1423 } 1424 return fmt.Sprintf("%v %v", s.Number, s.Size) 1425 } 1426 1427 func generateBool(s *bool, defaultS bool) bool { 1428 if s != nil { 1429 return *s 1430 } 1431 return defaultS 1432 } 1433 1434 func generatePath(path string) string { 1435 // Wrap the regular expression (if present) inside double quotes (") to avoid NGINX parsing errors 1436 if strings.HasPrefix(path, "~*") { 1437 return fmt.Sprintf(`~* "%v"`, strings.TrimPrefix(strings.TrimPrefix(path, "~*"), " ")) 1438 } 1439 if strings.HasPrefix(path, "~") { 1440 return fmt.Sprintf(`~ "%v"`, strings.TrimPrefix(strings.TrimPrefix(path, "~"), " ")) 1441 } 1442 1443 return path 1444 } 1445 1446 func generateReturnBlock(text string, code int, defaultCode int) *version2.Return { 1447 returnBlock := &version2.Return{ 1448 Code: defaultCode, 1449 Text: text, 1450 } 1451 1452 if code != 0 { 1453 returnBlock.Code = code 1454 } 1455 1456 return returnBlock 1457 } 1458 1459 func generateLocation(path string, upstreamName string, upstream conf_v1.Upstream, action *conf_v1.Action, 1460 cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, internal bool, errPageIndex int, proxySSLName string, 1461 originalPath string, locSnippets string, enableSnippets bool, retLocIndex int, isVSR bool, vsrName string, vsrNamespace string) (version2.Location, *version2.ReturnLocation) { 1462 locationSnippets := generateSnippets(enableSnippets, locSnippets, cfgParams.LocationSnippets) 1463 1464 if action.Redirect != nil { 1465 return generateLocationForRedirect(path, locationSnippets, action.Redirect), nil 1466 } 1467 1468 if action.Return != nil { 1469 return generateLocationForReturn(path, cfgParams.LocationSnippets, action.Return, retLocIndex) 1470 } 1471 1472 return generateLocationForProxying(path, upstreamName, upstream, cfgParams, errorPages, internal, 1473 errPageIndex, proxySSLName, action.Proxy, originalPath, locationSnippets, isVSR, vsrName, vsrNamespace), nil 1474 } 1475 1476 func generateProxySetHeaders(proxy *conf_v1.ActionProxy) []version2.Header { 1477 var headers []version2.Header 1478 1479 hasHostHeader := false 1480 1481 if proxy != nil && proxy.RequestHeaders != nil { 1482 for _, h := range proxy.RequestHeaders.Set { 1483 headers = append(headers, version2.Header{ 1484 Name: h.Name, 1485 Value: h.Value, 1486 }) 1487 1488 if strings.ToLower(h.Name) == "host" { 1489 hasHostHeader = true 1490 } 1491 } 1492 } 1493 1494 if !hasHostHeader { 1495 headers = append(headers, version2.Header{Name: "Host", Value: "$host"}) 1496 } 1497 1498 return headers 1499 } 1500 1501 func generateProxyPassRequestHeaders(proxy *conf_v1.ActionProxy) bool { 1502 if proxy == nil || proxy.RequestHeaders == nil { 1503 return true 1504 } 1505 1506 if proxy.RequestHeaders.Pass != nil { 1507 return *proxy.RequestHeaders.Pass 1508 } 1509 1510 return true 1511 } 1512 1513 func generateProxyHideHeaders(proxy *conf_v1.ActionProxy) []string { 1514 if proxy == nil || proxy.ResponseHeaders == nil { 1515 return nil 1516 } 1517 1518 return proxy.ResponseHeaders.Hide 1519 } 1520 1521 func generateProxyPassHeaders(proxy *conf_v1.ActionProxy) []string { 1522 if proxy == nil || proxy.ResponseHeaders == nil { 1523 return nil 1524 } 1525 1526 return proxy.ResponseHeaders.Pass 1527 } 1528 1529 func generateProxyIgnoreHeaders(proxy *conf_v1.ActionProxy) string { 1530 if proxy == nil || proxy.ResponseHeaders == nil { 1531 return "" 1532 } 1533 1534 return strings.Join(proxy.ResponseHeaders.Ignore, " ") 1535 } 1536 1537 func generateProxyAddHeaders(proxy *conf_v1.ActionProxy) []version2.AddHeader { 1538 if proxy == nil || proxy.ResponseHeaders == nil { 1539 return nil 1540 } 1541 1542 var addHeaders []version2.AddHeader 1543 for _, h := range proxy.ResponseHeaders.Add { 1544 addHeaders = append(addHeaders, version2.AddHeader{ 1545 Header: version2.Header{ 1546 Name: h.Name, 1547 Value: h.Value, 1548 }, 1549 Always: h.Always, 1550 }) 1551 } 1552 1553 return addHeaders 1554 } 1555 1556 func generateLocationForProxying(path string, upstreamName string, upstream conf_v1.Upstream, 1557 cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, internal bool, errPageIndex int, 1558 proxySSLName string, proxy *conf_v1.ActionProxy, originalPath string, locationSnippets []string, isVSR bool, vsrName string, vsrNamespace string) version2.Location { 1559 return version2.Location{ 1560 Path: generatePath(path), 1561 Internal: internal, 1562 Snippets: locationSnippets, 1563 ProxyConnectTimeout: generateTimeWithDefault(upstream.ProxyConnectTimeout, cfgParams.ProxyConnectTimeout), 1564 ProxyReadTimeout: generateTimeWithDefault(upstream.ProxyReadTimeout, cfgParams.ProxyReadTimeout), 1565 ProxySendTimeout: generateTimeWithDefault(upstream.ProxySendTimeout, cfgParams.ProxySendTimeout), 1566 ClientMaxBodySize: generateString(upstream.ClientMaxBodySize, cfgParams.ClientMaxBodySize), 1567 ProxyMaxTempFileSize: cfgParams.ProxyMaxTempFileSize, 1568 ProxyBuffering: generateBool(upstream.ProxyBuffering, cfgParams.ProxyBuffering), 1569 ProxyBuffers: generateBuffers(upstream.ProxyBuffers, cfgParams.ProxyBuffers), 1570 ProxyBufferSize: generateString(upstream.ProxyBufferSize, cfgParams.ProxyBufferSize), 1571 ProxyPass: generateProxyPass(upstream.TLS.Enable, upstreamName, internal, proxy), 1572 ProxyNextUpstream: generateString(upstream.ProxyNextUpstream, "error timeout"), 1573 ProxyNextUpstreamTimeout: generateTimeWithDefault(upstream.ProxyNextUpstreamTimeout, "0s"), 1574 ProxyNextUpstreamTries: upstream.ProxyNextUpstreamTries, 1575 ProxyInterceptErrors: generateProxyInterceptErrors(errorPages), 1576 ProxyPassRequestHeaders: generateProxyPassRequestHeaders(proxy), 1577 ProxySetHeaders: generateProxySetHeaders(proxy), 1578 ProxyHideHeaders: generateProxyHideHeaders(proxy), 1579 ProxyPassHeaders: generateProxyPassHeaders(proxy), 1580 ProxyIgnoreHeaders: generateProxyIgnoreHeaders(proxy), 1581 AddHeaders: generateProxyAddHeaders(proxy), 1582 ProxyPassRewrite: generateProxyPassRewrite(path, proxy, internal), 1583 Rewrites: generateRewrites(path, proxy, internal, originalPath), 1584 HasKeepalive: upstreamHasKeepalive(upstream, cfgParams), 1585 ErrorPages: generateErrorPages(errPageIndex, errorPages), 1586 ProxySSLName: proxySSLName, 1587 ServiceName: upstream.Service, 1588 IsVSR: isVSR, 1589 VSRName: vsrName, 1590 VSRNamespace: vsrNamespace, 1591 } 1592 } 1593 1594 func generateProxyInterceptErrors(errorPages []conf_v1.ErrorPage) bool { 1595 return len(errorPages) > 0 1596 } 1597 1598 func generateLocationForRedirect( 1599 path string, 1600 locationSnippets []string, 1601 redirect *conf_v1.ActionRedirect, 1602 ) version2.Location { 1603 code := redirect.Code 1604 if code == 0 { 1605 code = 301 1606 } 1607 1608 return version2.Location{ 1609 Path: path, 1610 Snippets: locationSnippets, 1611 ProxyInterceptErrors: true, 1612 InternalProxyPass: fmt.Sprintf("http://%s", nginx418Server), 1613 ErrorPages: []version2.ErrorPage{ 1614 { 1615 Name: redirect.URL, 1616 Codes: "418", 1617 ResponseCode: code, 1618 }, 1619 }, 1620 } 1621 } 1622 1623 func generateLocationForReturn(path string, locationSnippets []string, actionReturn *conf_v1.ActionReturn, 1624 retLocIndex int) (version2.Location, *version2.ReturnLocation) { 1625 defaultType := actionReturn.Type 1626 if defaultType == "" { 1627 defaultType = "text/plain" 1628 } 1629 code := actionReturn.Code 1630 if code == 0 { 1631 code = 200 1632 } 1633 1634 retLocName := fmt.Sprintf("@return_%d", retLocIndex) 1635 1636 return version2.Location{ 1637 Path: path, 1638 Snippets: locationSnippets, 1639 ProxyInterceptErrors: true, 1640 InternalProxyPass: fmt.Sprintf("http://%s", nginx418Server), 1641 ErrorPages: []version2.ErrorPage{ 1642 { 1643 Name: retLocName, 1644 Codes: "418", 1645 ResponseCode: code, 1646 }, 1647 }, 1648 }, 1649 &version2.ReturnLocation{ 1650 Name: retLocName, 1651 DefaultType: defaultType, 1652 Return: version2.Return{ 1653 Text: actionReturn.Body, 1654 }, 1655 } 1656 } 1657 1658 type routingCfg struct { 1659 Maps []version2.Map 1660 SplitClients []version2.SplitClient 1661 Locations []version2.Location 1662 InternalRedirectLocation version2.InternalRedirectLocation 1663 ReturnLocations []version2.ReturnLocation 1664 } 1665 1666 func generateSplits( 1667 splits []conf_v1.Split, 1668 upstreamNamer *upstreamNamer, 1669 crUpstreams map[string]conf_v1.Upstream, 1670 variableNamer *variableNamer, 1671 scIndex int, 1672 cfgParams *ConfigParams, 1673 errorPages []conf_v1.ErrorPage, 1674 errPageIndex int, 1675 originalPath string, 1676 locSnippets string, 1677 enableSnippets bool, 1678 retLocIndex int, 1679 isVSR bool, 1680 vsrName string, 1681 vsrNamespace string, 1682 ) (version2.SplitClient, []version2.Location, []version2.ReturnLocation) { 1683 var distributions []version2.Distribution 1684 1685 for i, s := range splits { 1686 d := version2.Distribution{ 1687 Weight: fmt.Sprintf("%d%%", s.Weight), 1688 Value: fmt.Sprintf("/%vsplits_%d_split_%d", internalLocationPrefix, scIndex, i), 1689 } 1690 distributions = append(distributions, d) 1691 } 1692 1693 splitClient := version2.SplitClient{ 1694 Source: "$request_id", 1695 Variable: variableNamer.GetNameForSplitClientVariable(scIndex), 1696 Distributions: distributions, 1697 } 1698 1699 var locations []version2.Location 1700 var returnLocations []version2.ReturnLocation 1701 1702 for i, s := range splits { 1703 path := fmt.Sprintf("/%vsplits_%d_split_%d", internalLocationPrefix, scIndex, i) 1704 upstreamName := upstreamNamer.GetNameForUpstreamFromAction(s.Action) 1705 upstream := crUpstreams[upstreamName] 1706 proxySSLName := generateProxySSLName(upstream.Service, upstreamNamer.namespace) 1707 newRetLocIndex := retLocIndex + len(returnLocations) 1708 loc, returnLoc := generateLocation(path, upstreamName, upstream, s.Action, cfgParams, errorPages, true, 1709 errPageIndex, proxySSLName, originalPath, locSnippets, enableSnippets, newRetLocIndex, isVSR, vsrName, vsrNamespace) 1710 locations = append(locations, loc) 1711 if returnLoc != nil { 1712 returnLocations = append(returnLocations, *returnLoc) 1713 } 1714 } 1715 1716 return splitClient, locations, returnLocations 1717 } 1718 1719 func generateDefaultSplitsConfig( 1720 route conf_v1.Route, 1721 upstreamNamer *upstreamNamer, 1722 crUpstreams map[string]conf_v1.Upstream, 1723 variableNamer *variableNamer, 1724 scIndex int, 1725 cfgParams *ConfigParams, 1726 errorPages []conf_v1.ErrorPage, 1727 errPageIndex int, 1728 originalPath string, 1729 locSnippets string, 1730 enableSnippets bool, 1731 retLocIndex int, 1732 isVSR bool, 1733 vsrName string, 1734 vsrNamespace string, 1735 ) routingCfg { 1736 sc, locs, returnLocs := generateSplits(route.Splits, upstreamNamer, crUpstreams, variableNamer, scIndex, cfgParams, 1737 errorPages, errPageIndex, originalPath, locSnippets, enableSnippets, retLocIndex, isVSR, vsrName, vsrNamespace) 1738 1739 splitClientVarName := variableNamer.GetNameForSplitClientVariable(scIndex) 1740 1741 irl := version2.InternalRedirectLocation{ 1742 Path: route.Path, 1743 Destination: splitClientVarName, 1744 } 1745 1746 return routingCfg{ 1747 SplitClients: []version2.SplitClient{sc}, 1748 Locations: locs, 1749 InternalRedirectLocation: irl, 1750 ReturnLocations: returnLocs, 1751 } 1752 } 1753 1754 func generateMatchesConfig(route conf_v1.Route, upstreamNamer *upstreamNamer, crUpstreams map[string]conf_v1.Upstream, 1755 variableNamer *variableNamer, index int, scIndex int, cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, 1756 errPageIndex int, locSnippets string, enableSnippets bool, retLocIndex int, isVSR bool, vsrName string, vsrNamespace string) routingCfg { 1757 // Generate maps 1758 var maps []version2.Map 1759 1760 for i, m := range route.Matches { 1761 for j, c := range m.Conditions { 1762 source := getNameForSourceForMatchesRouteMapFromCondition(c) 1763 variable := variableNamer.GetNameForVariableForMatchesRouteMap(index, i, j) 1764 successfulResult := "1" 1765 if j < len(m.Conditions)-1 { 1766 successfulResult = variableNamer.GetNameForVariableForMatchesRouteMap(index, i, j+1) 1767 } 1768 1769 params := generateParametersForMatchesRouteMap(c.Value, successfulResult) 1770 1771 matchMap := version2.Map{ 1772 Source: source, 1773 Variable: variable, 1774 Parameters: params, 1775 } 1776 maps = append(maps, matchMap) 1777 } 1778 } 1779 1780 scLocalIndex := 0 1781 1782 // Generate the main map 1783 source := "" 1784 var params []version2.Parameter 1785 for i, m := range route.Matches { 1786 source += variableNamer.GetNameForVariableForMatchesRouteMap(index, i, 0) 1787 1788 v := fmt.Sprintf("~^%s1", strings.Repeat("0", i)) 1789 r := fmt.Sprintf("/%vmatches_%d_match_%d", internalLocationPrefix, index, i) 1790 if len(m.Splits) > 0 { 1791 r = variableNamer.GetNameForSplitClientVariable(scIndex + scLocalIndex) 1792 scLocalIndex++ 1793 } 1794 1795 p := version2.Parameter{ 1796 Value: v, 1797 Result: r, 1798 } 1799 params = append(params, p) 1800 } 1801 1802 defaultResult := fmt.Sprintf("/%vmatches_%d_default", internalLocationPrefix, index) 1803 if len(route.Splits) > 0 { 1804 defaultResult = variableNamer.GetNameForSplitClientVariable(scIndex + scLocalIndex) 1805 } 1806 1807 defaultParam := version2.Parameter{ 1808 Value: "default", 1809 Result: defaultResult, 1810 } 1811 params = append(params, defaultParam) 1812 1813 variable := variableNamer.GetNameForVariableForMatchesRouteMainMap(index) 1814 1815 mainMap := version2.Map{ 1816 Source: source, 1817 Variable: variable, 1818 Parameters: params, 1819 } 1820 maps = append(maps, mainMap) 1821 1822 // Generate locations for each match and split client 1823 var locations []version2.Location 1824 var returnLocations []version2.ReturnLocation 1825 var splitClients []version2.SplitClient 1826 scLocalIndex = 0 1827 1828 for i, m := range route.Matches { 1829 if len(m.Splits) > 0 { 1830 newRetLocIndex := retLocIndex + len(returnLocations) 1831 sc, locs, returnLocs := generateSplits( 1832 m.Splits, 1833 upstreamNamer, 1834 crUpstreams, 1835 variableNamer, 1836 scIndex+scLocalIndex, 1837 cfgParams, 1838 errorPages, 1839 errPageIndex, 1840 route.Path, 1841 locSnippets, 1842 enableSnippets, 1843 newRetLocIndex, 1844 isVSR, 1845 vsrName, 1846 vsrNamespace, 1847 ) 1848 scLocalIndex++ 1849 splitClients = append(splitClients, sc) 1850 locations = append(locations, locs...) 1851 returnLocations = append(returnLocations, returnLocs...) 1852 } else { 1853 path := fmt.Sprintf("/%vmatches_%d_match_%d", internalLocationPrefix, index, i) 1854 upstreamName := upstreamNamer.GetNameForUpstreamFromAction(m.Action) 1855 upstream := crUpstreams[upstreamName] 1856 proxySSLName := generateProxySSLName(upstream.Service, upstreamNamer.namespace) 1857 newRetLocIndex := retLocIndex + len(returnLocations) 1858 loc, returnLoc := generateLocation(path, upstreamName, upstream, m.Action, cfgParams, errorPages, true, 1859 errPageIndex, proxySSLName, route.Path, locSnippets, enableSnippets, newRetLocIndex, isVSR, vsrName, vsrNamespace) 1860 locations = append(locations, loc) 1861 if returnLoc != nil { 1862 returnLocations = append(returnLocations, *returnLoc) 1863 } 1864 } 1865 } 1866 1867 // Generate default splits or default action 1868 if len(route.Splits) > 0 { 1869 newRetLocIndex := retLocIndex + len(returnLocations) 1870 sc, locs, returnLocs := generateSplits( 1871 route.Splits, 1872 upstreamNamer, 1873 crUpstreams, 1874 variableNamer, 1875 scIndex+scLocalIndex, 1876 cfgParams, 1877 errorPages, 1878 errPageIndex, 1879 route.Path, 1880 locSnippets, 1881 enableSnippets, 1882 newRetLocIndex, 1883 isVSR, 1884 vsrName, 1885 vsrNamespace, 1886 ) 1887 splitClients = append(splitClients, sc) 1888 locations = append(locations, locs...) 1889 returnLocations = append(returnLocations, returnLocs...) 1890 } else { 1891 path := fmt.Sprintf("/%vmatches_%d_default", internalLocationPrefix, index) 1892 upstreamName := upstreamNamer.GetNameForUpstreamFromAction(route.Action) 1893 upstream := crUpstreams[upstreamName] 1894 proxySSLName := generateProxySSLName(upstream.Service, upstreamNamer.namespace) 1895 newRetLocIndex := retLocIndex + len(returnLocations) 1896 loc, returnLoc := generateLocation(path, upstreamName, upstream, route.Action, cfgParams, errorPages, true, 1897 errPageIndex, proxySSLName, route.Path, locSnippets, enableSnippets, newRetLocIndex, isVSR, vsrName, vsrNamespace) 1898 locations = append(locations, loc) 1899 if returnLoc != nil { 1900 returnLocations = append(returnLocations, *returnLoc) 1901 } 1902 } 1903 1904 // Generate an InternalRedirectLocation to the location defined by the main map variable 1905 irl := version2.InternalRedirectLocation{ 1906 Path: route.Path, 1907 Destination: variable, 1908 } 1909 1910 return routingCfg{ 1911 Maps: maps, 1912 Locations: locations, 1913 InternalRedirectLocation: irl, 1914 SplitClients: splitClients, 1915 ReturnLocations: returnLocations, 1916 } 1917 } 1918 1919 var specialMapParameters = map[string]bool{ 1920 "default": true, 1921 "hostnames": true, 1922 "include": true, 1923 "volatile": true, 1924 } 1925 1926 func generateValueForMatchesRouteMap(matchedValue string) (value string, isNegative bool) { 1927 if len(matchedValue) == 0 { 1928 return `""`, false 1929 } 1930 1931 if matchedValue[0] == '!' { 1932 isNegative = true 1933 matchedValue = matchedValue[1:] 1934 } 1935 1936 if _, exists := specialMapParameters[matchedValue]; exists { 1937 return `\` + matchedValue, isNegative 1938 } 1939 1940 return fmt.Sprintf(`"%s"`, matchedValue), isNegative 1941 } 1942 1943 func generateParametersForMatchesRouteMap(matchedValue string, successfulResult string) []version2.Parameter { 1944 value, isNegative := generateValueForMatchesRouteMap(matchedValue) 1945 1946 valueResult := successfulResult 1947 defaultResult := "0" 1948 if isNegative { 1949 valueResult = "0" 1950 defaultResult = successfulResult 1951 } 1952 1953 params := []version2.Parameter{ 1954 { 1955 Value: value, 1956 Result: valueResult, 1957 }, 1958 { 1959 Value: "default", 1960 Result: defaultResult, 1961 }, 1962 } 1963 1964 return params 1965 } 1966 1967 func getNameForSourceForMatchesRouteMapFromCondition(condition conf_v1.Condition) string { 1968 if condition.Header != "" { 1969 return fmt.Sprintf("$http_%s", strings.ReplaceAll(condition.Header, "-", "_")) 1970 } 1971 1972 if condition.Cookie != "" { 1973 return fmt.Sprintf("$cookie_%s", condition.Cookie) 1974 } 1975 1976 if condition.Argument != "" { 1977 return fmt.Sprintf("$arg_%s", condition.Argument) 1978 } 1979 1980 return condition.Variable 1981 } 1982 1983 func (vsc *virtualServerConfigurator) generateSSLConfig(owner runtime.Object, tls *conf_v1.TLS, namespace string, 1984 secretRefs map[string]*secrets.SecretReference, cfgParams *ConfigParams) *version2.SSL { 1985 if tls == nil { 1986 return nil 1987 } 1988 1989 if tls.Secret == "" { 1990 return nil 1991 } 1992 1993 secretRef := secretRefs[fmt.Sprintf("%s/%s", namespace, tls.Secret)] 1994 var secretType api_v1.SecretType 1995 if secretRef.Secret != nil { 1996 secretType = secretRef.Secret.Type 1997 } 1998 var name string 1999 var rejectHandshake bool 2000 if secretType != "" && secretType != api_v1.SecretTypeTLS { 2001 rejectHandshake = true 2002 vsc.addWarningf(owner, "TLS secret %s is of a wrong type '%s', must be '%s'", tls.Secret, secretType, api_v1.SecretTypeTLS) 2003 } else if secretRef.Error != nil { 2004 rejectHandshake = true 2005 vsc.addWarningf(owner, "TLS secret %s is invalid: %v", tls.Secret, secretRef.Error) 2006 } else { 2007 name = secretRef.Path 2008 } 2009 2010 ssl := version2.SSL{ 2011 HTTP2: cfgParams.HTTP2, 2012 Certificate: name, 2013 CertificateKey: name, 2014 RejectHandshake: rejectHandshake, 2015 } 2016 2017 return &ssl 2018 } 2019 2020 func generateTLSRedirectConfig(tls *conf_v1.TLS) *version2.TLSRedirect { 2021 if tls == nil || tls.Redirect == nil || !tls.Redirect.Enable { 2022 return nil 2023 } 2024 2025 redirect := &version2.TLSRedirect{ 2026 Code: generateIntFromPointer(tls.Redirect.Code, 301), 2027 BasedOn: generateTLSRedirectBasedOn(tls.Redirect.BasedOn), 2028 } 2029 2030 return redirect 2031 } 2032 2033 func generateTLSRedirectBasedOn(basedOn string) string { 2034 if basedOn == "x-forwarded-proto" { 2035 return "$http_x_forwarded_proto" 2036 } 2037 return "$scheme" 2038 } 2039 2040 func createEndpointsFromUpstream(upstream version2.Upstream) []string { 2041 var endpoints []string 2042 2043 for _, server := range upstream.Servers { 2044 endpoints = append(endpoints, server.Address) 2045 } 2046 2047 return endpoints 2048 } 2049 2050 func createUpstreamsForPlus( 2051 virtualServerEx *VirtualServerEx, 2052 baseCfgParams *ConfigParams, 2053 staticParams *StaticConfigParams, 2054 ) []version2.Upstream { 2055 var upstreams []version2.Upstream 2056 2057 isPlus := true 2058 upstreamNamer := newUpstreamNamerForVirtualServer(virtualServerEx.VirtualServer) 2059 vsc := newVirtualServerConfigurator(baseCfgParams, isPlus, false, staticParams) 2060 2061 for _, u := range virtualServerEx.VirtualServer.Spec.Upstreams { 2062 isExternalNameSvc := virtualServerEx.ExternalNameSvcs[GenerateExternalNameSvcKey(virtualServerEx.VirtualServer.Namespace, u.Service)] 2063 if isExternalNameSvc { 2064 glog.V(3).Infof("Service %s is Type ExternalName, skipping NGINX Plus endpoints update via API", u.Service) 2065 continue 2066 } 2067 2068 upstreamName := upstreamNamer.GetNameForUpstream(u.Name) 2069 upstreamNamespace := virtualServerEx.VirtualServer.Namespace 2070 2071 endpointsKey := GenerateEndpointsKey(upstreamNamespace, u.Service, u.Subselector, u.Port) 2072 endpoints := virtualServerEx.Endpoints[endpointsKey] 2073 2074 ups := vsc.generateUpstream(virtualServerEx.VirtualServer, upstreamName, u, isExternalNameSvc, endpoints) 2075 upstreams = append(upstreams, ups) 2076 } 2077 2078 for _, vsr := range virtualServerEx.VirtualServerRoutes { 2079 upstreamNamer = newUpstreamNamerForVirtualServerRoute(virtualServerEx.VirtualServer, vsr) 2080 for _, u := range vsr.Spec.Upstreams { 2081 isExternalNameSvc := virtualServerEx.ExternalNameSvcs[GenerateExternalNameSvcKey(vsr.Namespace, u.Service)] 2082 if isExternalNameSvc { 2083 glog.V( 2084 3, 2085 ).Infof( 2086 "Service %s is Type ExternalName, skipping NGINX Plus endpoints update via API", 2087 u.Service, 2088 ) 2089 continue 2090 } 2091 2092 upstreamName := upstreamNamer.GetNameForUpstream(u.Name) 2093 upstreamNamespace := vsr.Namespace 2094 2095 endpointsKey := GenerateEndpointsKey(upstreamNamespace, u.Service, u.Subselector, u.Port) 2096 endpoints := virtualServerEx.Endpoints[endpointsKey] 2097 2098 ups := vsc.generateUpstream(vsr, upstreamName, u, isExternalNameSvc, endpoints) 2099 upstreams = append(upstreams, ups) 2100 } 2101 } 2102 2103 return upstreams 2104 } 2105 2106 func createUpstreamServersConfigForPlus(upstream version2.Upstream) nginx.ServerConfig { 2107 if len(upstream.Servers) == 0 { 2108 return nginx.ServerConfig{} 2109 } 2110 return nginx.ServerConfig{ 2111 MaxFails: upstream.MaxFails, 2112 FailTimeout: upstream.FailTimeout, 2113 MaxConns: upstream.MaxConns, 2114 SlowStart: upstream.SlowStart, 2115 } 2116 } 2117 2118 func generateQueueForPlus(upstreamQueue *conf_v1.UpstreamQueue, defaultTimeout string) *version2.Queue { 2119 if upstreamQueue == nil { 2120 return nil 2121 } 2122 2123 return &version2.Queue{ 2124 Size: upstreamQueue.Size, 2125 Timeout: generateTimeWithDefault(upstreamQueue.Timeout, defaultTimeout), 2126 } 2127 } 2128 2129 func generateErrorPageName(errPageIndex int, index int) string { 2130 return fmt.Sprintf("@error_page_%v_%v", errPageIndex, index) 2131 } 2132 2133 func generateErrorPageCodes(codes []int) string { 2134 var c []string 2135 for _, code := range codes { 2136 c = append(c, strconv.Itoa(code)) 2137 } 2138 return strings.Join(c, " ") 2139 } 2140 2141 func generateErrorPages(errPageIndex int, errorPages []conf_v1.ErrorPage) []version2.ErrorPage { 2142 var ePages []version2.ErrorPage 2143 2144 for i, e := range errorPages { 2145 var code int 2146 var name string 2147 2148 if e.Redirect != nil { 2149 code = 301 2150 if e.Redirect.Code != 0 { 2151 code = e.Redirect.Code 2152 } 2153 name = e.Redirect.URL 2154 } else { 2155 code = e.Return.Code 2156 name = generateErrorPageName(errPageIndex, i) 2157 } 2158 2159 ep := version2.ErrorPage{ 2160 Name: name, 2161 Codes: generateErrorPageCodes(e.Codes), 2162 ResponseCode: code, 2163 } 2164 2165 ePages = append(ePages, ep) 2166 } 2167 2168 return ePages 2169 } 2170 2171 func generateErrorPageLocations(errPageIndex int, errorPages []conf_v1.ErrorPage) []version2.ErrorPageLocation { 2172 var errorPageLocations []version2.ErrorPageLocation 2173 for i, e := range errorPages { 2174 if e.Redirect != nil { 2175 // Redirects are handled in the error_page of the location directly, no need for a named location. 2176 continue 2177 } 2178 2179 var headers []version2.Header 2180 2181 for _, h := range e.Return.Headers { 2182 headers = append(headers, version2.Header{ 2183 Name: h.Name, 2184 Value: h.Value, 2185 }) 2186 } 2187 2188 defaultType := "text/html" 2189 if e.Return.Type != "" { 2190 defaultType = e.Return.Type 2191 } 2192 2193 epl := version2.ErrorPageLocation{ 2194 Name: generateErrorPageName(errPageIndex, i), 2195 DefaultType: defaultType, 2196 Return: generateReturnBlock(e.Return.Body, 0, 0), 2197 Headers: headers, 2198 } 2199 2200 errorPageLocations = append(errorPageLocations, epl) 2201 } 2202 2203 return errorPageLocations 2204 } 2205 2206 func generateProxySSLName(svcName, ns string) string { 2207 return fmt.Sprintf("%s.%s.svc", svcName, ns) 2208 } 2209 2210 func isTLSEnabled(u conf_v1.Upstream, spiffeCerts bool) bool { 2211 return u.TLS.Enable || spiffeCerts 2212 }