github.com/nginxinc/kubernetes-ingress@v1.12.5/internal/configs/ingress.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/golang/glog"
     9  	"github.com/nginxinc/kubernetes-ingress/internal/k8s/secrets"
    10  	api_v1 "k8s.io/api/core/v1"
    11  	networking "k8s.io/api/networking/v1beta1"
    12  	"k8s.io/apimachinery/pkg/runtime"
    13  
    14  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    15  
    16  	"github.com/nginxinc/kubernetes-ingress/internal/configs/version1"
    17  )
    18  
    19  const emptyHost = ""
    20  
    21  // AppProtectResources holds namespace names of App Protect resources relavant to an Ingress
    22  type AppProtectResources struct {
    23  	AppProtectPolicy   string
    24  	AppProtectLogconfs []string
    25  }
    26  
    27  // AppProtectLog holds a single pair of log config and log destination
    28  type AppProtectLog struct {
    29  	LogConf *unstructured.Unstructured
    30  	Dest    string
    31  }
    32  
    33  // IngressEx holds an Ingress along with the resources that are referenced in this Ingress.
    34  type IngressEx struct {
    35  	Ingress          *networking.Ingress
    36  	Endpoints        map[string][]string
    37  	HealthChecks     map[string]*api_v1.Probe
    38  	ExternalNameSvcs map[string]bool
    39  	PodsByIP         map[string]PodInfo
    40  	ValidHosts       map[string]bool
    41  	ValidMinionPaths map[string]bool
    42  	AppProtectPolicy *unstructured.Unstructured
    43  	AppProtectLogs   []AppProtectLog
    44  	SecretRefs       map[string]*secrets.SecretReference
    45  }
    46  
    47  // JWTKey represents a secret that holds JSON Web Key.
    48  type JWTKey struct {
    49  	Name   string
    50  	Secret *api_v1.Secret
    51  }
    52  
    53  func (ingEx *IngressEx) String() string {
    54  	if ingEx.Ingress == nil {
    55  		return "IngressEx has no Ingress"
    56  	}
    57  
    58  	return fmt.Sprintf("%v/%v", ingEx.Ingress.Namespace, ingEx.Ingress.Name)
    59  }
    60  
    61  // MergeableIngresses is a mergeable ingress of a master and minions.
    62  type MergeableIngresses struct {
    63  	Master  *IngressEx
    64  	Minions []*IngressEx
    65  }
    66  
    67  func generateNginxCfg(ingEx *IngressEx, apResources AppProtectResources, isMinion bool, baseCfgParams *ConfigParams, isPlus bool,
    68  	isResolverConfigured bool, staticParams *StaticConfigParams, isWildcardEnabled bool) (version1.IngressNginxConfig, Warnings) {
    69  	hasAppProtect := staticParams.MainAppProtectLoadModule
    70  	cfgParams := parseAnnotations(ingEx, baseCfgParams, isPlus, hasAppProtect, staticParams.EnableInternalRoutes)
    71  
    72  	wsServices := getWebsocketServices(ingEx)
    73  	spServices := getSessionPersistenceServices(ingEx)
    74  	rewrites := getRewrites(ingEx)
    75  	sslServices := getSSLServices(ingEx)
    76  	grpcServices := getGrpcServices(ingEx)
    77  
    78  	upstreams := make(map[string]version1.Upstream)
    79  	healthChecks := make(map[string]version1.HealthCheck)
    80  
    81  	// HTTP2 is required for gRPC to function
    82  	if len(grpcServices) > 0 && !cfgParams.HTTP2 {
    83  		glog.Errorf("Ingress %s/%s: annotation nginx.org/grpc-services requires HTTP2, ignoring", ingEx.Ingress.Namespace, ingEx.Ingress.Name)
    84  		grpcServices = make(map[string]bool)
    85  	}
    86  
    87  	if ingEx.Ingress.Spec.Backend != nil {
    88  		name := getNameForUpstream(ingEx.Ingress, emptyHost, ingEx.Ingress.Spec.Backend)
    89  		upstream := createUpstream(ingEx, name, ingEx.Ingress.Spec.Backend, spServices[ingEx.Ingress.Spec.Backend.ServiceName], &cfgParams,
    90  			isPlus, isResolverConfigured, staticParams.EnableLatencyMetrics)
    91  		upstreams[name] = upstream
    92  
    93  		if cfgParams.HealthCheckEnabled {
    94  			if hc, exists := ingEx.HealthChecks[ingEx.Ingress.Spec.Backend.ServiceName+ingEx.Ingress.Spec.Backend.ServicePort.String()]; exists {
    95  				healthChecks[name] = createHealthCheck(hc, name, &cfgParams)
    96  			}
    97  		}
    98  	}
    99  
   100  	allWarnings := newWarnings()
   101  
   102  	var servers []version1.Server
   103  
   104  	for _, rule := range ingEx.Ingress.Spec.Rules {
   105  		// skipping invalid hosts
   106  		if !ingEx.ValidHosts[rule.Host] {
   107  			continue
   108  		}
   109  
   110  		httpIngressRuleValue := rule.HTTP
   111  
   112  		if httpIngressRuleValue == nil {
   113  			// the code in this loop expects non-nil
   114  			httpIngressRuleValue = &networking.HTTPIngressRuleValue{}
   115  		}
   116  
   117  		serverName := rule.Host
   118  
   119  		statusZone := rule.Host
   120  
   121  		server := version1.Server{
   122  			Name:                  serverName,
   123  			ServerTokens:          cfgParams.ServerTokens,
   124  			HTTP2:                 cfgParams.HTTP2,
   125  			RedirectToHTTPS:       cfgParams.RedirectToHTTPS,
   126  			SSLRedirect:           cfgParams.SSLRedirect,
   127  			ProxyProtocol:         cfgParams.ProxyProtocol,
   128  			HSTS:                  cfgParams.HSTS,
   129  			HSTSMaxAge:            cfgParams.HSTSMaxAge,
   130  			HSTSIncludeSubdomains: cfgParams.HSTSIncludeSubdomains,
   131  			HSTSBehindProxy:       cfgParams.HSTSBehindProxy,
   132  			StatusZone:            statusZone,
   133  			RealIPHeader:          cfgParams.RealIPHeader,
   134  			SetRealIPFrom:         cfgParams.SetRealIPFrom,
   135  			RealIPRecursive:       cfgParams.RealIPRecursive,
   136  			ProxyHideHeaders:      cfgParams.ProxyHideHeaders,
   137  			ProxyPassHeaders:      cfgParams.ProxyPassHeaders,
   138  			ServerSnippets:        cfgParams.ServerSnippets,
   139  			Ports:                 cfgParams.Ports,
   140  			SSLPorts:              cfgParams.SSLPorts,
   141  			TLSPassthrough:        staticParams.TLSPassthrough,
   142  			AppProtectEnable:      cfgParams.AppProtectEnable,
   143  			AppProtectLogEnable:   cfgParams.AppProtectLogEnable,
   144  			SpiffeCerts:           cfgParams.SpiffeServerCerts,
   145  		}
   146  
   147  		warnings := addSSLConfig(&server, ingEx.Ingress, rule.Host, ingEx.Ingress.Spec.TLS, ingEx.SecretRefs, isWildcardEnabled)
   148  		allWarnings.Add(warnings)
   149  
   150  		if hasAppProtect {
   151  			server.AppProtectPolicy = apResources.AppProtectPolicy
   152  			server.AppProtectLogConfs = apResources.AppProtectLogconfs
   153  		}
   154  
   155  		if !isMinion && cfgParams.JWTKey != "" {
   156  			jwtAuth, redirectLoc, warnings := generateJWTConfig(ingEx.Ingress, ingEx.SecretRefs, &cfgParams, getNameForRedirectLocation(ingEx.Ingress))
   157  			server.JWTAuth = jwtAuth
   158  			if redirectLoc != nil {
   159  				server.JWTRedirectLocations = append(server.JWTRedirectLocations, *redirectLoc)
   160  			}
   161  			allWarnings.Add(warnings)
   162  		}
   163  
   164  		var locations []version1.Location
   165  		healthChecks := make(map[string]version1.HealthCheck)
   166  
   167  		rootLocation := false
   168  
   169  		grpcOnly := true
   170  		if len(grpcServices) > 0 {
   171  			for _, path := range httpIngressRuleValue.Paths {
   172  				if _, exists := grpcServices[path.Backend.ServiceName]; !exists {
   173  					grpcOnly = false
   174  					break
   175  				}
   176  			}
   177  		} else {
   178  			grpcOnly = false
   179  		}
   180  
   181  		for _, path := range httpIngressRuleValue.Paths {
   182  			// skip invalid paths for minions
   183  			if isMinion && !ingEx.ValidMinionPaths[path.Path] {
   184  				continue
   185  			}
   186  
   187  			upsName := getNameForUpstream(ingEx.Ingress, rule.Host, &path.Backend)
   188  
   189  			if cfgParams.HealthCheckEnabled {
   190  				if hc, exists := ingEx.HealthChecks[path.Backend.ServiceName+path.Backend.ServicePort.String()]; exists {
   191  					healthChecks[upsName] = createHealthCheck(hc, upsName, &cfgParams)
   192  				}
   193  			}
   194  
   195  			if _, exists := upstreams[upsName]; !exists {
   196  				upstream := createUpstream(ingEx, upsName, &path.Backend, spServices[path.Backend.ServiceName], &cfgParams, isPlus, isResolverConfigured, staticParams.EnableLatencyMetrics)
   197  				upstreams[upsName] = upstream
   198  			}
   199  
   200  			ssl := isSSLEnabled(sslServices[path.Backend.ServiceName], cfgParams, staticParams)
   201  			proxySSLName := generateProxySSLName(path.Backend.ServiceName, ingEx.Ingress.Namespace)
   202  			loc := createLocation(pathOrDefault(path.Path), upstreams[upsName], &cfgParams, wsServices[path.Backend.ServiceName], rewrites[path.Backend.ServiceName],
   203  				ssl, grpcServices[path.Backend.ServiceName], proxySSLName, path.PathType, path.Backend.ServiceName)
   204  
   205  			if isMinion && cfgParams.JWTKey != "" {
   206  				jwtAuth, redirectLoc, warnings := generateJWTConfig(ingEx.Ingress, ingEx.SecretRefs, &cfgParams, getNameForRedirectLocation(ingEx.Ingress))
   207  				loc.JWTAuth = jwtAuth
   208  				if redirectLoc != nil {
   209  					server.JWTRedirectLocations = append(server.JWTRedirectLocations, *redirectLoc)
   210  				}
   211  				allWarnings.Add(warnings)
   212  			}
   213  
   214  			locations = append(locations, loc)
   215  
   216  			if loc.Path == "/" {
   217  				rootLocation = true
   218  			}
   219  		}
   220  
   221  		if !rootLocation && ingEx.Ingress.Spec.Backend != nil {
   222  			upsName := getNameForUpstream(ingEx.Ingress, emptyHost, ingEx.Ingress.Spec.Backend)
   223  			ssl := isSSLEnabled(sslServices[ingEx.Ingress.Spec.Backend.ServiceName], cfgParams, staticParams)
   224  			proxySSLName := generateProxySSLName(ingEx.Ingress.Spec.Backend.ServiceName, ingEx.Ingress.Namespace)
   225  			pathtype := networking.PathTypePrefix
   226  
   227  			loc := createLocation(pathOrDefault("/"), upstreams[upsName], &cfgParams, wsServices[ingEx.Ingress.Spec.Backend.ServiceName], rewrites[ingEx.Ingress.Spec.Backend.ServiceName],
   228  				ssl, grpcServices[ingEx.Ingress.Spec.Backend.ServiceName], proxySSLName, &pathtype, ingEx.Ingress.Spec.Backend.ServiceName)
   229  			locations = append(locations, loc)
   230  
   231  			if cfgParams.HealthCheckEnabled {
   232  				if hc, exists := ingEx.HealthChecks[ingEx.Ingress.Spec.Backend.ServiceName+ingEx.Ingress.Spec.Backend.ServicePort.String()]; exists {
   233  					healthChecks[upsName] = createHealthCheck(hc, upsName, &cfgParams)
   234  				}
   235  			}
   236  
   237  			if _, exists := grpcServices[ingEx.Ingress.Spec.Backend.ServiceName]; !exists {
   238  				grpcOnly = false
   239  			}
   240  		}
   241  
   242  		server.Locations = locations
   243  		server.HealthChecks = healthChecks
   244  		server.GRPCOnly = grpcOnly
   245  
   246  		servers = append(servers, server)
   247  	}
   248  
   249  	var keepalive string
   250  	if cfgParams.Keepalive > 0 {
   251  		keepalive = fmt.Sprint(cfgParams.Keepalive)
   252  	}
   253  
   254  	return version1.IngressNginxConfig{
   255  		Upstreams: upstreamMapToSlice(upstreams),
   256  		Servers:   servers,
   257  		Keepalive: keepalive,
   258  		Ingress: version1.Ingress{
   259  			Name:        ingEx.Ingress.Name,
   260  			Namespace:   ingEx.Ingress.Namespace,
   261  			Annotations: ingEx.Ingress.Annotations,
   262  		},
   263  		SpiffeClientCerts: staticParams.NginxServiceMesh && !cfgParams.SpiffeServerCerts,
   264  	}, allWarnings
   265  }
   266  
   267  func generateJWTConfig(owner runtime.Object, secretRefs map[string]*secrets.SecretReference, cfgParams *ConfigParams,
   268  	redirectLocationName string) (*version1.JWTAuth, *version1.JWTRedirectLocation, Warnings) {
   269  	warnings := newWarnings()
   270  
   271  	secretRef := secretRefs[cfgParams.JWTKey]
   272  	var secretType api_v1.SecretType
   273  	if secretRef.Secret != nil {
   274  		secretType = secretRef.Secret.Type
   275  	}
   276  	if secretType != "" && secretType != secrets.SecretTypeJWK {
   277  		warnings.AddWarningf(owner, "JWK secret %s is of a wrong type '%s', must be '%s'", cfgParams.JWTKey, secretType, secrets.SecretTypeJWK)
   278  	} else if secretRef.Error != nil {
   279  		warnings.AddWarningf(owner, "JWK secret %s is invalid: %v", cfgParams.JWTKey, secretRef.Error)
   280  	}
   281  
   282  	// Key is configured for all cases, including when the secret is (1) invalid or (2) of a wrong type.
   283  	// For (1) and (2), NGINX Plus will reject such a key at runtime and return 500 to clients.
   284  	jwtAuth := &version1.JWTAuth{
   285  		Key:   secretRef.Path,
   286  		Realm: cfgParams.JWTRealm,
   287  		Token: cfgParams.JWTToken,
   288  	}
   289  
   290  	var redirectLocation *version1.JWTRedirectLocation
   291  
   292  	if cfgParams.JWTLoginURL != "" {
   293  		jwtAuth.RedirectLocationName = redirectLocationName
   294  		redirectLocation = &version1.JWTRedirectLocation{
   295  			Name:     redirectLocationName,
   296  			LoginURL: cfgParams.JWTLoginURL,
   297  		}
   298  	}
   299  
   300  	return jwtAuth, redirectLocation, warnings
   301  }
   302  
   303  func addSSLConfig(server *version1.Server, owner runtime.Object, host string, ingressTLS []networking.IngressTLS,
   304  	secretRefs map[string]*secrets.SecretReference, isWildcardEnabled bool) Warnings {
   305  	warnings := newWarnings()
   306  
   307  	var tlsEnabled bool
   308  	var tlsSecret string
   309  
   310  	for _, tls := range ingressTLS {
   311  		for _, h := range tls.Hosts {
   312  			if h == host {
   313  				tlsEnabled = true
   314  				tlsSecret = tls.SecretName
   315  				break
   316  			}
   317  		}
   318  	}
   319  
   320  	if !tlsEnabled {
   321  		return warnings
   322  	}
   323  
   324  	var pemFile string
   325  	var rejectHandshake bool
   326  
   327  	if tlsSecret != "" {
   328  		secretRef := secretRefs[tlsSecret]
   329  		var secretType api_v1.SecretType
   330  		if secretRef.Secret != nil {
   331  			secretType = secretRef.Secret.Type
   332  		}
   333  		if secretType != "" && secretType != api_v1.SecretTypeTLS {
   334  			rejectHandshake = true
   335  			warnings.AddWarningf(owner, "TLS secret %s is of a wrong type '%s', must be '%s'", tlsSecret, secretType, api_v1.SecretTypeTLS)
   336  		} else if secretRef.Error != nil {
   337  			rejectHandshake = true
   338  			warnings.AddWarningf(owner, "TLS secret %s is invalid: %v", tlsSecret, secretRef.Error)
   339  		} else {
   340  			pemFile = secretRef.Path
   341  		}
   342  	} else if isWildcardEnabled {
   343  		pemFile = pemFileNameForWildcardTLSSecret
   344  	} else {
   345  		rejectHandshake = true
   346  		warnings.AddWarningf(owner, "TLS termination for host '%s' requires specifying a TLS secret or configuring a global wildcard TLS secret", host)
   347  	}
   348  
   349  	server.SSL = true
   350  	server.SSLCertificate = pemFile
   351  	server.SSLCertificateKey = pemFile
   352  	server.SSLRejectHandshake = rejectHandshake
   353  
   354  	return warnings
   355  }
   356  
   357  func generateIngressPath(path string, pathType *networking.PathType) string {
   358  	if pathType == nil {
   359  		return path
   360  	}
   361  	if *pathType == networking.PathTypeExact {
   362  		path = "= " + path
   363  	}
   364  
   365  	return path
   366  }
   367  
   368  func createLocation(path string, upstream version1.Upstream, cfg *ConfigParams, websocket bool, rewrite string, ssl bool, grpc bool, proxySSLName string, pathType *networking.PathType, serviceName string) version1.Location {
   369  	loc := version1.Location{
   370  		Path:                 generateIngressPath(path, pathType),
   371  		Upstream:             upstream,
   372  		ProxyConnectTimeout:  cfg.ProxyConnectTimeout,
   373  		ProxyReadTimeout:     cfg.ProxyReadTimeout,
   374  		ProxySendTimeout:     cfg.ProxySendTimeout,
   375  		ClientMaxBodySize:    cfg.ClientMaxBodySize,
   376  		Websocket:            websocket,
   377  		Rewrite:              rewrite,
   378  		SSL:                  ssl,
   379  		GRPC:                 grpc,
   380  		ProxyBuffering:       cfg.ProxyBuffering,
   381  		ProxyBuffers:         cfg.ProxyBuffers,
   382  		ProxyBufferSize:      cfg.ProxyBufferSize,
   383  		ProxyMaxTempFileSize: cfg.ProxyMaxTempFileSize,
   384  		ProxySSLName:         proxySSLName,
   385  		LocationSnippets:     cfg.LocationSnippets,
   386  		ServiceName:          serviceName,
   387  	}
   388  
   389  	return loc
   390  }
   391  
   392  // upstreamRequiresQueue checks if the upstream requires a queue.
   393  // Mandatory Health Checks can cause nginx to return errors on reload, since all Upstreams start
   394  // Unhealthy. By adding a queue to the Upstream we can avoid returning errors, at the cost of a short delay.
   395  func upstreamRequiresQueue(name string, ingEx *IngressEx, cfg *ConfigParams) (n int64, timeout int64) {
   396  	if cfg.HealthCheckEnabled && cfg.HealthCheckMandatory && cfg.HealthCheckMandatoryQueue > 0 {
   397  		if hc, exists := ingEx.HealthChecks[name]; exists {
   398  			return cfg.HealthCheckMandatoryQueue, int64(hc.TimeoutSeconds)
   399  		}
   400  	}
   401  	return 0, 0
   402  }
   403  
   404  func createUpstream(ingEx *IngressEx, name string, backend *networking.IngressBackend, stickyCookie string, cfg *ConfigParams,
   405  	isPlus bool, isResolverConfigured bool, isLatencyMetricsEnabled bool) version1.Upstream {
   406  	var ups version1.Upstream
   407  	labels := version1.UpstreamLabels{
   408  		Service:           backend.ServiceName,
   409  		ResourceType:      "ingress",
   410  		ResourceName:      ingEx.Ingress.Name,
   411  		ResourceNamespace: ingEx.Ingress.Namespace,
   412  	}
   413  	if isPlus {
   414  		queue, timeout := upstreamRequiresQueue(backend.ServiceName+backend.ServicePort.String(), ingEx, cfg)
   415  		ups = version1.Upstream{Name: name, StickyCookie: stickyCookie, Queue: queue, QueueTimeout: timeout, UpstreamLabels: labels}
   416  	} else {
   417  		ups = version1.NewUpstreamWithDefaultServer(name)
   418  		if isLatencyMetricsEnabled {
   419  			ups.UpstreamLabels = labels
   420  		}
   421  	}
   422  
   423  	endps, exists := ingEx.Endpoints[backend.ServiceName+backend.ServicePort.String()]
   424  	if exists {
   425  		var upsServers []version1.UpstreamServer
   426  		// Always false for NGINX OSS
   427  		_, isExternalNameSvc := ingEx.ExternalNameSvcs[backend.ServiceName]
   428  		if isExternalNameSvc && !isResolverConfigured {
   429  			glog.Warningf("A resolver must be configured for Type ExternalName service %s, no upstream servers will be created", backend.ServiceName)
   430  			endps = []string{}
   431  		}
   432  
   433  		for _, endp := range endps {
   434  			addressport := strings.Split(endp, ":")
   435  			upsServers = append(upsServers, version1.UpstreamServer{
   436  				Address:     addressport[0],
   437  				Port:        addressport[1],
   438  				MaxFails:    cfg.MaxFails,
   439  				MaxConns:    cfg.MaxConns,
   440  				FailTimeout: cfg.FailTimeout,
   441  				SlowStart:   cfg.SlowStart,
   442  				Resolve:     isExternalNameSvc,
   443  			})
   444  		}
   445  		if len(upsServers) > 0 {
   446  			ups.UpstreamServers = upsServers
   447  		}
   448  	}
   449  
   450  	ups.LBMethod = cfg.LBMethod
   451  	ups.UpstreamZoneSize = cfg.UpstreamZoneSize
   452  	return ups
   453  }
   454  
   455  func createHealthCheck(hc *api_v1.Probe, upstreamName string, cfg *ConfigParams) version1.HealthCheck {
   456  	return version1.HealthCheck{
   457  		UpstreamName:   upstreamName,
   458  		Fails:          hc.FailureThreshold,
   459  		Interval:       hc.PeriodSeconds,
   460  		Passes:         hc.SuccessThreshold,
   461  		URI:            hc.HTTPGet.Path,
   462  		Scheme:         strings.ToLower(string(hc.HTTPGet.Scheme)),
   463  		Mandatory:      cfg.HealthCheckMandatory,
   464  		Headers:        headersToString(hc.HTTPGet.HTTPHeaders),
   465  		TimeoutSeconds: int64(hc.TimeoutSeconds),
   466  	}
   467  }
   468  
   469  func headersToString(headers []api_v1.HTTPHeader) map[string]string {
   470  	m := make(map[string]string)
   471  	for _, header := range headers {
   472  		m[header.Name] = header.Value
   473  	}
   474  	return m
   475  }
   476  
   477  func pathOrDefault(path string) string {
   478  	if path == "" {
   479  		return "/"
   480  	}
   481  	return path
   482  }
   483  
   484  func getNameForUpstream(ing *networking.Ingress, host string, backend *networking.IngressBackend) string {
   485  	return fmt.Sprintf("%v-%v-%v-%v-%v", ing.Namespace, ing.Name, host, backend.ServiceName, backend.ServicePort.String())
   486  }
   487  
   488  func getNameForRedirectLocation(ing *networking.Ingress) string {
   489  	return fmt.Sprintf("@login_url_%v-%v", ing.Namespace, ing.Name)
   490  }
   491  
   492  func upstreamMapToSlice(upstreams map[string]version1.Upstream) []version1.Upstream {
   493  	keys := make([]string, 0, len(upstreams))
   494  	for k := range upstreams {
   495  		keys = append(keys, k)
   496  	}
   497  
   498  	// this ensures that the slice 'result' is sorted, which preserves the order of upstream servers
   499  	// in the generated configuration file from one version to another and is also required for repeatable
   500  	// Unit test results
   501  	sort.Strings(keys)
   502  
   503  	result := make([]version1.Upstream, 0, len(upstreams))
   504  
   505  	for _, k := range keys {
   506  		result = append(result, upstreams[k])
   507  	}
   508  
   509  	return result
   510  }
   511  
   512  func generateNginxCfgForMergeableIngresses(mergeableIngs *MergeableIngresses, masterApResources AppProtectResources,
   513  	baseCfgParams *ConfigParams, isPlus bool, isResolverConfigured bool, staticParams *StaticConfigParams,
   514  	isWildcardEnabled bool) (version1.IngressNginxConfig, Warnings) {
   515  
   516  	var masterServer version1.Server
   517  	var locations []version1.Location
   518  	var upstreams []version1.Upstream
   519  	healthChecks := make(map[string]version1.HealthCheck)
   520  	var keepalive string
   521  
   522  	// replace master with a deepcopy because we will modify it
   523  	originalMaster := mergeableIngs.Master.Ingress
   524  	mergeableIngs.Master.Ingress = mergeableIngs.Master.Ingress.DeepCopy()
   525  
   526  	removedAnnotations := filterMasterAnnotations(mergeableIngs.Master.Ingress.Annotations)
   527  	if len(removedAnnotations) != 0 {
   528  		glog.Errorf("Ingress Resource %v/%v with the annotation 'nginx.org/mergeable-ingress-type' set to 'master' cannot contain the '%v' annotation(s). They will be ignored",
   529  			mergeableIngs.Master.Ingress.Namespace, mergeableIngs.Master.Ingress.Name, strings.Join(removedAnnotations, ","))
   530  	}
   531  	isMinion := false
   532  
   533  	masterNginxCfg, warnings := generateNginxCfg(mergeableIngs.Master, masterApResources, isMinion, baseCfgParams, isPlus, isResolverConfigured, staticParams, isWildcardEnabled)
   534  
   535  	// because mergeableIngs.Master.Ingress is a deepcopy of the original master
   536  	// we need to change the key in the warnings to the original master
   537  	if _, exists := warnings[mergeableIngs.Master.Ingress]; exists {
   538  		warnings[originalMaster] = warnings[mergeableIngs.Master.Ingress]
   539  		delete(warnings, mergeableIngs.Master.Ingress)
   540  	}
   541  
   542  	masterServer = masterNginxCfg.Servers[0]
   543  	masterServer.Locations = []version1.Location{}
   544  
   545  	upstreams = append(upstreams, masterNginxCfg.Upstreams...)
   546  
   547  	if masterNginxCfg.Keepalive != "" {
   548  		keepalive = masterNginxCfg.Keepalive
   549  	}
   550  
   551  	minions := mergeableIngs.Minions
   552  	for _, minion := range minions {
   553  		// replace minion with a deepcopy because we will modify it
   554  		originalMinion := minion.Ingress
   555  		minion.Ingress = minion.Ingress.DeepCopy()
   556  
   557  		// Remove the default backend so that "/" will not be generated
   558  		minion.Ingress.Spec.Backend = nil
   559  
   560  		// Add acceptable master annotations to minion
   561  		mergeMasterAnnotationsIntoMinion(minion.Ingress.Annotations, mergeableIngs.Master.Ingress.Annotations)
   562  
   563  		removedAnnotations = filterMinionAnnotations(minion.Ingress.Annotations)
   564  		if len(removedAnnotations) != 0 {
   565  			glog.Errorf("Ingress Resource %v/%v with the annotation 'nginx.org/mergeable-ingress-type' set to 'minion' cannot contain the %v annotation(s). They will be ignored",
   566  				minion.Ingress.Namespace, minion.Ingress.Name, strings.Join(removedAnnotations, ","))
   567  		}
   568  
   569  		isMinion := true
   570  		// App Protect Resources not allowed in minions - pass empty struct
   571  		dummyApResources := AppProtectResources{}
   572  		nginxCfg, minionWarnings := generateNginxCfg(minion, dummyApResources, isMinion, baseCfgParams, isPlus, isResolverConfigured, staticParams, isWildcardEnabled)
   573  		warnings.Add(minionWarnings)
   574  
   575  		// because minion.Ingress is a deepcopy of the original minion
   576  		// we need to change the key in the warnings to the original minion
   577  		if _, exists := warnings[minion.Ingress]; exists {
   578  			warnings[originalMinion] = warnings[minion.Ingress]
   579  			delete(warnings, minion.Ingress)
   580  		}
   581  
   582  		for _, server := range nginxCfg.Servers {
   583  			for _, loc := range server.Locations {
   584  				loc.MinionIngress = &nginxCfg.Ingress
   585  				locations = append(locations, loc)
   586  			}
   587  			for hcName, healthCheck := range server.HealthChecks {
   588  				healthChecks[hcName] = healthCheck
   589  			}
   590  			masterServer.JWTRedirectLocations = append(masterServer.JWTRedirectLocations, server.JWTRedirectLocations...)
   591  		}
   592  
   593  		upstreams = append(upstreams, nginxCfg.Upstreams...)
   594  	}
   595  
   596  	masterServer.HealthChecks = healthChecks
   597  	masterServer.Locations = locations
   598  
   599  	return version1.IngressNginxConfig{
   600  		Servers:           []version1.Server{masterServer},
   601  		Upstreams:         upstreams,
   602  		Keepalive:         keepalive,
   603  		Ingress:           masterNginxCfg.Ingress,
   604  		SpiffeClientCerts: staticParams.NginxServiceMesh && !baseCfgParams.SpiffeServerCerts,
   605  	}, warnings
   606  }
   607  
   608  func isSSLEnabled(isSSLService bool, cfgParams ConfigParams, staticCfgParams *StaticConfigParams) bool {
   609  	return isSSLService || staticCfgParams.NginxServiceMesh && !cfgParams.SpiffeServerCerts
   610  }