-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
automatically reload TLS certificates when they change (#3598)
* Dynamically refresh tls certs for all servers * make sure that CertLoader is always closed --------- Co-authored-by: aler9 <[email protected]>
- Loading branch information
Showing
6 changed files
with
239 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Package certloader contains a certicate loader. | ||
package certloader | ||
|
||
import ( | ||
"crypto/tls" | ||
"sync" | ||
|
||
"github.com/bluenviron/mediamtx/internal/confwatcher" | ||
"github.com/bluenviron/mediamtx/internal/logger" | ||
) | ||
|
||
// CertLoader is a certificate loader. It watches for changes to the certificate and key files. | ||
type CertLoader struct { | ||
log logger.Writer | ||
certWatcher, keyWatcher *confwatcher.ConfWatcher | ||
certPath, keyPath string | ||
done chan struct{} | ||
|
||
cert *tls.Certificate | ||
certMu sync.RWMutex | ||
} | ||
|
||
// New allocates a CertLoader. | ||
func New(certPath, keyPath string, log logger.Writer) (*CertLoader, error) { | ||
cl := &CertLoader{ | ||
log: log, | ||
certPath: certPath, | ||
keyPath: keyPath, | ||
done: make(chan struct{}), | ||
} | ||
|
||
var err error | ||
cl.certWatcher, err = confwatcher.New(certPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cl.keyWatcher, err = confwatcher.New(keyPath) | ||
if err != nil { | ||
cl.certWatcher.Close() //nolint:errcheck | ||
return nil, err | ||
} | ||
|
||
cert, err := tls.LoadX509KeyPair(certPath, keyPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cl.certMu.Lock() | ||
cl.cert = &cert | ||
cl.certMu.Unlock() | ||
|
||
go cl.watch() | ||
|
||
return cl, nil | ||
} | ||
|
||
// Close closes a CertLoader and releases any underlying resources. | ||
func (cl *CertLoader) Close() { | ||
close(cl.done) | ||
cl.certWatcher.Close() //nolint:errcheck | ||
cl.keyWatcher.Close() //nolint:errcheck | ||
cl.certMu.Lock() | ||
defer cl.certMu.Unlock() | ||
cl.cert = nil | ||
} | ||
|
||
// GetCertificate returns a function that returns the certificate for use in a tls.Config. | ||
func (cl *CertLoader) GetCertificate() func(*tls.ClientHelloInfo) (*tls.Certificate, error) { | ||
return func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) { | ||
cl.certMu.RLock() | ||
defer cl.certMu.RUnlock() | ||
return cl.cert, nil | ||
} | ||
} | ||
|
||
func (cl *CertLoader) watch() { | ||
for { | ||
select { | ||
case <-cl.certWatcher.Watch(): | ||
cert, err := tls.LoadX509KeyPair(cl.certPath, cl.keyPath) | ||
if err != nil { | ||
cl.log.Log(logger.Error, "certloader failed to load after change to %s: %s", cl.certPath, err.Error()) | ||
continue | ||
} | ||
|
||
cl.certMu.Lock() | ||
cl.cert = &cert | ||
cl.certMu.Unlock() | ||
|
||
cl.log.Log(logger.Info, "certificate reloaded after change to %s", cl.certPath) | ||
case <-cl.keyWatcher.Watch(): | ||
cert, err := tls.LoadX509KeyPair(cl.certPath, cl.keyPath) | ||
if err != nil { | ||
cl.log.Log(logger.Error, "certloader failed to load after change to %s: %s", cl.keyPath, err.Error()) | ||
continue | ||
} | ||
|
||
cl.certMu.Lock() | ||
cl.cert = &cert | ||
cl.certMu.Unlock() | ||
|
||
cl.log.Log(logger.Info, "certificate reloaded after change to %s", cl.keyPath) | ||
case <-cl.done: | ||
return | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package certloader | ||
|
||
import ( | ||
"crypto/tls" | ||
"os" | ||
"testing" | ||
"time" | ||
|
||
"github.com/bluenviron/mediamtx/internal/test" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestCertReload(t *testing.T) { | ||
testData, err := tls.X509KeyPair(test.TLSCertPub, test.TLSCertKey) | ||
require.NoError(t, err) | ||
|
||
serverCertPath, err := test.CreateTempFile(test.TLSCertPub) | ||
require.NoError(t, err) | ||
defer os.Remove(serverCertPath) | ||
|
||
serverKeyPath, err := test.CreateTempFile(test.TLSCertKey) | ||
require.NoError(t, err) | ||
defer os.Remove(serverKeyPath) | ||
|
||
loader, err := New(serverCertPath, serverKeyPath, test.NilLogger) | ||
require.NoError(t, err) | ||
defer loader.Close() | ||
|
||
getCert := loader.GetCertificate() | ||
require.NotNil(t, getCert) | ||
|
||
cert, err := getCert(nil) | ||
require.NoError(t, err) | ||
require.NotNil(t, cert) | ||
require.Equal(t, &testData, cert) | ||
|
||
testData, err = tls.X509KeyPair(test.TLSCertPubAlt, test.TLSCertKeyAlt) | ||
require.NoError(t, err) | ||
|
||
err = os.WriteFile(serverCertPath, test.TLSCertPubAlt, 0o644) | ||
require.NoError(t, err) | ||
|
||
err = os.WriteFile(serverKeyPath, test.TLSCertKeyAlt, 0o644) | ||
require.NoError(t, err) | ||
|
||
time.Sleep(1 * time.Second) | ||
|
||
cert, err = getCert(nil) | ||
require.NoError(t, err) | ||
require.NotNil(t, cert) | ||
require.Equal(t, &testData, cert) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters