Merge pull request #1338 from aledbf/ssl-default-backend
[nginx-ingress-controller]: Add HTTPS default backend
This commit is contained in:
commit
eabad1c990
4 changed files with 156 additions and 62 deletions
|
@ -9,6 +9,7 @@ This is a nginx Ingress controller that uses [ConfigMap](https://github.com/kube
|
||||||
* [Deployment](#deployment)
|
* [Deployment](#deployment)
|
||||||
* [HTTP](#http)
|
* [HTTP](#http)
|
||||||
* [HTTPS](#https)
|
* [HTTPS](#https)
|
||||||
|
* [Default SSL Certificate](#default-ssl-certificate)
|
||||||
* [HTTPS enforcement](#server-side-https-enforcement)
|
* [HTTPS enforcement](#server-side-https-enforcement)
|
||||||
* [HSTS](#http-strict-transport-security)
|
* [HSTS](#http-strict-transport-security)
|
||||||
* [Kube-Lego](#automated-certificate-management-with-kube-lego)
|
* [Kube-Lego](#automated-certificate-management-with-kube-lego)
|
||||||
|
@ -135,6 +136,81 @@ Please follow [test.sh](https://github.com/bprashanth/Ingress/blob/master/exampl
|
||||||
|
|
||||||
Check the [example](examples/tls/README.md)
|
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
|
### 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.
|
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.
|
||||||
|
|
|
@ -105,6 +105,7 @@ type loadBalancerController struct {
|
||||||
nxgConfigMap string
|
nxgConfigMap string
|
||||||
tcpConfigMap string
|
tcpConfigMap string
|
||||||
udpConfigMap string
|
udpConfigMap string
|
||||||
|
defSSLCertificate string
|
||||||
|
|
||||||
recorder record.EventRecorder
|
recorder record.EventRecorder
|
||||||
|
|
||||||
|
@ -123,8 +124,9 @@ type loadBalancerController struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newLoadBalancerController creates a controller for nginx loadbalancer
|
// newLoadBalancerController creates a controller for nginx loadbalancer
|
||||||
func newLoadBalancerController(kubeClient *client.Client, resyncPeriod time.Duration, defaultSvc,
|
func newLoadBalancerController(kubeClient *client.Client, resyncPeriod time.Duration,
|
||||||
namespace, nxgConfigMapName, tcpConfigMapName, udpConfigMapName string, runtimeInfo *podInfo) (*loadBalancerController, error) {
|
defaultSvc, namespace, nxgConfigMapName, tcpConfigMapName, udpConfigMapName,
|
||||||
|
defSSLCertificate string, runtimeInfo *podInfo) (*loadBalancerController, error) {
|
||||||
|
|
||||||
eventBroadcaster := record.NewBroadcaster()
|
eventBroadcaster := record.NewBroadcaster()
|
||||||
eventBroadcaster.StartLogging(glog.Infof)
|
eventBroadcaster.StartLogging(glog.Infof)
|
||||||
|
@ -138,6 +140,7 @@ func newLoadBalancerController(kubeClient *client.Client, resyncPeriod time.Dura
|
||||||
nxgConfigMap: nxgConfigMapName,
|
nxgConfigMap: nxgConfigMapName,
|
||||||
tcpConfigMap: tcpConfigMapName,
|
tcpConfigMap: tcpConfigMapName,
|
||||||
udpConfigMap: udpConfigMapName,
|
udpConfigMap: udpConfigMapName,
|
||||||
|
defSSLCertificate: defSSLCertificate,
|
||||||
defaultSvc: defaultSvc,
|
defaultSvc: defaultSvc,
|
||||||
recorder: eventBroadcaster.NewRecorder(api.EventSource{
|
recorder: eventBroadcaster.NewRecorder(api.EventSource{
|
||||||
Component: "nginx-ingress-controller",
|
Component: "nginx-ingress-controller",
|
||||||
|
@ -867,6 +870,14 @@ func (lbc *loadBalancerController) createServers(data []interface{}) map[string]
|
||||||
servers := make(map[string]*nginx.Server)
|
servers := make(map[string]*nginx.Server)
|
||||||
|
|
||||||
pems := lbc.getPemsFromIngress(data)
|
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 {
|
for _, ingIf := range data {
|
||||||
ing := ingIf.(*extensions.Ingress)
|
ing := ingIf.(*extensions.Ingress)
|
||||||
|
@ -908,41 +919,10 @@ func (lbc *loadBalancerController) getPemsFromIngress(data []interface{}) map[st
|
||||||
for _, tls := range ing.Spec.TLS {
|
for _, tls := range ing.Spec.TLS {
|
||||||
secretName := tls.SecretName
|
secretName := tls.SecretName
|
||||||
secretKey := fmt.Sprintf("%s/%s", ing.Namespace, secretName)
|
secretKey := fmt.Sprintf("%s/%s", ing.Namespace, secretName)
|
||||||
secretInterface, exists, err := lbc.secrLister.Store.GetByKey(secretKey)
|
|
||||||
|
ngxCert, err := lbc.getPemCertificate(secretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Warningf("Error retriveing secret %v for ing %v: %v", secretName, ing.Name, err)
|
glog.Warningf("%v", 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))
|
|
||||||
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)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -959,6 +939,29 @@ func (lbc *loadBalancerController) getPemsFromIngress(data []interface{}) map[st
|
||||||
return pems
|
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
|
// check if secret is referenced in this controller's config
|
||||||
func (lbc *loadBalancerController) secrReferenced(namespace string, name string) bool {
|
func (lbc *loadBalancerController) secrReferenced(namespace string, name string) bool {
|
||||||
for _, ingIf := range lbc.ingLister.Store.List() {
|
for _, ingIf := range lbc.ingLister.Store.List() {
|
||||||
|
|
|
@ -85,6 +85,9 @@ var (
|
||||||
This can be used as a guide to create a custom configuration.`)
|
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/`)
|
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() {
|
func main() {
|
||||||
|
@ -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 {
|
if err != nil {
|
||||||
glog.Fatalf("%v", err)
|
glog.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,6 +180,16 @@ http {
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ range $server := .servers }}
|
{{ 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 {
|
||||||
server_name {{ $server.Name }};
|
server_name {{ $server.Name }};
|
||||||
listen 80{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }};
|
listen 80{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }};
|
||||||
|
|
Loading…
Reference in a new issue