Refactor nginx certificate creation.

This commit is contained in:
Manuel de Brito Fontes 2016-07-18 17:08:00 -04:00
parent 8f4efb4e3d
commit d3d6c879d5
4 changed files with 145 additions and 62 deletions

View file

@ -140,6 +140,74 @@ NGINX provides the option [default_server](http://nginx.org/en/docs/http/server_
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

View file

@ -105,6 +105,7 @@ type loadBalancerController struct {
nxgConfigMap string
tcpConfigMap string
udpConfigMap string
defSSLCertificate string
recorder record.EventRecorder
@ -123,8 +124,9 @@ 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)
@ -138,6 +140,7 @@ func newLoadBalancerController(kubeClient *client.Client, resyncPeriod time.Dura
nxgConfigMap: nxgConfigMapName,
tcpConfigMap: tcpConfigMapName,
udpConfigMap: udpConfigMapName,
defSSLCertificate: defSSLCertificate,
defaultSvc: defaultSvc,
recorder: eventBroadcaster.NewRecorder(api.EventSource{
Component: "nginx-ingress-controller",
@ -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)
@ -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)
ngxCert, err := lbc.getPemCertificate(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))
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
}
@ -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() {

View file

@ -140,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)
}

View file

@ -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 }};