diff --git a/cmd/karma/main.go b/cmd/karma/main.go index dfc14737f..0bb56cff2 100644 --- a/cmd/karma/main.go +++ b/cmd/karma/main.go @@ -439,17 +439,34 @@ func serve(errorHandling pflag.ErrorHandling) error { if err != nil { return err } - log.Info().Str("address", listener.Addr().String()).Msg("Starting HTTP server") httpServer := &http.Server{ Addr: listen, Handler: router, } - go func() { - _ = httpServer.Serve(listener) - }() quit := make(chan os.Signal, 1) + + if config.Config.Listen.TLS.Cert != "" { + log.Info().Str("address", listener.Addr().String()).Msg("Starting HTTPS server") + go func() { + err := httpServer.ServeTLS(listener, config.Config.Listen.TLS.Cert, config.Config.Listen.TLS.Key) + if err != nil && err != http.ErrServerClosed { + log.Error().Err(err).Msg("HTTPS server startup error") + quit <- syscall.SIGTERM + } + }() + } else { + log.Info().Str("address", listener.Addr().String()).Msg("Starting HTTP server") + go func() { + err := httpServer.Serve(listener) + if err != nil && err != http.ErrServerClosed { + log.Error().Err(err).Msg("HTTP server startup error") + quit <- syscall.SIGTERM + } + }() + } + signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) <-quit log.Info().Msg("Shutting down HTTP server") diff --git a/cmd/karma/tests/testscript/059_log_full_config_env.txt b/cmd/karma/tests/testscript/059_log_full_config_env.txt index 5a2d4887c..ded1cd3a3 100644 --- a/cmd/karma/tests/testscript/059_log_full_config_env.txt +++ b/cmd/karma/tests/testscript/059_log_full_config_env.txt @@ -158,6 +158,9 @@ level=info msg=" - instance" level=info msg=" - cluster" level=info msg="listen:" level=info msg=" address: 127.0.0.1" +level=info msg=" tls:" +level=info msg=" cert: \"\"" +level=info msg=" key: \"\"" level=info msg=" port: 1234" level=info msg=" prefix: /prefix/" level=info msg="log:" diff --git a/cmd/karma/tests/testscript/060_log_full_config_file.txt b/cmd/karma/tests/testscript/060_log_full_config_file.txt index 7b415ffcf..553e3de43 100644 --- a/cmd/karma/tests/testscript/060_log_full_config_file.txt +++ b/cmd/karma/tests/testscript/060_log_full_config_file.txt @@ -162,6 +162,9 @@ level=info msg=" - instance" level=info msg=" - '@receiver'" level=info msg="listen:" level=info msg=" address: \"\"" +level=info msg=" tls:" +level=info msg=" cert: \"\"" +level=info msg=" key: \"\"" level=info msg=" port: 8080" level=info msg=" prefix: /" level=info msg="log:" diff --git a/cmd/karma/tests/testscript/065_proxy-with-readonly.txt b/cmd/karma/tests/testscript/065_proxy-with-readonly.txt index de9fd4b93..b7490a9e4 100644 --- a/cmd/karma/tests/testscript/065_proxy-with-readonly.txt +++ b/cmd/karma/tests/testscript/065_proxy-with-readonly.txt @@ -72,6 +72,9 @@ level=info msg=" static: []" level=info msg=" unique: []" level=info msg="listen:" level=info msg=" address: \"\"" +level=info msg=" tls:" +level=info msg=" cert: \"\"" +level=info msg=" key: \"\"" level=info msg=" port: 8080" level=info msg=" prefix: /" level=info msg="log:" diff --git a/cmd/karma/tests/testscript/066_proxy.txt b/cmd/karma/tests/testscript/066_proxy.txt index 83ba76f7e..4923cfa8b 100644 --- a/cmd/karma/tests/testscript/066_proxy.txt +++ b/cmd/karma/tests/testscript/066_proxy.txt @@ -72,6 +72,9 @@ level=info msg=" static: []" level=info msg=" unique: []" level=info msg="listen:" level=info msg=" address: \"\"" +level=info msg=" tls:" +level=info msg=" cert: \"\"" +level=info msg=" key: \"\"" level=info msg=" port: 8080" level=info msg=" prefix: /" level=info msg="log:" diff --git a/cmd/karma/tests/testscript/067_readonly.txt b/cmd/karma/tests/testscript/067_readonly.txt index aac32a15e..ed0f0a3d8 100644 --- a/cmd/karma/tests/testscript/067_readonly.txt +++ b/cmd/karma/tests/testscript/067_readonly.txt @@ -72,6 +72,9 @@ level=info msg=" static: []" level=info msg=" unique: []" level=info msg="listen:" level=info msg=" address: \"\"" +level=info msg=" tls:" +level=info msg=" cert: \"\"" +level=info msg=" key: \"\"" level=info msg=" port: 8080" level=info msg=" prefix: /" level=info msg="log:" diff --git a/cmd/karma/tests/testscript/068_sentry.txt b/cmd/karma/tests/testscript/068_sentry.txt index 8a19e4cc2..f195e244f 100644 --- a/cmd/karma/tests/testscript/068_sentry.txt +++ b/cmd/karma/tests/testscript/068_sentry.txt @@ -74,6 +74,9 @@ level=info msg=" static: []" level=info msg=" unique: []" level=info msg="listen:" level=info msg=" address: 127.0.0.1" +level=info msg=" tls:" +level=info msg=" cert: \"\"" +level=info msg=" key: \"\"" level=info msg=" port: 8068" level=info msg=" prefix: /" level=info msg="log:" diff --git a/cmd/karma/tests/testscript/070_upper_case_keys.txt b/cmd/karma/tests/testscript/070_upper_case_keys.txt index 2d17482fa..a81ef9f65 100644 --- a/cmd/karma/tests/testscript/070_upper_case_keys.txt +++ b/cmd/karma/tests/testscript/070_upper_case_keys.txt @@ -82,6 +82,9 @@ level=info msg=" static: []" level=info msg=" unique: []" level=info msg="listen:" level=info msg=" address: \"\"" +level=info msg=" tls:" +level=info msg=" cert: \"\"" +level=info msg=" key: \"\"" level=info msg=" port: 8080" level=info msg=" prefix: /" level=info msg="log:" diff --git a/cmd/karma/tests/testscript/087_listen_tls.txt b/cmd/karma/tests/testscript/087_listen_tls.txt new file mode 100644 index 000000000..aedce5ec2 --- /dev/null +++ b/cmd/karma/tests/testscript/087_listen_tls.txt @@ -0,0 +1,57 @@ +# Listens on HTTPS when enabled + +exec sh -x ./tls.sh +exec sh -x ./test.sh & +karma.bin-should-work --pid-file=karma.pid --config.file=karma.yaml --listen.address=127.0.0.1 --listen.port=8087 +! stdout . +wait +cmp http1.txt pong.txt +cmp http2.txt pong.txt + +-- pong.txt -- +Pong +-- stderr.txt -- +level=info msg="Reading configuration file" path=karma.yaml +level=info msg="Version: dev" +level=info msg="Configured Alertmanager source" name=local proxy=false readonly=false uri=https://127.0.0.1:9093 +level=info msg="Writing PID file" path=karma.pid +level=info msg="Initial Alertmanager collection" +level=info msg="Pulling latest alerts and silences from Alertmanager" +level=info msg="Collecting alerts and silences" alertmanager=local +level=info msg="GET request" timeout=10 uri=https://127.0.0.1:9093/metrics +level=error msg="Request failed" error="Get \"https://127.0.0.1:9093/metrics\": dial tcp 127.0.0.1:9093: connect: connection refused" alertmanager=local uri=https://127.0.0.1:9093 +level=error msg="Collection failed" error="Get \"https://127.0.0.1:9093/api/v2/status\": dial tcp 127.0.0.1:9093: connect: connection refused" alertmanager=local +level=info msg="Collection completed" +level=info msg="Done, starting HTTP server" +level=info msg="Starting HTTPS server" address=127.0.0.1:8087 +level=info msg="Shutting down HTTP server" +level=error msg="HTTPS server startup error" error="http: Server closed" +level=info msg="HTTP server shut down" +level=info msg="Removing PID file" path=karma.pid +-- karma.yaml -- +alertmanager: + servers: + - name: local + uri: https://127.0.0.1:9093 + timeout: 10s +listen: + tls: + cert: server.pem + key: server.key + +-- tls.sh -- +openssl genrsa -out ca.key 2048 +openssl req -x509 -new -nodes -key ca.key -days 7 -out ca.pem -subj "/C=CI/ST=CI/L=CI/O=CI/CN=FakeCA" + +openssl genrsa -out server.key 2048 +openssl req -new -key server.key -out server.csr -subj "/C=CI/ST=CI/L=CI/O=CI/CN=karma.example.com" +openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 7 + +-- test.sh -- +#!/bin/sh + +while [ ! -f karma.pid ]; do sleep 1 ; done +sleep 1 +curl -s --resolve karma.example.com:8087:127.0.0.1 --cacert ca.pem -o http1.txt --http1.1 https://karma.example.com:8087/health +curl -s --resolve karma.example.com:8087:127.0.0.1 --cacert ca.pem -o http2.txt --http2 https://karma.example.com:8087/health +cat karma.pid | xargs kill diff --git a/cmd/karma/tests/testscript/088_listen_tls_key_invalid.txt b/cmd/karma/tests/testscript/088_listen_tls_key_invalid.txt new file mode 100644 index 000000000..d0dc08d75 --- /dev/null +++ b/cmd/karma/tests/testscript/088_listen_tls_key_invalid.txt @@ -0,0 +1,45 @@ +# Fails on invalid TLS key + +exec sh -x ./tls.sh +karma.bin-should-work --pid-file=karma.pid --config.file=karma.yaml --listen.address=127.0.0.1 --listen.port=8088 +! stdout . +cmp stderr stderr.txt + +-- server.bad -- +BAD KEY +-- stderr.txt -- +level=info msg="Reading configuration file" path=karma.yaml +level=info msg="Version: dev" +level=info msg="Configured Alertmanager source" name=local proxy=false readonly=false uri=https://127.0.0.1:9093 +level=info msg="Writing PID file" path=karma.pid +level=info msg="Initial Alertmanager collection" +level=info msg="Pulling latest alerts and silences from Alertmanager" +level=info msg="Collecting alerts and silences" alertmanager=local +level=info msg="GET request" timeout=10 uri=https://127.0.0.1:9093/metrics +level=error msg="Request failed" error="Get \"https://127.0.0.1:9093/metrics\": dial tcp 127.0.0.1:9093: connect: connection refused" alertmanager=local uri=https://127.0.0.1:9093 +level=error msg="Collection failed" error="Get \"https://127.0.0.1:9093/api/v2/status\": dial tcp 127.0.0.1:9093: connect: connection refused" alertmanager=local +level=info msg="Collection completed" +level=info msg="Done, starting HTTP server" +level=info msg="Starting HTTPS server" address=127.0.0.1:8088 +level=error msg="HTTPS server startup error" error="tls: failed to find any PEM data in key input" +level=info msg="Shutting down HTTP server" +level=info msg="HTTP server shut down" +level=info msg="Removing PID file" path=karma.pid +-- karma.yaml -- +alertmanager: + servers: + - name: local + uri: https://127.0.0.1:9093 + timeout: 10s +listen: + tls: + cert: server.pem + key: server.bad + +-- tls.sh -- +openssl genrsa -out ca.key 2048 +openssl req -x509 -new -nodes -key ca.key -days 7 -out ca.pem -subj "/C=CI/ST=CI/L=CI/O=CI/CN=FakeCA" + +openssl genrsa -out server.key 2048 +openssl req -new -key server.key -out server.csr -subj "/C=CI/ST=CI/L=CI/O=CI/CN=karma.example.com" +openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 7 diff --git a/cmd/karma/tests/testscript/089_listen_tls_cert_invalid.txt b/cmd/karma/tests/testscript/089_listen_tls_cert_invalid.txt new file mode 100644 index 000000000..acda5b223 --- /dev/null +++ b/cmd/karma/tests/testscript/089_listen_tls_cert_invalid.txt @@ -0,0 +1,45 @@ +# Fails on invalid TLS cert + +exec sh -x ./tls.sh +karma.bin-should-work --pid-file=karma.pid --config.file=karma.yaml --listen.address=127.0.0.1 --listen.port=8089 +! stdout . +cmp stderr stderr.txt + +-- server.bad -- +BAD CERT +-- stderr.txt -- +level=info msg="Reading configuration file" path=karma.yaml +level=info msg="Version: dev" +level=info msg="Configured Alertmanager source" name=local proxy=false readonly=false uri=https://127.0.0.1:9093 +level=info msg="Writing PID file" path=karma.pid +level=info msg="Initial Alertmanager collection" +level=info msg="Pulling latest alerts and silences from Alertmanager" +level=info msg="Collecting alerts and silences" alertmanager=local +level=info msg="GET request" timeout=10 uri=https://127.0.0.1:9093/metrics +level=error msg="Request failed" error="Get \"https://127.0.0.1:9093/metrics\": dial tcp 127.0.0.1:9093: connect: connection refused" alertmanager=local uri=https://127.0.0.1:9093 +level=error msg="Collection failed" error="Get \"https://127.0.0.1:9093/api/v2/status\": dial tcp 127.0.0.1:9093: connect: connection refused" alertmanager=local +level=info msg="Collection completed" +level=info msg="Done, starting HTTP server" +level=info msg="Starting HTTPS server" address=127.0.0.1:8089 +level=error msg="HTTPS server startup error" error="tls: failed to find any PEM data in certificate input" +level=info msg="Shutting down HTTP server" +level=info msg="HTTP server shut down" +level=info msg="Removing PID file" path=karma.pid +-- karma.yaml -- +alertmanager: + servers: + - name: local + uri: https://127.0.0.1:9093 + timeout: 10s +listen: + tls: + cert: server.bad + key: server.key + +-- tls.sh -- +openssl genrsa -out ca.key 2048 +openssl req -x509 -new -nodes -key ca.key -days 7 -out ca.pem -subj "/C=CI/ST=CI/L=CI/O=CI/CN=FakeCA" + +openssl genrsa -out server.key 2048 +openssl req -new -key server.key -out server.csr -subj "/C=CI/ST=CI/L=CI/O=CI/CN=karma.example.com" +openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 7 diff --git a/cmd/karma/tests/testscript/090_listen_tls_key_missing.txt b/cmd/karma/tests/testscript/090_listen_tls_key_missing.txt new file mode 100644 index 000000000..615f0629f --- /dev/null +++ b/cmd/karma/tests/testscript/090_listen_tls_key_missing.txt @@ -0,0 +1,45 @@ +# Fails on missing TLS key + +exec sh -x ./tls.sh +karma.bin-should-work --pid-file=karma.pid --config.file=karma.yaml --listen.address=127.0.0.1 --listen.port=8090 +! stdout . +cmp stderr stderr.txt + +-- server.bad -- +BAD CERT +-- stderr.txt -- +level=info msg="Reading configuration file" path=karma.yaml +level=info msg="Version: dev" +level=info msg="Configured Alertmanager source" name=local proxy=false readonly=false uri=https://127.0.0.1:9093 +level=info msg="Writing PID file" path=karma.pid +level=info msg="Initial Alertmanager collection" +level=info msg="Pulling latest alerts and silences from Alertmanager" +level=info msg="Collecting alerts and silences" alertmanager=local +level=info msg="GET request" timeout=10 uri=https://127.0.0.1:9093/metrics +level=error msg="Request failed" error="Get \"https://127.0.0.1:9093/metrics\": dial tcp 127.0.0.1:9093: connect: connection refused" alertmanager=local uri=https://127.0.0.1:9093 +level=error msg="Collection failed" error="Get \"https://127.0.0.1:9093/api/v2/status\": dial tcp 127.0.0.1:9093: connect: connection refused" alertmanager=local +level=info msg="Collection completed" +level=info msg="Done, starting HTTP server" +level=info msg="Starting HTTPS server" address=127.0.0.1:8090 +level=error msg="HTTPS server startup error" error="open server.nofound: no such file or directory" +level=info msg="Shutting down HTTP server" +level=info msg="HTTP server shut down" +level=info msg="Removing PID file" path=karma.pid +-- karma.yaml -- +alertmanager: + servers: + - name: local + uri: https://127.0.0.1:9093 + timeout: 10s +listen: + tls: + cert: server.pem + key: server.nofound + +-- tls.sh -- +openssl genrsa -out ca.key 2048 +openssl req -x509 -new -nodes -key ca.key -days 7 -out ca.pem -subj "/C=CI/ST=CI/L=CI/O=CI/CN=FakeCA" + +openssl genrsa -out server.key 2048 +openssl req -new -key server.key -out server.csr -subj "/C=CI/ST=CI/L=CI/O=CI/CN=karma.example.com" +openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 7 diff --git a/cmd/karma/tests/testscript/091_listen_tls_cert_missing.txt b/cmd/karma/tests/testscript/091_listen_tls_cert_missing.txt new file mode 100644 index 000000000..5073b5571 --- /dev/null +++ b/cmd/karma/tests/testscript/091_listen_tls_cert_missing.txt @@ -0,0 +1,45 @@ +# Fails on missing TLS cert + +exec sh -x ./tls.sh +karma.bin-should-work --pid-file=karma.pid --config.file=karma.yaml --listen.address=127.0.0.1 --listen.port=8091 +! stdout . +cmp stderr stderr.txt + +-- server.bad -- +BAD CERT +-- stderr.txt -- +level=info msg="Reading configuration file" path=karma.yaml +level=info msg="Version: dev" +level=info msg="Configured Alertmanager source" name=local proxy=false readonly=false uri=https://127.0.0.1:9093 +level=info msg="Writing PID file" path=karma.pid +level=info msg="Initial Alertmanager collection" +level=info msg="Pulling latest alerts and silences from Alertmanager" +level=info msg="Collecting alerts and silences" alertmanager=local +level=info msg="GET request" timeout=10 uri=https://127.0.0.1:9093/metrics +level=error msg="Request failed" error="Get \"https://127.0.0.1:9093/metrics\": dial tcp 127.0.0.1:9093: connect: connection refused" alertmanager=local uri=https://127.0.0.1:9093 +level=error msg="Collection failed" error="Get \"https://127.0.0.1:9093/api/v2/status\": dial tcp 127.0.0.1:9093: connect: connection refused" alertmanager=local +level=info msg="Collection completed" +level=info msg="Done, starting HTTP server" +level=info msg="Starting HTTPS server" address=127.0.0.1:8091 +level=error msg="HTTPS server startup error" error="open server.notfound: no such file or directory" +level=info msg="Shutting down HTTP server" +level=info msg="HTTP server shut down" +level=info msg="Removing PID file" path=karma.pid +-- karma.yaml -- +alertmanager: + servers: + - name: local + uri: https://127.0.0.1:9093 + timeout: 10s +listen: + tls: + cert: server.notfound + key: server.key + +-- tls.sh -- +openssl genrsa -out ca.key 2048 +openssl req -x509 -new -nodes -key ca.key -days 7 -out ca.pem -subj "/C=CI/ST=CI/L=CI/O=CI/CN=FakeCA" + +openssl genrsa -out server.key 2048 +openssl req -new -key server.key -out server.csr -subj "/C=CI/ST=CI/L=CI/O=CI/CN=karma.example.com" +openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 7 diff --git a/cmd/karma/tests/testscript/092_listen_tls_key_empty.txt b/cmd/karma/tests/testscript/092_listen_tls_key_empty.txt new file mode 100644 index 000000000..b548c2021 --- /dev/null +++ b/cmd/karma/tests/testscript/092_listen_tls_key_empty.txt @@ -0,0 +1,18 @@ +# Fails on empty TLS key + +karma.bin-should-fail --pid-file=karma.pid --config.file=karma.yaml --listen.address=127.0.0.1 --listen.port=8092 +! stdout . +cmp stderr stderr.txt + +-- stderr.txt -- +level=error msg="Execution failed" error="listen.tls.key must be set when listen.tls.cert is set" +-- karma.yaml -- +alertmanager: + servers: + - name: local + uri: https://127.0.0.1:9093 + timeout: 10s +listen: + tls: + cert: server.pem + diff --git a/cmd/karma/tests/testscript/093_listen_tls_cert_empty.txt b/cmd/karma/tests/testscript/093_listen_tls_cert_empty.txt new file mode 100644 index 000000000..183e93c07 --- /dev/null +++ b/cmd/karma/tests/testscript/093_listen_tls_cert_empty.txt @@ -0,0 +1,18 @@ +# Fails on empty TLS cert + +karma.bin-should-fail --pid-file=karma.pid --config.file=karma.yaml --listen.address=127.0.0.1 --listen.port=8092 +! stdout . +cmp stderr stderr.txt + +-- stderr.txt -- +level=error msg="Execution failed" error="listen.tls.cert must be set when listen.tls.key is set" +-- karma.yaml -- +alertmanager: + servers: + - name: local + uri: https://127.0.0.1:9093 + timeout: 10s +listen: + tls: + key: server.key + diff --git a/cmd/karma/views.go b/cmd/karma/views.go index 69d655f30..a6750e53a 100644 --- a/cmd/karma/views.go +++ b/cmd/karma/views.go @@ -38,7 +38,7 @@ func badRequestJSON(w http.ResponseWriter, error string) { } func pong(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write([]byte("Pong")) + _, _ = w.Write([]byte("Pong\n")) } func compressResponse(data []byte) ([]byte, error) { @@ -82,7 +82,7 @@ func decompressCachedResponse(data []byte) ([]byte, error) { func pushPath(w http.ResponseWriter, path string) { if pusher, ok := w.(http.Pusher); ok { if err := pusher.Push(path, nil); err != nil { - log.Warn().Str("path", path).Msg("Failed to push server path via HTTP/2") + log.Debug().Err(err).Str("path", path).Msg("Failed to push server path via HTTP/2") } } } diff --git a/internal/config/config.go b/internal/config/config.go index 7a349ca41..00c2759a1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -112,6 +112,8 @@ func SetupFlags(f *pflag.FlagSet) { f.String("listen.address", "", "IP/Hostname to listen on") f.Int("listen.port", 8080, "HTTP port to listen on") f.String("listen.prefix", "/", "URL prefix") + f.String("listen.tls.cert", "", "TLS certificate path (enables HTTPS)") + f.String("listen.tls.key", "", "TLS key path (enables HTTPS)") f.String("sentry.public", "", "Sentry DSN for Go exceptions") f.String("sentry.private", "", "Sentry DSN for JavaScript exceptions") @@ -349,6 +351,14 @@ func (config *configSchema) Read(flags *pflag.FlagSet) (string, error) { return "", fmt.Errorf("listen.prefix must start with '/', got %q", config.Listen.Prefix) } + if config.Listen.TLS.Cert != "" && config.Listen.TLS.Key == "" { + return "", fmt.Errorf("listen.tls.key must be set when listen.tls.cert is set") + } + + if config.Listen.TLS.Key != "" && config.Listen.TLS.Cert == "" { + return "", fmt.Errorf("listen.tls.cert must be set when listen.tls.key is set") + } + // accept single Alertmanager server from flag/env if nothing is set yet if len(config.Alertmanager.Servers) == 0 && config.Alertmanager.URI != "" { config.Alertmanager.Servers = []AlertmanagerConfig{ diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 13b8dd95f..e9df3f8f4 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -97,6 +97,9 @@ labels: - gg listen: address: 0.0.0.0 + tls: + cert: "" + key: "" port: 80 prefix: / log: diff --git a/internal/config/models.go b/internal/config/models.go index 3c79312f0..f0c164628 100644 --- a/internal/config/models.go +++ b/internal/config/models.go @@ -128,8 +128,12 @@ type configSchema struct { } Listen struct { Address string - Port int - Prefix string + TLS struct { + Cert string + Key string + } + Port int + Prefix string } Log struct { Config bool