fix(tls): Pass certificate and private key files to listener method (#531)

Fixes #530
This commit is contained in:
TwiN 2023-07-20 19:02:34 -04:00 committed by GitHub
parent 2f6b8f23f7
commit fd17dcd204
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 26 deletions

View File

@ -1278,14 +1278,14 @@ Confused? Read [Securing Gatus with OIDC using Auth0](https://twin.sh/articles/5
### TLS Encryption ### TLS Encryption
Gatus supports basic encryption with TLS. To enable this, certificate files in PEM format have to be provided. Gatus supports basic encryption with TLS. To enable this, certificate files in PEM format have to be provided.
The example below shows an example configuration which makes gatus respond on port 4443 to HTTPS requests.
The example below shows an example configuration which makes gatus respond on port 4443 to HTTPS requests:
```yaml ```yaml
web: web:
port: 4443 port: 4443
tls: tls:
certificate-file: "server.crt" certificate-file: "certificate.crt"
private-key-file: "server.key" private-key-file: "private.key"
``` ```
### Metrics ### Metrics

View File

@ -34,8 +34,6 @@ type TLSConfig struct {
// PrivateKeyFile is the private key file for TLS in PEM format. // PrivateKeyFile is the private key file for TLS in PEM format.
PrivateKeyFile string `yaml:"private-key-file,omitempty"` PrivateKeyFile string `yaml:"private-key-file,omitempty"`
tlsConfig *tls.Config
} }
// GetDefaultConfig returns a Config struct with the default values // GetDefaultConfig returns a Config struct with the default values
@ -57,33 +55,29 @@ func (web *Config) ValidateAndSetDefaults() error {
} }
// Try to load the TLS certificates // Try to load the TLS certificates
if web.TLS != nil { if web.TLS != nil {
if err := web.TLS.loadConfig(); err != nil { if err := web.TLS.isValid(); err != nil {
return fmt.Errorf("invalid tls config: %w", err) return fmt.Errorf("invalid tls config: %w", err)
} }
} }
return nil return nil
} }
func (web *Config) HasTLS() bool {
return web.TLS != nil && len(web.TLS.CertificateFile) > 0 && len(web.TLS.PrivateKeyFile) > 0
}
// SocketAddress returns the combination of the Address and the Port // SocketAddress returns the combination of the Address and the Port
func (web *Config) SocketAddress() string { func (web *Config) SocketAddress() string {
return fmt.Sprintf("%s:%d", web.Address, web.Port) return fmt.Sprintf("%s:%d", web.Address, web.Port)
} }
func (t *TLSConfig) loadConfig() error { func (t *TLSConfig) isValid() error {
if len(t.CertificateFile) > 0 && len(t.PrivateKeyFile) > 0 { if len(t.CertificateFile) > 0 && len(t.PrivateKeyFile) > 0 {
certificate, err := tls.LoadX509KeyPair(t.CertificateFile, t.PrivateKeyFile) _, err := tls.LoadX509KeyPair(t.CertificateFile, t.PrivateKeyFile)
if err != nil { if err != nil {
return err return err
} }
t.tlsConfig = &tls.Config{Certificates: []tls.Certificate{certificate}}
return nil return nil
} }
return errors.New("certificate-file and private-key-file must be specified") return errors.New("certificate-file and private-key-file must be specified")
} }
func (web *Config) TLSConfig() *tls.Config {
if web.TLS != nil {
return web.TLS.tlsConfig
}
return nil
}

View File

