Merge pull request #4273 from aledbf/ssh-chain-dynamic

Check and complete intermediate SSL certificates
This commit is contained in:
Kubernetes Prow Robot 2019-07-04 16:32:36 -07:00 committed by GitHub
commit 930e37a0b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 132 additions and 214 deletions

View file

@ -131,8 +131,7 @@ Requires the update-status parameter.`)
enableSSLChainCompletion = flags.Bool("enable-ssl-chain-completion", false, enableSSLChainCompletion = flags.Bool("enable-ssl-chain-completion", false,
`Autocomplete SSL certificate chains with missing intermediate CA certificates. `Autocomplete SSL certificate chains with missing intermediate CA certificates.
A valid certificate chain is required to enable OCSP stapling. Certificates Certificates uploaded to Kubernetes must have the "Authority Information Access" X.509 v3
uploaded to Kubernetes must have the "Authority Information Access" X.509 v3
extension for this to succeed.`) extension for this to succeed.`)
syncRateLimit = flags.Float32("sync-rate-limit", 0.3, syncRateLimit = flags.Float32("sync-rate-limit", 0.3,
@ -142,9 +141,8 @@ extension for this to succeed.`)
`Customized address to set as the load-balancer status of Ingress objects this controller satisfies. `Customized address to set as the load-balancer status of Ingress objects this controller satisfies.
Requires the update-status parameter.`) Requires the update-status parameter.`)
dynamicCertificatesEnabled = flags.Bool("enable-dynamic-certificates", true, enableDynamicCertificates = flags.Bool("enable-dynamic-certificates", true,
`Dynamically update SSL certificates instead of reloading NGINX. `Dynamically update SSL certificates instead of reloading NGINX. Feature backed by OpenResty Lua libraries.`)
Feature backed by OpenResty Lua libraries. Requires that OCSP stapling is not enabled`)
enableMetrics = flags.Bool("enable-metrics", true, enableMetrics = flags.Bool("enable-metrics", true,
`Enables the collection of NGINX metrics`) `Enables the collection of NGINX metrics`)
@ -223,10 +221,6 @@ Takes the form "<host>:port". If not provided, no admission controller is starte
klog.Warningf("SSL certificate chain completion is disabled (--enable-ssl-chain-completion=false)") klog.Warningf("SSL certificate chain completion is disabled (--enable-ssl-chain-completion=false)")
} }
if *enableSSLChainCompletion && *dynamicCertificatesEnabled {
return false, nil, fmt.Errorf(`SSL certificate chain completion cannot be enabled when dynamic certificates functionality is enabled. Please check the flags --enable-ssl-chain-completion`)
}
if *publishSvc != "" && *publishStatusAddress != "" { if *publishSvc != "" && *publishStatusAddress != "" {
return false, nil, fmt.Errorf("Flags --publish-service and --publish-status-address are mutually exclusive") return false, nil, fmt.Errorf("Flags --publish-service and --publish-status-address are mutually exclusive")
} }
@ -237,6 +231,9 @@ Takes the form "<host>:port". If not provided, no admission controller is starte
nginx.HealthCheckTimeout = time.Duration(*defHealthCheckTimeout) * time.Second nginx.HealthCheckTimeout = time.Duration(*defHealthCheckTimeout) * time.Second
} }
ngx_config.EnableSSLChainCompletion = *enableSSLChainCompletion
ngx_config.EnableDynamicCertificates = *enableDynamicCertificates
config := &controller.Configuration{ config := &controller.Configuration{
APIServerHost: *apiserverHost, APIServerHost: *apiserverHost,
KubeConfigFile: *kubeConfigFile, KubeConfigFile: *kubeConfigFile,
@ -246,7 +243,6 @@ Takes the form "<host>:port". If not provided, no admission controller is starte
EnableMetrics: *enableMetrics, EnableMetrics: *enableMetrics,
MetricsPerHost: *metricsPerHost, MetricsPerHost: *metricsPerHost,
EnableSSLPassthrough: *enableSSLPassthrough, EnableSSLPassthrough: *enableSSLPassthrough,
EnableSSLChainCompletion: *enableSSLChainCompletion,
ResyncPeriod: *resyncPeriod, ResyncPeriod: *resyncPeriod,
DefaultService: *defaultSvc, DefaultService: *defaultSvc,
Namespace: *watchNamespace, Namespace: *watchNamespace,
@ -259,7 +255,6 @@ Takes the form "<host>:port". If not provided, no admission controller is starte
UpdateStatusOnShutdown: *updateStatusOnShutdown, UpdateStatusOnShutdown: *updateStatusOnShutdown,
UseNodeInternalIP: *useNodeInternalIP, UseNodeInternalIP: *useNodeInternalIP,
SyncRateLimit: *syncRateLimit, SyncRateLimit: *syncRateLimit,
DynamicCertificatesEnabled: *dynamicCertificatesEnabled,
ListenPorts: &ngx_config.ListenPorts{ ListenPorts: &ngx_config.ListenPorts{
Default: *defServerPort, Default: *defServerPort,
Health: *healthzPort, Health: *healthzPort,

View file

@ -30,6 +30,13 @@ import (
"k8s.io/ingress-nginx/internal/runtime" "k8s.io/ingress-nginx/internal/runtime"
) )
var (
// EnableSSLChainCompletion Autocomplete SSL certificate chains with missing intermediate CA certificates.
EnableSSLChainCompletion = false
// EnableDynamicCertificates Dynamically update SSL certificates instead of reloading NGINX
EnableDynamicCertificates = true
)
const ( const (
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size // http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
// Sets the maximum allowed size of the client request body // Sets the maximum allowed size of the client request body
@ -772,7 +779,7 @@ type TemplateConfig struct {
RedirectServers interface{} RedirectServers interface{}
ListenPorts *ListenPorts ListenPorts *ListenPorts
PublishService *apiv1.Service PublishService *apiv1.Service
DynamicCertificatesEnabled bool EnableDynamicCertificates bool
EnableMetrics bool EnableMetrics bool
PID string PID string

View file

@ -84,14 +84,10 @@ type Configuration struct {
EnableMetrics bool EnableMetrics bool
MetricsPerHost bool MetricsPerHost bool
EnableSSLChainCompletion bool
FakeCertificate *ingress.SSLCert FakeCertificate *ingress.SSLCert
SyncRateLimit float32 SyncRateLimit float32
DynamicCertificatesEnabled bool
DisableCatchAll bool DisableCatchAll bool
ValidationWebhook string ValidationWebhook string
@ -171,7 +167,7 @@ func (n *NGINXController) syncIngress(interface{}) error {
} }
err := wait.ExponentialBackoff(retry, func() (bool, error) { err := wait.ExponentialBackoff(retry, func() (bool, error) {
err := configureDynamically(pcfg, n.cfg.DynamicCertificatesEnabled) err := configureDynamically(pcfg)
if err == nil { if err == nil {
klog.V(2).Infof("Dynamic reconfiguration succeeded.") klog.V(2).Infof("Dynamic reconfiguration succeeded.")
return true, nil return true, nil
@ -890,7 +886,7 @@ func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingres
return upstreams, nil return upstreams, nil
} }
// overridePemFileNameAndPemSHA should only be called when DynamicCertificatesEnabled // overridePemFileNameAndPemSHA should only be called when EnableDynamicCertificates
// ideally this function should not exist, the only reason why we use it is that // ideally this function should not exist, the only reason why we use it is that
// we rely on PemFileName in nginx.tmpl to configure SSL directives // we rely on PemFileName in nginx.tmpl to configure SSL directives
// and PemSHA to force reload // and PemSHA to force reload
@ -940,7 +936,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
certificate, err := n.store.GetLocalSSLCert(n.cfg.DefaultSSLCertificate) certificate, err := n.store.GetLocalSSLCert(n.cfg.DefaultSSLCertificate)
if err == nil { if err == nil {
defaultCertificate = certificate defaultCertificate = certificate
if n.cfg.DynamicCertificatesEnabled { if ngx_config.EnableDynamicCertificates {
n.overridePemFileNameAndPemSHA(defaultCertificate) n.overridePemFileNameAndPemSHA(defaultCertificate)
} }
} else { } else {
@ -1123,7 +1119,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
} }
} }
if n.cfg.DynamicCertificatesEnabled { if ngx_config.EnableDynamicCertificates {
n.overridePemFileNameAndPemSHA(cert) n.overridePemFileNameAndPemSHA(cert)
} }

View file

@ -1116,7 +1116,7 @@ func newNGINXController(t *testing.T) *NGINXController {
t.Fatalf("error: %v", err) t.Fatalf("error: %v", err)
} }
storer := store.New(true, storer := store.New(
ns, ns,
fmt.Sprintf("%v/config", ns), fmt.Sprintf("%v/config", ns),
fmt.Sprintf("%v/tcp", ns), fmt.Sprintf("%v/tcp", ns),
@ -1126,7 +1126,6 @@ func newNGINXController(t *testing.T) *NGINXController {
clientSet, clientSet,
fs, fs,
channels.NewRingChannel(10), channels.NewRingChannel(10),
false,
pod, pod,
false) false)

View file

@ -129,7 +129,6 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
n.podInfo = pod n.podInfo = pod
n.store = store.New( n.store = store.New(
config.EnableSSLChainCompletion,
config.Namespace, config.Namespace,
config.ConfigMapName, config.ConfigMapName,
config.TCPConfigMapName, config.TCPConfigMapName,
@ -139,7 +138,6 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
config.Client, config.Client,
fs, fs,
n.updateCh, n.updateCh,
config.DynamicCertificatesEnabled,
pod, pod,
config.DisableCatchAll) config.DisableCatchAll)
@ -614,7 +612,7 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC
IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough, IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough,
ListenPorts: n.cfg.ListenPorts, ListenPorts: n.cfg.ListenPorts,
PublishService: n.GetPublishService(), PublishService: n.GetPublishService(),
DynamicCertificatesEnabled: n.cfg.DynamicCertificatesEnabled, EnableDynamicCertificates: ngx_config.EnableDynamicCertificates,
EnableMetrics: n.cfg.EnableMetrics, EnableMetrics: n.cfg.EnableMetrics,
HealthzURI: nginx.HealthPath, HealthzURI: nginx.HealthPath,
@ -851,7 +849,7 @@ func (n *NGINXController) IsDynamicConfigurationEnough(pcfg *ingress.Configurati
copyOfRunningConfig.ControllerPodsCount = 0 copyOfRunningConfig.ControllerPodsCount = 0
copyOfPcfg.ControllerPodsCount = 0 copyOfPcfg.ControllerPodsCount = 0
if n.cfg.DynamicCertificatesEnabled { if ngx_config.EnableDynamicCertificates {
clearCertificates(&copyOfRunningConfig) clearCertificates(&copyOfRunningConfig)
clearCertificates(&copyOfPcfg) clearCertificates(&copyOfPcfg)
} }
@ -861,7 +859,7 @@ func (n *NGINXController) IsDynamicConfigurationEnough(pcfg *ingress.Configurati
// configureDynamically encodes new Backends in JSON format and POSTs the // configureDynamically encodes new Backends in JSON format and POSTs the
// payload to an internal HTTP endpoint handled by Lua. // payload to an internal HTTP endpoint handled by Lua.
func configureDynamically(pcfg *ingress.Configuration, isDynamicCertificatesEnabled bool) error { func configureDynamically(pcfg *ingress.Configuration) error {
backends := make([]*ingress.Backend, len(pcfg.Backends)) backends := make([]*ingress.Backend, len(pcfg.Backends))
for i, backend := range pcfg.Backends { for i, backend := range pcfg.Backends {
@ -949,7 +947,7 @@ func configureDynamically(pcfg *ingress.Configuration, isDynamicCertificatesEnab
return fmt.Errorf("unexpected error code: %d", statusCode) return fmt.Errorf("unexpected error code: %d", statusCode)
} }
if isDynamicCertificatesEnabled { if ngx_config.EnableDynamicCertificates {
err = configureCertificates(pcfg) err = configureCertificates(pcfg)
if err != nil { if err != nil {
return err return err

View file

@ -32,10 +32,14 @@ import (
apiv1 "k8s.io/api/core/v1" apiv1 "k8s.io/api/core/v1"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/nginx" "k8s.io/ingress-nginx/internal/nginx"
) )
func TestIsDynamicConfigurationEnough(t *testing.T) { func TestIsDynamicConfigurationEnough(t *testing.T) {
ngx_config.EnableDynamicCertificates = false
defer func() { ngx_config.EnableDynamicCertificates = true }()
backends := []*ingress.Backend{{ backends := []*ingress.Backend{{
Name: "fakenamespace-myapp-80", Name: "fakenamespace-myapp-80",
Endpoints: []ingress.Endpoint{ Endpoints: []ingress.Endpoint{
@ -73,9 +77,7 @@ func TestIsDynamicConfigurationEnough(t *testing.T) {
Backends: backends, Backends: backends,
Servers: servers, Servers: servers,
}, },
cfg: &Configuration{ cfg: &Configuration{},
DynamicCertificatesEnabled: false,
},
} }
newConfig := commonConfig newConfig := commonConfig
@ -95,12 +97,13 @@ func TestIsDynamicConfigurationEnough(t *testing.T) {
Backends: []*ingress.Backend{{Name: "a-backend-8080"}}, Backends: []*ingress.Backend{{Name: "a-backend-8080"}},
Servers: servers, Servers: servers,
} }
ngx_config.EnableDynamicCertificates = true
if !n.IsDynamicConfigurationEnough(newConfig) { if !n.IsDynamicConfigurationEnough(newConfig) {
t.Errorf("Expected to be dynamically configurable when only backends change") t.Errorf("Expected to be dynamically configurable when only backends change")
} }
n.cfg.DynamicCertificatesEnabled = true
newServers := []*ingress.Server{{ newServers := []*ingress.Server{{
Hostname: "myapp1.fake", Hostname: "myapp1.fake",
Locations: []*ingress.Location{ Locations: []*ingress.Location{
@ -243,7 +246,10 @@ func TestConfigureDynamically(t *testing.T) {
ControllerPodsCount: 2, ControllerPodsCount: 2,
} }
err = configureDynamically(commonConfig, false) ngx_config.EnableDynamicCertificates = false
defer func() { ngx_config.EnableDynamicCertificates = true }()
err = configureDynamically(commonConfig)
if err != nil { if err != nil {
t.Errorf("unexpected error posting dynamic configuration: %v", err) t.Errorf("unexpected error posting dynamic configuration: %v", err)
} }

View file

@ -20,16 +20,14 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/imdario/mergo"
"k8s.io/klog" "k8s.io/klog"
apiv1 "k8s.io/api/core/v1" apiv1 "k8s.io/api/core/v1"
networking "k8s.io/api/networking/v1beta1" networking "k8s.io/api/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/k8s" ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/net/ssl" "k8s.io/ingress-nginx/internal/net/ssl"
) )
@ -103,7 +101,7 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
return nil, fmt.Errorf("unexpected error creating SSL Cert: %v", err) return nil, fmt.Errorf("unexpected error creating SSL Cert: %v", err)
} }
if !s.isDynamicCertificatesEnabled || len(ca) > 0 { if !ngx_config.EnableDynamicCertificates || len(ca) > 0 {
err = ssl.StoreSSLCertOnDisk(s.filesystem, nsSecName, sslCert) err = ssl.StoreSSLCertOnDisk(s.filesystem, nsSecName, sslCert)
if err != nil { if err != nil {
return nil, fmt.Errorf("error while storing certificate and key: %v", err) return nil, fmt.Errorf("error while storing certificate and key: %v", err)
@ -152,57 +150,6 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
return sslCert, nil return sslCert, nil
} }
func (s *k8sStore) checkSSLChainIssues() {
for _, item := range s.ListLocalSSLCerts() {
secrKey := k8s.MetaNamespaceKey(item)
secret, err := s.GetLocalSSLCert(secrKey)
if err != nil {
continue
}
if secret.FullChainPemFileName != "" {
// chain already checked
continue
}
data, err := ssl.FullChainCert(secret.PemFileName, s.filesystem)
if err != nil {
klog.Errorf("Error generating CA certificate chain for Secret %q: %v", secrKey, err)
continue
}
fullChainPemFileName := fmt.Sprintf("%v/%v-%v-full-chain.pem", file.DefaultSSLDirectory, secret.Namespace, secret.Name)
file, err := s.filesystem.Create(fullChainPemFileName)
if err != nil {
klog.Errorf("Error creating SSL certificate file for Secret %q: %v", secrKey, err)
continue
}
_, err = file.Write(data)
if err != nil {
klog.Errorf("Error creating SSL certificate for Secret %q: %v", secrKey, err)
continue
}
dst := &ingress.SSLCert{}
err = mergo.MergeWithOverwrite(dst, secret)
if err != nil {
klog.Errorf("Error creating SSL certificate for Secret %q: %v", secrKey, err)
continue
}
dst.FullChainPemFileName = fullChainPemFileName
klog.Infof("Updating local copy of SSL certificate %q with missing intermediate CA certs", secrKey)
s.sslStore.Update(secrKey, dst)
// this update must trigger an update
// (like an update event from a change in Ingress)
s.sendDummyEvent()
}
}
// sendDummyEvent sends a dummy event to trigger an update // sendDummyEvent sends a dummy event to trigger an update
// This is used in when a secret change // This is used in when a secret change
func (s *k8sStore) sendDummyEvent() { func (s *k8sStore) sendDummyEvent() {

View file

@ -35,7 +35,6 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
k8sruntime "k8s.io/apimachinery/pkg/runtime" k8sruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
@ -187,8 +186,6 @@ func (i *Informer) Run(stopCh chan struct{}) {
// k8sStore internal Storer implementation using informers and thread safe stores // k8sStore internal Storer implementation using informers and thread safe stores
type k8sStore struct { type k8sStore struct {
isOCSPCheckEnabled bool
// backendConfig contains the running configuration from the configmap // backendConfig contains the running configuration from the configmap
// this is required because this rarely changes but is a very expensive // this is required because this rarely changes but is a very expensive
// operation to execute in each OnUpdate invocation // operation to execute in each OnUpdate invocation
@ -224,24 +221,20 @@ type k8sStore struct {
defaultSSLCertificate string defaultSSLCertificate string
isDynamicCertificatesEnabled bool
pod *k8s.PodInfo pod *k8s.PodInfo
} }
// New creates a new object store to be used in the ingress controller // New creates a new object store to be used in the ingress controller
func New(checkOCSP bool, func New(
namespace, configmap, tcp, udp, defaultSSLCertificate string, namespace, configmap, tcp, udp, defaultSSLCertificate string,
resyncPeriod time.Duration, resyncPeriod time.Duration,
client clientset.Interface, client clientset.Interface,
fs file.Filesystem, fs file.Filesystem,
updateCh *channels.RingChannel, updateCh *channels.RingChannel,
isDynamicCertificatesEnabled bool,
pod *k8s.PodInfo, pod *k8s.PodInfo,
disableCatchAll bool) Storer { disableCatchAll bool) Storer {
store := &k8sStore{ store := &k8sStore{
isOCSPCheckEnabled: checkOCSP,
informers: &Informer{}, informers: &Informer{},
listers: &Lister{}, listers: &Lister{},
sslStore: NewSSLCertTracker(), sslStore: NewSSLCertTracker(),
@ -252,7 +245,6 @@ func New(checkOCSP bool,
backendConfigMu: &sync.RWMutex{}, backendConfigMu: &sync.RWMutex{},
secretIngressMap: NewObjectRefMap(), secretIngressMap: NewObjectRefMap(),
defaultSSLCertificate: defaultSSLCertificate, defaultSSLCertificate: defaultSSLCertificate,
isDynamicCertificatesEnabled: isDynamicCertificatesEnabled,
pod: pod, pod: pod,
} }
@ -878,10 +870,6 @@ func (s *k8sStore) setConfig(cmap *corev1.ConfigMap) {
func (s *k8sStore) Run(stopCh chan struct{}) { func (s *k8sStore) Run(stopCh chan struct{}) {
// start informers // start informers
s.informers.Run(stopCh) s.informers.Run(stopCh)
if s.isOCSPCheckEnabled {
go wait.Until(s.checkSSLChainIssues, 60*time.Second, stopCh)
}
} }
// GetRunningControllerPodsCount returns the number of Running ingress-nginx controller Pods // GetRunningControllerPodsCount returns the number of Running ingress-nginx controller Pods

View file

@ -41,6 +41,7 @@ import (
"k8s.io/ingress-nginx/internal/file" "k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser" "k8s.io/ingress-nginx/internal/ingress/annotations/parser"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/k8s" "k8s.io/ingress-nginx/internal/k8s"
"k8s.io/ingress-nginx/test/e2e/framework" "k8s.io/ingress-nginx/test/e2e/framework"
) )
@ -87,7 +88,7 @@ func TestStore(t *testing.T) {
}(updateCh) }(updateCh)
fs := newFS(t) fs := newFS(t)
storer := New(true, storer := New(
ns, ns,
fmt.Sprintf("%v/config", ns), fmt.Sprintf("%v/config", ns),
fmt.Sprintf("%v/tcp", ns), fmt.Sprintf("%v/tcp", ns),
@ -97,7 +98,6 @@ func TestStore(t *testing.T) {
clientSet, clientSet,
fs, fs,
updateCh, updateCh,
false,
pod, pod,
false) false)
@ -168,7 +168,7 @@ func TestStore(t *testing.T) {
}(updateCh) }(updateCh)
fs := newFS(t) fs := newFS(t)
storer := New(true, storer := New(
ns, ns,
fmt.Sprintf("%v/config", ns), fmt.Sprintf("%v/config", ns),
fmt.Sprintf("%v/tcp", ns), fmt.Sprintf("%v/tcp", ns),
@ -178,7 +178,6 @@ func TestStore(t *testing.T) {
clientSet, clientSet,
fs, fs,
updateCh, updateCh,
false,
pod, pod,
false) false)
@ -319,7 +318,7 @@ func TestStore(t *testing.T) {
}(updateCh) }(updateCh)
fs := newFS(t) fs := newFS(t)
storer := New(true, storer := New(
ns, ns,
fmt.Sprintf("%v/config", ns), fmt.Sprintf("%v/config", ns),
fmt.Sprintf("%v/tcp", ns), fmt.Sprintf("%v/tcp", ns),
@ -329,7 +328,6 @@ func TestStore(t *testing.T) {
clientSet, clientSet,
fs, fs,
updateCh, updateCh,
false,
pod, pod,
false) false)
@ -426,7 +424,7 @@ func TestStore(t *testing.T) {
}(updateCh) }(updateCh)
fs := newFS(t) fs := newFS(t)
storer := New(true, storer := New(
ns, ns,
fmt.Sprintf("%v/config", ns), fmt.Sprintf("%v/config", ns),
fmt.Sprintf("%v/tcp", ns), fmt.Sprintf("%v/tcp", ns),
@ -436,7 +434,6 @@ func TestStore(t *testing.T) {
clientSet, clientSet,
fs, fs,
updateCh, updateCh,
false,
pod, pod,
false) false)
@ -516,7 +513,7 @@ func TestStore(t *testing.T) {
}(updateCh) }(updateCh)
fs := newFS(t) fs := newFS(t)
storer := New(true, storer := New(
ns, ns,
fmt.Sprintf("%v/config", ns), fmt.Sprintf("%v/config", ns),
fmt.Sprintf("%v/tcp", ns), fmt.Sprintf("%v/tcp", ns),
@ -526,7 +523,6 @@ func TestStore(t *testing.T) {
clientSet, clientSet,
fs, fs,
updateCh, updateCh,
false,
pod, pod,
false) false)
@ -628,7 +624,7 @@ func TestStore(t *testing.T) {
}(updateCh) }(updateCh)
fs := newFS(t) fs := newFS(t)
storer := New(true, storer := New(
ns, ns,
fmt.Sprintf("%v/config", ns), fmt.Sprintf("%v/config", ns),
fmt.Sprintf("%v/tcp", ns), fmt.Sprintf("%v/tcp", ns),
@ -638,7 +634,6 @@ func TestStore(t *testing.T) {
clientSet, clientSet,
fs, fs,
updateCh, updateCh,
false,
pod, pod,
false) false)
@ -708,6 +703,9 @@ func TestStore(t *testing.T) {
} }
t.Run("should exists a secret in the local store and filesystem", func(t *testing.T) { t.Run("should exists a secret in the local store and filesystem", func(t *testing.T) {
ngx_config.EnableDynamicCertificates = false
defer func() { ngx_config.EnableDynamicCertificates = true }()
err := framework.WaitForSecretInNamespace(clientSet, ns, name) err := framework.WaitForSecretInNamespace(clientSet, ns, name)
if err != nil { if err != nil {
t.Errorf("error waiting for secret: %v", err) t.Errorf("error waiting for secret: %v", err)

View file

@ -32,9 +32,6 @@ type SSLCert struct {
CAFileName string `json:"caFileName"` CAFileName string `json:"caFileName"`
// PemFileName contains the path to the file with the certificate and key concatenated // PemFileName contains the path to the file with the certificate and key concatenated
PemFileName string `json:"pemFileName"` PemFileName string `json:"pemFileName"`
// FullChainPemFileName contains the path to the file with the certificate and key concatenated
// This certificate contains the full chain (ca + intermediates + cert)
FullChainPemFileName string `json:"fullChainPemFileName"`
// PemSHA contains the sha1 of the pem file. // PemSHA contains the sha1 of the pem file.
// This is used to detect changes in the secret that contains the certificates // This is used to detect changes in the secret that contains the certificates
PemSHA string `json:"pemSha"` PemSHA string `json:"pemSha"`

View file

@ -523,9 +523,6 @@ func (s1 *SSLCert) Equal(s2 *SSLCert) bool {
if !s1.ExpireTime.Equal(s2.ExpireTime) { if !s1.ExpireTime.Equal(s2.ExpireTime) {
return false return false
} }
if s1.FullChainPemFileName != s2.FullChainPemFileName {
return false
}
if s1.PemCertKey != s2.PemCertKey { if s1.PemCertKey != s2.PemCertKey {
return false return false
} }

View file

@ -38,6 +38,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/ingress-nginx/internal/file" "k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/watch" "k8s.io/ingress-nginx/internal/watch"
"k8s.io/klog" "k8s.io/klog"
) )
@ -74,8 +75,18 @@ func verifyPemCertAgainstRootCA(pemCert *x509.Certificate, ca []byte) error {
// CreateSSLCert validates cert and key, extracts common names and returns corresponding SSLCert object // CreateSSLCert validates cert and key, extracts common names and returns corresponding SSLCert object
func CreateSSLCert(cert, key []byte) (*ingress.SSLCert, error) { func CreateSSLCert(cert, key []byte) (*ingress.SSLCert, error) {
var pemCertBuffer bytes.Buffer var pemCertBuffer bytes.Buffer
pemCertBuffer.Write(cert) pemCertBuffer.Write(cert)
if ngx_config.EnableSSLChainCompletion {
data, err := fullChainCert(cert)
if err != nil {
klog.Errorf("Error generating certificate chain for Secret: %v", err)
} else {
pemCertBuffer.Reset()
pemCertBuffer.Write(data)
}
}
pemCertBuffer.Write([]byte("\n")) pemCertBuffer.Write([]byte("\n"))
pemCertBuffer.Write(key) pemCertBuffer.Write(key)
@ -376,7 +387,6 @@ func GetFakeSSLCert(fs file.Filesystem) *ingress.SSLCert {
} }
func getFakeHostSSLCert(host string) ([]byte, []byte) { func getFakeHostSSLCert(host string) ([]byte, []byte) {
var priv interface{} var priv interface{}
var err error var err error
@ -423,16 +433,11 @@ func getFakeHostSSLCert(host string) ([]byte, []byte) {
return cert, key return cert, key
} }
// FullChainCert checks if a certificate file contains issues in the intermediate CA chain // fullChainCert checks if a certificate file contains issues in the intermediate CA chain
// Returns a new certificate with the intermediate certificates. // Returns a new certificate with the intermediate certificates.
// If the certificate does not contains issues with the chain it return an empty byte array // If the certificate does not contains issues with the chain it return an empty byte array
func FullChainCert(in string, fs file.Filesystem) ([]byte, error) { func fullChainCert(in []byte) ([]byte, error) {
data, err := fs.ReadFile(in) cert, err := certUtil.DecodeCertificate(in)
if err != nil {
return nil, err
}
cert, err := certUtil.DecodeCertificate(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -452,11 +457,6 @@ func FullChainCert(in string, fs file.Filesystem) ([]byte, error) {
return nil, err return nil, err
} }
certs, err = certUtil.AddRootCA(certs)
if err != nil {
return nil, err
}
return certUtil.EncodeCertificates(certs), nil return certUtil.EncodeCertificates(certs), nil
} }

View file

@ -96,7 +96,7 @@ http {
end end
{{ end }} {{ end }}
{{ if $all.DynamicCertificatesEnabled }} {{ if $all.EnableDynamicCertificates }}
ok, res = pcall(require, "certificate") ok, res = pcall(require, "certificate")
if not ok then if not ok then
error("require failed: " .. tostring(res)) error("require failed: " .. tostring(res))
@ -492,13 +492,8 @@ http {
# PEM sha: {{ $redirect.SSLCert.PemSHA }} # PEM sha: {{ $redirect.SSLCert.PemSHA }}
ssl_certificate {{ $redirect.SSLCert.PemFileName }}; ssl_certificate {{ $redirect.SSLCert.PemFileName }};
ssl_certificate_key {{ $redirect.SSLCert.PemFileName }}; ssl_certificate_key {{ $redirect.SSLCert.PemFileName }};
{{ if not (empty $redirect.SSLCert.FullChainPemFileName)}}
ssl_trusted_certificate {{ $redirect.SSLCert.FullChainPemFileName }};
ssl_stapling on;
ssl_stapling_verify on;
{{ end }}
{{ if $all.DynamicCertificatesEnabled}} {{ if $all.EnableDynamicCertificates}}
ssl_certificate_by_lua_block { ssl_certificate_by_lua_block {
certificate.call() certificate.call()
} }
@ -841,13 +836,8 @@ stream {
# PEM sha: {{ $server.SSLCert.PemSHA }} # PEM sha: {{ $server.SSLCert.PemSHA }}
ssl_certificate {{ $server.SSLCert.PemFileName }}; ssl_certificate {{ $server.SSLCert.PemFileName }};
ssl_certificate_key {{ $server.SSLCert.PemFileName }}; ssl_certificate_key {{ $server.SSLCert.PemFileName }};
{{ if not (empty $server.SSLCert.FullChainPemFileName)}}
ssl_trusted_certificate {{ $server.SSLCert.FullChainPemFileName }};
ssl_stapling on;
ssl_stapling_verify on;
{{ end }}
{{ if $all.DynamicCertificatesEnabled}} {{ if $all.EnableDynamicCertificates}}
ssl_certificate_by_lua_block { ssl_certificate_by_lua_block {
certificate.call() certificate.call()
} }