Skip to content

Commit

Permalink
feat: HTTPS server skeleton (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
kayra1 committed Apr 18, 2024
1 parent 435f407 commit bd0a096
Show file tree
Hide file tree
Showing 6 changed files with 483 additions and 1 deletion.
93 changes: 93 additions & 0 deletions api/server.go
Original file line number Diff line number Diff line change
@@ -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
}
186 changes: 186 additions & 0 deletions api/server_test.go
Original file line number Diff line number Diff line change
@@ -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-----
MIIJKQIBAAKCAgEAwD/fI3HzcONB20tcUelKUkE6eABpXRVkGQThWlcw/noe7GDP
wRXU9HWURxo18S++3sA28HcABSec7saPrvP1YChkaSuigpymopZla3UQ4QOOWoqz
EUDekF7qzeDWYF3AhyVhgECgPVyc3MBiRZeS8aBWOfMAr9HdST/H4Jm3ICEUEwiL
4a5YKyeQHM/UD+gbmsRBym2GL1sk1rQIYh0v8OdchG4evefXHiRgQ2T8hFXfbHxo
qbx6wO9/t9/lwZbkXFLlleHQnnMj/5vvAo0wZlubPIEuiVAxPMpffapuwp105DQ6
j104a6K3CkcHB68N2RDpua1Dy7ssxgcfwg2VG2PW7LxyTBzh5W8ImPjNjBSDvtx+
qsnz33B7PDRZK2Yf2rPvYaxM0mHM/rRgYjcp5EpjT0IuMs09NIpSmIHAYT3Qw5QW
3YXJnXhQN1xPPNFZEHSYHn7GKQoGEXSC5gw3emTH2bEDiY7kQvK0Cizvs9V+SYYj
pZDcWWjpKvW/Xru9t9K8UxQhqHJ9JfaCsNKvDiAzk2+1CnSGhqv8vvrkcXtIYTaI
ozdTib4fkd/yhrs7buWPHo5LCMSga1KgoG1A15kPNsW3Sd+qhfjRwahahWioR9V7
iungCH6oEGpv7vRzWWcN5jnZva7yGX8CExfYL0sJtW8c8iBy/81joMn83JECAwEA
AQKCAgEAmtqX7SAbXCHh6TchrOUCNZFO/Fwwgob5cuGod7FlyIUrpXExxzDDsQmI
n2EwdA7matxfJIBmJsDKutZ75Auj6Yl/n+tC4nw2CR6loNHR/71yi+HO7SXYYGfk
MGNbqpG5w+JLUBg+Ok8AFxxry+yUs0ZYTiM7uWONIDRc1sBabmnWlqI6slVRtakP
fvW0tf9bROWyrNBd1oVO/hZT7lveQujJb+6XmpZFg4T/eSm98QaOif8H+zjTk9cW
hFC366CUXv1y6rDS7t6F7511/xMlGj3NpAXWK0rJ7lKAamO/Bcn43txnExWenaya
TY/6zKinueHSsforcs5Y+UXBwfhY0in4lbOmAauF10eTufpnxR3G5+dNOBrq9oXu
zSk2R7RmbitIY49xAcuYKDhLkr9C0jexh433piHgRlBAcWqbjCc8GyK8hdiI+tGA
mt66jSRTSe70EfPj8xH6EUOLjcKNER4iVUAt4kdYWcvwgamW5CWtRB1bql8YYbiw
9xYtE2QsYbCk8pZ2yIK8R2ejRxoAZzHSjGi9c7qoCMeSNWpv2dso+hOtXlLnFdX7
aQ11I1vqhzn2Ls2aTgKFUcb0q3JkCQr19lkGy0qoSwjw+ZtlA4qpIcQ8aO6c4FqK
QkKZ/pfmuP8CafaNH6sbNoGAS8nEwnnQo5C8iMMsR8o4WblllkECggEBAO1xZznn
ubIPYxyL+NCIm1lDsNsT508gZWGXhQf1qqvOdY7zsPQeI9/5v1OpkMFe0Di8Zwr/
wiQcqP5hyXv7c1wJJxsOWhaI5QpiJDkbM89NPR0nJGF1k/d71fQ6z08yNrqeAruy
jOhXjOhkUAIBmSgZeUzp5f2we1n/35GdVcGy9g7V/4dMfrV9z/qRhD8mIeeZlvU3
icinpqWtcWY4jn5rwyM7Jpau2m2wu1m3G/vQiKAcJQrIirSdOyJ8a82f7mKv9LsI
rMJGPJ4Q3TTkhcx9U0utQw8wPFJC94Z4RWriM+VYSjUKoHYOHCwmRqJrTXMPaSR8
fnnLb2PynfViQfkCggEBAM9GRKMY7WVl6RJAGKvlQJ/NTXrFLPSlI0HvCKZSfv5E
tzu3AzSRs84BkiMXtMB9/Q47+/XVXnGC2mgVrRhgf1HCFzgYZwLruLuLSepxVpm7
QTmgaQ59hxKBXwkE0yj+02cbdsLdzKsnU60zHL4v6wEH8lE7TS5qIsU4Szm/YQhb
3Eq2bAOKqku+SfZwf7b2e0jzTZl0dzqXpz5rImXQdwm1exy6Wmc/XtTmjC/kCOnr
SghgoBSSeTCNDFlUtBKlhBJDQqXhOfM8sl6DBRYZrJGgZzAzaAkO+o/JhYPYJ3W5
5bZ+gnZNJYh8ZYG63Ae1KudDRXinIIlzX7/nBNlelVkCggEAPbB/9EBrM4Lh6jHH
lE5Zpih7E4ApUZqGHIPkUTwXeomqa1iO+e22vmNBvTfJ3yOGD6eLUgU+6Gj10xmO
4oJi51+NZG8nIsGwWDFFXfzeSha0MRXRUuzcY6kt3kVFRTszkuqopSFvkJHmjx44
1zyZER0FMeF3GqE2exyKdmedNzUKzrH0sK9EIF0uotgZttpuZqC14sHqL1K3bkYQ
t1EsXFYdHdMpZG7LW0JWeqmjQJpeVNLbIOEXgHN1QLF4xLSvl75FZC6Ny++5oguZ
nTteM9G/yWKbkJ+knG6/ppUq2+knOIfmx78aD3H9Cc9r/JjKR4GSfKNHrNcY+qu3
NGCx6QKCAQAZDhNp6692nFUKIblZvgKLzpNZDdCbWgLjC3PuNvam4cOMclju19X2
RvZVS55Lzm7yc4nHc51Q91JTVptv4OpDBcUswLZjAf94nCO5NS4Usy/1OVC5sa7M
K9tDCdREllkTk5xNfeYpoj1ZKF6HFt+/ZiiCbTqtK6M8V8uwFVQzYHdGiLqRywc+
1Ke4JG0rvqu0a8Srkgp/iKlswCKOUB6zi75wAI7BAEYEUkIL3/K74/c1AAkZs4L2
vXYKrlR+FIfcdUjvKESLBIFDL29D9qKHj+4pQ22F+suK6f87qrtKXchIwQ4gIr8w
umjCv8WtINco0VbqeLlUJCAk4FYTuH0xAoIBAQCA+A2l7DCMCb7MjkjdyNFqkzpg
2ou3WkCf3j7txqg8oGxQ5eCg45BU1zTOW35YVCtP/PMU0tLo7iPudL79jArv+GfS
6SbLz3OEzQb6HU9/4JA5fldHv+6XJLZA27b8LnfhL1Iz6dS+MgH53+OJdkQBc+Dm
Q53tuiWQeoxNOjHiWstBPELxGbW6447JyVVbNYGUk+VFU7okzA6sRTJ/5Ysda4Sf
auNQc2hruhr/2plhFUYoZHPzGz7d5zUGKymhCoS8BsFVtD0WDL4srdtY/W2Us7TD
D7DC34n8CH9+avz9sCRwxpjxKnYW/BeyK0c4n9uZpjI8N4sOVqy6yWBUseww
-----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) {

})
}
}
33 changes: 32 additions & 1 deletion cmd/gocert/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading

0 comments on commit bd0a096

Please sign in to comment.