From bd0a09624fc6f5df0454c05b3f16dc602e9bbdce Mon Sep 17 00:00:00 2001 From: Kayra Date: Thu, 18 Apr 2024 09:15:56 +0300 Subject: [PATCH] feat: HTTPS server skeleton (#6) --- api/server.go | 93 ++++++++++++++++++++ api/server_test.go | 186 ++++++++++++++++++++++++++++++++++++++++ cmd/gocert/main.go | 33 ++++++- cmd/gocert/main_test.go | 167 ++++++++++++++++++++++++++++++++++++ go.mod | 2 + go.sum | 3 + 6 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 api/server.go create mode 100644 api/server_test.go create mode 100644 cmd/gocert/main_test.go diff --git a/api/server.go b/api/server.go new file mode 100644 index 0000000..386b7d7 --- /dev/null +++ b/api/server.go @@ -0,0 +1,93 @@ +// Package server provides a server object that represents the GoCert backend +package server + +import ( + "crypto/tls" + "errors" + "fmt" + "net/http" + "os" + "time" + + "gopkg.in/yaml.v3" +) + +type ConfigYAML struct { + KeyPath string + CertPath string + DBPath string + Port int +} + +type Config struct { + Key []byte + Cert []byte + DBPath string + Port int +} + +func ValidateConfigFile(filePath string) (Config, error) { + validationErr := errors.New("config file validation failed: ") + config := Config{} + configYaml, err := os.ReadFile(filePath) + if err != nil { + return config, errors.Join(validationErr, err) + } + c := ConfigYAML{} + if err := yaml.Unmarshal(configYaml, &c); err != nil { + return config, errors.Join(validationErr, err) + } + cert, err := os.ReadFile(c.CertPath) + if err != nil { + return config, errors.Join(validationErr, err) + } + key, err := os.ReadFile(c.KeyPath) + if err != nil { + return config, errors.Join(validationErr, err) + } + dbfile, err := os.OpenFile(c.DBPath, os.O_CREATE|os.O_RDONLY, 0644) + if err != nil { + return config, errors.Join(validationErr, err) + } + err = dbfile.Close() + if err != nil { + return config, errors.Join(validationErr, err) + } + + config.Cert = cert + config.Key = key + config.DBPath = c.DBPath + config.Port = c.Port + return config, nil +} + +// NewServer creates a new http server with handlers that Go can start listening to +func NewServer(certificate, key []byte, port int) (*http.Server, error) { + serverCerts, err := tls.X509KeyPair(certificate, key) + if err != nil { + return nil, err + } + router := http.NewServeMux() + router.HandleFunc("GET /", HealthCheck) + + v1 := http.NewServeMux() + v1.Handle("/v1/", http.StripPrefix("/v1", router)) + + s := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + Handler: v1, + MaxHeaderBytes: 1 << 20, + TLSConfig: &tls.Config{ + Certificates: []tls.Certificate{serverCerts}, + }, + } + + return s, nil +} + +func HealthCheck(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Server Alive")) //nolint:errcheck +} diff --git a/api/server_test.go b/api/server_test.go new file mode 100644 index 0000000..336c055 --- /dev/null +++ b/api/server_test.go @@ -0,0 +1,186 @@ +package server_test + +import ( + "log" + "os" + "testing" + + server "github.com/canonical/gocert/api" +) + +const ( + validCert = `-----BEGIN CERTIFICATE----- +MIIELjCCAxagAwIBAgICBnowDQYJKoZIhvcNAQELBQAwJzELMAkGA1UEBhMCVVMx +GDAWBgNVBAoTD0Nhbm9uaWNhbCwgSU5DLjAeFw0yNDA0MDUxMDAzMjhaFw0zNDA0 +MDUxMDAzMjhaMCcxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9DYW5vbmljYWwsIElO +Qy4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDAP98jcfNw40HbS1xR +6UpSQTp4AGldFWQZBOFaVzD+eh7sYM/BFdT0dZRHGjXxL77ewDbwdwAFJ5zuxo+u +8/VgKGRpK6KCnKailmVrdRDhA45airMRQN6QXurN4NZgXcCHJWGAQKA9XJzcwGJF +l5LxoFY58wCv0d1JP8fgmbcgIRQTCIvhrlgrJ5Acz9QP6BuaxEHKbYYvWyTWtAhi +HS/w51yEbh6959ceJGBDZPyEVd9sfGipvHrA73+33+XBluRcUuWV4dCecyP/m+8C +jTBmW5s8gS6JUDE8yl99qm7CnXTkNDqPXThrorcKRwcHrw3ZEOm5rUPLuyzGBx/C +DZUbY9bsvHJMHOHlbwiY+M2MFIO+3H6qyfPfcHs8NFkrZh/as+9hrEzSYcz+tGBi +NynkSmNPQi4yzT00ilKYgcBhPdDDlBbdhcmdeFA3XE880VkQdJgefsYpCgYRdILm +DDd6ZMfZsQOJjuRC8rQKLO+z1X5JhiOlkNxZaOkq9b9eu7230rxTFCGocn0l9oKw +0q8OIDOTb7UKdIaGq/y++uRxe0hhNoijN1OJvh+R3/KGuztu5Y8ejksIxKBrUqCg +bUDXmQ82xbdJ36qF+NHBqFqFaKhH1XuK6eAIfqgQam/u9HNZZw3mOdm9rvIZfwIT +F9gvSwm1bxzyIHL/zWOgyfzckQIDAQABo2QwYjAOBgNVHQ8BAf8EBAMCB4AwHQYD +VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4GA1UdDgQHBAUBAgMEBjAhBgNV +HREEGjAYhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IB +AQB4UEu1/vTpEuuwoqgFpp8tEMewwBQ/CXPBN5seDnd/SUMXFrxk58f498qI3FQy +q98a+89jPWRGA5LY+DfIS82NYCwbKuvTzuJRoUpMPbebrhu7OQl7qQT6n8VOCy6x +IaRnPI0zEGbg2v340jMbB26FiyaFKyHEc24nnq3suZFmbslXzRE2Ebut+Qtft8he +0pSNQXtz5ULt0c8DTje7j+mRABzus45cj3HMDO4vcVRrHegdTE8YcZjwAFTKxqpg +W7GwJ5qPjnm6EMe8da55m8Q0hZchwGZreXNG7iCaw98pACBNgOOxh4LOhEZy25Bv +ayrvWnmPfg1u47sduuhHeUid +-----END CERTIFICATE-----` + validPK = `-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY-----` + validConfig = `keypath: "./key_test.pem" +certpath: "./cert_test.pem" +dbpath: "./certs.db" +port: 8000` + invalidYAMLConfig = `wrong: fields +every: where` + invalidFileConfig = `keypath: "./nokeyfile.pem" +certpath: "./nocertfile.pem" +dbpath: "./certs.db" +port: 8000` +) + +func TestMain(m *testing.M) { + testfolder, err := os.MkdirTemp("./", "configtest-") + if err != nil { + log.Fatalf("couldn't create temp directory") + } + writeConfigErr := os.WriteFile(testfolder+"/config.yaml", []byte(validConfig), 0644) + writeCertErr := os.WriteFile(testfolder+"/cert_test.pem", []byte(validCert), 0644) + writeKeyErr := os.WriteFile(testfolder+"/key_test.pem", []byte(validPK), 0644) + if writeConfigErr != nil || writeCertErr != nil || writeKeyErr != nil { + log.Fatalf("couldn't create temp testing file") + } + if err := os.Chdir(testfolder); err != nil { + log.Fatalf("couldn't enter testing directory") + } + + exitval := m.Run() + + if err := os.Chdir("../"); err != nil { + log.Fatalf("couldn't change back to parent directory") + } + if err := os.RemoveAll(testfolder); err != nil { + log.Fatalf("couldn't remove temp testing directory") + } + os.Exit(exitval) +} + +func TestNewServerSuccess(t *testing.T) { + s, err := server.NewServer([]byte(validCert), []byte(validPK), 8000) + if err != nil { + t.Errorf("Error occured: %s", err) + } + if s.TLSConfig.Certificates == nil { + t.Errorf("No certificates were configured for server") + } +} + +func TestNewServerFail(t *testing.T) { + testCases := []struct { + desc string + cert string + key string + }{ + { + desc: "wrong certificate", + cert: "some cert", + key: validPK, + }, + { + desc: "wrong key", + cert: validCert, + key: "some pk", + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + _, err := server.NewServer([]byte(tC.cert), []byte(tC.key), 8000) + if err == nil { + t.Errorf("Expected error") + } + }) + } +} + +func TestConfigFileSuccess(t *testing.T) { + config, err := server.ValidateConfigFile("./config.yaml") + if err != nil { + t.Errorf("Error occured: %s", err) + } + if config.Cert == nil || config.Key == nil || config.DBPath == "" { + t.Errorf("Expected values were not read: %s", err) + } +} + +func TestConfigFileFail(t *testing.T) { + testCases := []struct { + desc string + }{ + { + desc: "", + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + + }) + } +} diff --git a/cmd/gocert/main.go b/cmd/gocert/main.go index 42d1148..a3d179a 100644 --- a/cmd/gocert/main.go +++ b/cmd/gocert/main.go @@ -1,5 +1,36 @@ package main +import ( + "flag" + "log" + "os" + + server "github.com/canonical/gocert/api" + "github.com/canonical/gocert/internal/certdb" +) + func main() { - // ListenAndServe + log.SetOutput(os.Stderr) + configFilePtr := flag.String("config", "", "The config file to be provided to the server") + flag.Parse() + + if *configFilePtr == "" { + log.Fatalf("Providing a valid config file is required.") + } + config, err := server.ValidateConfigFile(*configFilePtr) + if err != nil { + log.Fatalf("Config file validation failed: %s.", err) + } + _, err = certdb.NewCertificateRequestsRepository(config.DBPath, "CertificateRequests") + if err != nil { + log.Fatalf("Couldn't connect to database: %s", err) + } + srv, err := server.NewServer(config.Cert, config.Key, config.Port) + if err != nil { + log.Fatalf("Couldn't create server: %s", err) + } + log.Printf("Starting server at %s", srv.Addr) + if err := srv.ListenAndServeTLS("", ""); err != nil { + log.Fatalf("Server ran into error: %s", err) + } } diff --git a/cmd/gocert/main_test.go b/cmd/gocert/main_test.go new file mode 100644 index 0000000..3a784a9 --- /dev/null +++ b/cmd/gocert/main_test.go @@ -0,0 +1,167 @@ +package main + +import ( + "flag" + "io" + "log" + "os" + "os/exec" + "strings" + "testing" +) + +const ( + validCert = `-----BEGIN CERTIFICATE----- +MIIELjCCAxagAwIBAgICBnowDQYJKoZIhvcNAQELBQAwJzELMAkGA1UEBhMCVVMx +GDAWBgNVBAoTD0Nhbm9uaWNhbCwgSU5DLjAeFw0yNDA0MDUxMDAzMjhaFw0zNDA0 +MDUxMDAzMjhaMCcxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9DYW5vbmljYWwsIElO +Qy4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDAP98jcfNw40HbS1xR +6UpSQTp4AGldFWQZBOFaVzD+eh7sYM/BFdT0dZRHGjXxL77ewDbwdwAFJ5zuxo+u +8/VgKGRpK6KCnKailmVrdRDhA45airMRQN6QXurN4NZgXcCHJWGAQKA9XJzcwGJF +l5LxoFY58wCv0d1JP8fgmbcgIRQTCIvhrlgrJ5Acz9QP6BuaxEHKbYYvWyTWtAhi +HS/w51yEbh6959ceJGBDZPyEVd9sfGipvHrA73+33+XBluRcUuWV4dCecyP/m+8C +jTBmW5s8gS6JUDE8yl99qm7CnXTkNDqPXThrorcKRwcHrw3ZEOm5rUPLuyzGBx/C +DZUbY9bsvHJMHOHlbwiY+M2MFIO+3H6qyfPfcHs8NFkrZh/as+9hrEzSYcz+tGBi +NynkSmNPQi4yzT00ilKYgcBhPdDDlBbdhcmdeFA3XE880VkQdJgefsYpCgYRdILm +DDd6ZMfZsQOJjuRC8rQKLO+z1X5JhiOlkNxZaOkq9b9eu7230rxTFCGocn0l9oKw +0q8OIDOTb7UKdIaGq/y++uRxe0hhNoijN1OJvh+R3/KGuztu5Y8ejksIxKBrUqCg +bUDXmQ82xbdJ36qF+NHBqFqFaKhH1XuK6eAIfqgQam/u9HNZZw3mOdm9rvIZfwIT +F9gvSwm1bxzyIHL/zWOgyfzckQIDAQABo2QwYjAOBgNVHQ8BAf8EBAMCB4AwHQYD +VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4GA1UdDgQHBAUBAgMEBjAhBgNV +HREEGjAYhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IB +AQB4UEu1/vTpEuuwoqgFpp8tEMewwBQ/CXPBN5seDnd/SUMXFrxk58f498qI3FQy +q98a+89jPWRGA5LY+DfIS82NYCwbKuvTzuJRoUpMPbebrhu7OQl7qQT6n8VOCy6x +IaRnPI0zEGbg2v340jMbB26FiyaFKyHEc24nnq3suZFmbslXzRE2Ebut+Qtft8he +0pSNQXtz5ULt0c8DTje7j+mRABzus45cj3HMDO4vcVRrHegdTE8YcZjwAFTKxqpg +W7GwJ5qPjnm6EMe8da55m8Q0hZchwGZreXNG7iCaw98pACBNgOOxh4LOhEZy25Bv +ayrvWnmPfg1u47sduuhHeUid +-----END CERTIFICATE-----` + validPK = `-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY-----` + validConfig = `keypath: "./key_test.pem" +certpath: "./cert_test.pem" +dbpath: "./certs.db" +port: 8000` + invalidConfig = `hello: "world" +goodbye: "world"` + invalidDBConfig = `keypath: "./key_test.pem" +certpath: "./cert_test.pem" +dbpath: "/etc/hosts" +port: 8000` +) + +func TestMain(m *testing.M) { + cmd := exec.Command("go", "install", "./...") + if err := cmd.Run(); err != nil { + log.Fatalf("couldn't install the gocert CLI") + } + + testfolder, err := os.MkdirTemp("./", "configtest-") + if err != nil { + log.Fatalf("couldn't create temp directory") + } + writeCertErr := os.WriteFile(testfolder+"/cert_test.pem", []byte(validCert), 0644) + writeKeyErr := os.WriteFile(testfolder+"/key_test.pem", []byte(validPK), 0644) + if writeCertErr != nil || writeKeyErr != nil { + log.Fatalf("couldn't create temp testing file") + } + if err := os.Chdir(testfolder); err != nil { + log.Fatalf("couldn't enter testing directory") + } + + exitval := m.Run() + + if err := os.Chdir("../"); err != nil { + log.Fatalf("couldn't change back to parent directory") + } + if err := os.RemoveAll(testfolder); err != nil { + log.Fatalf("couldn't remove temp testing directory") + } + os.Exit(exitval) +} + +func TestGoCertFail(t *testing.T) { + oldArgs := os.Args + defer func() { os.Args = oldArgs }() + cases := []struct { + Name string + Args []string + ConfigYAML string + ExpectedOutput string + }{ + {"flags not set", []string{}, validConfig, "Providing a valid config file is required."}, + {"config file not valid", []string{"-config", "config.yaml"}, invalidConfig, "Config file validation failed:"}, + {"database not connectable", []string{"-config", "config.yaml"}, invalidDBConfig, "Couldn't connect to database:"}, + } + for _, tc := range cases { + writeConfigErr := os.WriteFile("config.yaml", []byte(tc.ConfigYAML), 0644) + if writeConfigErr != nil { + t.Errorf("Failed writing config file") + } + flag.CommandLine = flag.NewFlagSet(tc.Name, flag.ExitOnError) + cmd := exec.Command("gocert", tc.Args...) + stderr, _ := cmd.StderrPipe() + + if err := cmd.Start(); err != nil { + t.Errorf("Failed running command") + } + + slurp, _ := io.ReadAll(stderr) + + if err := cmd.Wait(); err == nil { + t.Errorf("Command did not fail") + } + if !strings.Contains(string(slurp), tc.ExpectedOutput) { + t.Errorf("%s: Expected error not found: %s", tc.Name, slurp) + } + } +} diff --git a/go.mod b/go.mod index 103d8a4..52e4d74 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,5 @@ module github.com/canonical/gocert go 1.22.1 require github.com/mattn/go-sqlite3 v1.14.22 + +require gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index e8d092a..7016d6d 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,5 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=