From 4d8cf3d3121457b910d8d6178b78e3f22a6ca550 Mon Sep 17 00:00:00 2001 From: Manuel de Brito Fontes Date: Tue, 24 May 2016 11:02:29 -0300 Subject: [PATCH] Add ssl certificate checksum to template --- ingress/controllers/nginx/controller.go | 24 +++++----- ingress/controllers/nginx/nginx.tmpl | 2 + ingress/controllers/nginx/nginx/nginx.go | 1 + ingress/controllers/nginx/nginx/ssl.go | 49 ++++++++++++++++++--- ingress/controllers/nginx/nginx/ssl_test.go | 15 +++---- 5 files changed, 61 insertions(+), 30 deletions(-) diff --git a/ingress/controllers/nginx/controller.go b/ingress/controllers/nginx/controller.go index 4e4293060d..e9f67dbd3c 100644 --- a/ingress/controllers/nginx/controller.go +++ b/ingress/controllers/nginx/controller.go @@ -733,11 +733,12 @@ func (lbc *loadBalancerController) createServers(data []interface{}) map[string] servers[host] = &nginx.Server{Name: host, Locations: locs} } - if pemFile, ok := pems[host]; ok { + if ngxCert, ok := pems[host]; ok { server := servers[host] server.SSL = true - server.SSLCertificate = pemFile - server.SSLCertificateKey = pemFile + server.SSLCertificate = ngxCert.PemFileName + server.SSLCertificateKey = ngxCert.PemFileName + server.SSLPemChecksum = ngxCert.PemSHA } } } @@ -745,8 +746,8 @@ func (lbc *loadBalancerController) createServers(data []interface{}) map[string] return servers } -func (lbc *loadBalancerController) getPemsFromIngress(data []interface{}) map[string]string { - pems := make(map[string]string) +func (lbc *loadBalancerController) getPemsFromIngress(data []interface{}) map[string]nginx.SSLCert { + pems := make(map[string]nginx.SSLCert) for _, ingIf := range data { ing := ingIf.(*extensions.Ingress) @@ -769,12 +770,7 @@ func (lbc *loadBalancerController) getPemsFromIngress(data []interface{}) map[st continue } - pemFileName, 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 - } - cn, err := lbc.nginx.CheckSSLCertificate(pemFileName) + 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 @@ -786,14 +782,14 @@ func (lbc *loadBalancerController) getPemsFromIngress(data []interface{}) map[st continue } - pems["_"] = pemFileName + pems["_"] = ngxCert glog.Infof("Using the secret %v as source for the default SSL certificate", secretName) continue } for _, host := range tls.Hosts { - if isHostValid(host, cn) { - pems[host] = pemFileName + if isHostValid(host, ngxCert.CN) { + pems[host] = ngxCert } else { glog.Warningf("SSL Certificate stored in secret %v is not valid for the host %v defined in the Ingress rule %v", secretName, host, ing.Name) } diff --git a/ingress/controllers/nginx/nginx.tmpl b/ingress/controllers/nginx/nginx.tmpl index 77ff2ac21c..bb85c78745 100644 --- a/ingress/controllers/nginx/nginx.tmpl +++ b/ingress/controllers/nginx/nginx.tmpl @@ -155,6 +155,8 @@ http { server { listen 80{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }}; {{ if $server.SSL }}listen 443{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }} ssl http2; + {{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}} + # PEM sha: {{ $server.SSLPemChecksum }} ssl_certificate {{ $server.SSLCertificate }}; ssl_certificate_key {{ $server.SSLCertificateKey }};{{ end }} diff --git a/ingress/controllers/nginx/nginx/nginx.go b/ingress/controllers/nginx/nginx/nginx.go index bc744a3b26..1ac6f9d88b 100644 --- a/ingress/controllers/nginx/nginx/nginx.go +++ b/ingress/controllers/nginx/nginx/nginx.go @@ -69,6 +69,7 @@ type Server struct { SSL bool SSLCertificate string SSLCertificateKey string + SSLPemChecksum string } // ServerByName sorts server by name diff --git a/ingress/controllers/nginx/nginx/ssl.go b/ingress/controllers/nginx/nginx/ssl.go index 82d1923035..dce7253502 100644 --- a/ingress/controllers/nginx/nginx/ssl.go +++ b/ingress/controllers/nginx/nginx/ssl.go @@ -17,7 +17,9 @@ limitations under the License. package nginx import ( + "crypto/sha1" "crypto/x509" + "encoding/hex" "encoding/pem" "fmt" "io/ioutil" @@ -26,28 +28,52 @@ import ( "github.com/golang/glog" ) +// SSLCert describes a SSL certificate to be used in NGINX +type SSLCert struct { + CertFileName string + KeyFileName string + // PemFileName contains the path to the file with the certificate and key concatenated + PemFileName string + // PemSHA contains the sha1 of the pem file. + // This is used to detect changes in the secret that contains the certificates + PemSHA string + // CN contains all the common names defined in the SSL certificate + CN []string +} + // AddOrUpdateCertAndKey creates a .pem file wth the cert and the key with the specified name -func (nginx *Manager) AddOrUpdateCertAndKey(name string, cert string, key string) (string, error) { +func (nginx *Manager) AddOrUpdateCertAndKey(name string, cert string, key string) (SSLCert, error) { pemFileName := sslDirectory + "/" + name + ".pem" pem, err := os.Create(pemFileName) if err != nil { - return "", fmt.Errorf("Couldn't create pem file %v: %v", pemFileName, err) + return SSLCert{}, fmt.Errorf("Couldn't create pem file %v: %v", pemFileName, err) } defer pem.Close() _, err = pem.WriteString(fmt.Sprintf("%v\n%v", cert, key)) if err != nil { - return "", fmt.Errorf("Couldn't write to pem file %v: %v", pemFileName, err) + return SSLCert{}, fmt.Errorf("Couldn't write to pem file %v: %v", pemFileName, err) + } + + cn, err := nginx.commonNames(pemFileName) + if err != nil { + return SSLCert{}, err } - return pemFileName, nil + return SSLCert{ + CertFileName: cert, + KeyFileName: key, + PemFileName: pemFileName, + PemSHA: nginx.pemSHA1(pemFileName), + CN: cn, + }, nil } -// CheckSSLCertificate checks if the certificate and key file are valid +// commonNames checks if the certificate and key file are valid // returning the result of the validation and the list of hostnames // contained in the common name/s -func (nginx *Manager) CheckSSLCertificate(pemFileName string) ([]string, error) { +func (nginx *Manager) commonNames(pemFileName string) ([]string, error) { pemCerts, err := ioutil.ReadFile(pemFileName) if err != nil { return []string{}, err @@ -92,3 +118,14 @@ func (nginx *Manager) SearchDHParamFile(baseDir string) string { glog.Warning("no file dhparam.pem found in secrets") return "" } + +func (nginx *Manager) pemSHA1(filename string) string { + hasher := sha1.New() + s, err := ioutil.ReadFile(filename) + if err != nil { + return "" + } + + hasher.Write(s) + return hex.EncodeToString(hasher.Sum(nil)) +} diff --git a/ingress/controllers/nginx/nginx/ssl_test.go b/ingress/controllers/nginx/nginx/ssl_test.go index 10a0c1b67c..3408806743 100644 --- a/ingress/controllers/nginx/nginx/ssl_test.go +++ b/ingress/controllers/nginx/nginx/ssl_test.go @@ -44,25 +44,20 @@ func TestAddOrUpdateCertAndKey(t *testing.T) { ngx := &Manager{} name := fmt.Sprintf("test-%v", time.Now().UnixNano()) - pemPath, err := ngx.AddOrUpdateCertAndKey(name, string(dCrt), string(dKey)) + ngxCert, err := ngx.AddOrUpdateCertAndKey(name, string(dCrt), string(dKey)) if err != nil { t.Fatalf("unexpected error checking SSL certificate: %v", err) } - if pemPath == "" { + if ngxCert.PemFileName == "" { t.Fatalf("expected path to pem file but returned empty") } - cnames, err := ngx.CheckSSLCertificate(pemPath) - if err != nil { - t.Fatalf("unexpected error checking SSL certificate: %v", err) - } - - if len(cnames) == 0 { + if len(ngxCert.CN) == 0 { t.Fatalf("expected at least one cname but none returned") } - if cnames[0] != "echoheaders" { - t.Fatalf("expected cname echoheaders but %v returned", cnames[0]) + if ngxCert.CN[0] != "echoheaders" { + t.Fatalf("expected cname echoheaders but %v returned", ngxCert.CN[0]) } }