@ -37,6 +37,27 @@ func TestConfig_ValidateAndSetDefaults(t *testing.T) {
cfg: &Config{Port: 100000000}, cfg: &Config{Port: 100000000},
expectedErr: true, expectedErr: true,
}, },
{
name: "with-good-tls-config",
cfg: &Config{Port: 443, TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: "../../testdata/cert.key"}},
expectedAddress: "0.0.0.0",
expectedPort: 443,
expectedErr: false,
},
{
name: "with-bad-tls-config",
cfg: &Config{Port: 443, TLS: &TLSConfig{CertificateFile: "../../testdata/badcert.pem", PrivateKeyFile: "../../testdata/cert.key"}},
expectedAddress: "0.0.0.0",
expectedPort: 443,
expectedErr: true,
},
{
name: "with-partial-tls-config",
cfg: &Config{Port: 443, TLS: &TLSConfig{CertificateFile: "", PrivateKeyFile: "../../testdata/cert.key"}},
expectedAddress: "0.0.0.0",
expectedPort: 443,
expectedErr: true,
},
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) { t.Run(scenario.name, func(t *testing.T) {
@ -67,7 +88,7 @@ func TestConfig_SocketAddress(t *testing.T) {
} }
} }
func TestConfig_TLSConfig(t *testing.T) { func TestConfig_isValid(t *testing.T) {
scenarios := []struct { scenarios := []struct {
name string name string
cfg *Config cfg *Config
@ -79,27 +100,37 @@ func TestConfig_TLSConfig(t *testing.T) {
expectedErr: false, expectedErr: false,
}, },
{ {
name: "missing-crt-file", name: "missing-certificate-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "doesnotexist", PrivateKeyFile: "../../testdata/cert.key"}}, cfg: &Config{TLS: &TLSConfig{CertificateFile: "doesnotexist", PrivateKeyFile: "../../testdata/cert.key"}},
expectedErr: true, expectedErr: true,
}, },
{ {
name: "bad-crt-file", name: "bad-certificate-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/badcert.pem", PrivateKeyFile: "../../testdata/cert.key"}}, cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/badcert.pem", PrivateKeyFile: "../../testdata/cert.key"}},
expectedErr: true, expectedErr: true,
}, },
{
name: "no-certificate-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "", PrivateKeyFile: "../../testdata/cert.key"}},
expectedErr: true,
},
{ {
name: "missing-private-key-file", name: "missing-private-key-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: "doesnotexist"}}, cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: "doesnotexist"}},
expectedErr: true, expectedErr: true,
}, },
{
name: "no-private-key-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: ""}},
expectedErr: true,
},
{ {
name: "bad-private-key-file", name: "bad-private-key-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: "../../testdata/badcert.key"}}, cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: "../../testdata/badcert.key"}},
expectedErr: true, expectedErr: true,
}, },
{ {
name: "bad-cert-and-private-key-file", name: "bad-certificate-and-private-key-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/badcert.pem", PrivateKeyFile: "../../testdata/badcert.key"}}, cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/badcert.pem", PrivateKeyFile: "../../testdata/badcert.key"}},
expectedErr: true, expectedErr: true,
}, },
@ -112,8 +143,8 @@ func TestConfig_TLSConfig(t *testing.T) {
return return
} }
if !scenario.expectedErr { if !scenario.expectedErr {
if scenario.cfg.TLS.tlsConfig == nil { if scenario.cfg.TLS.isValid() != nil {
t.Error("TLS configuration was not correctly loaded although no error was returned") t.Error("cfg.TLS.isValid() returned an error even though no error was expected")
} }
} }
}) })

View File

@ -22,16 +22,22 @@ func Handle(cfg *config.Config) {
server.ReadTimeout = 15 * time.Second server.ReadTimeout = 15 * time.Second
server.WriteTimeout = 15 * time.Second server.WriteTimeout = 15 * time.Second
server.IdleTimeout = 15 * time.Second server.IdleTimeout = 15 * time.Second
server.TLSConfig = cfg.Web.TLSConfig()
if os.Getenv("ROUTER_TEST") == "true" { if os.Getenv("ROUTER_TEST") == "true" {
return return
} }
log.Println("[controller][Handle] Listening on " + cfg.Web.SocketAddress()) log.Println("[controller][Handle] Listening on " + cfg.Web.SocketAddress())
if server.TLSConfig != nil { if cfg.Web.HasTLS() {
log.Println("[controller][Handle]", app.ListenTLS(cfg.Web.SocketAddress(), "", "")) err := app.ListenTLS(cfg.Web.SocketAddress(), cfg.Web.TLS.CertificateFile, cfg.Web.TLS.PrivateKeyFile)
if err != nil {
log.Fatal("[controller][Handle]", err)
}
} else { } else {
log.Println("[controller][Handle]", app.Listen(cfg.Web.SocketAddress())) err := app.Listen(cfg.Web.SocketAddress())
if err != nil {
log.Fatal("[controller][Handle]", err)
}
} }
log.Println("[controller][Handle] Server has shut down successfully")
} }
// Shutdown stops the server // Shutdown stops the server