mirror of
https://github.com/TwiN/gatus.git
synced 2024-11-21 23:43:27 +01:00
feat(web): Support TLS encryption (#322)
* Basic setup to serve HTTPS * Correctly handle the case of missing TLS configs * Documenting TLS * Refactor TLS configuration setup * Add TLS Encryption section again to README * Extending TOC in README * Moving TLS settings to subsection of web settings * Adding tests for config/web * Add test for handling TLS * Rename some variables as suggested * Corrected error formatting * Update test module import * Polishing the readme file * Error handling for TLSConfig() --------- Co-authored-by: TwiN <twin@linux.com>
This commit is contained in:
parent
0bd0c1fd15
commit
a05daeda2e
66
README.md
66
README.md
@ -67,8 +67,9 @@ Have any feedback or questions? [Create a discussion](https://github.com/TwiN/ga
|
|||||||
- [Setting a default alert](#setting-a-default-alert)
|
- [Setting a default alert](#setting-a-default-alert)
|
||||||
- [Maintenance](#maintenance)
|
- [Maintenance](#maintenance)
|
||||||
- [Security](#security)
|
- [Security](#security)
|
||||||
- [Basic](#basic)
|
- [Basic Authentication](#basic-authentication)
|
||||||
- [OIDC](#oidc)
|
- [OIDC](#oidc)
|
||||||
|
- [TLS Encryption](#tls-encryption)
|
||||||
- [Metrics](#metrics)
|
- [Metrics](#metrics)
|
||||||
- [Remote instances (EXPERIMENTAL)](#remote-instances-experimental)
|
- [Remote instances (EXPERIMENTAL)](#remote-instances-experimental)
|
||||||
- [Deployment](#deployment)
|
- [Deployment](#deployment)
|
||||||
@ -87,7 +88,7 @@ Have any feedback or questions? [Create a discussion](https://github.com/TwiN/ga
|
|||||||
- [Monitoring an endpoint using ICMP](#monitoring-an-endpoint-using-icmp)
|
- [Monitoring an endpoint using ICMP](#monitoring-an-endpoint-using-icmp)
|
||||||
- [Monitoring an endpoint using DNS queries](#monitoring-an-endpoint-using-dns-queries)
|
- [Monitoring an endpoint using DNS queries](#monitoring-an-endpoint-using-dns-queries)
|
||||||
- [Monitoring an endpoint using STARTTLS](#monitoring-an-endpoint-using-starttls)
|
- [Monitoring an endpoint using STARTTLS](#monitoring-an-endpoint-using-starttls)
|
||||||
- [Monitoring an endpoint using TLS](#monitoring-an-endpoint-using-tls)
|
- [Monitoring an endpoint using TLS](#monitoring-an-endpoint-using-tls)>
|
||||||
- [Monitoring domain expiration](#monitoring-domain-expiration)
|
- [Monitoring domain expiration](#monitoring-domain-expiration)
|
||||||
- [disable-monitoring-lock](#disable-monitoring-lock)
|
- [disable-monitoring-lock](#disable-monitoring-lock)
|
||||||
- [Reloading configuration on the fly](#reloading-configuration-on-the-fly)
|
- [Reloading configuration on the fly](#reloading-configuration-on-the-fly)
|
||||||
@ -228,6 +229,8 @@ If you want to test it locally, see [Docker](#docker).
|
|||||||
| `web` | Web configuration. | `{}` |
|
| `web` | Web configuration. | `{}` |
|
||||||
| `web.address` | Address to listen on. | `0.0.0.0` |
|
| `web.address` | Address to listen on. | `0.0.0.0` |
|
||||||
| `web.port` | Port to listen on. | `8080` |
|
| `web.port` | Port to listen on. | `8080` |
|
||||||
|
| `web.tls.certificate-file` | Optional public certificate file for TLS in PEM format. | `` |
|
||||||
|
| `web.tls.private-key-file` | Optional private key file for TLS in PEM format. | `` |
|
||||||
| `ui` | UI configuration. | `{}` |
|
| `ui` | UI configuration. | `{}` |
|
||||||
| `ui.title` | [Title of the document](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title). | `Health Dashboard ǀ Gatus` |
|
| `ui.title` | [Title of the document](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title). | `Health Dashboard ǀ Gatus` |
|
||||||
| `ui.description` | Meta description for the page. | `Gatus is an advanced...`. |
|
| `ui.description` | Meta description for the page. | `Gatus is an advanced...`. |
|
||||||
@ -1053,13 +1056,13 @@ As a result, the `[ALERT_TRIGGERED_OR_RESOLVED]` in the body of first example of
|
|||||||
|
|
||||||
|
|
||||||
#### Setting a default alert
|
#### Setting a default alert
|
||||||
| Parameter | Description | Default |
|
| Parameter | Description | Default |
|
||||||
|:----------------------------------------------|:------------------------------------------------------------------------------|:--------|
|
|:---------------------------------------------|:------------------------------------------------------------------------------|:--------|
|
||||||
| `alerting.*.default-alert.enabled` | Whether to enable the alert | N/A |
|
| `alerting.*.default-alert.enabled` | Whether to enable the alert | N/A |
|
||||||
| `alerting.*.default-alert.failure-threshold` | Number of failures in a row needed before triggering the alert | N/A |
|
| `alerting.*.default-alert.failure-threshold` | Number of failures in a row needed before triggering the alert | N/A |
|
||||||
| `alerting.*.default-alert.success-threshold` | Number of successes in a row before an ongoing incident is marked as resolved | N/A |
|
| `alerting.*.default-alert.success-threshold` | Number of successes in a row before an ongoing incident is marked as resolved | N/A |
|
||||||
| `alerting.*.default-alert.send-on-resolved` | Whether to send a notification once a triggered alert is marked as resolved | N/A |
|
| `alerting.*.default-alert.send-on-resolved` | Whether to send a notification once a triggered alert is marked as resolved | N/A |
|
||||||
| `alerting.*.default-alert.description` | Description of the alert. Will be included in the alert sent | N/A |
|
| `alerting.*.default-alert.description` | Description of the alert. Will be included in the alert sent | N/A |
|
||||||
|
|
||||||
> ⚠ You must still specify the `type` of the alert in the endpoint configuration even if you set the default alert of a provider.
|
> ⚠ You must still specify the `type` of the alert in the endpoint configuration even if you set the default alert of a provider.
|
||||||
|
|
||||||
@ -1175,14 +1178,14 @@ maintenance:
|
|||||||
|
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
| Parameter | Description | Default |
|
| Parameter | Description | Default |
|
||||||
|:---------------------------------|:-----------------------------|:--------------|
|
|:-----------------|:-----------------------------|:--------|
|
||||||
| `security` | Security configuration | `{}` |
|
| `security` | Security configuration | `{}` |
|
||||||
| `security.basic` | HTTP Basic configuration | `{}` |
|
| `security.basic` | HTTP Basic configuration | `{}` |
|
||||||
| `security.oidc` | OpenID Connect configuration | `{}` |
|
| `security.oidc` | OpenID Connect configuration | `{}` |
|
||||||
|
|
||||||
|
|
||||||
#### Basic
|
#### Basic Authentication
|
||||||
| Parameter | Description | Default |
|
| Parameter | Description | Default |
|
||||||
|:----------------------------------------|:-----------------------------------------------------------------------------------|:--------------|
|
|:----------------------------------------|:-----------------------------------------------------------------------------------|:--------------|
|
||||||
| `security.basic` | HTTP Basic configuration | `{}` |
|
| `security.basic` | HTTP Basic configuration | `{}` |
|
||||||
@ -1226,6 +1229,17 @@ security:
|
|||||||
|
|
||||||
Confused? Read [Securing Gatus with OIDC using Auth0](https://twin.sh/articles/56/securing-gatus-with-oidc-using-auth0).
|
Confused? Read [Securing Gatus with OIDC using Auth0](https://twin.sh/articles/56/securing-gatus-with-oidc-using-auth0).
|
||||||
|
|
||||||
|
### TLS Encryption
|
||||||
|
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.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
web:
|
||||||
|
port: 4443
|
||||||
|
tls:
|
||||||
|
certificate-file: "server.crt"
|
||||||
|
private-key-file: "server.key"
|
||||||
|
```
|
||||||
|
|
||||||
### Metrics
|
### Metrics
|
||||||
To enable metrics, you must set `metrics` to `true`. Doing so will expose Prometheus-friendly metrics at the `/metrics`
|
To enable metrics, you must set `metrics` to `true`. Doing so will expose Prometheus-friendly metrics at the `/metrics`
|
||||||
@ -1253,12 +1267,12 @@ This is an experimental feature. It may be removed or updated in a breaking mann
|
|||||||
there are known issues with this feature. If you'd like to provide some feedback, please write a comment in [#64](https://github.com/TwiN/gatus/issues/64).
|
there are known issues with this feature. If you'd like to provide some feedback, please write a comment in [#64](https://github.com/TwiN/gatus/issues/64).
|
||||||
Use at your own risk.
|
Use at your own risk.
|
||||||
|
|
||||||
| Parameter | Description | Default |
|
| Parameter | Description | Default |
|
||||||
|:-----------------------------------|:---------------------------------------------|:---------------|
|
|:-----------------------------------|:---------------------------------------------|:--------------|
|
||||||
| `remote` | Remote configuration | `{}` |
|
| `remote` | Remote configuration | `{}` |
|
||||||
| `remote.instances` | List of remote instances | Required `[]` |
|
| `remote.instances` | List of remote instances | Required `[]` |
|
||||||
| `remote.instances.endpoint-prefix` | String to prefix all endpoint names with | `""` |
|
| `remote.instances.endpoint-prefix` | String to prefix all endpoint names with | `""` |
|
||||||
| `remote.instances.url` | URL from which to retrieve endpoint statuses | Required `""` |
|
| `remote.instances.url` | URL from which to retrieve endpoint statuses | Required `""` |
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
remote:
|
remote:
|
||||||
@ -1386,11 +1400,11 @@ simple health checks used for alerting (PagerDuty/Twilio) to `30s`.
|
|||||||
|
|
||||||
|
|
||||||
### Default timeouts
|
### Default timeouts
|
||||||
| Endpoint type | Timeout |
|
| Endpoint type | Timeout |
|
||||||
|:---------------|:--------|
|
|:--------------|:--------|
|
||||||
| HTTP | 10s |
|
| HTTP | 10s |
|
||||||
| TCP | 10s |
|
| TCP | 10s |
|
||||||
| ICMP | 10s |
|
| ICMP | 10s |
|
||||||
|
|
||||||
To modify the timeout, see [Client configuration](#client-configuration).
|
To modify the timeout, see [Client configuration](#client-configuration).
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
@ -21,6 +22,21 @@ type Config struct {
|
|||||||
|
|
||||||
// Port to listen on (default to 8080 specified by DefaultPort)
|
// Port to listen on (default to 8080 specified by DefaultPort)
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
|
|
||||||
|
// TLS configuration
|
||||||
|
Tls TLSConfig `yaml:"tls"`
|
||||||
|
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
tlsConfigError error
|
||||||
|
}
|
||||||
|
|
||||||
|
type TLSConfig struct {
|
||||||
|
|
||||||
|
// Optional public certificate for TLS in PEM format.
|
||||||
|
CertificateFile string `yaml:"certificate-file,omitempty"`
|
||||||
|
|
||||||
|
// Optional private key file for TLS in PEM format.
|
||||||
|
PrivateKeyFile string `yaml:"private-key-file,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultConfig returns a Config struct with the default values
|
// GetDefaultConfig returns a Config struct with the default values
|
||||||
@ -40,6 +56,11 @@ func (web *Config) ValidateAndSetDefaults() error {
|
|||||||
} else if web.Port < 0 || web.Port > math.MaxUint16 {
|
} else if web.Port < 0 || web.Port > math.MaxUint16 {
|
||||||
return fmt.Errorf("invalid port: value should be between %d and %d", 0, math.MaxUint16)
|
return fmt.Errorf("invalid port: value should be between %d and %d", 0, math.MaxUint16)
|
||||||
}
|
}
|
||||||
|
// Try to load the TLS certificates
|
||||||
|
_, err := web.TLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid tls config: %w", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,3 +68,19 @@ func (web *Config) ValidateAndSetDefaults() error {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TLSConfig returns a tls.Config object for serving over an encrypted channel
|
||||||
|
func (web *Config) TLSConfig() (*tls.Config, error) {
|
||||||
|
if web.tlsConfig == nil && len(web.Tls.CertificateFile) > 0 && len(web.Tls.PrivateKeyFile) > 0 {
|
||||||
|
web.loadTLSConfig()
|
||||||
|
}
|
||||||
|
return web.tlsConfig, web.tlsConfigError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (web *Config) loadTLSConfig() {
|
||||||
|
cer, err := tls.LoadX509KeyPair(web.Tls.CertificateFile, web.Tls.PrivateKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
web.tlsConfigError = err
|
||||||
|
}
|
||||||
|
web.tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}}
|
||||||
|
}
|
||||||
|
@ -2,6 +2,8 @@ package web
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/TwiN/gatus/v5/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetDefaultConfig(t *testing.T) {
|
func TestGetDefaultConfig(t *testing.T) {
|
||||||
@ -12,6 +14,9 @@ func TestGetDefaultConfig(t *testing.T) {
|
|||||||
if defaultConfig.Address != DefaultAddress {
|
if defaultConfig.Address != DefaultAddress {
|
||||||
t.Error("expected default config to have the default address")
|
t.Error("expected default config to have the default address")
|
||||||
}
|
}
|
||||||
|
if defaultConfig.Tls != (TLSConfig{}) {
|
||||||
|
t.Error("expected default config to have TLS disabled")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfig_ValidateAndSetDefaults(t *testing.T) {
|
func TestConfig_ValidateAndSetDefaults(t *testing.T) {
|
||||||
@ -63,3 +68,43 @@ func TestConfig_SocketAddress(t *testing.T) {
|
|||||||
t.Errorf("expected %s, got %s", "0.0.0.0:8081", web.SocketAddress())
|
t.Errorf("expected %s, got %s", "0.0.0.0:8081", web.SocketAddress())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfig_TLSConfig(t *testing.T) {
|
||||||
|
privateKeyPath, publicKeyPath := test.UnsafeSelfSignedCertificates(t.TempDir())
|
||||||
|
|
||||||
|
scenarios := []struct {
|
||||||
|
name string
|
||||||
|
cfg *Config
|
||||||
|
expectedErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "including TLS",
|
||||||
|
cfg: &Config{Tls: (TLSConfig{CertificateFile: publicKeyPath, PrivateKeyFile: privateKeyPath})},
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TLS with missing crt file",
|
||||||
|
cfg: &Config{Tls: (TLSConfig{CertificateFile: "doesnotexist", PrivateKeyFile: privateKeyPath})},
|
||||||
|
expectedErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TLS with missing key file",
|
||||||
|
cfg: &Config{Tls: (TLSConfig{CertificateFile: publicKeyPath, PrivateKeyFile: "doesnotexist"})},
|
||||||
|
expectedErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
t.Run(scenario.name, func(t *testing.T) {
|
||||||
|
cfg, err := scenario.cfg.TLSConfig()
|
||||||
|
if (err != nil) != scenario.expectedErr {
|
||||||
|
t.Errorf("expected the existence of an error to be %v, got %v", scenario.expectedErr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !scenario.expectedErr {
|
||||||
|
if cfg == nil {
|
||||||
|
t.Error("TLS configuration was not correctly loaded although no error was returned")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,8 +24,14 @@ func Handle(cfg *config.Config) {
|
|||||||
if os.Getenv("ENVIRONMENT") == "dev" {
|
if os.Getenv("ENVIRONMENT") == "dev" {
|
||||||
router = handler.DevelopmentCORS(router)
|
router = handler.DevelopmentCORS(router)
|
||||||
}
|
}
|
||||||
|
tlsConfig, err := cfg.Web.TLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // Should be unreachable, because the config is validated before
|
||||||
|
}
|
||||||
|
|
||||||
server = &http.Server{
|
server = &http.Server{
|
||||||
Addr: fmt.Sprintf("%s:%d", cfg.Web.Address, cfg.Web.Port),
|
Addr: fmt.Sprintf("%s:%d", cfg.Web.Address, cfg.Web.Port),
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
Handler: router,
|
Handler: router,
|
||||||
ReadTimeout: 15 * time.Second,
|
ReadTimeout: 15 * time.Second,
|
||||||
WriteTimeout: 15 * time.Second,
|
WriteTimeout: 15 * time.Second,
|
||||||
@ -35,7 +41,11 @@ func Handle(cfg *config.Config) {
|
|||||||
if os.Getenv("ROUTER_TEST") == "true" {
|
if os.Getenv("ROUTER_TEST") == "true" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("[controller][Handle]", server.ListenAndServe())
|
if tlsConfig != nil {
|
||||||
|
log.Println("[controller][Handle]", server.ListenAndServeTLS("", ""))
|
||||||
|
} else {
|
||||||
|
log.Println("[controller][Handle]", server.ListenAndServe())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown stops the server
|
// Shutdown stops the server
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/TwiN/gatus/v5/config"
|
"github.com/TwiN/gatus/v5/config"
|
||||||
"github.com/TwiN/gatus/v5/config/web"
|
"github.com/TwiN/gatus/v5/config/web"
|
||||||
"github.com/TwiN/gatus/v5/core"
|
"github.com/TwiN/gatus/v5/core"
|
||||||
|
"github.com/TwiN/gatus/v5/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHandle(t *testing.T) {
|
func TestHandle(t *testing.T) {
|
||||||
@ -45,6 +46,41 @@ func TestHandle(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandleTls(t *testing.T) {
|
||||||
|
privateKeyPath, publicKeyPath := test.UnsafeSelfSignedCertificates(t.TempDir())
|
||||||
|
cfg := &config.Config{
|
||||||
|
Web: &web.Config{
|
||||||
|
Address: "0.0.0.0",
|
||||||
|
Port: rand.Intn(65534),
|
||||||
|
Tls: (web.TLSConfig{CertificateFile: publicKeyPath, PrivateKeyFile: privateKeyPath}),
|
||||||
|
},
|
||||||
|
Endpoints: []*core.Endpoint{
|
||||||
|
{
|
||||||
|
Name: "frontend",
|
||||||
|
Group: "core",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "backend",
|
||||||
|
Group: "core",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_ = os.Setenv("ROUTER_TEST", "true")
|
||||||
|
_ = os.Setenv("ENVIRONMENT", "dev")
|
||||||
|
defer os.Clearenv()
|
||||||
|
Handle(cfg)
|
||||||
|
defer Shutdown()
|
||||||
|
request, _ := http.NewRequest("GET", "/health", http.NoBody)
|
||||||
|
responseRecorder := httptest.NewRecorder()
|
||||||
|
server.Handler.ServeHTTP(responseRecorder, request)
|
||||||
|
if responseRecorder.Code != http.StatusOK {
|
||||||
|
t.Error("expected GET /health to return status code 200")
|
||||||
|
}
|
||||||
|
if server == nil {
|
||||||
|
t.Fatal("server should've been set (but because we set ROUTER_TEST, it shouldn't have been started)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestShutdown(t *testing.T) {
|
func TestShutdown(t *testing.T) {
|
||||||
// Pretend that we called controller.Handle(), which initializes the server variable
|
// Pretend that we called controller.Handle(), which initializes the server variable
|
||||||
server = &http.Server{}
|
server = &http.Server{}
|
||||||
|
72
test/tls.go
Normal file
72
test/tls.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnsafeSelfSignedCertificates creates a pair of test certificates in the given test folder
|
||||||
|
func UnsafeSelfSignedCertificates(testfolder string) (privateKeyPath string, publicKeyPath string) {
|
||||||
|
privateKeyPath = fmt.Sprintf("%s/cert.key", testfolder)
|
||||||
|
publicKeyPath = fmt.Sprintf("%s/cert.pem", testfolder)
|
||||||
|
|
||||||
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to generatekey: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(1234),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{"Gatus test"},
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().Add(time.Hour * 24),
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
DNSNames: []string{"localhost"},
|
||||||
|
}
|
||||||
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certOut, err := os.Create(publicKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open cert.pem for writing: %v", err)
|
||||||
|
}
|
||||||
|
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
||||||
|
log.Fatalf("Failed to write data to cert.pem: %v", err)
|
||||||
|
}
|
||||||
|
if err := certOut.Close(); err != nil {
|
||||||
|
log.Fatalf("Error closing cert.pem: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyOut, err := os.OpenFile(privateKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open %s for writing: %v", privateKeyPath, err)
|
||||||
|
}
|
||||||
|
privBytes, err := x509.MarshalPKCS8PrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to marshal private key: %v", err)
|
||||||
|
}
|
||||||
|
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
|
||||||
|
log.Fatalf("Failed to write data to key.pem: %v", err)
|
||||||
|
}
|
||||||
|
if err := keyOut.Close(); err != nil {
|
||||||
|
log.Fatalf("Error closing key.pem: %v", err)
|
||||||
|
}
|
||||||
|
log.Print("wrote key.pem\n")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user