Detect certificates content modifications
Co-authored-by: Romain <rtribotte@users.noreply.github.com> Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
This commit is contained in:
parent
3c1ed0d9b2
commit
e1f5866989
5 changed files with 140 additions and 3 deletions
|
@ -124,3 +124,16 @@ http:
|
||||||
If there is a need for a response code other than a `503` and/or a custom message,
|
If there is a need for a response code other than a `503` and/or a custom message,
|
||||||
the principle of the above example above (a catchall router) still stands,
|
the principle of the above example above (a catchall router) still stands,
|
||||||
but the `unavailable` service should be adapted to fit such a need.
|
but the `unavailable` service should be adapted to fit such a need.
|
||||||
|
|
||||||
|
## Why Is My TLS Certificate Not Reloaded When Its Contents Change ?
|
||||||
|
|
||||||
|
With the file provider,
|
||||||
|
a configuration update is only triggered when one of the [watched](../providers/file.md#provider-configuration) configuration files is modified.
|
||||||
|
|
||||||
|
Which is why, when a certificate is defined by path,
|
||||||
|
and the actual contents of this certificate change,
|
||||||
|
a configuration update is _not_ triggered.
|
||||||
|
|
||||||
|
To take into account the new certificate contents, the update of the dynamic configuration must be forced.
|
||||||
|
One way to achieve that, is to trigger a file notification,
|
||||||
|
for example, by using the `touch` command on the configuration file.
|
||||||
|
|
|
@ -167,6 +167,86 @@ func (p *Provider) loadFileConfig(ctx context.Context, filename string, parseTem
|
||||||
|
|
||||||
if configuration.TLS != nil {
|
if configuration.TLS != nil {
|
||||||
configuration.TLS.Certificates = flattenCertificates(ctx, configuration.TLS)
|
configuration.TLS.Certificates = flattenCertificates(ctx, configuration.TLS)
|
||||||
|
|
||||||
|
// TLS Options
|
||||||
|
if configuration.TLS.Options != nil {
|
||||||
|
for name, options := range configuration.TLS.Options {
|
||||||
|
var caCerts []tls.FileOrContent
|
||||||
|
|
||||||
|
for _, caFile := range options.ClientAuth.CAFiles {
|
||||||
|
content, err := caFile.Read()
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(ctx).Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
caCerts = append(caCerts, tls.FileOrContent(content))
|
||||||
|
}
|
||||||
|
options.ClientAuth.CAFiles = caCerts
|
||||||
|
|
||||||
|
configuration.TLS.Options[name] = options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS stores
|
||||||
|
if len(configuration.TLS.Stores) > 0 {
|
||||||
|
for name, store := range configuration.TLS.Stores {
|
||||||
|
content, err := store.DefaultCertificate.CertFile.Read()
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(ctx).Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
store.DefaultCertificate.CertFile = tls.FileOrContent(content)
|
||||||
|
|
||||||
|
content, err = store.DefaultCertificate.KeyFile.Read()
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(ctx).Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
store.DefaultCertificate.KeyFile = tls.FileOrContent(content)
|
||||||
|
|
||||||
|
configuration.TLS.Stores[name] = store
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServersTransport
|
||||||
|
if configuration.HTTP != nil && len(configuration.HTTP.ServersTransports) > 0 {
|
||||||
|
for name, st := range configuration.HTTP.ServersTransports {
|
||||||
|
var certificates []tls.Certificate
|
||||||
|
for _, cert := range st.Certificates {
|
||||||
|
content, err := cert.CertFile.Read()
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(ctx).Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cert.CertFile = tls.FileOrContent(content)
|
||||||
|
|
||||||
|
content, err = cert.KeyFile.Read()
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(ctx).Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cert.KeyFile = tls.FileOrContent(content)
|
||||||
|
|
||||||
|
certificates = append(certificates, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration.HTTP.ServersTransports[name].Certificates = certificates
|
||||||
|
|
||||||
|
var rootCAs []tls.FileOrContent
|
||||||
|
for _, rootCA := range st.RootCAs {
|
||||||
|
content, err := rootCA.Read()
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(ctx).Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCAs = append(rootCAs, tls.FileOrContent(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
st.RootCAs = rootCAs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return configuration, nil
|
return configuration, nil
|
||||||
|
|
|
@ -25,19 +25,35 @@ type ProvideTestCase struct {
|
||||||
expectedNumTLSOptions int
|
expectedNumTLSOptions int
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTLSContent(t *testing.T) {
|
func TestTLSCertificateContent(t *testing.T) {
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
fileTLS, err := createTempFile("./fixtures/toml/tls_file.cert", tempDir)
|
fileTLS, err := createTempFile("./fixtures/toml/tls_file.cert", tempDir)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fileTLSKey, err := createTempFile("./fixtures/toml/tls_file_key.cert", tempDir)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
fileConfig, err := os.CreateTemp(tempDir, "temp*.toml")
|
fileConfig, err := os.CreateTemp(tempDir, "temp*.toml")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
content := `
|
content := `
|
||||||
[[tls.certificates]]
|
[[tls.certificates]]
|
||||||
certFile = "` + fileTLS.Name() + `"
|
certFile = "` + fileTLS.Name() + `"
|
||||||
keyFile = "` + fileTLS.Name() + `"
|
keyFile = "` + fileTLSKey.Name() + `"
|
||||||
|
|
||||||
|
[tls.options.default.clientAuth]
|
||||||
|
caFiles = ["` + fileTLS.Name() + `"]
|
||||||
|
|
||||||
|
[tls.stores.default.defaultCertificate]
|
||||||
|
certFile = "` + fileTLS.Name() + `"
|
||||||
|
keyFile = "` + fileTLSKey.Name() + `"
|
||||||
|
|
||||||
|
[http.serversTransports.default]
|
||||||
|
rootCAs = ["` + fileTLS.Name() + `"]
|
||||||
|
[[http.serversTransports.default.certificates]]
|
||||||
|
certFile = "` + fileTLS.Name() + `"
|
||||||
|
keyFile = "` + fileTLSKey.Name() + `"
|
||||||
`
|
`
|
||||||
|
|
||||||
_, err = fileConfig.Write([]byte(content))
|
_, err = fileConfig.Write([]byte(content))
|
||||||
|
@ -48,7 +64,16 @@ func TestTLSContent(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, "CONTENT", configuration.TLS.Certificates[0].Certificate.CertFile.String())
|
require.Equal(t, "CONTENT", configuration.TLS.Certificates[0].Certificate.CertFile.String())
|
||||||
require.Equal(t, "CONTENT", configuration.TLS.Certificates[0].Certificate.KeyFile.String())
|
require.Equal(t, "CONTENTKEY", configuration.TLS.Certificates[0].Certificate.KeyFile.String())
|
||||||
|
|
||||||
|
require.Equal(t, "CONTENT", configuration.TLS.Options["default"].ClientAuth.CAFiles[0].String())
|
||||||
|
|
||||||
|
require.Equal(t, "CONTENT", configuration.TLS.Stores["default"].DefaultCertificate.CertFile.String())
|
||||||
|
require.Equal(t, "CONTENTKEY", configuration.TLS.Stores["default"].DefaultCertificate.KeyFile.String())
|
||||||
|
|
||||||
|
require.Equal(t, "CONTENT", configuration.HTTP.ServersTransports["default"].Certificates[0].CertFile.String())
|
||||||
|
require.Equal(t, "CONTENTKEY", configuration.HTTP.ServersTransports["default"].Certificates[0].KeyFile.String())
|
||||||
|
require.Equal(t, "CONTENT", configuration.HTTP.ServersTransports["default"].RootCAs[0].String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorWhenEmptyConfig(t *testing.T) {
|
func TestErrorWhenEmptyConfig(t *testing.T) {
|
||||||
|
|
1
pkg/provider/file/fixtures/toml/tls_file_key.cert
Normal file
1
pkg/provider/file/fixtures/toml/tls_file_key.cert
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CONTENTKEY
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
"github.com/traefik/traefik/v2/pkg/provider"
|
"github.com/traefik/traefik/v2/pkg/provider"
|
||||||
"github.com/traefik/traefik/v2/pkg/safe"
|
"github.com/traefik/traefik/v2/pkg/safe"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigurationWatcher watches configuration changes.
|
// ConfigurationWatcher watches configuration changes.
|
||||||
|
@ -164,6 +165,16 @@ func (c *ConfigurationWatcher) preLoadConfiguration(configMsg dynamic.Message) {
|
||||||
if copyConf.TLS != nil {
|
if copyConf.TLS != nil {
|
||||||
copyConf.TLS.Certificates = nil
|
copyConf.TLS.Certificates = nil
|
||||||
|
|
||||||
|
if copyConf.TLS.Options != nil {
|
||||||
|
cleanedOptions := make(map[string]tls.Options, len(copyConf.TLS.Options))
|
||||||
|
for name, option := range copyConf.TLS.Options {
|
||||||
|
option.ClientAuth.CAFiles = []tls.FileOrContent{}
|
||||||
|
cleanedOptions[name] = option
|
||||||
|
}
|
||||||
|
|
||||||
|
copyConf.TLS.Options = cleanedOptions
|
||||||
|
}
|
||||||
|
|
||||||
for k := range copyConf.TLS.Stores {
|
for k := range copyConf.TLS.Stores {
|
||||||
st := copyConf.TLS.Stores[k]
|
st := copyConf.TLS.Stores[k]
|
||||||
st.DefaultCertificate = nil
|
st.DefaultCertificate = nil
|
||||||
|
@ -171,6 +182,13 @@ func (c *ConfigurationWatcher) preLoadConfiguration(configMsg dynamic.Message) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if copyConf.HTTP != nil {
|
||||||
|
for _, transport := range copyConf.HTTP.ServersTransports {
|
||||||
|
transport.Certificates = tls.Certificates{}
|
||||||
|
transport.RootCAs = []tls.FileOrContent{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
jsonConf, err := json.Marshal(copyConf)
|
jsonConf, err := json.Marshal(copyConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Could not marshal dynamic configuration: %v", err)
|
logger.Errorf("Could not marshal dynamic configuration: %v", err)
|
||||||
|
|
Loading…
Reference in a new issue