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. 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. 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

View file

@ -88,23 +88,24 @@ func (npm namedPortMapping) getPortMappings() map[string]string {
// loadBalancerController watches the kubernetes api and adds/removes services // loadBalancerController watches the kubernetes api and adds/removes services
// from the loadbalancer // from the loadbalancer
type loadBalancerController struct { type loadBalancerController struct {
client *client.Client client *client.Client
ingController *framework.Controller ingController *framework.Controller
endpController *framework.Controller endpController *framework.Controller
svcController *framework.Controller svcController *framework.Controller
secrController *framework.Controller secrController *framework.Controller
mapController *framework.Controller mapController *framework.Controller
ingLister StoreToIngressLister ingLister StoreToIngressLister
svcLister cache.StoreToServiceLister svcLister cache.StoreToServiceLister
endpLister cache.StoreToEndpointsLister endpLister cache.StoreToEndpointsLister
secrLister StoreToSecretsLister secrLister StoreToSecretsLister
mapLister StoreToConfigmapLister mapLister StoreToConfigmapLister
nginx *nginx.Manager nginx *nginx.Manager
podInfo *podInfo podInfo *podInfo
defaultSvc string defaultSvc string
nxgConfigMap string nxgConfigMap string
tcpConfigMap string tcpConfigMap string
udpConfigMap string udpConfigMap string
defSSLCertificate string
recorder record.EventRecorder recorder record.EventRecorder
@ -123,22 +124,24 @@ 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)
eventBroadcaster.StartRecordingToSink(kubeClient.Events(namespace)) eventBroadcaster.StartRecordingToSink(kubeClient.Events(namespace))
lbc := loadBalancerController{ lbc := loadBalancerController{
client: kubeClient, client: kubeClient,
stopCh: make(chan struct{}), stopCh: make(chan struct{}),
podInfo: runtimeInfo, podInfo: runtimeInfo,
nginx: nginx.NewManager(kubeClient), nginx: nginx.NewManager(kubeClient),
nxgConfigMap: nxgConfigMapName, nxgConfigMap: nxgConfigMapName,
tcpConfigMap: tcpConfigMapName, tcpConfigMap: tcpConfigMapName,
udpConfigMap: udpConfigMapName, udpConfigMap: udpConfigMapName,
defaultSvc: defaultSvc, defSSLCertificate: defSSLCertificate,
defaultSvc: defaultSvc,
recorder: eventBroadcaster.NewRecorder(api.EventSource{ recorder: eventBroadcaster.NewRecorder(api.EventSource{
Component: "nginx-ingress-controller", Component: "nginx-ingress-controller",
}), }),
@ -855,6 +858,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)
@ -896,41 +907,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
} }
@ -947,6 +927,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() {

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 { if err != nil {
glog.Fatalf("%v", err) glog.Fatalf("%v", err)
} }

View file

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