Skip to content
This repository has been archived by the owner on Apr 17, 2019. It is now read-only.

[nginx-ingress-controller]: Add HTTPS default backend #1338

Merged
merged 2 commits into from
Jul 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions hack/verify-flags/known-flags.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,4 @@ whitelist-override-label
config-file-path
required-retest-contexts
retest-body
default-ssl-certificate
76 changes: 76 additions & 0 deletions ingress/controllers/nginx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This is a nginx Ingress controller that uses [ConfigMap](https://github.com/kube
* [Deployment](#deployment)
* [HTTP](#http)
* [HTTPS](#https)
* [Default SSL Certificate](#default-ssl-certificate)
* [HTTPS enforcement](#server-side-https-enforcement)
* [HSTS](#http-strict-transport-security)
* [TCP Services](#exposing-tcp-services)
Expand Down Expand Up @@ -133,6 +134,81 @@ Please follow [test.sh](https://github.com/bprashanth/Ingress/blob/master/exampl

Check the [example](examples/tls/README.md)

### Default SSL Certificate

NGINX provides the option [default_server](http://nginx.org/en/docs/http/server_names.html) to allow a catch-all server in case of request with a not configured server name. This configuration works without issues for HTTP traffic.
In case of HTTPS NGINX requires a certificate. For this reason the Ingress controller provides the flag `--default-ssl-certificate`. The secret behind this flag contains the default certificate to be used in the mentioned case.
If this flag is not provided NGINX will reject the request with the HTTP code 444.

Running without the flag `--default-ssl-certificate`:

```
$ curl -v https://10.2.78.7:443
* Rebuilt URL to: https://10.2.78.7:443/
* Trying 10.2.78.7...
* Connected to 10.2.78.7 (10.2.78.7) port 443 (#0)
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* Unknown SSL protocol error in connection to 10.2.78.7:443
* Closing connection 0
curl: (35) Unknown SSL protocol error in connection to 10.2.78.7:443
```

Specifyng `--default-ssl-certificate=default/foo-tls`:

```
core@localhost ~ $ curl -v https://10.2.78.7:443 -k
* Rebuilt URL to: https://10.2.78.7:443/
* Trying 10.2.78.7...
* Connected to 10.2.78.7 (10.2.78.7) port 443 (#0)
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: CN=foo.bar.com
* start date: Apr 13 00:50:56 2016 GMT
* expire date: Apr 13 00:50:56 2017 GMT
* issuer: CN=foo.bar.com
* SSL certificate verify result: self signed certificate (18), continuing anyway.
> GET / HTTP/1.1
> Host: 10.2.78.7
> User-Agent: curl/7.47.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Server: nginx/1.11.1
< Date: Mon, 18 Jul 2016 21:02:59 GMT
< Content-Type: text/html
< Transfer-Encoding: chunked
< Connection: keep-alive
< Strict-Transport-Security: max-age=15724800; includeSubDomains; preload
<
<span>The page you're looking for could not be found.</span>

* Connection #0 to host 10.2.78.7 left intact
```


### Server-side HTTPS enforcement

By default the controller redirects (301) to HTTPS if TLS is enabled for that ingress . If you want to disable that behaviour globally, you can use `ssl-redirect: "false"` in the NGINX config map.
Expand Down
123 changes: 63 additions & 60 deletions ingress/controllers/nginx/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,23 +88,24 @@ func (npm namedPortMapping) getPortMappings() map[string]string {
// loadBalancerController watches the kubernetes api and adds/removes services
// from the loadbalancer
type loadBalancerController struct {
client *client.Client
ingController *framework.Controller
endpController *framework.Controller
svcController *framework.Controller
secrController *framework.Controller
mapController *framework.Controller
ingLister StoreToIngressLister
svcLister cache.StoreToServiceLister
endpLister cache.StoreToEndpointsLister
secrLister StoreToSecretsLister
mapLister StoreToConfigmapLister
nginx *nginx.Manager
podInfo *podInfo
defaultSvc string
nxgConfigMap string
tcpConfigMap string
udpConfigMap string
client *client.Client
ingController *framework.Controller
endpController *framework.Controller
svcController *framework.Controller
secrController *framework.Controller
mapController *framework.Controller
ingLister StoreToIngressLister
svcLister cache.StoreToServiceLister
endpLister cache.StoreToEndpointsLister
secrLister StoreToSecretsLister
mapLister StoreToConfigmapLister
nginx *nginx.Manager
podInfo *podInfo
defaultSvc string
nxgConfigMap string
tcpConfigMap string
udpConfigMap string
defSSLCertificate string

recorder record.EventRecorder

Expand All @@ -123,22 +124,24 @@ type loadBalancerController struct {
}

// newLoadBalancerController creates a controller for nginx loadbalancer
func newLoadBalancerController(kubeClient *client.Client, resyncPeriod time.Duration, defaultSvc,
namespace, nxgConfigMapName, tcpConfigMapName, udpConfigMapName string, runtimeInfo *podInfo) (*loadBalancerController, error) {
func newLoadBalancerController(kubeClient *client.Client, resyncPeriod time.Duration,
defaultSvc, namespace, nxgConfigMapName, tcpConfigMapName, udpConfigMapName,
defSSLCertificate string, runtimeInfo *podInfo) (*loadBalancerController, error) {

eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(glog.Infof)
eventBroadcaster.StartRecordingToSink(kubeClient.Events(namespace))

lbc := loadBalancerController{
client: kubeClient,
stopCh: make(chan struct{}),
podInfo: runtimeInfo,
nginx: nginx.NewManager(kubeClient),
nxgConfigMap: nxgConfigMapName,
tcpConfigMap: tcpConfigMapName,
udpConfigMap: udpConfigMapName,
defaultSvc: defaultSvc,
client: kubeClient,
stopCh: make(chan struct{}),
podInfo: runtimeInfo,
nginx: nginx.NewManager(kubeClient),
nxgConfigMap: nxgConfigMapName,
tcpConfigMap: tcpConfigMapName,
udpConfigMap: udpConfigMapName,
defSSLCertificate: defSSLCertificate,
defaultSvc: defaultSvc,
recorder: eventBroadcaster.NewRecorder(api.EventSource{
Component: "nginx-ingress-controller",
}),
Expand Down Expand Up @@ -855,6 +858,14 @@ func (lbc *loadBalancerController) createServers(data []interface{}) map[string]
servers := make(map[string]*nginx.Server)

pems := lbc.getPemsFromIngress(data)
if lbc.defSSLCertificate != "" {
ngxCert, err := lbc.getPemCertificate(lbc.defSSLCertificate)
if err == nil {
pems["_"] = ngxCert
} else {
glog.Warningf("%v", err)
}
}

for _, ingIf := range data {
ing := ingIf.(*extensions.Ingress)
Expand Down Expand Up @@ -896,41 +907,10 @@ func (lbc *loadBalancerController) getPemsFromIngress(data []interface{}) map[st
for _, tls := range ing.Spec.TLS {
secretName := tls.SecretName
secretKey := fmt.Sprintf("%s/%s", ing.Namespace, secretName)
secretInterface, exists, err := lbc.secrLister.Store.GetByKey(secretKey)
if err != nil {
glog.Warningf("Error retriveing secret %v for ing %v: %v", secretName, ing.Name, err)
continue
}
if !exists {
glog.Warningf("Secret %v is not existing", secretKey)
continue
}
secret := secretInterface.(*api.Secret)
cert, ok := secret.Data[api.TLSCertKey]
if !ok {
glog.Warningf("Secret %v has no private key", secretName)
continue
}
key, ok := secret.Data[api.TLSPrivateKeyKey]
if !ok {
glog.Warningf("Secret %v has no cert", secretName)
continue
}

ngxCert, err := lbc.nginx.AddOrUpdateCertAndKey(fmt.Sprintf("%v-%v", ing.Namespace, secretName), string(cert), string(key))
ngxCert, err := lbc.getPemCertificate(secretKey)
if err != nil {
glog.Errorf("No valid SSL certificate found in secret %v: %v", secretName, err)
continue
}

if len(tls.Hosts) == 0 {
if _, ok := pems["_"]; ok {
glog.Warningf("It is not possible to use %v secret for default SSL certificate because there is one already defined", secretName)
continue
}

pems["_"] = ngxCert
glog.Infof("Using the secret %v as source for the default SSL certificate", secretName)
glog.Warningf("%v", err)
continue
}

Expand All @@ -947,6 +927,29 @@ func (lbc *loadBalancerController) getPemsFromIngress(data []interface{}) map[st
return pems
}

func (lbc *loadBalancerController) getPemCertificate(secretName string) (nginx.SSLCert, error) {
secretInterface, exists, err := lbc.secrLister.Store.GetByKey(secretName)
if err != nil {
return nginx.SSLCert{}, fmt.Errorf("Error retriveing secret %v: %v", secretName, err)
}
if !exists {
return nginx.SSLCert{}, fmt.Errorf("Secret %v does not exists", secretName)
}

secret := secretInterface.(*api.Secret)
cert, ok := secret.Data[api.TLSCertKey]
if !ok {
return nginx.SSLCert{}, fmt.Errorf("Secret %v has no private key", secretName)
}
key, ok := secret.Data[api.TLSPrivateKeyKey]
if !ok {
return nginx.SSLCert{}, fmt.Errorf("Secret %v has no cert", secretName)
}

nsSecName := strings.Replace(secretName, "/", "-", -1)
return lbc.nginx.AddOrUpdateCertAndKey(nsSecName, string(cert), string(key))
}

// check if secret is referenced in this controller's config
func (lbc *loadBalancerController) secrReferenced(namespace string, name string) bool {
for _, ingIf := range lbc.ingLister.Store.List() {
Expand Down
7 changes: 6 additions & 1 deletion ingress/controllers/nginx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ var (
This can be used as a guide to create a custom configuration.`)

profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`)

defSSLCertificate = flags.String("default-ssl-certificate", "", `Name of the secret that contains a SSL
certificate to be used as default for a HTTPS catch-all server`)
)

func main() {
Expand Down Expand Up @@ -137,7 +140,9 @@ func main() {
}
}

lbc, err := newLoadBalancerController(kubeClient, *resyncPeriod, *defaultSvc, *watchNamespace, *nxgConfigMap, *tcpConfigMapName, *udpConfigMapName, runtimePodInfo)
lbc, err := newLoadBalancerController(kubeClient, *resyncPeriod,
*defaultSvc, *watchNamespace, *nxgConfigMap, *tcpConfigMapName,
*udpConfigMapName, *defSSLCertificate, runtimePodInfo)
if err != nil {
glog.Fatalf("%v", err)
}
Expand Down
10 changes: 10 additions & 0 deletions ingress/controllers/nginx/nginx.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ http {
{{ end }}

{{ range $server := .servers }}
{{/* Check for default SSL backend */}}
{{ if and (eq $server.Name "_") (not $server.SSL) -}}
server {
server_name {{ $server.Name }};
listen 443;
# return protocol error.
return 444;
}
{{ end }}

server {
server_name {{ $server.Name }};
listen 80{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }};
Expand Down