Merge pull request #1699 from aledbf/disable-cert-chain-validation
Refactor SSL intermediate CA certificate check
This commit is contained in:
commit
a479bcd4fb
7 changed files with 139 additions and 42 deletions
|
@ -124,6 +124,11 @@ func parseFlags() (bool, *controller.Configuration, error) {
|
||||||
healthzPort = flags.Int("healthz-port", 10254, "port for healthz endpoint.")
|
healthzPort = flags.Int("healthz-port", 10254, "port for healthz endpoint.")
|
||||||
|
|
||||||
annotationsPrefix = flags.String("annotations-prefix", "nginx.ingress.kubernetes.io", `Prefix of the ingress annotations.`)
|
annotationsPrefix = flags.String("annotations-prefix", "nginx.ingress.kubernetes.io", `Prefix of the ingress annotations.`)
|
||||||
|
|
||||||
|
enableSSLChainCompletion = flags.Bool("enable-ssl-chain-completion", true,
|
||||||
|
`Defines if the nginx ingress controller should check the secrets for missing intermediate CA certificates.
|
||||||
|
If the certificate contain issues chain issues is not possible to enable OCSP.
|
||||||
|
Default is true.`)
|
||||||
)
|
)
|
||||||
|
|
||||||
flag.Set("logtostderr", "true")
|
flag.Set("logtostderr", "true")
|
||||||
|
@ -178,6 +183,10 @@ func parseFlags() (bool, *controller.Configuration, error) {
|
||||||
glog.Warningf("%s is DEPRECATED and will be removed in a future version.", disableNodeList)
|
glog.Warningf("%s is DEPRECATED and will be removed in a future version.", disableNodeList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !*enableSSLChainCompletion {
|
||||||
|
glog.Warningf("Check of SSL certificate chain is disabled (--enable-ssl-chain-completion=false)")
|
||||||
|
}
|
||||||
|
|
||||||
config := &controller.Configuration{
|
config := &controller.Configuration{
|
||||||
AnnotationsPrefix: *annotationsPrefix,
|
AnnotationsPrefix: *annotationsPrefix,
|
||||||
APIServerHost: *apiserverHost,
|
APIServerHost: *apiserverHost,
|
||||||
|
@ -186,6 +195,7 @@ func parseFlags() (bool, *controller.Configuration, error) {
|
||||||
ElectionID: *electionID,
|
ElectionID: *electionID,
|
||||||
EnableProfiling: *profiling,
|
EnableProfiling: *profiling,
|
||||||
EnableSSLPassthrough: *enableSSLPassthrough,
|
EnableSSLPassthrough: *enableSSLPassthrough,
|
||||||
|
EnableSSLChainCompletion: *enableSSLChainCompletion,
|
||||||
ResyncPeriod: *resyncPeriod,
|
ResyncPeriod: *resyncPeriod,
|
||||||
DefaultService: *defaultSvc,
|
DefaultService: *defaultSvc,
|
||||||
IngressClass: *ingressClass,
|
IngressClass: *ingressClass,
|
||||||
|
|
|
@ -31,6 +31,6 @@ CODEGEN_PKG=${CODEGEN_PKG:-$(cd ${SCRIPT_ROOT}; ls -d -1 ./vendor/k8s.io/code-ge
|
||||||
# --output-base "$(dirname ${BASH_SOURCE})/../../.."
|
# --output-base "$(dirname ${BASH_SOURCE})/../../.."
|
||||||
|
|
||||||
${CODEGEN_PKG}/generate-groups.sh "deepcopy" \
|
${CODEGEN_PKG}/generate-groups.sh "deepcopy" \
|
||||||
k8s.io/ingress-nginx/pkg k8s.io/ingress-nginx/pkg \
|
k8s.io/ingress-nginx/internal k8s.io/ingress-nginx/internal \
|
||||||
.:ingress \
|
.:ingress \
|
||||||
--output-base "$(dirname ${BASH_SOURCE})/../../.."
|
--output-base "$(dirname ${BASH_SOURCE})/../../.."
|
||||||
|
|
|
@ -18,10 +18,11 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
"github.com/imdario/mergo"
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
@ -48,7 +49,7 @@ func (ic *NGINXController) syncSecret(key string) {
|
||||||
cur, exists := ic.sslCertTracker.Get(key)
|
cur, exists := ic.sslCertTracker.Get(key)
|
||||||
if exists {
|
if exists {
|
||||||
s := cur.(*ingress.SSLCert)
|
s := cur.(*ingress.SSLCert)
|
||||||
if reflect.DeepEqual(s, cert) {
|
if s.Equal(cert) {
|
||||||
// no need to update
|
// no need to update
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -123,6 +124,47 @@ func (ic *NGINXController) getPemCertificate(secretName string) (*ingress.SSLCer
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ic *NGINXController) checkSSLChainIssues() {
|
||||||
|
for _, secretName := range ic.sslCertTracker.ListKeys() {
|
||||||
|
s, _ := ic.sslCertTracker.Get(secretName)
|
||||||
|
secret := s.(*ingress.SSLCert)
|
||||||
|
|
||||||
|
if secret.FullChainPemFileName != "" {
|
||||||
|
// chain already checked
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ssl.FullChainCert(secret.PemFileName)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("unexpected error generating SSL certificate with full intermediate chain CA certs: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fullChainPemFileName := fmt.Sprintf("%v/%v-%v-full-chain.pem", ingress.DefaultSSLDirectory, secret.Namespace, secret.Name)
|
||||||
|
err = ioutil.WriteFile(fullChainPemFileName, data, 0655)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("unexpected error creating SSL certificate: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := &ingress.SSLCert{}
|
||||||
|
|
||||||
|
err = mergo.MergeWithOverwrite(dst, secret)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("unexpected error creating SSL certificate: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.FullChainPemFileName = fullChainPemFileName
|
||||||
|
|
||||||
|
glog.Infof("updating local copy of ssl certificate %v with missing intermediate CA certs", secretName)
|
||||||
|
ic.sslCertTracker.Update(secretName, dst)
|
||||||
|
// this update must trigger an update
|
||||||
|
// (like an update event from a change in Ingress)
|
||||||
|
ic.syncQueue.Enqueue(&extensions.Ingress{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// checkMissingSecrets verify if one or more ingress rules contains a reference
|
// checkMissingSecrets verify if one or more ingress rules contains a reference
|
||||||
// to a secret that is not present in the local secret store.
|
// to a secret that is not present in the local secret store.
|
||||||
// In this case we call syncSecret.
|
// In this case we call syncSecret.
|
||||||
|
|
|
@ -106,6 +106,8 @@ type Configuration struct {
|
||||||
|
|
||||||
EnableProfiling bool
|
EnableProfiling bool
|
||||||
|
|
||||||
|
EnableSSLChainCompletion bool
|
||||||
|
|
||||||
FakeCertificatePath string
|
FakeCertificatePath string
|
||||||
FakeCertificateSHA string
|
FakeCertificateSHA string
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,6 +257,10 @@ func (n *NGINXController) Start() {
|
||||||
|
|
||||||
go n.syncQueue.Run(time.Second, n.stopCh)
|
go n.syncQueue.Run(time.Second, n.stopCh)
|
||||||
|
|
||||||
|
if n.cfg.EnableSSLChainCompletion {
|
||||||
|
go wait.Until(n.checkSSLChainIssues, 60*time.Second, n.stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
if n.syncStatus != nil {
|
if n.syncStatus != nil {
|
||||||
go n.syncStatus.Run(n.stopCh)
|
go n.syncStatus.Run(n.stopCh)
|
||||||
}
|
}
|
||||||
|
|
|
@ -271,6 +271,9 @@ func (s1 *Server) Equal(s2 *Server) bool {
|
||||||
if !(&s1.CertificateAuth).Equal(&s2.CertificateAuth) {
|
if !(&s1.CertificateAuth).Equal(&s2.CertificateAuth) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if s1.SSLFullChainCertificate != s2.SSLFullChainCertificate {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if s1.RedirectFromToWWW != s2.RedirectFromToWWW {
|
if s1.RedirectFromToWWW != s2.RedirectFromToWWW {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -461,3 +464,37 @@ func (l4b1 *L4Backend) Equal(l4b2 *L4Backend) bool {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equal tests for equality between two L4Backend types
|
||||||
|
func (s1 *SSLCert) Equal(s2 *SSLCert) bool {
|
||||||
|
if s1 == s2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if s1 == nil || s2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s1.PemFileName != s2.PemFileName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s1.PemSHA != s2.PemSHA {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !s1.ExpireTime.Equal(s2.ExpireTime) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cn1 := range s1.CN {
|
||||||
|
found := false
|
||||||
|
for _, cn2 := range s2.CN {
|
||||||
|
if cn1 == cn2 {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -50,8 +50,6 @@ var (
|
||||||
func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert, error) {
|
func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert, error) {
|
||||||
pemName := fmt.Sprintf("%v.pem", name)
|
pemName := fmt.Sprintf("%v.pem", name)
|
||||||
pemFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, pemName)
|
pemFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, pemName)
|
||||||
fullChainPemFileName := fmt.Sprintf("%v/%v-full-chain.pem", ingress.DefaultSSLDirectory, name)
|
|
||||||
|
|
||||||
tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName)
|
tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -180,14 +178,6 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
||||||
ExpireTime: pemCert.NotAfter,
|
ExpireTime: pemCert.NotAfter,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fullChainCert(pemFileName, fullChainPemFileName)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("unexpected error generating SSL certificate with full chain: %v", err)
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s.FullChainPemFileName = fullChainPemFileName
|
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,32 +379,44 @@ func GetFakeSSLCert() ([]byte, []byte) {
|
||||||
return cert, key
|
return cert, key
|
||||||
}
|
}
|
||||||
|
|
||||||
func fullChainCert(in, out string) error {
|
// FullChainCert checks if a certificate file contains issues in the intermediate CA chain
|
||||||
|
// Returns a new certificate with the intermediate certificates.
|
||||||
|
// If the certificate does not contains issues with the chain it return an empty byte array
|
||||||
|
func FullChainCert(in string) ([]byte, error) {
|
||||||
inputFile, err := os.Open(in)
|
inputFile, err := os.Open(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(inputFile)
|
data, err := ioutil.ReadAll(inputFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cert, err := certUtil.DecodeCertificate(data)
|
cert, err := certUtil.DecodeCertificate(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
certPool.AddCert(cert)
|
||||||
|
|
||||||
|
_, err = cert.Verify(x509.VerifyOptions{
|
||||||
|
Intermediates: certPool,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
certs, err := certUtil.FetchCertificateChain(cert)
|
certs, err := certUtil.FetchCertificateChain(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
certs, err = certUtil.AddRootCA(certs)
|
certs, err = certUtil.AddRootCA(certs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data = certUtil.EncodeCertificates(certs)
|
return certUtil.EncodeCertificates(certs), nil
|
||||||
return ioutil.WriteFile(out, data, 0644)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue