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 .
* /
2017-10-06 20:33:32 +00:00
package controller
2016-11-10 22:56:29 +00:00
import (
"bytes"
2020-12-02 14:23:39 +00:00
"crypto/tls"
2018-03-18 13:13:41 +00:00
"encoding/json"
2016-11-29 01:39:17 +00:00
"errors"
2016-11-10 22:56:29 +00:00
"fmt"
2016-11-29 01:39:17 +00:00
"net"
2018-03-18 13:13:41 +00:00
"net/http"
2016-11-10 22:56:29 +00:00
"os"
"os/exec"
2018-04-24 18:02:52 +00:00
"path/filepath"
2019-08-15 18:57:51 +00:00
"reflect"
2017-03-28 16:39:44 +00:00
"strconv"
2017-04-09 23:51:38 +00:00
"strings"
2017-11-05 01:18:28 +00:00
"sync"
2016-11-29 01:39:17 +00:00
"syscall"
2018-06-21 22:15:18 +00:00
"text/template"
2016-11-29 01:39:17 +00:00
"time"
2016-11-10 22:56:29 +00:00
2018-01-18 19:14:42 +00:00
proxyproto "github.com/armon/go-proxyproto"
2018-02-14 01:46:18 +00:00
"github.com/eapache/channels"
2017-09-17 18:42:31 +00:00
apiv1 "k8s.io/api/core/v1"
2018-11-16 16:48:47 +00:00
"k8s.io/apimachinery/pkg/util/intstr"
2019-01-09 03:33:16 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2017-11-05 01:18:28 +00:00
"k8s.io/client-go/kubernetes/scheme"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/flowcontrol"
2019-05-18 10:08:05 +00:00
2020-06-19 06:04:38 +00:00
adm_controller "k8s.io/ingress-nginx/internal/admission/controller"
2017-11-07 22:02:12 +00:00
"k8s.io/ingress-nginx/internal/ingress"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/ingress/controller/process"
2018-01-18 19:14:42 +00:00
"k8s.io/ingress-nginx/internal/ingress/controller/store"
2017-11-07 22:02:12 +00:00
ngx_template "k8s.io/ingress-nginx/internal/ingress/controller/template"
2018-07-07 17:46:18 +00:00
"k8s.io/ingress-nginx/internal/ingress/metric"
2017-11-07 22:02:12 +00:00
"k8s.io/ingress-nginx/internal/ingress/status"
ing_net "k8s.io/ingress-nginx/internal/net"
"k8s.io/ingress-nginx/internal/net/dns"
"k8s.io/ingress-nginx/internal/net/ssl"
2019-01-21 14:29:36 +00:00
"k8s.io/ingress-nginx/internal/nginx"
2017-11-07 22:02:12 +00:00
"k8s.io/ingress-nginx/internal/task"
2017-11-22 13:40:54 +00:00
"k8s.io/ingress-nginx/internal/watch"
2022-07-20 18:53:44 +00:00
"k8s.io/ingress-nginx/pkg/util/file"
klog "k8s.io/klog/v2"
2016-11-29 01:39:17 +00:00
)
const (
2019-01-21 14:29:36 +00:00
tempNginxPattern = "nginx-cfg"
2019-09-24 13:53:22 +00:00
emptyUID = "-1"
2016-11-10 22:56:29 +00:00
)
2017-10-06 20:33:32 +00:00
// NewNGINXController creates a new NGINX Ingress controller.
2019-08-13 21:14:55 +00:00
func NewNGINXController ( config * Configuration , mc metric . Collector ) * NGINXController {
2017-11-05 01:18:28 +00:00
eventBroadcaster := record . NewBroadcaster ( )
2018-12-05 16:27:55 +00:00
eventBroadcaster . StartLogging ( klog . Infof )
2017-11-05 01:18:28 +00:00
eventBroadcaster . StartRecordingToSink ( & v1core . EventSinkImpl {
Interface : config . Client . CoreV1 ( ) . Events ( config . Namespace ) ,
} )
2017-04-11 14:47:49 +00:00
h , err := dns . GetSystemNameServers ( )
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Error reading system nameservers: %v" , err )
2017-04-11 14:47:49 +00:00
}
2017-03-12 15:27:05 +00:00
n := & NGINXController {
2017-11-05 01:18:28 +00:00
isIPV6Enabled : ing_net . IsIPv6Enabled ( ) ,
2017-08-29 19:40:03 +00:00
resolver : h ,
2017-11-05 01:18:28 +00:00
cfg : config ,
2017-12-05 19:07:34 +00:00
syncRateLimiter : flowcontrol . NewTokenBucketRateLimiter ( config . SyncRateLimit , 1 ) ,
2017-11-05 01:18:28 +00:00
recorder : eventBroadcaster . NewRecorder ( scheme . Scheme , apiv1 . EventSource {
Component : "nginx-ingress-controller" ,
} ) ,
stopCh : make ( chan struct { } ) ,
2018-02-14 01:46:18 +00:00
updateCh : channels . NewRingChannel ( 1024 ) ,
2018-01-18 19:14:42 +00:00
2019-08-22 14:25:47 +00:00
ngxErrCh : make ( chan error ) ,
2017-11-05 01:18:28 +00:00
stopLock : & sync . Mutex { } ,
2017-11-06 22:34:30 +00:00
2018-06-13 18:15:45 +00:00
runningConfig : new ( ingress . Configuration ) ,
2018-01-23 20:10:02 +00:00
Proxy : & TCPProxy { } ,
2018-07-07 17:46:18 +00:00
metricCollector : mc ,
2019-02-21 19:45:21 +00:00
command : NewNginxCommand ( ) ,
}
if n . cfg . ValidationWebhook != "" {
n . validationWebhookServer = & http . Server {
2022-06-15 17:19:30 +00:00
Addr : config . ValidationWebhook ,
//G112 (CWE-400): Potential Slowloris Attack
ReadHeaderTimeout : 10 * time . Second ,
Handler : adm_controller . NewAdmissionControllerServer ( & adm_controller . IngressAdmission { Checker : n } ) ,
TLSConfig : ssl . NewTLSListener ( n . cfg . ValidationWebhookCertPath , n . cfg . ValidationWebhookKeyPath ) . TLSConfig ( ) ,
2020-12-02 14:23:39 +00:00
// disable http/2
// https://github.com/kubernetes/kubernetes/issues/80313
// https://github.com/kubernetes/ingress-nginx/issues/6323#issuecomment-737239159
TLSNextProto : make ( map [ string ] func ( * http . Server , * tls . Conn , http . Handler ) ) ,
2019-02-21 19:45:21 +00:00
}
2017-04-09 23:51:38 +00:00
}
2018-01-25 13:46:20 +00:00
n . store = store . New (
2018-01-18 19:14:42 +00:00
config . Namespace ,
2021-11-12 19:46:28 +00:00
config . WatchNamespaceSelector ,
2018-01-18 19:14:42 +00:00
config . ConfigMapName ,
2018-11-16 16:48:47 +00:00
config . TCPConfigMapName ,
config . UDPConfigMapName ,
2018-01-25 13:46:20 +00:00
config . DefaultSSLCertificate ,
2018-01-18 19:14:42 +00:00
config . ResyncPeriod ,
config . Client ,
2018-06-04 21:48:30 +00:00
n . updateCh ,
2021-08-21 20:42:00 +00:00
config . DisableCatchAll ,
2022-04-11 14:06:07 +00:00
config . DeepInspector ,
2021-08-21 20:42:00 +00:00
config . IngressClassConfiguration )
2017-11-07 16:36:51 +00:00
2017-11-05 01:18:28 +00:00
n . syncQueue = task . NewTaskQueue ( n . syncIngress )
2019-03-08 00:20:34 +00:00
2017-11-05 01:18:28 +00:00
if config . UpdateStatus {
2020-09-26 23:27:19 +00:00
n . syncStatus = status . NewStatusSyncer ( status . Config {
2017-11-05 01:18:28 +00:00
Client : config . Client ,
2017-11-06 01:22:49 +00:00
PublishService : config . PublishService ,
2018-02-27 03:02:19 +00:00
PublishStatusAddress : config . PublishStatusAddress ,
2018-01-18 19:14:42 +00:00
IngressLister : n . store ,
2017-11-05 01:18:28 +00:00
UpdateStatusOnShutdown : config . UpdateStatusOnShutdown ,
2017-11-06 01:22:49 +00:00
UseNodeInternalIP : config . UseNodeInternalIP ,
2017-11-05 01:18:28 +00:00
} )
} else {
2018-12-05 16:27:55 +00:00
klog . Warning ( "Update of Ingress status is disabled (flag --update-status)" )
2017-11-05 01:18:28 +00:00
}
2018-04-25 21:53:49 +00:00
onTemplateChange := func ( ) {
2019-08-13 21:14:55 +00:00
template , err := ngx_template . NewTemplate ( nginx . TemplatePath )
2016-11-10 22:56:29 +00:00
if err != nil {
// this error is different from the rest because it must be clear why nginx is not working
2020-09-27 20:32:40 +00:00
klog . ErrorS ( err , "Error loading new template" )
2016-11-10 22:56:29 +00:00
return
}
n . t = template
2020-09-27 20:32:40 +00:00
klog . InfoS ( "New NGINX configuration template loaded" )
2018-06-21 14:50:57 +00:00
n . syncQueue . EnqueueTask ( task . GetDummyObject ( "template-change" ) )
2016-11-10 22:56:29 +00:00
}
2019-08-13 21:14:55 +00:00
ngxTpl , err := ngx_template . NewTemplate ( nginx . TemplatePath )
2016-11-10 22:56:29 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "Invalid NGINX configuration template: %v" , err )
2016-11-10 22:56:29 +00:00
}
n . t = ngxTpl
2016-11-29 01:39:17 +00:00
2019-08-13 21:14:55 +00:00
_ , err = watch . NewFileWatcher ( nginx . TemplatePath , onTemplateChange )
2018-06-16 20:22:59 +00:00
if err != nil {
2019-08-13 21:14:55 +00:00
klog . Fatalf ( "Error creating file watcher for %v: %v" , nginx . TemplatePath , err )
2018-06-16 20:22:59 +00:00
}
2018-02-17 20:24:50 +00:00
2018-06-16 20:22:59 +00:00
filesToWatch := [ ] string { }
err = filepath . Walk ( "/etc/nginx/geoip/" , func ( path string , info os . FileInfo , err error ) error {
2018-02-17 20:24:50 +00:00
if err != nil {
2018-06-16 20:22:59 +00:00
return err
}
if info . IsDir ( ) {
return nil
2018-02-17 20:24:50 +00:00
}
2018-06-16 20:22:59 +00:00
filesToWatch = append ( filesToWatch , path )
return nil
} )
2018-02-20 16:27:02 +00:00
2018-06-16 20:22:59 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "Error creating file watchers: %v" , err )
2018-06-16 20:22:59 +00:00
}
2018-02-17 20:24:50 +00:00
2018-06-16 20:22:59 +00:00
for _ , f := range filesToWatch {
_ , err = watch . NewFileWatcher ( f , func ( ) {
2020-09-27 20:32:40 +00:00
klog . InfoS ( "File changed detected. Reloading NGINX" , "path" , f )
2018-06-21 14:50:57 +00:00
n . syncQueue . EnqueueTask ( task . GetDummyObject ( "file-change" ) )
2018-02-17 20:24:50 +00:00
} )
2017-11-22 13:40:54 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "Error creating file watcher for %v: %v" , f , err )
2018-02-17 20:24:50 +00:00
}
2017-11-22 13:40:54 +00:00
}
2017-08-28 16:06:58 +00:00
return n
2016-11-10 22:56:29 +00:00
}
2018-06-13 18:15:45 +00:00
// NGINXController describes a NGINX Ingress controller.
2016-11-10 22:56:29 +00:00
type NGINXController struct {
2017-11-05 01:18:28 +00:00
cfg * Configuration
recorder record . EventRecorder
syncQueue * task . Queue
2019-03-08 00:20:34 +00:00
syncStatus status . Syncer
2017-11-05 01:18:28 +00:00
syncRateLimiter flowcontrol . RateLimiter
2018-06-13 18:15:45 +00:00
// stopLock is used to enforce that only a single call to Stop send at
// a given time. We allow stopping through an HTTP endpoint and
2017-11-05 01:18:28 +00:00
// allowing concurrent stoppers leads to stack traces.
stopLock * sync . Mutex
2018-01-18 19:14:42 +00:00
stopCh chan struct { }
2018-02-14 01:46:18 +00:00
updateCh * channels . RingChannel
2017-11-05 01:18:28 +00:00
2018-06-13 18:15:45 +00:00
// ngxErrCh is used to detect errors with the NGINX processes
2017-11-05 01:18:28 +00:00
ngxErrCh chan error
// runningConfig contains the running configuration in the Backend
runningConfig * ingress . Configuration
2021-08-12 18:13:50 +00:00
t ngx_template . Writer
2016-11-10 22:56:29 +00:00
2017-04-09 23:51:38 +00:00
resolver [ ] net . IP
2017-03-10 13:01:26 +00:00
2017-04-09 18:03:27 +00:00
isIPV6Enabled bool
2017-04-11 14:47:49 +00:00
2017-08-28 16:06:58 +00:00
isShuttingDown bool
2017-11-05 01:18:28 +00:00
Proxy * TCPProxy
2017-08-26 03:46:17 +00:00
2018-01-18 19:14:42 +00:00
store store . Storer
2017-11-06 22:34:30 +00:00
2021-11-02 17:54:34 +00:00
metricCollector metric . Collector
admissionCollector metric . Collector
2019-03-11 16:31:38 +00:00
2019-02-21 19:45:21 +00:00
validationWebhookServer * http . Server
command NginxExecTester
2016-11-10 22:56:29 +00:00
}
2018-06-13 18:15:45 +00:00
// Start starts a new NGINX master process running in the foreground.
2017-03-12 15:27:05 +00:00
func ( n * NGINXController ) Start ( ) {
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Starting NGINX Ingress controller" )
2017-11-05 01:18:28 +00:00
2018-01-18 19:14:42 +00:00
n . store . Run ( n . stopCh )
2017-11-13 01:43:28 +00:00
2019-03-11 15:57:28 +00:00
// we need to use the defined ingress class to allow multiple leaders
// in order to update information about ingress status
2021-08-21 20:42:00 +00:00
// TODO: For now, as the the IngressClass logics has changed, is up to the
// cluster admin to create different Leader Election IDs.
// Should revisit this in a future
electionID := n . cfg . ElectionID
2019-03-11 15:57:28 +00:00
2019-03-08 00:20:34 +00:00
setupLeaderElection ( & leaderElectionConfig {
Client : n . cfg . Client ,
2019-03-11 15:57:28 +00:00
ElectionID : electionID ,
2019-03-08 00:20:34 +00:00
OnStartedLeading : func ( stopCh chan struct { } ) {
if n . syncStatus != nil {
go n . syncStatus . Run ( stopCh )
}
2019-03-10 22:12:33 +00:00
2019-03-11 15:57:28 +00:00
n . metricCollector . OnStartedLeading ( electionID )
2019-03-11 16:31:38 +00:00
// manually update SSL expiration metrics
// (to not wait for a reload)
n . metricCollector . SetSSLExpireTime ( n . runningConfig . Servers )
Add a certificate info metric (#8253)
When the ingress controller loads certificates (new ones or following a
secret update), it performs a series of check to ensure its validity.
In our systems, we detected a case where, when the secret object is
compromised, for example when the certificate does not match the secret
key, different pods of the ingress controller are serving a different
version of the certificate.
This behaviour is due to the cache mechanism of the ingress controller,
keeping the last known certificate in case of corruption. When this
happens, old ingress-controller pods will keep serving the old one,
while new pods, by failing to load the corrupted certificates, would
use the default certificate, causing invalid certificates for its
clients.
This generates a random error on the client side, depending on the
actual pod instance it reaches.
In order to allow detecting occurences of those situations, add a metric
to expose, for all ingress controlller pods, detailed informations of
the currently loaded certificate.
This will, for example, allow setting an alert when there is a
certificate discrepency across all ingress controller pods using a query
similar to `sum(nginx_ingress_controller_ssl_certificate_info{host="name.tld"})by(serial_number)`
This also allows to catch other exceptions loading certificates (failing
to load the certificate from the k8s API, ...
Co-authored-by: Daniel Ricart <danielricart@users.noreply.github.com>
Co-authored-by: Daniel Ricart <danielricart@users.noreply.github.com>
2022-02-24 15:08:32 +00:00
n . metricCollector . SetSSLInfo ( n . runningConfig . Servers )
2019-03-08 00:20:34 +00:00
} ,
OnStoppedLeading : func ( ) {
2019-03-11 15:57:28 +00:00
n . metricCollector . OnStoppedLeading ( electionID )
2019-03-08 00:20:34 +00:00
} ,
} )
2017-11-05 01:18:28 +00:00
2019-02-21 19:45:21 +00:00
cmd := n . command . ExecCommand ( )
2017-08-28 16:06:58 +00:00
2018-06-13 18:15:45 +00:00
// put NGINX in another process group to prevent it
2017-08-28 16:06:58 +00:00
// to receive signals meant for the controller
cmd . SysProcAttr = & syscall . SysProcAttr {
Setpgid : true ,
Pgid : 0 ,
}
2018-01-18 19:14:42 +00:00
if n . cfg . EnableSSLPassthrough {
n . setupSSLProxy ( )
}
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Starting NGINX process" )
2017-11-05 01:18:28 +00:00
n . start ( cmd )
2016-11-29 01:39:17 +00:00
2017-11-27 22:22:59 +00:00
go n . syncQueue . Run ( time . Second , n . stopCh )
2017-11-05 01:18:28 +00:00
// force initial sync
2018-06-21 14:50:57 +00:00
n . syncQueue . EnqueueTask ( task . GetDummyObject ( "initial-sync" ) )
2017-08-28 16:06:58 +00:00
2018-12-03 17:27:29 +00:00
// In case of error the temporal configuration file will
// be available up to five minutes after the error
go func ( ) {
for {
time . Sleep ( 5 * time . Minute )
err := cleanTempNginxCfg ( )
if err != nil {
2020-09-27 20:32:40 +00:00
klog . ErrorS ( err , "Unexpected error removing temporal configuration files" )
2018-12-03 17:27:29 +00:00
}
}
} ( )
2019-02-21 19:45:21 +00:00
if n . validationWebhookServer != nil {
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Starting validation webhook" , "address" , n . validationWebhookServer . Addr ,
"certPath" , n . cfg . ValidationWebhookCertPath , "keyPath" , n . cfg . ValidationWebhookKeyPath )
2019-02-21 19:45:21 +00:00
go func ( ) {
2020-09-27 20:32:40 +00:00
klog . ErrorS ( n . validationWebhookServer . ListenAndServeTLS ( "" , "" ) , "Error listening for TLS connections" )
2019-02-21 19:45:21 +00:00
} ( )
}
2017-11-05 01:18:28 +00:00
for {
select {
2018-03-03 12:23:06 +00:00
case err := <- n . ngxErrCh :
2017-11-05 01:18:28 +00:00
if n . isShuttingDown {
2020-01-25 17:52:31 +00:00
return
2016-11-29 01:39:17 +00:00
}
2017-09-28 14:52:12 +00:00
2020-06-23 20:07:48 +00:00
// if the nginx master process dies, the workers continue to process requests
// until the failure of the configured livenessProbe and restart of the pod.
2017-11-05 01:18:28 +00:00
if process . IsRespawnIfRequired ( err ) {
2020-05-31 16:54:51 +00:00
return
2017-09-28 14:52:12 +00:00
}
2020-05-31 16:54:51 +00:00
2018-02-14 01:46:18 +00:00
case event := <- n . updateCh . Out ( ) :
2018-01-18 19:14:42 +00:00
if n . isShuttingDown {
break
}
2020-01-25 17:52:31 +00:00
2018-02-14 01:46:18 +00:00
if evt , ok := event . ( store . Event ) ; ok {
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Event received" , "type" , evt . Type , "object" , evt . Obj )
2018-02-14 01:46:18 +00:00
if evt . Type == store . ConfigurationEvent {
2018-06-21 14:50:57 +00:00
// TODO: is this necessary? Consider removing this special case
n . syncQueue . EnqueueTask ( task . GetDummyObject ( "configmap-change" ) )
continue
2018-02-14 01:46:18 +00:00
}
2018-06-21 14:50:57 +00:00
n . syncQueue . EnqueueSkippableTask ( evt . Obj )
2018-02-14 01:46:18 +00:00
} else {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Unexpected event type received %T" , event )
2018-01-18 23:04:40 +00:00
}
2017-11-05 01:18:28 +00:00
case <- n . stopCh :
2020-01-25 17:52:31 +00:00
return
2016-11-29 01:39:17 +00:00
}
}
}
2017-08-28 16:06:58 +00:00
// Stop gracefully stops the NGINX master process.
func ( n * NGINXController ) Stop ( ) error {
n . isShuttingDown = true
2017-11-05 01:18:28 +00:00
n . stopLock . Lock ( )
defer n . stopLock . Unlock ( )
if n . syncQueue . IsShuttingDown ( ) {
return fmt . Errorf ( "shutdown already in progress" )
}
2020-07-06 19:05:16 +00:00
time . Sleep ( time . Duration ( n . cfg . ShutdownGracePeriod ) * time . Second )
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Shutting down controller queues" )
2017-11-05 01:18:28 +00:00
close ( n . stopCh )
go n . syncQueue . Shutdown ( )
if n . syncStatus != nil {
n . syncStatus . Shutdown ( )
}
2017-08-28 16:06:58 +00:00
2019-02-21 19:45:21 +00:00
if n . validationWebhookServer != nil {
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Stopping admission controller" )
2019-02-21 19:45:21 +00:00
err := n . validationWebhookServer . Close ( )
if err != nil {
return err
}
}
2018-06-12 13:04:26 +00:00
// send stop signal to NGINX
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Stopping NGINX process" )
2019-02-21 19:45:21 +00:00
cmd := n . command . ExecCommand ( "-s" , "quit" )
2017-08-28 16:06:58 +00:00
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
err := cmd . Run ( )
if err != nil {
return err
}
2018-06-13 18:15:45 +00:00
// wait for the NGINX process to terminate
2017-11-05 01:18:28 +00:00
timer := time . NewTicker ( time . Second * 1 )
2017-11-06 22:38:16 +00:00
for range timer . C {
2019-08-31 00:18:11 +00:00
if ! nginx . IsRunning ( ) {
2020-09-27 20:32:40 +00:00
klog . InfoS ( "NGINX process has stopped" )
2017-11-05 01:18:28 +00:00
timer . Stop ( )
break
}
}
2017-08-28 16:06:58 +00:00
return nil
}
2017-11-05 01:18:28 +00:00
func ( n * NGINXController ) start ( cmd * exec . Cmd ) {
2016-11-10 22:56:29 +00:00
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
if err := cmd . Start ( ) ; err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "NGINX error: %v" , err )
2017-11-05 01:18:28 +00:00
n . ngxErrCh <- err
2016-11-29 01:39:17 +00:00
return
2016-11-10 22:56:29 +00:00
}
2017-03-10 13:01:26 +00:00
2016-11-29 01:39:17 +00:00
go func ( ) {
2017-11-05 01:18:28 +00:00
n . ngxErrCh <- cmd . Wait ( )
2016-11-29 01:39:17 +00:00
} ( )
2016-11-10 22:56:29 +00:00
}
2017-08-24 13:33:26 +00:00
// DefaultEndpoint returns the default endpoint to be use as default server that returns 404.
func ( n NGINXController ) DefaultEndpoint ( ) ingress . Endpoint {
return ingress . Endpoint {
Address : "127.0.0.1" ,
2017-11-05 01:18:28 +00:00
Port : fmt . Sprintf ( "%v" , n . cfg . ListenPorts . Default ) ,
2017-09-17 18:42:31 +00:00
Target : & apiv1 . ObjectReference { } ,
2017-08-24 13:33:26 +00:00
}
}
2019-02-21 19:45:21 +00:00
// generateTemplate returns the nginx configuration file content
func ( n NGINXController ) generateTemplate ( cfg ngx_config . Configuration , ingressCfg ingress . Configuration ) ( [ ] byte , error ) {
2017-03-10 13:01:26 +00:00
2018-01-23 20:10:02 +00:00
if n . cfg . EnableSSLPassthrough {
servers := [ ] * TCPServer { }
for _ , pb := range ingressCfg . PassthroughBackends {
svc := pb . Service
if svc == nil {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Missing Service for SSL Passthrough backend %q" , pb . Backend )
2018-01-23 20:10:02 +00:00
continue
2017-05-26 18:25:06 +00:00
}
2020-12-04 12:40:42 +00:00
port , err := strconv . Atoi ( pb . Port . String ( ) ) // #nosec
2018-01-23 20:10:02 +00:00
if err != nil {
for _ , sp := range svc . Spec . Ports {
if sp . Name == pb . Port . String ( ) {
port = int ( sp . Port )
break
}
}
} else {
for _ , sp := range svc . Spec . Ports {
if sp . Port == int32 ( port ) {
port = int ( sp . Port )
break
}
2017-05-26 18:25:06 +00:00
}
}
2018-01-23 20:10:02 +00:00
2018-06-13 18:15:45 +00:00
// TODO: Allow PassthroughBackends to specify they support proxy-protocol
2018-01-23 20:10:02 +00:00
servers = append ( servers , & TCPServer {
Hostname : pb . Hostname ,
IP : svc . Spec . ClusterIP ,
Port : port ,
ProxyProtocol : false ,
} )
2017-05-26 18:25:06 +00:00
}
2018-01-23 20:10:02 +00:00
n . Proxy . ServerList = servers
2017-05-26 18:25:06 +00:00
}
2018-06-12 13:04:26 +00:00
// NGINX cannot resize the hash tables used to store server names. For
// this reason we check if the current size is correct for the host
// names defined in the Ingress rules and adjust the value if
// necessary.
2016-11-10 22:56:29 +00:00
// https://trac.nginx.org/nginx/ticket/352
// https://trac.nginx.org/nginx/ticket/631
2017-08-16 07:02:30 +00:00
var longestName int
var serverNameBytes int
2019-01-09 03:33:16 +00:00
2017-08-16 07:02:30 +00:00
for _ , srv := range ingressCfg . Servers {
2020-01-31 16:01:28 +00:00
hostnameLength := len ( srv . Hostname )
if srv . RedirectFromToWWW {
hostnameLength += 4
2017-08-16 07:02:30 +00:00
}
2020-01-31 16:01:28 +00:00
if longestName < hostnameLength {
longestName = hostnameLength
}
for _ , alias := range srv . Aliases {
if longestName < len ( alias ) {
longestName = len ( alias )
}
}
serverNameBytes += hostnameLength
2017-08-16 07:02:30 +00:00
}
2019-01-09 03:33:16 +00:00
2020-01-31 16:01:28 +00:00
nameHashBucketSize := nginxHashBucketSize ( longestName )
if cfg . ServerNameHashBucketSize < nameHashBucketSize {
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Adjusting ServerNameHashBucketSize variable" , "value" , nameHashBucketSize )
2016-11-10 22:56:29 +00:00
cfg . ServerNameHashBucketSize = nameHashBucketSize
}
2019-01-09 03:33:16 +00:00
2017-05-24 00:02:12 +00:00
serverNameHashMaxSize := nextPowerOf2 ( serverNameBytes )
if cfg . ServerNameHashMaxSize < serverNameHashMaxSize {
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Adjusting ServerNameHashMaxSize variable" , "value" , serverNameHashMaxSize )
2016-11-10 22:56:29 +00:00
cfg . ServerNameHashMaxSize = serverNameHashMaxSize
}
2018-12-27 16:24:09 +00:00
if cfg . MaxWorkerOpenFiles == 0 {
// the limit of open files is per worker process
// and we leave some room to avoid consuming all the FDs available
2021-08-21 20:42:00 +00:00
maxOpenFiles := rlimitMaxNumFiles ( ) - 1024
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Maximum number of open file descriptors" , "value" , maxOpenFiles )
2018-12-27 16:24:09 +00:00
if maxOpenFiles < 1024 {
// this means the value of RLIMIT_NOFILE is too low.
maxOpenFiles = 1024
}
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Adjusting MaxWorkerOpenFiles variable" , "value" , maxOpenFiles )
2018-12-27 16:24:09 +00:00
cfg . MaxWorkerOpenFiles = maxOpenFiles
2017-03-28 16:39:44 +00:00
}
2018-12-27 16:24:09 +00:00
if cfg . MaxWorkerConnections == 0 {
2019-07-08 20:10:38 +00:00
maxWorkerConnections := int ( float64 ( cfg . MaxWorkerOpenFiles * 3.0 / 4 ) )
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Adjusting MaxWorkerConnections variable" , "value" , maxWorkerConnections )
2019-01-15 20:38:30 +00:00
cfg . MaxWorkerConnections = maxWorkerConnections
2017-03-30 13:10:47 +00:00
}
2017-01-19 02:31:33 +00:00
2017-02-07 18:13:08 +00:00
setHeaders := map [ string ] string { }
if cfg . ProxySetHeaders != "" {
2018-01-18 19:14:42 +00:00
cmap , err := n . store . GetConfigMap ( cfg . ProxySetHeaders )
2017-02-07 18:13:08 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Error reading ConfigMap %q from local store: %v" , cfg . ProxySetHeaders , err )
2019-04-02 20:39:42 +00:00
} else {
setHeaders = cmap . Data
2017-02-07 18:13:08 +00:00
}
}
2017-05-18 10:21:03 +00:00
addHeaders := map [ string ] string { }
if cfg . AddHeaders != "" {
2018-01-18 19:14:42 +00:00
cmap , err := n . store . GetConfigMap ( cfg . AddHeaders )
2017-05-18 10:21:03 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Error reading ConfigMap %q from local store: %v" , cfg . AddHeaders , err )
2019-04-02 20:39:42 +00:00
} else {
addHeaders = cmap . Data
2017-05-18 10:21:03 +00:00
}
}
2017-03-08 13:41:55 +00:00
sslDHParam := ""
if cfg . SSLDHParam != "" {
secretName := cfg . SSLDHParam
2018-01-18 19:14:42 +00:00
secret , err := n . store . GetSecret ( secretName )
2017-03-08 13:41:55 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Error reading Secret %q from local store: %v" , secretName , err )
2019-03-05 19:31:33 +00:00
} else {
nsSecName := strings . Replace ( secretName , "/" , "-" , - 1 )
dh , ok := secret . Data [ "dhparam.pem" ]
if ok {
2019-08-13 21:14:55 +00:00
pemFileName , err := ssl . AddOrUpdateDHParam ( nsSecName , dh )
2019-03-05 19:31:33 +00:00
if err != nil {
klog . Warningf ( "Error adding or updating dhparam file %v: %v" , nsSecName , err )
} else {
sslDHParam = pemFileName
}
2017-03-08 13:41:55 +00:00
}
}
}
cfg . SSLDHParam = sslDHParam
2019-08-13 21:14:55 +00:00
cfg . DefaultSSLCertificate = n . getDefaultSSLCertificate ( )
2022-04-09 04:48:04 +00:00
if n . cfg . IsChroot {
if cfg . AccessLogPath == "/var/log/nginx/access.log" {
cfg . AccessLogPath = fmt . Sprintf ( "syslog:server=%s" , n . cfg . InternalLoggerAddress )
}
if cfg . ErrorLogPath == "/var/log/nginx/error.log" {
cfg . ErrorLogPath = fmt . Sprintf ( "syslog:server=%s" , n . cfg . InternalLoggerAddress )
}
}
2017-11-05 01:18:28 +00:00
tc := ngx_config . TemplateConfig {
2019-08-13 21:14:55 +00:00
ProxySetHeaders : setHeaders ,
AddHeaders : addHeaders ,
BacklogSize : sysctlSomaxconn ( ) ,
Backends : ingressCfg . Backends ,
PassthroughBackends : ingressCfg . PassthroughBackends ,
Servers : ingressCfg . Servers ,
TCPBackends : ingressCfg . TCPEndpoints ,
UDPBackends : ingressCfg . UDPEndpoints ,
Cfg : cfg ,
IsIPV6Enabled : n . isIPV6Enabled && ! cfg . DisableIpv6 ,
NginxStatusIpv4Whitelist : cfg . NginxStatusIpv4Whitelist ,
NginxStatusIpv6Whitelist : cfg . NginxStatusIpv6Whitelist ,
RedirectServers : buildRedirects ( ingressCfg . Servers ) ,
IsSSLPassthroughEnabled : n . cfg . EnableSSLPassthrough ,
ListenPorts : n . cfg . ListenPorts ,
PublishService : n . GetPublishService ( ) ,
EnableMetrics : n . cfg . EnableMetrics ,
2020-03-16 07:26:33 +00:00
MaxmindEditionFiles : n . cfg . MaxmindEditionFiles ,
HealthzURI : nginx . HealthPath ,
2020-06-20 06:58:14 +00:00
MonitorMaxBatchSize : n . cfg . MonitorMaxBatchSize ,
2020-03-16 07:26:33 +00:00
PID : nginx . PID ,
StatusPath : nginx . StatusPath ,
StatusPort : nginx . StatusPort ,
StreamPort : nginx . StreamPort ,
2021-12-23 19:46:30 +00:00
StreamSnippets : append ( ingressCfg . StreamSnippets , cfg . StreamSnippet ) ,
2017-08-03 14:51:39 +00:00
}
2018-07-07 17:46:18 +00:00
tc . Cfg . Checksum = ingressCfg . ConfigurationChecksum
2019-02-21 19:45:21 +00:00
return n . t . Write ( tc )
}
// testTemplate checks if the NGINX configuration inside the byte array is valid
// running the command "nginx -t" using a temporal file.
func ( n NGINXController ) testTemplate ( cfg [ ] byte ) error {
if len ( cfg ) == 0 {
return fmt . Errorf ( "invalid NGINX configuration (empty)" )
}
2022-04-09 04:48:04 +00:00
tmpDir := os . TempDir ( ) + "/nginx"
tmpfile , err := os . CreateTemp ( tmpDir , tempNginxPattern )
2019-02-21 19:45:21 +00:00
if err != nil {
return err
}
defer tmpfile . Close ( )
2021-08-06 14:18:17 +00:00
err = os . WriteFile ( tmpfile . Name ( ) , cfg , file . ReadWriteByUser )
2019-02-21 19:45:21 +00:00
if err != nil {
return err
}
out , err := n . command . Test ( tmpfile . Name ( ) )
if err != nil {
// this error is different from the rest because it must be clear why nginx is not working
oe := fmt . Sprintf ( `
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
Error : % v
% v
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
` , err , string ( out ) )
return errors . New ( oe )
}
os . Remove ( tmpfile . Name ( ) )
return nil
}
// OnUpdate is called by the synchronization loop whenever configuration
// changes were detected. The received backend Configuration is merged with the
// configuration ConfigMap before generating the final configuration file.
// Returns nil in case the backend was successfully reloaded.
func ( n * NGINXController ) OnUpdate ( ingressCfg ingress . Configuration ) error {
cfg := n . store . GetBackendConfiguration ( )
cfg . Resolver = n . resolver
content , err := n . generateTemplate ( cfg , ingressCfg )
2017-06-11 19:56:40 +00:00
if err != nil {
return err
}
2020-01-29 15:20:05 +00:00
err = createOpentracingCfg ( cfg )
if err != nil {
return err
2018-06-21 22:15:18 +00:00
}
2017-06-11 19:56:40 +00:00
err = n . testTemplate ( content )
if err != nil {
return err
}
2020-08-08 23:31:02 +00:00
if klog . V ( 2 ) . Enabled ( ) {
2021-08-06 14:18:17 +00:00
src , _ := os . ReadFile ( cfgPath )
2017-11-05 01:18:28 +00:00
if ! bytes . Equal ( src , content ) {
2021-08-06 14:18:17 +00:00
tmpfile , err := os . CreateTemp ( "" , "new-nginx-cfg" )
2017-11-05 01:18:28 +00:00
if err != nil {
return err
}
defer tmpfile . Close ( )
2021-08-06 14:18:17 +00:00
err = os . WriteFile ( tmpfile . Name ( ) , content , file . ReadWriteByUser )
2017-11-05 01:18:28 +00:00
if err != nil {
return err
}
2020-02-21 19:41:03 +00:00
diffOutput , err := exec . Command ( "diff" , "-I" , "'# Configuration.*'" , "-u" , cfgPath , tmpfile . Name ( ) ) . CombinedOutput ( )
2018-12-12 01:08:02 +00:00
if err != nil {
2019-08-08 16:53:23 +00:00
if exitError , ok := err . ( * exec . ExitError ) ; ok {
ws := exitError . Sys ( ) . ( syscall . WaitStatus )
if ws . ExitStatus ( ) == 2 {
klog . Warningf ( "Failed to executing diff command: %v" , err )
}
}
2018-12-12 01:08:02 +00:00
}
2017-11-05 01:18:28 +00:00
2020-09-27 20:32:40 +00:00
klog . InfoS ( "NGINX configuration change" , "diff" , string ( diffOutput ) )
2017-11-05 01:18:28 +00:00
2018-06-13 18:15:45 +00:00
// we do not defer the deletion of temp files in order
// to keep them around for inspection in case of error
2017-11-05 01:18:28 +00:00
os . Remove ( tmpfile . Name ( ) )
}
}
2017-06-11 19:56:40 +00:00
2021-08-06 14:18:17 +00:00
err = os . WriteFile ( cfgPath , content , file . ReadWriteByUser )
2017-02-20 02:34:05 +00:00
if err != nil {
2017-06-11 19:56:40 +00:00
return err
2017-02-20 02:34:05 +00:00
}
2019-02-21 19:45:21 +00:00
o , err := n . command . ExecCommand ( "-s" , "reload" ) . CombinedOutput ( )
2017-06-11 19:56:40 +00:00
if err != nil {
2017-06-23 13:55:45 +00:00
return fmt . Errorf ( "%v\n%v" , err , string ( o ) )
2017-02-20 02:34:05 +00:00
}
2017-06-11 19:56:40 +00:00
return nil
2016-11-10 22:56:29 +00:00
}
2018-06-13 18:15:45 +00:00
// nginxHashBucketSize computes the correct NGINX hash_bucket_size for a hash
// with the given longest key.
2017-04-19 02:29:51 +00:00
func nginxHashBucketSize ( longestString int ) int {
2018-06-13 18:15:45 +00:00
// see https://github.com/kubernetes/ingress-nginxs/issues/623 for an explanation
2017-04-19 02:29:51 +00:00
wordSize := 8 // Assume 64 bit CPU
n := longestString + 2
aligned := ( n + wordSize - 1 ) & ^ ( wordSize - 1 )
rawSize := wordSize + wordSize + aligned
return nextPowerOf2 ( rawSize )
}
2016-11-10 22:56:29 +00:00
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
// https://play.golang.org/p/TVSyCcdxUh
func nextPowerOf2 ( v int ) int {
v --
v |= v >> 1
v |= v >> 2
v |= v >> 4
v |= v >> 8
v |= v >> 16
v ++
return v
}
2018-01-18 19:14:42 +00:00
func ( n * NGINXController ) setupSSLProxy ( ) {
2018-06-04 01:10:41 +00:00
cfg := n . store . GetBackendConfiguration ( )
2018-01-18 19:14:42 +00:00
sslPort := n . cfg . ListenPorts . HTTPS
proxyPort := n . cfg . ListenPorts . SSLProxy
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Starting TLS proxy for SSL Passthrough" )
2018-01-18 19:14:42 +00:00
n . Proxy = & TCPProxy {
Default : & TCPServer {
Hostname : "localhost" ,
IP : "127.0.0.1" ,
Port : proxyPort ,
ProxyProtocol : true ,
} ,
}
listener , err := net . Listen ( "tcp" , fmt . Sprintf ( ":%v" , sslPort ) )
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "%v" , err )
2018-01-18 19:14:42 +00:00
}
2018-06-04 01:10:41 +00:00
proxyList := & proxyproto . Listener { Listener : listener , ProxyHeaderTimeout : cfg . ProxyProtocolHeaderTimeout }
2018-01-18 19:14:42 +00:00
2018-06-13 18:15:45 +00:00
// accept TCP connections on the configured HTTPS port
2018-01-18 19:14:42 +00:00
go func ( ) {
for {
var conn net . Conn
var err error
if n . store . GetBackendConfiguration ( ) . UseProxyProtocol {
2018-06-13 18:15:45 +00:00
// wrap the listener in order to decode Proxy
// Protocol before handling the connection
2018-01-18 19:14:42 +00:00
conn , err = proxyList . Accept ( )
} else {
conn , err = listener . Accept ( )
}
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Error accepting TCP connection: %v" , err )
2018-01-18 19:14:42 +00:00
continue
}
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Handling TCP connection" , "remote" , conn . RemoteAddr ( ) , "local" , conn . LocalAddr ( ) )
2018-01-18 19:14:42 +00:00
go n . Proxy . Handle ( conn )
}
} ( )
}
2018-03-18 13:13:41 +00:00
2018-06-04 21:48:30 +00:00
// Helper function to clear Certificates from the ingress configuration since they should be ignored when
// checking if the new configuration changes can be applied dynamically if dynamic certificates is on
func clearCertificates ( config * ingress . Configuration ) {
var clearedServers [ ] * ingress . Server
for _ , server := range config . Servers {
copyOfServer := * server
2019-09-25 01:36:37 +00:00
copyOfServer . SSLCert = nil
2018-06-04 21:48:30 +00:00
clearedServers = append ( clearedServers , & copyOfServer )
}
config . Servers = clearedServers
}
2019-01-24 14:03:33 +00:00
// Helper function to clear endpoints from the ingress configuration since they should be ignored when
// checking if the new configuration changes can be applied dynamically.
func clearL4serviceEndpoints ( config * ingress . Configuration ) {
var clearedTCPL4Services [ ] ingress . L4Service
var clearedUDPL4Services [ ] ingress . L4Service
for _ , service := range config . TCPEndpoints {
copyofService := ingress . L4Service {
Port : service . Port ,
Backend : service . Backend ,
Endpoints : [ ] ingress . Endpoint { } ,
Service : nil ,
}
clearedTCPL4Services = append ( clearedTCPL4Services , copyofService )
}
for _ , service := range config . UDPEndpoints {
copyofService := ingress . L4Service {
Port : service . Port ,
Backend : service . Backend ,
Endpoints : [ ] ingress . Endpoint { } ,
Service : nil ,
}
clearedUDPL4Services = append ( clearedUDPL4Services , copyofService )
}
config . TCPEndpoints = clearedTCPL4Services
config . UDPEndpoints = clearedUDPL4Services
}
2018-06-13 18:15:45 +00:00
// IsDynamicConfigurationEnough returns whether a Configuration can be
// dynamically applied, without reloading the backend.
2018-04-01 20:09:27 +00:00
func ( n * NGINXController ) IsDynamicConfigurationEnough ( pcfg * ingress . Configuration ) bool {
2018-04-24 18:02:52 +00:00
copyOfRunningConfig := * n . runningConfig
copyOfPcfg := * pcfg
2018-03-18 13:13:41 +00:00
copyOfRunningConfig . Backends = [ ] * ingress . Backend { }
copyOfPcfg . Backends = [ ] * ingress . Backend { }
2019-01-24 14:03:33 +00:00
clearL4serviceEndpoints ( & copyOfRunningConfig )
clearL4serviceEndpoints ( & copyOfPcfg )
2019-08-13 21:14:55 +00:00
clearCertificates ( & copyOfRunningConfig )
clearCertificates ( & copyOfPcfg )
2018-06-04 21:48:30 +00:00
2018-03-18 13:13:41 +00:00
return copyOfRunningConfig . Equal ( & copyOfPcfg )
}
2018-06-13 18:15:45 +00:00
// configureDynamically encodes new Backends in JSON format and POSTs the
// payload to an internal HTTP endpoint handled by Lua.
2019-08-15 18:57:51 +00:00
func ( n * NGINXController ) configureDynamically ( pcfg * ingress . Configuration ) error {
backendsChanged := ! reflect . DeepEqual ( n . runningConfig . Backends , pcfg . Backends )
if backendsChanged {
err := configureBackends ( pcfg . Backends )
if err != nil {
return err
2018-04-24 18:02:52 +00:00
}
2019-08-15 18:57:51 +00:00
}
2018-04-24 18:02:52 +00:00
2019-08-15 18:57:51 +00:00
streamConfigurationChanged := ! reflect . DeepEqual ( n . runningConfig . TCPEndpoints , pcfg . TCPEndpoints ) || ! reflect . DeepEqual ( n . runningConfig . UDPEndpoints , pcfg . UDPEndpoints )
if streamConfigurationChanged {
err := updateStreamConfiguration ( pcfg . TCPEndpoints , pcfg . UDPEndpoints )
if err != nil {
return err
2018-04-24 18:02:52 +00:00
}
2018-03-22 02:47:39 +00:00
}
2019-08-15 18:57:51 +00:00
serversChanged := ! reflect . DeepEqual ( n . runningConfig . Servers , pcfg . Servers )
if serversChanged {
err := configureCertificates ( pcfg . Servers )
if err != nil {
return err
}
2019-01-21 14:29:36 +00:00
}
2019-08-15 18:57:51 +00:00
return nil
}
func updateStreamConfiguration ( TCPEndpoints [ ] ingress . L4Service , UDPEndpoints [ ] ingress . L4Service ) error {
2018-11-16 20:33:56 +00:00
streams := make ( [ ] ingress . Backend , 0 )
2019-08-15 18:57:51 +00:00
for _ , ep := range TCPEndpoints {
2019-01-02 17:32:57 +00:00
var service * apiv1 . Service
if ep . Service != nil {
service = & apiv1 . Service { Spec : ep . Service . Spec }
}
2018-11-16 16:48:47 +00:00
key := fmt . Sprintf ( "tcp-%v-%v-%v" , ep . Backend . Namespace , ep . Backend . Name , ep . Backend . Port . String ( ) )
2018-11-16 20:33:56 +00:00
streams = append ( streams , ingress . Backend {
2018-11-16 16:48:47 +00:00
Name : key ,
Endpoints : ep . Endpoints ,
Port : intstr . FromInt ( ep . Port ) ,
2019-01-02 17:32:57 +00:00
Service : service ,
2018-11-16 16:48:47 +00:00
} )
}
2019-08-15 18:57:51 +00:00
for _ , ep := range UDPEndpoints {
2019-01-02 17:32:57 +00:00
var service * apiv1 . Service
if ep . Service != nil {
service = & apiv1 . Service { Spec : ep . Service . Spec }
}
2018-11-16 16:48:47 +00:00
key := fmt . Sprintf ( "udp-%v-%v-%v" , ep . Backend . Namespace , ep . Backend . Name , ep . Backend . Port . String ( ) )
2018-11-16 20:33:56 +00:00
streams = append ( streams , ingress . Backend {
2018-11-16 16:48:47 +00:00
Name : key ,
Endpoints : ep . Endpoints ,
Port : intstr . FromInt ( ep . Port ) ,
2019-01-02 17:32:57 +00:00
Service : service ,
2018-11-16 16:48:47 +00:00
} )
}
2019-09-08 21:14:54 +00:00
buf , err := json . Marshal ( streams )
2018-11-16 20:52:46 +00:00
if err != nil {
return err
}
2020-09-03 02:01:13 +00:00
hostPort := net . JoinHostPort ( "127.0.0.1" , fmt . Sprintf ( "%v" , nginx . StreamPort ) )
conn , err := net . Dial ( "tcp" , hostPort )
2018-11-27 14:53:51 +00:00
if err != nil {
return err
}
2019-09-08 21:14:54 +00:00
defer conn . Close ( )
2018-11-27 14:53:51 +00:00
2019-08-15 18:57:51 +00:00
_ , err = conn . Write ( buf )
if err != nil {
return err
2019-01-21 14:29:36 +00:00
}
2019-08-15 18:57:51 +00:00
_ , err = fmt . Fprintf ( conn , "\r\n" )
2019-08-13 21:14:55 +00:00
if err != nil {
return err
2018-11-16 20:52:46 +00:00
}
return nil
}
2019-08-15 18:57:51 +00:00
func configureBackends ( rawBackends [ ] * ingress . Backend ) error {
backends := make ( [ ] * ingress . Backend , len ( rawBackends ) )
2018-11-16 20:52:46 +00:00
2019-08-15 18:57:51 +00:00
for i , backend := range rawBackends {
var service * apiv1 . Service
if backend . Service != nil {
service = & apiv1 . Service { Spec : backend . Service . Spec }
}
luaBackend := & ingress . Backend {
Name : backend . Name ,
Port : backend . Port ,
SSLPassthrough : backend . SSLPassthrough ,
SessionAffinity : backend . SessionAffinity ,
UpstreamHashBy : backend . UpstreamHashBy ,
LoadBalancing : backend . LoadBalancing ,
Service : service ,
NoServer : backend . NoServer ,
TrafficShapingPolicy : backend . TrafficShapingPolicy ,
AlternativeBackends : backend . AlternativeBackends ,
}
var endpoints [ ] ingress . Endpoint
for _ , endpoint := range backend . Endpoints {
endpoints = append ( endpoints , ingress . Endpoint {
Address : endpoint . Address ,
Port : endpoint . Port ,
} )
}
luaBackend . Endpoints = endpoints
backends [ i ] = luaBackend
2018-11-16 16:48:47 +00:00
}
2019-08-15 18:57:51 +00:00
statusCode , _ , err := nginx . NewPostStatusRequest ( "/configuration/backends" , "application/json" , backends )
2018-11-16 16:48:47 +00:00
if err != nil {
return err
}
2019-08-15 18:57:51 +00:00
if statusCode != http . StatusCreated {
return fmt . Errorf ( "unexpected error code: %d" , statusCode )
2018-11-16 20:33:56 +00:00
}
2018-06-04 21:48:30 +00:00
return nil
}
2019-08-26 14:58:44 +00:00
type sslConfiguration struct {
Certificates map [ string ] string ` json:"certificates" `
Servers map [ string ] string ` json:"servers" `
}
2018-06-04 21:48:30 +00:00
// configureCertificates JSON encodes certificates and POSTs it to an internal HTTP endpoint
// that is handled by Lua
2019-08-15 18:57:51 +00:00
func configureCertificates ( rawServers [ ] * ingress . Server ) error {
2019-08-26 14:58:44 +00:00
configuration := & sslConfiguration {
Certificates : map [ string ] string { } ,
Servers : map [ string ] string { } ,
}
2018-06-04 21:48:30 +00:00
2019-09-24 13:53:22 +00:00
configure := func ( hostname string , sslCert * ingress . SSLCert ) {
uid := emptyUID
2019-06-25 11:49:00 +00:00
2019-09-24 13:53:22 +00:00
if sslCert != nil {
uid = sslCert . UID
2019-03-26 17:24:47 +00:00
2019-09-24 13:53:22 +00:00
if _ , ok := configuration . Certificates [ uid ] ; ! ok {
configuration . Certificates [ uid ] = sslCert . PemCertKey
}
2019-08-26 14:58:44 +00:00
}
2019-09-24 13:53:22 +00:00
configuration . Servers [ hostname ] = uid
}
for _ , rawServer := range rawServers {
configure ( rawServer . Hostname , rawServer . SSLCert )
2019-08-26 14:58:44 +00:00
for _ , alias := range rawServer . Aliases {
2019-09-24 13:53:22 +00:00
if rawServer . SSLCert != nil && ssl . IsValidHostname ( alias , rawServer . SSLCert . CN ) {
configuration . Servers [ alias ] = rawServer . SSLCert . UID
} else {
configuration . Servers [ alias ] = emptyUID
2019-08-26 14:58:44 +00:00
}
2019-03-26 17:24:47 +00:00
}
}
2019-08-15 18:57:51 +00:00
redirects := buildRedirects ( rawServers )
2019-03-26 17:24:47 +00:00
for _ , redirect := range redirects {
2019-09-24 13:53:22 +00:00
configure ( redirect . From , redirect . SSLCert )
2018-06-04 21:48:30 +00:00
}
2019-08-26 14:58:44 +00:00
statusCode , _ , err := nginx . NewPostStatusRequest ( "/configuration/servers" , "application/json" , configuration )
2018-03-18 13:13:41 +00:00
if err != nil {
return err
}
2019-01-21 14:29:36 +00:00
if statusCode != http . StatusCreated {
return fmt . Errorf ( "unexpected error code: %d" , statusCode )
2018-03-18 13:13:41 +00:00
}
return nil
}
2018-06-21 22:15:18 +00:00
const zipkinTmpl = ` {
"service_name" : "{{ .ZipkinServiceName }}" ,
"collector_host" : "{{ .ZipkinCollectorHost }}" ,
2018-06-28 14:42:32 +00:00
"collector_port" : { { . ZipkinCollectorPort } } ,
"sample_rate" : { { . ZipkinSampleRate } }
2018-06-21 22:15:18 +00:00
} `
const jaegerTmpl = ` {
"service_name" : "{{ .JaegerServiceName }}" ,
2021-03-23 23:43:34 +00:00
"propagation_format" : "{{ .JaegerPropagationFormat }}" ,
2018-06-21 22:15:18 +00:00
"sampler" : {
"type" : "{{ .JaegerSamplerType }}" ,
2019-05-21 03:06:41 +00:00
"param" : { { . JaegerSamplerParam } } ,
"samplingServerURL" : "{{ .JaegerSamplerHost }}:{{ .JaegerSamplerPort }}/sampling"
2018-06-21 22:15:18 +00:00
} ,
"reporter" : {
2021-02-18 19:40:04 +00:00
"endpoint" : "{{ .JaegerEndpoint }}" ,
2018-06-21 22:15:18 +00:00
"localAgentHostPort" : "{{ .JaegerCollectorHost }}:{{ .JaegerCollectorPort }}"
2019-09-17 09:35:53 +00:00
} ,
"headers" : {
"TraceContextHeaderName" : "{{ .JaegerTraceContextHeaderName }}" ,
"jaegerDebugHeader" : "{{ .JaegerDebugHeader }}" ,
"jaegerBaggageHeader" : "{{ .JaegerBaggageHeader }}" ,
"traceBaggageHeaderPrefix" : "{{ .JaegerTraceBaggageHeaderPrefix }}"
2021-02-01 15:52:02 +00:00
}
2018-06-21 22:15:18 +00:00
} `
2019-02-15 20:20:10 +00:00
const datadogTmpl = ` {
"service" : "{{ .DatadogServiceName }}" ,
"agent_host" : "{{ .DatadogCollectorHost }}" ,
"agent_port" : { { . DatadogCollectorPort } } ,
2020-10-01 21:42:22 +00:00
"environment" : "{{ .DatadogEnvironment }}" ,
2020-01-08 00:59:59 +00:00
"operation_name_override" : "{{ .DatadogOperationNameOverride }}" ,
"sample_rate" : { { . DatadogSampleRate } } ,
"dd.priority.sampling" : { { . DatadogPrioritySampling } }
2019-02-15 20:20:10 +00:00
} `
2018-06-21 22:15:18 +00:00
func createOpentracingCfg ( cfg ngx_config . Configuration ) error {
var tmpl * template . Template
var err error
if cfg . ZipkinCollectorHost != "" {
tmpl , err = template . New ( "zipkin" ) . Parse ( zipkinTmpl )
if err != nil {
return err
}
2021-02-18 19:40:04 +00:00
} else if cfg . JaegerCollectorHost != "" || cfg . JaegerEndpoint != "" {
2019-02-10 15:59:05 +00:00
tmpl , err = template . New ( "jaeger" ) . Parse ( jaegerTmpl )
2018-06-21 22:15:18 +00:00
if err != nil {
return err
}
2019-02-15 20:20:10 +00:00
} else if cfg . DatadogCollectorHost != "" {
tmpl , err = template . New ( "datadog" ) . Parse ( datadogTmpl )
if err != nil {
return err
}
2018-06-21 22:15:18 +00:00
} else {
tmpl , _ = template . New ( "empty" ) . Parse ( "{}" )
}
tmplBuf := bytes . NewBuffer ( make ( [ ] byte , 0 ) )
err = tmpl . Execute ( tmplBuf , cfg )
if err != nil {
return err
}
2019-02-10 15:59:05 +00:00
// Expand possible environment variables before writing the configuration to file.
2019-07-08 20:10:38 +00:00
expanded := os . ExpandEnv ( tmplBuf . String ( ) )
2019-02-10 15:59:05 +00:00
2021-08-06 14:18:17 +00:00
return os . WriteFile ( "/etc/nginx/opentracing.json" , [ ] byte ( expanded ) , file . ReadWriteByUser )
2018-06-21 22:15:18 +00:00
}
2018-12-03 17:27:29 +00:00
func cleanTempNginxCfg ( ) error {
var files [ ] string
err := filepath . Walk ( os . TempDir ( ) , func ( path string , info os . FileInfo , err error ) error {
2021-01-21 02:17:25 +00:00
if err != nil {
return err
}
2018-12-03 17:27:29 +00:00
if info . IsDir ( ) && os . TempDir ( ) != path {
return filepath . SkipDir
}
dur , _ := time . ParseDuration ( "-5m" )
fiveMinutesAgo := time . Now ( ) . Add ( dur )
if strings . HasPrefix ( info . Name ( ) , tempNginxPattern ) && info . ModTime ( ) . Before ( fiveMinutesAgo ) {
files = append ( files , path )
}
return nil
} )
if err != nil {
return err
}
for _ , file := range files {
err := os . Remove ( file )
if err != nil {
return err
}
}
return nil
}
2019-01-09 03:33:16 +00:00
type redirect struct {
From string
To string
2019-08-13 21:14:55 +00:00
SSLCert * ingress . SSLCert
2019-01-09 03:33:16 +00:00
}
func buildRedirects ( servers [ ] * ingress . Server ) [ ] * redirect {
names := sets . String { }
redirectServers := make ( [ ] * redirect , 0 )
for _ , srv := range servers {
if ! srv . RedirectFromToWWW {
continue
}
to := srv . Hostname
var from string
if strings . HasPrefix ( to , "www." ) {
from = strings . TrimPrefix ( to , "www." )
} else {
from = fmt . Sprintf ( "www.%v" , to )
}
if names . Has ( to ) {
continue
}
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Creating redirect" , "from" , from , "to" , to )
2019-01-09 03:33:16 +00:00
found := false
for _ , esrv := range servers {
if esrv . Hostname == from {
found = true
break
}
}
if found {
klog . Warningf ( "Already exists an Ingress with %q hostname. Skipping creation of redirection from %q to %q." , from , from , to )
continue
}
r := & redirect {
From : from ,
To : to ,
}
2019-08-13 21:14:55 +00:00
if srv . SSLCert != nil {
2019-01-09 03:33:16 +00:00
if ssl . IsValidHostname ( from , srv . SSLCert . CN ) {
r . SSLCert = srv . SSLCert
} else {
klog . Warningf ( "the server %v has SSL configured but the SSL certificate does not contains a CN for %v. Redirects will not work for HTTPS to HTTPS" , from , to )
}
}
redirectServers = append ( redirectServers , r )
names . Insert ( to )
}
return redirectServers
}