diff --git a/.golangci.yaml b/.golangci.yaml index 6232ae8..61df222 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -11,6 +11,7 @@ linters: - perfsprint - testpackage - varnamelen + - wrapcheck - wsl - wsl_v5 settings: diff --git a/config/config.go b/config/config.go index 31d0526..a86fdf5 100644 --- a/config/config.go +++ b/config/config.go @@ -26,6 +26,9 @@ func Load(cfgPath string) (*Config, error) { // Config is the structure of the JSON configuration file. type Config struct { + // ListenAddr for the demo site to listen on. Eg, ":443". + ListenAddr string + // Sites is a list of sites to host. Sites []Site diff --git a/config/config_test.go b/config/config_test.go index 9ef71dc..b65fe4a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -11,6 +11,8 @@ import ( func TestLoadConfig(t *testing.T) { t.Parallel() expected := config.Config{ + ListenAddr: "localhost:8443", + Sites: []config.Site{ { IssuerCN: "minica root ca 5345e6", diff --git a/config/test.json b/config/test.json index 6c9c7b5..e347d6f 100644 --- a/config/test.json +++ b/config/test.json @@ -1,4 +1,6 @@ { + "listenAddr": "localhost:8443", + "sites": [ { "issuerCN": "minica root ca 5345e6", diff --git a/main.go b/main.go index 5302930..c04d0f5 100644 --- a/main.go +++ b/main.go @@ -2,12 +2,14 @@ package main import ( + "crypto/tls" "flag" "fmt" "log/slog" "os" "github.com/letsencrypt/test-certs-site/config" + "github.com/letsencrypt/test-certs-site/server" ) func run(args []string) error { @@ -30,9 +32,19 @@ func run(args []string) error { return fmt.Errorf("loading config: %w", err) } - slog.Info("Loaded configuration! This program doesn't do anything yet.", slog.String("configFile", *cfgPath), slog.Any("config", cfg)) + // This is just a temporary placeholder, using a single static test certificate + // This will normally be provided by the key storage part of this program + cert := os.Getenv("TEST_CERT") + key := os.Getenv("TEST_KEY") + temporaryStaticCert, err := tls.LoadX509KeyPair(cert, key) + if err != nil { + return fmt.Errorf("loading temporary certificate: %w", err) + } + todoGetCert := func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) { + return &temporaryStaticCert, nil + } - return nil + return server.Run(cfg.ListenAddr, todoGetCert) } func main() { diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..c3c733d --- /dev/null +++ b/server/server.go @@ -0,0 +1,67 @@ +// Package server is the HTTPS server for test-certs-site. +package server + +import ( + "context" + "crypto/tls" + "fmt" + "log/slog" + "net/http" + "os" + "os/signal" + "syscall" + "time" +) + +// handle an http request with a placeholder response. +func handle(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet || r.URL.Path != "/" { + w.WriteHeader(http.StatusNotFound) + _, _ = fmt.Fprint(w, "404 Not Found") + + return + } + + // This is just a placeholder until we make a nice site. + _, _ = fmt.Fprintf(w, "This is a demontration site for %s", r.TLS.ServerName) +} + +// GetCertificateFunc is the type of the TLSConfig.GetCertificate function. +// The webserver will use it to obtain certificates, including fulfilling +// ACME TLS-ALPN-01 challenges. +type GetCertificateFunc func(info *tls.ClientHelloInfo) (*tls.Certificate, error) + +// Run the server, until the process is signaled to exit. +func Run(addr string, getCert GetCertificateFunc) error { + // We want http requests to time out relatively quickly, as this server shouldn't be doing much. + const timeout = 5 * time.Second + + srv := http.Server{ + Addr: addr, + Handler: http.HandlerFunc(handle), + + IdleTimeout: timeout, + ReadHeaderTimeout: timeout, + ReadTimeout: timeout, + WriteTimeout: timeout, + + TLSConfig: &tls.Config{ + GetCertificate: getCert, + MinVersion: tls.VersionTLS12, + }, + } + + // Wait for a signal to shut down the server. + go func() { + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + <-sigChan + + err := srv.Shutdown(context.Background()) + if err != nil { + slog.Error(err.Error()) + } + }() + + return srv.ListenAndServeTLS("", "") +}