Merge pull request #4273 from aledbf/ssh-chain-dynamic
Check and complete intermediate SSL certificates
This commit is contained in:
commit
930e37a0b5
13 changed files with 132 additions and 214 deletions
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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(©OfRunningConfig)
|
clearCertificates(©OfRunningConfig)
|
||||||
clearCertificates(©OfPcfg)
|
clearCertificates(©OfPcfg)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"`
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue