2016-11-10 22:56:29 +00:00
/ *
Copyright 2015 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package controller
import (
"fmt"
2017-06-14 21:33:12 +00:00
"math/rand"
2016-11-10 22:56:29 +00:00
"sort"
"strconv"
"strings"
"time"
"github.com/golang/glog"
2017-09-17 18:42:31 +00:00
apiv1 "k8s.io/api/core/v1"
2017-07-16 19:19:59 +00:00
extensions "k8s.io/api/extensions/v1beta1"
2017-04-01 14:39:42 +00:00
"k8s.io/apimachinery/pkg/util/intstr"
2017-08-15 12:32:26 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2017-04-01 14:39:42 +00:00
clientset "k8s.io/client-go/kubernetes"
2017-10-06 20:33:32 +00:00
2017-11-07 22:02:12 +00:00
"k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/healthcheck"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/k8s"
2016-11-10 22:56:29 +00:00
)
const (
2017-06-14 23:49:35 +00:00
defUpstreamName = "upstream-default-backend"
defServerName = "_"
rootLocation = "/"
2016-11-10 22:56:29 +00:00
)
// Configuration contains all the settings required by an Ingress controller
type Configuration struct {
2017-11-05 01:18:28 +00:00
APIServerHost string
KubeConfigFile string
Client clientset . Interface
2016-11-10 22:56:29 +00:00
2017-11-05 01:18:28 +00:00
ResyncPeriod time . Duration
ConfigMapName string
2016-11-10 22:56:29 +00:00
DefaultService string
2017-11-22 13:40:54 +00:00
Namespace string
2017-04-13 01:50:54 +00:00
ForceNamespaceIsolation bool
2018-06-13 18:15:45 +00:00
// +optional
2016-11-10 22:56:29 +00:00
TCPConfigMapName string
2018-06-13 18:15:45 +00:00
// +optional
2017-11-05 01:18:28 +00:00
UDPConfigMapName string
2016-11-10 22:56:29 +00:00
DefaultHealthzURL string
2017-11-05 01:18:28 +00:00
DefaultSSLCertificate string
2018-06-13 18:15:45 +00:00
// +optional
2018-02-27 03:02:19 +00:00
PublishService string
PublishStatusAddress string
2017-01-20 22:01:37 +00:00
2017-06-20 01:22:08 +00:00
UpdateStatus bool
2017-10-08 17:29:19 +00:00
UseNodeInternalIP bool
2017-06-20 01:22:08 +00:00
ElectionID string
UpdateStatusOnShutdown bool
2017-10-08 17:29:19 +00:00
SortBackends bool
2017-02-07 18:13:08 +00:00
2017-11-05 01:18:28 +00:00
ListenPorts * ngx_config . ListenPorts
2017-08-25 15:50:08 +00:00
2017-11-05 01:18:28 +00:00
EnableSSLPassthrough bool
2016-11-10 22:56:29 +00:00
2017-11-05 01:18:28 +00:00
EnableProfiling bool
2016-11-10 22:56:29 +00:00
2017-11-13 01:43:28 +00:00
EnableSSLChainCompletion bool
2017-11-05 01:18:28 +00:00
FakeCertificatePath string
FakeCertificateSHA string
2017-12-05 19:07:34 +00:00
SyncRateLimit float32
2018-03-18 13:13:41 +00:00
DynamicConfigurationEnabled bool
2018-04-11 14:52:42 +00:00
DisableLua bool
2016-11-10 22:56:29 +00:00
}
2018-06-13 18:15:45 +00:00
// GetPublishService returns the Service used to set the load-balancer status of Ingresses.
2017-11-05 01:18:28 +00:00
func ( n NGINXController ) GetPublishService ( ) * apiv1 . Service {
2018-01-18 19:14:42 +00:00
s , err := n . store . GetService ( n . cfg . PublishService )
2017-09-17 16:34:29 +00:00
if err != nil {
return nil
}
return s
}
2018-06-13 18:15:45 +00:00
// syncIngress collects all the pieces required to assemble the NGINX
// configuration file and passes the resulting data structures to the backend
// (OnUpdate) when a reload is deemed necessary.
2018-04-12 22:26:10 +00:00
func ( n * NGINXController ) syncIngress ( interface { } ) error {
2017-11-05 01:18:28 +00:00
n . syncRateLimiter . Accept ( )
2016-11-10 22:56:29 +00:00
2017-11-05 01:18:28 +00:00
if n . syncQueue . IsShuttingDown ( ) {
2016-11-10 22:56:29 +00:00
return nil
}
2018-06-13 18:15:45 +00:00
// sort Ingresses using the ResourceVersion field
2018-01-18 19:14:42 +00:00
ings := n . store . ListIngresses ( )
2017-09-25 19:40:55 +00:00
sort . SliceStable ( ings , func ( i , j int ) bool {
2018-01-18 19:14:42 +00:00
ir := ings [ i ] . ResourceVersion
jr := ings [ j ] . ResourceVersion
2017-09-25 19:40:55 +00:00
return ir < jr
} )
2018-01-18 19:14:42 +00:00
upstreams , servers := n . getBackendServers ( ings )
2016-11-11 23:43:35 +00:00
var passUpstreams [ ] * ingress . SSLPassthroughBackend
2017-06-12 12:45:08 +00:00
2016-11-10 22:56:29 +00:00
for _ , server := range servers {
if ! server . SSLPassthrough {
continue
}
for _ , loc := range server . Locations {
if loc . Path != rootLocation {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Ignoring SSL Passthrough for location %q in server %q" , loc . Path , server . Hostname )
2016-11-10 22:56:29 +00:00
continue
}
2016-11-11 23:43:35 +00:00
passUpstreams = append ( passUpstreams , & ingress . SSLPassthroughBackend {
2016-11-16 18:24:26 +00:00
Backend : loc . Backend ,
Hostname : server . Hostname ,
2017-04-09 23:51:38 +00:00
Service : loc . Service ,
Port : loc . Port ,
2016-11-10 22:56:29 +00:00
} )
break
}
}
2017-09-13 19:15:47 +00:00
pcfg := ingress . Configuration {
2016-11-16 18:24:26 +00:00
Backends : upstreams ,
Servers : servers ,
2017-11-05 01:18:28 +00:00
TCPEndpoints : n . getStreamServices ( n . cfg . TCPConfigMapName , apiv1 . ProtocolTCP ) ,
UDPEndpoints : n . getStreamServices ( n . cfg . UDPConfigMapName , apiv1 . ProtocolUDP ) ,
2016-11-16 18:24:26 +00:00
PassthroughBackends : passUpstreams ,
2018-06-21 14:50:57 +00:00
ConfigurationChecksum : n . store . GetBackendConfiguration ( ) . Checksum ,
2017-06-14 21:33:12 +00:00
}
2018-06-21 14:50:57 +00:00
if n . runningConfig . Equal ( & pcfg ) {
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "No configuration change detected, skipping backend reload." )
2017-06-14 21:33:12 +00:00
return nil
}
2018-06-21 14:50:57 +00:00
if n . cfg . DynamicConfigurationEnabled && n . IsDynamicConfigurationEnough ( & pcfg ) {
2018-06-13 18:15:45 +00:00
glog . Infof ( "Changes handled by the dynamic configuration, skipping backend reload." )
2018-04-01 20:09:27 +00:00
} else {
2018-06-13 18:15:45 +00:00
glog . Infof ( "Configuration changes detected, backend reload required." )
2016-11-10 22:56:29 +00:00
2018-04-01 20:09:27 +00:00
err := n . OnUpdate ( pcfg )
if err != nil {
2018-05-18 18:27:32 +00:00
IncReloadErrorCount ( )
ConfigSuccess ( false )
2018-06-13 18:15:45 +00:00
glog . Errorf ( "Unexpected failure reloading the backend:\n%v" , err )
2018-04-01 20:09:27 +00:00
return err
}
2018-06-13 18:15:45 +00:00
glog . Infof ( "Backend successfully reloaded." )
2018-05-18 18:27:32 +00:00
ConfigSuccess ( true )
IncReloadCount ( )
2018-04-01 20:09:27 +00:00
setSSLExpireTime ( servers )
2016-11-10 22:56:29 +00:00
}
2017-06-11 19:56:30 +00:00
2018-04-01 20:09:27 +00:00
if n . cfg . DynamicConfigurationEnabled {
isFirstSync := n . runningConfig . Equal ( & ingress . Configuration { } )
go func ( isFirstSync bool ) {
if isFirstSync {
2018-06-13 18:15:45 +00:00
glog . Infof ( "Initial synchronization of the NGINX configuration." )
2017-06-11 19:56:30 +00:00
2018-06-13 18:15:45 +00:00
// it takes time for NGINX to start listening on the configured ports
2018-04-01 20:09:27 +00:00
time . Sleep ( 1 * time . Second )
}
2018-04-24 18:02:52 +00:00
err := configureDynamically ( & pcfg , n . cfg . ListenPorts . Status )
2018-03-18 13:13:41 +00:00
if err == nil {
2018-06-13 18:15:45 +00:00
glog . Infof ( "Dynamic reconfiguration succeeded." )
2018-03-18 13:13:41 +00:00
} else {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Dynamic reconfiguration failed: %v" , err )
2018-03-18 13:13:41 +00:00
}
2018-04-01 20:09:27 +00:00
} ( isFirstSync )
2018-03-18 13:13:41 +00:00
}
2017-11-05 01:18:28 +00:00
n . runningConfig = & pcfg
2017-06-14 21:33:12 +00:00
2016-11-10 22:56:29 +00:00
return nil
}
2017-11-05 01:18:28 +00:00
func ( n * NGINXController ) getStreamServices ( configmapName string , proto apiv1 . Protocol ) [ ] ingress . L4Service {
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Obtaining information about %v stream services from ConfigMap %q" , proto , configmapName )
2017-02-02 22:41:02 +00:00
if configmapName == "" {
2017-02-24 21:46:39 +00:00
return [ ] ingress . L4Service { }
2016-11-10 22:56:29 +00:00
}
2017-09-18 23:53:26 +00:00
_ , _ , err := k8s . ParseNameNS ( configmapName )
2016-11-10 22:56:29 +00:00
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Errorf ( "Error parsing ConfigMap reference %q: %v" , configmapName , err )
2017-02-24 21:46:39 +00:00
return [ ] ingress . L4Service { }
2016-11-10 22:56:29 +00:00
}
2018-01-18 19:14:42 +00:00
configmap , err := n . store . GetConfigMap ( configmapName )
2016-11-10 22:56:29 +00:00
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Errorf ( "Error reading ConfigMap %q: %v" , configmapName , err )
2017-02-24 21:46:39 +00:00
return [ ] ingress . L4Service { }
2016-11-10 22:56:29 +00:00
}
2017-02-24 21:46:39 +00:00
var svcs [ ] ingress . L4Service
2017-09-27 14:10:16 +00:00
var svcProxyProtocol ingress . ProxyProtocol
2018-01-19 18:44:31 +00:00
rp := [ ] int {
n . cfg . ListenPorts . HTTP ,
n . cfg . ListenPorts . HTTPS ,
n . cfg . ListenPorts . SSLProxy ,
n . cfg . ListenPorts . Status ,
n . cfg . ListenPorts . Health ,
n . cfg . ListenPorts . Default ,
}
reserverdPorts := sets . NewInt ( rp ... )
2018-06-13 18:15:45 +00:00
// svcRef format: <(str)namespace>/<(str)service>:<(intstr)port>[:<(bool)decode>:<(bool)encode>]
for port , svcRef := range configmap . Data {
externalPort , err := strconv . Atoi ( port )
2016-11-10 22:56:29 +00:00
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "%q is not a valid %v port number" , port , proto )
2016-11-10 22:56:29 +00:00
continue
}
2018-01-19 18:44:31 +00:00
if reserverdPorts . Has ( externalPort ) {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Port %d cannot be used for %v stream services. It is reserved for the Ingress controller." , externalPort , proto )
2016-11-10 22:56:29 +00:00
continue
}
2018-06-13 18:15:45 +00:00
nsSvcPort := strings . Split ( svcRef , ":" )
2017-07-02 20:46:15 +00:00
if len ( nsSvcPort ) < 2 {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Invalid Service reference %q for %v port %d" , svcRef , proto , externalPort )
2016-11-10 22:56:29 +00:00
continue
}
nsName := nsSvcPort [ 0 ]
svcPort := nsSvcPort [ 1 ]
2017-09-27 14:10:16 +00:00
svcProxyProtocol . Decode = false
svcProxyProtocol . Encode = false
2017-07-02 20:46:15 +00:00
2018-06-13 18:15:45 +00:00
// Proxy Protocol is only compatible with TCP Services
2017-09-27 14:10:16 +00:00
if len ( nsSvcPort ) >= 3 && proto == apiv1 . ProtocolTCP {
if len ( nsSvcPort ) >= 3 && strings . ToUpper ( nsSvcPort [ 2 ] ) == "PROXY" {
svcProxyProtocol . Decode = true
}
if len ( nsSvcPort ) == 4 && strings . ToUpper ( nsSvcPort [ 3 ] ) == "PROXY" {
svcProxyProtocol . Encode = true
2017-07-02 20:46:15 +00:00
}
}
2016-11-10 22:56:29 +00:00
svcNs , svcName , err := k8s . ParseNameNS ( nsName )
if err != nil {
glog . Warningf ( "%v" , err )
continue
}
2018-01-18 19:14:42 +00:00
svc , err := n . store . GetService ( nsName )
2016-11-10 22:56:29 +00:00
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Error getting Service %q from local store: %v" , nsName , err )
2016-11-10 22:56:29 +00:00
continue
}
2016-11-11 23:43:35 +00:00
var endps [ ] ingress . Endpoint
2016-11-10 22:56:29 +00:00
targetPort , err := strconv . Atoi ( svcPort )
if err != nil {
2018-06-13 18:15:45 +00:00
// not a port number, fall back to using port name
glog . V ( 3 ) . Infof ( "Searching Endpoints with %v port name %q for Service %q" , proto , svcPort , nsName )
2016-11-10 22:56:29 +00:00
for _ , sp := range svc . Spec . Ports {
if sp . Name == svcPort {
2017-08-07 03:22:06 +00:00
if sp . Protocol == proto {
2018-04-22 01:51:58 +00:00
endps = getEndpoints ( svc , & sp , proto , & healthcheck . Config { } , n . store . GetServiceEndpoints )
2017-08-07 03:22:06 +00:00
break
}
2016-11-10 22:56:29 +00:00
}
}
} else {
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Searching Endpoints with %v port number %d for Service %q" , proto , targetPort , nsName )
2016-11-10 22:56:29 +00:00
for _ , sp := range svc . Spec . Ports {
if sp . Port == int32 ( targetPort ) {
2017-08-07 03:22:06 +00:00
if sp . Protocol == proto {
2018-04-22 01:51:58 +00:00
endps = getEndpoints ( svc , & sp , proto , & healthcheck . Config { } , n . store . GetServiceEndpoints )
2017-08-07 03:22:06 +00:00
break
}
2016-11-10 22:56:29 +00:00
}
}
}
2018-06-13 18:15:45 +00:00
// stream services cannot contain empty upstreams and there is
// no default backend equivalent
2016-11-10 22:56:29 +00:00
if len ( endps ) == 0 {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Service %q does not have any active Endpoint for %v port %v" , nsName , proto , svcPort )
2016-11-10 22:56:29 +00:00
continue
}
2017-02-24 21:46:39 +00:00
svcs = append ( svcs , ingress . L4Service {
Port : externalPort ,
Backend : ingress . L4Backend {
2017-09-27 14:10:16 +00:00
Name : svcName ,
Namespace : svcNs ,
Port : intstr . FromString ( svcPort ) ,
Protocol : proto ,
ProxyProtocol : svcProxyProtocol ,
2017-02-24 21:46:39 +00:00
} ,
Endpoints : endps ,
2016-11-10 22:56:29 +00:00
} )
}
return svcs
}
2018-06-13 18:15:45 +00:00
// getDefaultUpstream returns the upstream associated with the default backend.
// Configures the upstream to return HTTP code 503 in case of error.
2017-11-05 01:18:28 +00:00
func ( n * NGINXController ) getDefaultUpstream ( ) * ingress . Backend {
2016-11-11 23:43:35 +00:00
upstream := & ingress . Backend {
2016-11-10 22:56:29 +00:00
Name : defUpstreamName ,
}
2017-11-05 01:18:28 +00:00
svcKey := n . cfg . DefaultService
2018-01-18 19:14:42 +00:00
svc , err := n . store . GetService ( svcKey )
2016-11-10 22:56:29 +00:00
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Unexpected error getting default backend %q from local store: %v" , n . cfg . DefaultService , err )
2017-11-05 01:18:28 +00:00
upstream . Endpoints = append ( upstream . Endpoints , n . DefaultEndpoint ( ) )
2016-11-10 22:56:29 +00:00
return upstream
}
2018-04-22 01:51:58 +00:00
endps := getEndpoints ( svc , & svc . Spec . Ports [ 0 ] , apiv1 . ProtocolTCP , & healthcheck . Config { } , n . store . GetServiceEndpoints )
2016-11-10 22:56:29 +00:00
if len ( endps ) == 0 {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Service %q does not have any active Endpoint" , svcKey )
2017-11-05 01:18:28 +00:00
endps = [ ] ingress . Endpoint { n . DefaultEndpoint ( ) }
2016-11-10 22:56:29 +00:00
}
2017-08-11 02:51:32 +00:00
upstream . Service = svc
2016-11-11 23:43:35 +00:00
upstream . Endpoints = append ( upstream . Endpoints , endps ... )
2016-11-10 22:56:29 +00:00
return upstream
}
2018-06-13 18:15:45 +00:00
// getBackendServers returns a list of Upstream and Server to be used by the
// backend. An upstream can be used in multiple servers if the namespace,
// service name and port are the same.
2017-11-05 01:18:28 +00:00
func ( n * NGINXController ) getBackendServers ( ingresses [ ] * extensions . Ingress ) ( [ ] * ingress . Backend , [ ] * ingress . Server ) {
du := n . getDefaultUpstream ( )
upstreams := n . createUpstreams ( ingresses , du )
servers := n . createServers ( ingresses , upstreams , du )
2016-11-10 22:56:29 +00:00
2017-09-25 19:40:55 +00:00
for _ , ing := range ingresses {
2018-01-18 19:14:42 +00:00
anns , err := n . store . GetIngressAnnotations ( ing )
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Errorf ( "Unexpected error reading annotations for Ingress %q from local store: %v" , ing . Name , err )
2018-01-18 19:14:42 +00:00
}
2016-11-10 22:56:29 +00:00
for _ , rule := range ing . Spec . Rules {
host := rule . Host
if host == "" {
host = defServerName
}
server := servers [ host ]
if server == nil {
server = servers [ defServerName ]
}
if rule . HTTP == nil &&
host != defServerName {
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Ingress \"%v/%v\" does not contain any HTTP rule, using default backend." , ing . Namespace , ing . Name )
2016-11-10 22:56:29 +00:00
continue
}
2018-02-25 20:20:14 +00:00
if server . AuthTLSError == "" && anns . CertificateAuth . AuthTLSError != "" {
server . AuthTLSError = anns . CertificateAuth . AuthTLSError
}
2017-08-22 20:16:59 +00:00
if server . CertificateAuth . CAFileName == "" {
2018-03-28 23:33:03 +00:00
server . CertificateAuth = anns . CertificateAuth
2018-06-13 18:15:45 +00:00
if server . CertificateAuth . Secret != "" && server . CertificateAuth . CAFileName == "" {
glog . V ( 3 ) . Infof ( "Secret %q does not contain 'ca.crt' key, mutual authentication disabled for Ingress \"%v/%v\"" , server . CertificateAuth . Secret , ing . Namespace , ing . Name )
2017-08-22 20:16:59 +00:00
}
} else {
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Server %v is already configured for mutual authentication (Ingress \"%v/%v\")" , server . Hostname , ing . Namespace , ing . Name )
2017-08-22 20:16:59 +00:00
}
2016-11-10 22:56:29 +00:00
for _ , path := range rule . HTTP . Paths {
upsName := fmt . Sprintf ( "%v-%v-%v" ,
2018-06-13 18:15:45 +00:00
ing . Namespace ,
2016-11-10 22:56:29 +00:00
path . Backend . ServiceName ,
path . Backend . ServicePort . String ( ) )
ups := upstreams [ upsName ]
nginxPath := rootLocation
if path . Path != "" {
nginxPath = path . Path
}
addLoc := true
for _ , loc := range server . Locations {
if loc . Path == nginxPath {
addLoc = false
if ! loc . IsDefBackend {
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Location %q already configured for server %q with upstream %q (Ingress \"%v/%v\")" , loc . Path , server . Hostname , loc . Backend , ing . Namespace , ing . Name )
2016-11-10 22:56:29 +00:00
break
}
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Replacing location %q for server %q with upstream %q to use upstream %q (Ingress \"%v/%v\")" , loc . Path , server . Hostname , loc . Backend , ups . Name , ing . Namespace , ing . Name )
2016-11-16 18:24:26 +00:00
loc . Backend = ups . Name
2016-11-10 22:56:29 +00:00
loc . IsDefBackend = false
2017-04-09 23:51:38 +00:00
loc . Port = ups . Port
loc . Service = ups . Service
2017-08-25 15:50:08 +00:00
loc . Ingress = ing
2017-11-07 16:36:51 +00:00
loc . BasicDigestAuth = anns . BasicDigestAuth
loc . ClientBodyBufferSize = anns . ClientBodyBufferSize
loc . ConfigurationSnippet = anns . ConfigurationSnippet
loc . CorsConfig = anns . CorsConfig
loc . ExternalAuth = anns . ExternalAuth
loc . Proxy = anns . Proxy
loc . RateLimit = anns . RateLimit
loc . Redirect = anns . Redirect
loc . Rewrite = anns . Rewrite
loc . UpstreamVhost = anns . UpstreamVhost
loc . Whitelist = anns . Whitelist
2017-11-08 21:36:03 +00:00
loc . Denied = anns . Denied
2017-12-06 20:11:18 +00:00
loc . XForwardedPrefix = anns . XForwardedPrefix
2018-01-02 17:48:42 +00:00
loc . UsePortInRedirects = anns . UsePortInRedirects
2018-01-30 04:29:03 +00:00
loc . Connection = anns . Connection
2018-02-25 14:38:54 +00:00
loc . Logs = anns . Logs
2018-03-22 03:38:47 +00:00
loc . GRPC = anns . GRPC
2018-04-08 20:37:13 +00:00
loc . LuaRestyWAF = anns . LuaRestyWAF
2018-05-17 12:25:38 +00:00
loc . InfluxDB = anns . InfluxDB
2018-05-26 23:08:07 +00:00
loc . DefaultBackend = anns . DefaultBackend
2017-11-07 16:36:51 +00:00
2017-08-19 21:13:02 +00:00
if loc . Redirect . FromToWWW {
server . RedirectFromToWWW = true
}
2016-11-10 22:56:29 +00:00
break
}
}
2018-06-13 18:15:45 +00:00
// new location
2016-11-10 22:56:29 +00:00
if addLoc {
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Adding location %q for server %q with upstream %q (Ingress \"%v/%v\")" , nginxPath , server . Hostname , ups . Name , ing . Namespace , ing . Name )
2016-12-29 20:02:06 +00:00
loc := & ingress . Location {
2017-11-07 16:36:51 +00:00
Path : nginxPath ,
Backend : ups . Name ,
IsDefBackend : false ,
Service : ups . Service ,
Port : ups . Port ,
Ingress : ing ,
BasicDigestAuth : anns . BasicDigestAuth ,
ClientBodyBufferSize : anns . ClientBodyBufferSize ,
ConfigurationSnippet : anns . ConfigurationSnippet ,
CorsConfig : anns . CorsConfig ,
ExternalAuth : anns . ExternalAuth ,
Proxy : anns . Proxy ,
RateLimit : anns . RateLimit ,
Redirect : anns . Redirect ,
Rewrite : anns . Rewrite ,
UpstreamVhost : anns . UpstreamVhost ,
Whitelist : anns . Whitelist ,
2017-11-08 21:36:03 +00:00
Denied : anns . Denied ,
2017-12-06 20:11:18 +00:00
XForwardedPrefix : anns . XForwardedPrefix ,
2018-01-02 17:48:42 +00:00
UsePortInRedirects : anns . UsePortInRedirects ,
2018-01-30 04:29:03 +00:00
Connection : anns . Connection ,
2018-02-25 14:38:54 +00:00
Logs : anns . Logs ,
2018-03-22 03:38:47 +00:00
GRPC : anns . GRPC ,
2018-04-08 20:37:13 +00:00
LuaRestyWAF : anns . LuaRestyWAF ,
2018-05-17 12:25:38 +00:00
InfluxDB : anns . InfluxDB ,
2018-05-26 23:08:07 +00:00
DefaultBackend : anns . DefaultBackend ,
2016-12-29 20:02:06 +00:00
}
2017-11-07 16:36:51 +00:00
2017-08-19 21:13:02 +00:00
if loc . Redirect . FromToWWW {
server . RedirectFromToWWW = true
}
2016-12-29 20:02:06 +00:00
server . Locations = append ( server . Locations , loc )
2016-11-10 22:56:29 +00:00
}
2017-06-26 01:30:30 +00:00
if ups . SessionAffinity . AffinityType == "" {
2017-11-07 16:36:51 +00:00
ups . SessionAffinity . AffinityType = anns . SessionAffinity . Type
2017-06-26 01:30:30 +00:00
}
2017-11-07 16:36:51 +00:00
if anns . SessionAffinity . Type == "cookie" {
ups . SessionAffinity . CookieSessionAffinity . Name = anns . SessionAffinity . Cookie . Name
ups . SessionAffinity . CookieSessionAffinity . Hash = anns . SessionAffinity . Cookie . Hash
2017-06-26 01:30:30 +00:00
locs := ups . SessionAffinity . CookieSessionAffinity . Locations
if _ , ok := locs [ host ] ; ! ok {
locs [ host ] = [ ] string { }
}
locs [ host ] = append ( locs [ host ] , path . Path )
}
2016-11-10 22:56:29 +00:00
}
}
}
2017-08-25 15:50:08 +00:00
aUpstreams := make ( [ ] * ingress . Backend , 0 , len ( upstreams ) )
2017-04-02 02:32:22 +00:00
for _ , upstream := range upstreams {
isHTTPSfrom := [ ] * ingress . Server { }
for _ , server := range servers {
for _ , location := range server . Locations {
if upstream . Name == location . Backend {
2017-08-25 15:50:08 +00:00
if len ( upstream . Endpoints ) == 0 {
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Upstream %q does not have any active endpoints." , upstream . Name )
2018-06-08 03:45:51 +00:00
location . Backend = "" // for nginx.tmpl checking
2017-08-25 15:50:08 +00:00
// check if the location contains endpoints and a custom default backend
if location . DefaultBackend != nil {
sp := location . DefaultBackend . Spec . Ports [ 0 ]
2018-04-22 01:51:58 +00:00
endps := getEndpoints ( location . DefaultBackend , & sp , apiv1 . ProtocolTCP , & healthcheck . Config { } , n . store . GetServiceEndpoints )
2017-08-25 15:50:08 +00:00
if len ( endps ) > 0 {
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Using custom default backend for location %q in server %q (Service \"%v/%v\")" ,
location . Path , server . Hostname , location . DefaultBackend . Namespace , location . DefaultBackend . Name )
2018-01-07 15:03:00 +00:00
nb := upstream . DeepCopy ( )
name := fmt . Sprintf ( "custom-default-backend-%v" , upstream . Name )
nb . Name = name
nb . Endpoints = endps
aUpstreams = append ( aUpstreams , nb )
location . Backend = name
2017-08-25 15:50:08 +00:00
}
}
}
2017-04-02 02:32:22 +00:00
if server . SSLPassthrough {
if location . Path == rootLocation {
if location . Backend == defUpstreamName {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Server %q has no default backend, ignoring SSL Passthrough." , server . Hostname )
2017-04-09 23:51:38 +00:00
continue
2017-04-02 02:32:22 +00:00
}
2017-04-09 23:51:38 +00:00
isHTTPSfrom = append ( isHTTPSfrom , server )
2017-04-02 02:32:22 +00:00
}
}
}
}
}
2017-08-25 15:50:08 +00:00
2017-04-02 02:32:22 +00:00
if len ( isHTTPSfrom ) > 0 {
2017-04-09 23:51:38 +00:00
upstream . SSLPassthrough = true
2017-04-02 02:32:22 +00:00
}
}
2018-06-13 18:15:45 +00:00
// create the list of upstreams and skip those without Endpoints
2017-08-25 15:50:08 +00:00
for _ , upstream := range upstreams {
if len ( upstream . Endpoints ) == 0 {
continue
2016-11-10 22:56:29 +00:00
}
2017-08-25 15:50:08 +00:00
aUpstreams = append ( aUpstreams , upstream )
2016-11-10 22:56:29 +00:00
}
2017-08-25 15:50:08 +00:00
2016-11-10 22:56:29 +00:00
aServers := make ( [ ] * ingress . Server , 0 , len ( servers ) )
for _ , value := range servers {
2017-09-25 19:40:55 +00:00
sort . SliceStable ( value . Locations , func ( i , j int ) bool {
2017-09-18 23:53:26 +00:00
return value . Locations [ i ] . Path > value . Locations [ j ] . Path
} )
2016-11-10 22:56:29 +00:00
aServers = append ( aServers , value )
}
2017-09-18 23:53:26 +00:00
2018-06-02 19:17:14 +00:00
sort . SliceStable ( aUpstreams , func ( a , b int ) bool {
return aUpstreams [ a ] . Name < aUpstreams [ b ] . Name
} )
2017-09-25 19:40:55 +00:00
sort . SliceStable ( aServers , func ( i , j int ) bool {
2017-09-18 23:53:26 +00:00
return aServers [ i ] . Hostname < aServers [ j ] . Hostname
} )
2016-11-10 22:56:29 +00:00
return aUpstreams , aServers
}
2018-06-13 18:15:45 +00:00
// createUpstreams creates the NGINX upstreams (Endpoints) for each Service
// referenced in Ingress rules.
2017-11-05 01:18:28 +00:00
func ( n * NGINXController ) createUpstreams ( data [ ] * extensions . Ingress , du * ingress . Backend ) map [ string ] * ingress . Backend {
2016-11-11 23:43:35 +00:00
upstreams := make ( map [ string ] * ingress . Backend )
2017-09-18 23:53:26 +00:00
upstreams [ defUpstreamName ] = du
2016-11-10 22:56:29 +00:00
2017-09-25 19:40:55 +00:00
for _ , ing := range data {
2018-01-18 19:14:42 +00:00
anns , err := n . store . GetIngressAnnotations ( ing )
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Errorf ( "Error reading Ingress annotations: %v" , err )
2018-01-18 19:14:42 +00:00
}
2016-11-10 22:56:29 +00:00
var defBackend string
if ing . Spec . Backend != nil {
defBackend = fmt . Sprintf ( "%v-%v-%v" ,
2018-06-13 18:15:45 +00:00
ing . Namespace ,
2016-11-10 22:56:29 +00:00
ing . Spec . Backend . ServiceName ,
ing . Spec . Backend . ServicePort . String ( ) )
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Creating upstream %q" , defBackend )
2016-11-10 22:56:29 +00:00
upstreams [ defBackend ] = newUpstream ( defBackend )
2017-11-29 18:04:51 +00:00
if ! upstreams [ defBackend ] . Secure {
upstreams [ defBackend ] . Secure = anns . SecureUpstream . Secure
}
if upstreams [ defBackend ] . SecureCACert . Secret == "" {
upstreams [ defBackend ] . SecureCACert = anns . SecureUpstream . CACert
}
if upstreams [ defBackend ] . UpstreamHashBy == "" {
upstreams [ defBackend ] . UpstreamHashBy = anns . UpstreamHashBy
}
2018-03-09 21:09:41 +00:00
if upstreams [ defBackend ] . LoadBalancing == "" {
upstreams [ defBackend ] . LoadBalancing = anns . LoadBalancing
}
2017-11-29 18:04:51 +00:00
2018-06-13 18:15:45 +00:00
svcKey := fmt . Sprintf ( "%v/%v" , ing . Namespace , ing . Spec . Backend . ServiceName )
2017-07-16 20:19:45 +00:00
2018-06-13 18:15:45 +00:00
// add the service ClusterIP as a single Endpoint instead of individual Endpoints
2017-11-07 16:36:51 +00:00
if anns . ServiceUpstream {
2017-11-05 01:18:28 +00:00
endpoint , err := n . getServiceClusterEndpoint ( svcKey , ing . Spec . Backend )
2017-09-13 19:15:47 +00:00
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Errorf ( "Failed to determine a suitable ClusterIP Endpoint for Service %q: %v" , svcKey , err )
2017-09-13 19:15:47 +00:00
} else {
upstreams [ defBackend ] . Endpoints = [ ] ingress . Endpoint { endpoint }
}
}
if len ( upstreams [ defBackend ] . Endpoints ) == 0 {
2017-11-07 16:36:51 +00:00
endps , err := n . serviceEndpoints ( svcKey , ing . Spec . Backend . ServicePort . String ( ) , & anns . HealthCheck )
2017-09-13 19:15:47 +00:00
upstreams [ defBackend ] . Endpoints = append ( upstreams [ defBackend ] . Endpoints , endps ... )
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Error creating upstream %q: %v" , defBackend , err )
2017-09-13 19:15:47 +00:00
}
}
2016-11-10 22:56:29 +00:00
}
for _ , rule := range ing . Spec . Rules {
if rule . HTTP == nil {
continue
}
for _ , path := range rule . HTTP . Paths {
name := fmt . Sprintf ( "%v-%v-%v" ,
2018-06-13 18:15:45 +00:00
ing . Namespace ,
2016-11-10 22:56:29 +00:00
path . Backend . ServiceName ,
path . Backend . ServicePort . String ( ) )
2017-04-09 23:51:38 +00:00
if _ , ok := upstreams [ name ] ; ok {
continue
2016-11-10 22:56:29 +00:00
}
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Creating upstream %q" , name )
2017-04-09 23:51:38 +00:00
upstreams [ name ] = newUpstream ( name )
2017-08-11 02:51:32 +00:00
upstreams [ name ] . Port = path . Backend . ServicePort
2017-04-09 23:51:38 +00:00
if ! upstreams [ name ] . Secure {
2017-11-07 16:36:51 +00:00
upstreams [ name ] . Secure = anns . SecureUpstream . Secure
2017-05-14 22:14:27 +00:00
}
2017-06-16 00:43:17 +00:00
2017-05-14 22:14:27 +00:00
if upstreams [ name ] . SecureCACert . Secret == "" {
2017-11-07 16:36:51 +00:00
upstreams [ name ] . SecureCACert = anns . SecureUpstream . CACert
2016-11-16 18:24:26 +00:00
}
2017-06-16 00:43:17 +00:00
2017-09-30 21:29:16 +00:00
if upstreams [ name ] . UpstreamHashBy == "" {
2017-11-07 16:36:51 +00:00
upstreams [ name ] . UpstreamHashBy = anns . UpstreamHashBy
2017-09-30 21:29:16 +00:00
}
2018-03-09 21:09:41 +00:00
if upstreams [ name ] . LoadBalancing == "" {
upstreams [ name ] . LoadBalancing = anns . LoadBalancing
}
2018-06-13 18:15:45 +00:00
svcKey := fmt . Sprintf ( "%v/%v" , ing . Namespace , path . Backend . ServiceName )
2017-07-16 20:19:45 +00:00
2018-06-13 18:15:45 +00:00
// add the service ClusterIP as a single Endpoint instead of individual Endpoints
2017-11-07 16:36:51 +00:00
if anns . ServiceUpstream {
2017-11-05 01:18:28 +00:00
endpoint , err := n . getServiceClusterEndpoint ( svcKey , & path . Backend )
2017-09-13 19:15:47 +00:00
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Errorf ( "Failed to determine a suitable ClusterIP Endpoint for Service %q: %v" , svcKey , err )
2017-09-13 19:15:47 +00:00
} else {
upstreams [ name ] . Endpoints = [ ] ingress . Endpoint { endpoint }
}
}
if len ( upstreams [ name ] . Endpoints ) == 0 {
2017-11-07 16:36:51 +00:00
endp , err := n . serviceEndpoints ( svcKey , path . Backend . ServicePort . String ( ) , & anns . HealthCheck )
2017-09-13 19:15:47 +00:00
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Error obtaining Endpoints for Service %q: %v" , svcKey , err )
2017-09-13 19:15:47 +00:00
continue
}
upstreams [ name ] . Endpoints = endp
}
2017-04-09 23:51:38 +00:00
2018-01-18 19:14:42 +00:00
s , err := n . store . GetService ( svcKey )
2017-04-09 23:51:38 +00:00
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Error obtaining Service %q: %v" , svcKey , err )
2017-04-09 23:51:38 +00:00
continue
}
2017-09-18 23:53:26 +00:00
upstreams [ name ] . Service = s
2016-11-10 22:56:29 +00:00
}
}
}
return upstreams
}
2018-06-13 18:15:45 +00:00
// getServiceClusterEndpoint returns an Endpoint corresponding to the ClusterIP
// field of a Service.
2017-11-05 01:18:28 +00:00
func ( n * NGINXController ) getServiceClusterEndpoint ( svcKey string , backend * extensions . IngressBackend ) ( endpoint ingress . Endpoint , err error ) {
2018-01-18 19:14:42 +00:00
svc , err := n . store . GetService ( svcKey )
if err != nil {
2018-06-13 18:15:45 +00:00
return endpoint , fmt . Errorf ( "service %q does not exist" , svcKey )
2017-07-16 20:19:45 +00:00
}
2017-10-26 23:23:48 +00:00
if svc . Spec . ClusterIP == "" || svc . Spec . ClusterIP == "None" {
2018-06-13 18:15:45 +00:00
return endpoint , fmt . Errorf ( "no ClusterIP found for Service %q" , svcKey )
2017-07-16 20:19:45 +00:00
}
endpoint . Address = svc . Spec . ClusterIP
2017-10-26 22:48:50 +00:00
2018-06-13 18:15:45 +00:00
// if the Service port is referenced by name in the Ingress, lookup the
// actual port in the service spec
2017-10-26 22:48:50 +00:00
if backend . ServicePort . Type == intstr . String {
var port int32 = - 1
for _ , svcPort := range svc . Spec . Ports {
if svcPort . Name == backend . ServicePort . String ( ) {
port = svcPort . Port
break
}
}
if port == - 1 {
2018-06-13 18:15:45 +00:00
return endpoint , fmt . Errorf ( "service %q does not have a port named %q" , svc . Name , backend . ServicePort )
2017-10-26 22:48:50 +00:00
}
endpoint . Port = fmt . Sprintf ( "%d" , port )
} else {
endpoint . Port = backend . ServicePort . String ( )
}
2017-07-16 20:19:45 +00:00
return endpoint , err
}
2018-06-13 18:15:45 +00:00
// serviceEndpoints returns the upstream servers (Endpoints) associated with a
// Service.
2017-11-05 01:18:28 +00:00
func ( n * NGINXController ) serviceEndpoints ( svcKey , backendPort string ,
2017-11-07 16:36:51 +00:00
hz * healthcheck . Config ) ( [ ] ingress . Endpoint , error ) {
2018-01-18 19:14:42 +00:00
svc , err := n . store . GetService ( svcKey )
2017-09-13 19:15:47 +00:00
var upstreams [ ] ingress . Endpoint
2016-11-10 22:56:29 +00:00
if err != nil {
2018-06-13 18:15:45 +00:00
return upstreams , fmt . Errorf ( "error getting Service %q from local store: %v" , svcKey , err )
2016-11-10 22:56:29 +00:00
}
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Obtaining ports information for Service %q" , svcKey )
2016-11-10 22:56:29 +00:00
for _ , servicePort := range svc . Spec . Ports {
2018-06-13 18:15:45 +00:00
// targetPort could be a string, use either the port name or number (int)
2016-11-10 22:56:29 +00:00
if strconv . Itoa ( int ( servicePort . Port ) ) == backendPort ||
servicePort . TargetPort . String ( ) == backendPort ||
servicePort . Name == backendPort {
2018-04-22 01:51:58 +00:00
endps := getEndpoints ( svc , & servicePort , apiv1 . ProtocolTCP , hz , n . store . GetServiceEndpoints )
2016-11-10 22:56:29 +00:00
if len ( endps ) == 0 {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Service %q does not have any active Endpoint." , svcKey )
2016-11-10 22:56:29 +00:00
}
2017-11-05 01:18:28 +00:00
if n . cfg . SortBackends {
2017-09-25 19:40:55 +00:00
sort . SliceStable ( endps , func ( i , j int ) bool {
2017-09-18 23:53:26 +00:00
iName := endps [ i ] . Address
jName := endps [ j ] . Address
if iName != jName {
return iName < jName
}
return endps [ i ] . Port < endps [ j ] . Port
} )
2017-07-13 00:35:36 +00:00
}
2016-11-10 22:56:29 +00:00
upstreams = append ( upstreams , endps ... )
break
}
}
2018-06-13 18:15:45 +00:00
// Ingress with an ExternalName Service and no port defined for that Service
2017-10-26 22:17:18 +00:00
if len ( svc . Spec . Ports ) == 0 && svc . Spec . Type == apiv1 . ServiceTypeExternalName {
externalPort , err := strconv . Atoi ( backendPort )
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Only numeric ports are allowed in ExternalName Services: %q is not a valid port number." , backendPort )
2017-10-26 22:17:18 +00:00
return upstreams , nil
}
servicePort := apiv1 . ServicePort {
Protocol : "TCP" ,
Port : int32 ( externalPort ) ,
TargetPort : intstr . FromString ( backendPort ) ,
}
2018-04-22 01:51:58 +00:00
endps := getEndpoints ( svc , & servicePort , apiv1 . ProtocolTCP , hz , n . store . GetServiceEndpoints )
2017-10-26 22:17:18 +00:00
if len ( endps ) == 0 {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Service %q does not have any active Endpoint." , svcKey )
2017-10-26 22:17:18 +00:00
return upstreams , nil
}
upstreams = append ( upstreams , endps ... )
return upstreams , nil
}
2017-11-05 01:18:28 +00:00
if ! n . cfg . SortBackends {
2017-07-13 00:35:36 +00:00
rand . Seed ( time . Now ( ) . UnixNano ( ) )
for i := range upstreams {
j := rand . Intn ( i + 1 )
upstreams [ i ] , upstreams [ j ] = upstreams [ j ] , upstreams [ i ]
}
2017-06-14 21:33:12 +00:00
}
2016-11-10 22:56:29 +00:00
return upstreams , nil
}
2018-06-13 18:15:45 +00:00
// createServers builds a map of host name to Server structs from a map of
// already computed Upstream structs. Each Server is configured with at least
// one root location, which uses a default backend if left unspecified.
2017-11-05 01:18:28 +00:00
func ( n * NGINXController ) createServers ( data [ ] * extensions . Ingress ,
2017-09-18 23:53:26 +00:00
upstreams map [ string ] * ingress . Backend ,
du * ingress . Backend ) map [ string ] * ingress . Server {
servers := make ( map [ string ] * ingress . Server , len ( data ) )
aliases := make ( map [ string ] string , len ( data ) )
2017-09-13 19:15:47 +00:00
2018-01-18 19:14:42 +00:00
bdef := n . store . GetDefaultBackend ( )
2017-11-07 16:36:51 +00:00
ngxProxy := proxy . Config {
2017-11-13 22:57:41 +00:00
BodySize : bdef . ProxyBodySize ,
ConnectTimeout : bdef . ProxyConnectTimeout ,
SendTimeout : bdef . ProxySendTimeout ,
ReadTimeout : bdef . ProxyReadTimeout ,
BufferSize : bdef . ProxyBufferSize ,
CookieDomain : bdef . ProxyCookieDomain ,
CookiePath : bdef . ProxyCookiePath ,
NextUpstream : bdef . ProxyNextUpstream ,
2018-03-22 11:12:36 +00:00
NextUpstreamTries : bdef . ProxyNextUpstreamTries ,
2017-11-13 22:57:41 +00:00
RequestBuffering : bdef . ProxyRequestBuffering ,
ProxyRedirectFrom : bdef . ProxyRedirectFrom ,
2018-01-29 14:43:55 +00:00
ProxyBuffering : bdef . ProxyBuffering ,
2016-12-29 20:02:06 +00:00
}
2017-09-27 03:46:22 +00:00
// generated on Start() with createDefaultSSLCertificate()
2017-11-05 01:18:28 +00:00
defaultPemFileName := n . cfg . FakeCertificatePath
defaultPemSHA := n . cfg . FakeCertificateSHA
2017-03-06 19:29:33 +00:00
2018-06-13 18:15:45 +00:00
// read custom default SSL certificate, fall back to generated default certificate
2018-04-12 22:26:10 +00:00
defaultCertificate , err := n . store . GetLocalSSLCert ( n . cfg . DefaultSSLCertificate )
2017-08-26 03:46:17 +00:00
if err == nil {
2017-01-26 18:51:55 +00:00
defaultPemFileName = defaultCertificate . PemFileName
defaultPemSHA = defaultCertificate . PemSHA
}
2018-06-13 18:15:45 +00:00
// initialize default server and root location
2016-11-10 22:56:29 +00:00
servers [ defServerName ] = & ingress . Server {
2018-06-11 17:42:40 +00:00
Hostname : defServerName ,
SSLCert : ingress . SSLCert {
PemFileName : defaultPemFileName ,
PemSHA : defaultPemSHA ,
} ,
2016-11-10 22:56:29 +00:00
Locations : [ ] * ingress . Location {
{
Path : rootLocation ,
IsDefBackend : true ,
2017-08-11 02:51:32 +00:00
Backend : du . Name ,
2016-11-10 22:56:29 +00:00
Proxy : ngxProxy ,
2017-08-11 02:51:32 +00:00
Service : du . Service ,
2016-11-10 22:56:29 +00:00
} ,
2017-09-13 19:15:47 +00:00
} }
2016-11-10 22:56:29 +00:00
2018-06-13 18:15:45 +00:00
// initialize all other servers
2017-09-25 19:40:55 +00:00
for _ , ing := range data {
2018-01-18 19:14:42 +00:00
anns , err := n . store . GetIngressAnnotations ( ing )
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Errorf ( "Error reading Ingress %q annotations from local store: %v" , ing . Name , err )
2018-01-18 19:14:42 +00:00
}
2017-09-17 14:54:00 +00:00
2018-06-13 18:15:45 +00:00
// default upstream name
2017-08-11 02:51:32 +00:00
un := du . Name
2017-09-17 14:54:00 +00:00
2017-02-16 17:26:58 +00:00
if ing . Spec . Backend != nil {
2018-06-13 18:15:45 +00:00
defUpstream := fmt . Sprintf ( "%v-%v-%v" , ing . Namespace , ing . Spec . Backend . ServiceName , ing . Spec . Backend . ServicePort . String ( ) )
2017-02-16 17:26:58 +00:00
if backendUpstream , ok := upstreams [ defUpstream ] ; ok {
2018-06-13 18:15:45 +00:00
// use backend specified in Ingress as the default backend for all its rules
2017-08-11 02:51:32 +00:00
un = backendUpstream . Name
2017-09-17 14:54:00 +00:00
2018-06-13 18:15:45 +00:00
// special "catch all" case, Ingress with a backend but no rule
2017-09-17 14:54:00 +00:00
defLoc := servers [ defServerName ] . Locations [ 0 ]
if defLoc . IsDefBackend && len ( ing . Spec . Rules ) == 0 {
2018-06-13 18:15:45 +00:00
glog . Infof ( "Ingress \"%v/%v\" defines a backend but no rule. Using it to configure the catch-all server %q" , ing . Namespace , ing . Name , defServerName )
2017-09-17 14:54:00 +00:00
defLoc . IsDefBackend = false
defLoc . Backend = backendUpstream . Name
defLoc . Service = backendUpstream . Service
2017-11-29 18:04:51 +00:00
defLoc . Ingress = ing
2018-06-13 18:15:45 +00:00
// customize using Ingress annotations
2018-05-31 17:46:35 +00:00
defLoc . Logs = anns . Logs
2017-11-29 18:04:51 +00:00
defLoc . BasicDigestAuth = anns . BasicDigestAuth
defLoc . ClientBodyBufferSize = anns . ClientBodyBufferSize
defLoc . ConfigurationSnippet = anns . ConfigurationSnippet
defLoc . CorsConfig = anns . CorsConfig
defLoc . ExternalAuth = anns . ExternalAuth
defLoc . Proxy = anns . Proxy
defLoc . RateLimit = anns . RateLimit
2018-06-13 18:15:45 +00:00
// TODO: Redirect and rewrite can affect the catch all behavior, skip for now
2017-11-29 18:04:51 +00:00
// defLoc.Redirect = anns.Redirect
// defLoc.Rewrite = anns.Rewrite
defLoc . UpstreamVhost = anns . UpstreamVhost
defLoc . Whitelist = anns . Whitelist
defLoc . Denied = anns . Denied
2018-03-22 03:38:47 +00:00
defLoc . GRPC = anns . GRPC
2018-04-08 20:37:13 +00:00
defLoc . LuaRestyWAF = anns . LuaRestyWAF
2018-05-17 12:25:38 +00:00
defLoc . InfluxDB = anns . InfluxDB
2018-06-13 18:15:45 +00:00
} else {
glog . V ( 3 ) . Infof ( "Ingress \"%v/%v\" defines both a backend and rules. Using its backend as default upstream for all its rules." , ing . Namespace , ing . Name )
2017-09-17 14:54:00 +00:00
}
2017-02-16 17:26:58 +00:00
}
}
2016-11-10 22:56:29 +00:00
for _ , rule := range ing . Spec . Rules {
host := rule . Host
if host == "" {
host = defServerName
}
if _ , ok := servers [ host ] ; ok {
// server already configured
continue
}
2017-04-09 23:51:38 +00:00
2016-11-10 22:56:29 +00:00
servers [ host ] = & ingress . Server {
2016-11-16 18:24:26 +00:00
Hostname : host ,
2016-11-10 22:56:29 +00:00
Locations : [ ] * ingress . Location {
{
2017-08-23 17:39:40 +00:00
Path : rootLocation ,
IsDefBackend : true ,
Backend : un ,
Proxy : ngxProxy ,
2017-09-17 18:42:31 +00:00
Service : & apiv1 . Service { } ,
2016-11-10 22:56:29 +00:00
} ,
2017-09-18 23:53:26 +00:00
} ,
2017-11-07 16:36:51 +00:00
SSLPassthrough : anns . SSLPassthrough ,
2018-01-31 16:53:07 +00:00
SSLCiphers : anns . SSLCiphers ,
2017-09-18 23:53:26 +00:00
}
2016-11-10 22:56:29 +00:00
}
}
2017-08-10 03:22:54 +00:00
// configure default location, alias, and SSL
2017-09-25 19:40:55 +00:00
for _ , ing := range data {
2018-01-18 19:14:42 +00:00
anns , err := n . store . GetIngressAnnotations ( ing )
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Errorf ( "Error reading Ingress %q annotations from local store: %v" , ing . Name , err )
2018-01-18 19:14:42 +00:00
}
2017-08-10 03:22:54 +00:00
2016-11-10 22:56:29 +00:00
for _ , rule := range ing . Spec . Rules {
host := rule . Host
if host == "" {
host = defServerName
}
2017-11-07 16:36:51 +00:00
if anns . Alias != "" {
2017-10-27 11:46:32 +00:00
if servers [ host ] . Alias == "" {
2017-11-07 16:36:51 +00:00
servers [ host ] . Alias = anns . Alias
if _ , ok := aliases [ "Alias" ] ; ! ok {
aliases [ "Alias" ] = host
2017-10-27 11:46:32 +00:00
}
} else {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Aliases already configured for server %q, skipping (Ingress \"%v/%v\")" ,
host , ing . Namespace , ing . Name )
2017-09-18 23:53:26 +00:00
}
}
2017-08-10 03:22:54 +00:00
2018-06-13 18:15:45 +00:00
if anns . ServerSnippet != "" {
if servers [ host ] . ServerSnippet == "" {
servers [ host ] . ServerSnippet = anns . ServerSnippet
} else {
glog . Warningf ( "Server snippet already configured for server %q, skipping (Ingress \"%v/%v\")" ,
host , ing . Namespace , ing . Name )
}
2017-09-27 06:59:10 +00:00
}
2018-06-13 18:15:45 +00:00
// only add SSL ciphers if the server does not have them previously configured
2018-01-31 16:53:07 +00:00
if servers [ host ] . SSLCiphers == "" && anns . SSLCiphers != "" {
servers [ host ] . SSLCiphers = anns . SSLCiphers
}
2016-11-24 00:14:14 +00:00
// only add a certificate if the server does not have one previously configured
2018-06-11 17:42:40 +00:00
if servers [ host ] . SSLCert . PemFileName != "" {
2017-08-29 19:40:03 +00:00
continue
}
if len ( ing . Spec . TLS ) == 0 {
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Ingress \"%v/%v\" does not contains a TLS section." , ing . Namespace , ing . Name )
2017-07-12 17:31:15 +00:00
continue
}
2016-12-25 19:48:10 +00:00
2018-04-21 21:25:53 +00:00
tlsSecretName := extractTLSSecretName ( host , ing , n . store . GetLocalSSLCert )
2017-03-16 15:07:07 +00:00
2017-07-12 17:31:15 +00:00
if tlsSecretName == "" {
2018-06-13 18:15:45 +00:00
glog . V ( 3 ) . Infof ( "Host %q is listed in the TLS section but secretName is empty. Using default certificate." , host )
2018-06-11 17:42:40 +00:00
servers [ host ] . SSLCert . PemFileName = defaultPemFileName
servers [ host ] . SSLCert . PemSHA = defaultPemSHA
2017-07-12 17:31:15 +00:00
continue
}
2017-06-12 12:45:08 +00:00
2017-07-12 17:31:15 +00:00
key := fmt . Sprintf ( "%v/%v" , ing . Namespace , tlsSecretName )
2018-04-12 22:26:10 +00:00
cert , err := n . store . GetLocalSSLCert ( key )
2018-01-18 19:14:42 +00:00
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "SSL certificate %q does not exist in local store." , key )
2017-07-12 17:31:15 +00:00
continue
}
2017-05-12 00:50:43 +00:00
2017-08-10 03:27:57 +00:00
err = cert . Certificate . VerifyHostname ( host )
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Unexpected error validating SSL certificate %q for server %q: %v" , key , host , err )
2017-11-29 21:02:10 +00:00
glog . Warningf ( "Validating certificate against DNS names. This will be deprecated in a future version." )
2018-06-13 18:15:45 +00:00
// check the Common Name field
2017-11-29 21:02:10 +00:00
// https://github.com/golang/go/issues/22922
err := verifyHostname ( host , cert . Certificate )
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "SSL certificate %q does not contain a Common Name or Subject Alternative Name for server %q: %v" , key , host , err )
2017-11-29 21:02:10 +00:00
continue
}
2017-07-12 17:31:15 +00:00
}
2017-05-12 00:50:43 +00:00
2018-06-11 17:42:40 +00:00
servers [ host ] . SSLCert = * cert
2017-07-12 17:31:15 +00:00
if cert . ExpireTime . Before ( time . Now ( ) . Add ( 240 * time . Hour ) ) {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "SSL certificate for server %q is about to expire (%v)" , cert . ExpireTime )
2016-11-10 22:56:29 +00:00
}
}
}
2017-09-18 23:53:26 +00:00
for alias , host := range aliases {
if _ , ok := servers [ alias ] ; ok {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "Conflicting hostname (%v) and alias (%v) in server %q. Removing alias to avoid conflicts." , alias , host )
2017-09-18 23:53:26 +00:00
servers [ host ] . Alias = ""
}
}
2017-09-20 09:35:16 +00:00
2016-11-10 22:56:29 +00:00
return servers
}
2018-06-13 18:15:45 +00:00
// extractTLSSecretName returns the name of the Secret containing a SSL
// certificate for the given host name, or an empty string.
2018-04-21 21:25:53 +00:00
func extractTLSSecretName ( host string , ing * extensions . Ingress ,
getLocalSSLCert func ( string ) ( * ingress . SSLCert , error ) ) string {
2018-06-13 18:15:45 +00:00
2018-04-21 21:25:53 +00:00
if ing == nil {
return ""
}
2018-06-13 18:15:45 +00:00
// naively return Secret name from TLS spec if host name matches
2018-04-21 21:25:53 +00:00
for _ , tls := range ing . Spec . TLS {
if sets . NewString ( tls . Hosts ... ) . Has ( host ) {
return tls . SecretName
}
}
2018-06-13 18:15:45 +00:00
// no TLS host matching host name, try each TLS host for matching CN
2018-04-21 21:25:53 +00:00
for _ , tls := range ing . Spec . TLS {
key := fmt . Sprintf ( "%v/%v" , ing . Namespace , tls . SecretName )
cert , err := getLocalSSLCert ( key )
if err != nil {
2018-06-13 18:15:45 +00:00
glog . Warningf ( "SSL certificate %q does not exist in local store." , key )
2018-04-21 21:25:53 +00:00
continue
}
if cert == nil {
continue
}
if sets . NewString ( cert . CN ... ) . Has ( host ) {
return tls . SecretName
}
}
return ""
}