Skip to content

Commit

Permalink
Merge branch 'main' into TLSENG-265-add-csr-q
Browse files Browse the repository at this point in the history
  • Loading branch information
kayra1 committed Jun 20, 2024
2 parents f02a3a8 + 84ff608 commit 01ca625
Show file tree
Hide file tree
Showing 15 changed files with 267 additions and 134 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build-rock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ jobs:
id: rockcraft
with:
rockcraft-channel: edge

- name: Import the image to Docker registry
run: |
sudo rockcraft.skopeo --insecure-policy copy oci-archive:${{ steps.rockcraft.outputs.rock }} docker-daemon:gocert:latest
- name: Create files required by GoCert
run: |
printf 'key_path: "/etc/config/key.pem"\ncert_path: "/etc/config/cert.pem"\ndb_path: "/etc/config/certs.db"\nport: 3000\npebble_notifications: true\n' > config.yaml
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/publish-rock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Install skopeo
- name: Install rockcraft
run: |
sudo snap install --devmode --channel edge skopeo
sudo snap install rockcraft --classic --channel edge
- uses: actions/download-artifact@v4
with:
name: rock
Expand All @@ -29,7 +30,7 @@ jobs:
image_name="$(yq '.name' rockcraft.yaml)"
version="$(yq '.version' rockcraft.yaml)"
rock_file=$(ls *.rock | tail -n 1)
sudo skopeo \
sudo rockcraft.skopeo \
--insecure-policy \
copy \
oci-archive:"${rock_file}" \
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/scan-rock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install skopeo
- name: Install rockcraft
run: |
sudo snap install --devmode --channel edge skopeo
sudo snap install rockcraft --classic --channel edge
- name: Install yq
run: |
Expand All @@ -29,7 +29,7 @@ jobs:
version="$(yq '.version' rockcraft.yaml)"
echo "version=${version}" >> $GITHUB_ENV
rock_file=$(ls *.rock | tail -n 1)
sudo skopeo \
sudo rockcraft.skopeo \
--insecure-policy \
copy \
oci-archive:"${rock_file}" \
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ npm run lint
```bash
rockcraft pack -v
version=$(yq '.version' rockcraft.yaml)
sudo skopeo --insecure-policy copy oci-archive:gocert_${version}_amd64.rock docker-daemon:gocert:${version}
sudo rockcraft.skopeo --insecure-policy copy oci-archive:gocert_${version}_amd64.rock docker-daemon:gocert:${version}
docker run gocert:${version}
```
10 changes: 7 additions & 3 deletions cmd/gocert/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@ import (
"os"

server "github.com/canonical/gocert/internal/api"
"github.com/canonical/gocert/internal/config"
)

func main() {
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.")
log.Fatalf("Providing a config file is required.")
}
conf, err := config.Validate(*configFilePtr)
if err != nil {
log.Fatalf("Couldn't validate config file: %s", err)
}
srv, err := server.NewServer(*configFilePtr)
srv, err := server.NewServer(conf.Port, conf.Cert, conf.Key, conf.DBPath, conf.PebbleNotificationsEnabled)
if err != nil {
log.Fatalf("Couldn't create server: %s", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/gocert/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func TestGoCertFail(t *testing.T) {
ConfigYAML string
ExpectedOutput string
}{
{"flags not set", []string{}, validConfig, "Providing a valid config file is required."},
{"flags not set", []string{}, validConfig, "Providing a 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:"},
}
Expand Down
14 changes: 13 additions & 1 deletion internal/api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,19 @@ func newFrontendFileServer() http.Handler {
if err != nil {
log.Fatal(err)
}
return http.FileServer(http.FS(frontendFS))

fileServer := http.FileServer(http.FS(frontendFS))

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
if strings.HasSuffix(path, "/") || path == "/" {
path = "/certificate_requests.html"
} else if !strings.Contains(path, ".") {
path += ".html"
}
r.URL.Path = path
fileServer.ServeHTTP(w, r)
})
}

// the health check endpoint simply returns a http.StatusOK
Expand Down
14 changes: 10 additions & 4 deletions internal/api/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package server
import (
"log"
"net/http"
"strings"

"github.com/canonical/gocert/internal/metrics"
"github.com/prometheus/client_golang/prometheus/promhttp"
Expand Down Expand Up @@ -71,10 +72,15 @@ func metricsMiddleware(ctx *middlewareContext) middleware {
func loggingMiddleware(ctx *middlewareContext) middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
clonedWwriter := newResponseWriter(w)
next.ServeHTTP(w, r)
log.Println(r.Method, r.URL.Path, clonedWwriter.statusCode, http.StatusText(clonedWwriter.statusCode))
ctx.responseStatusCode = clonedWwriter.statusCode
clonedWriter := newResponseWriter(w)
next.ServeHTTP(clonedWriter, r)

// Suppress logging for static files
if !strings.HasPrefix(r.URL.Path, "/_next") {
log.Println(r.Method, r.URL.Path, clonedWriter.statusCode, http.StatusText(clonedWriter.statusCode))
}

ctx.responseStatusCode = clonedWriter.statusCode
})
}
}
69 changes: 5 additions & 64 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,72 +7,17 @@ import (
"fmt"
"log"
"net/http"
"os"
"os/exec"
"time"

"github.com/canonical/gocert/internal/certdb"
"gopkg.in/yaml.v3"
)

type ConfigYAML struct {
KeyPath string `yaml:"key_path"`
CertPath string `yaml:"cert_path"`
DBPath string `yaml:"db_path"`
Port int `yaml:"port"`
Pebblenotificationsenabled bool `yaml:"pebble_notifications"`
}

type Config struct {
Key []byte
Cert []byte
DBPath string
Port int
PebbleNotificationsEnabled bool
}

type Environment struct {
DB *certdb.CertificateRequestsRepository
SendPebbleNotifications bool
}

// validateConfigFile opens and processes the given yaml file, and catches errors in the process
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
config.PebbleNotificationsEnabled = c.Pebblenotificationsenabled
return config, nil
}

func SendPebbleNotification(key, request_id string) error {
cmd := exec.Command("pebble", "notify", key, fmt.Sprintf("request_id=%s", request_id))
if err := cmd.Run(); err != nil {
Expand All @@ -82,27 +27,23 @@ func SendPebbleNotification(key, request_id string) error {
}

// NewServer creates an environment and an http server with handlers that Go can start listening to
func NewServer(configFile string) (*http.Server, error) {
config, err := validateConfigFile(configFile)
if err != nil {
return nil, err
}
serverCerts, err := tls.X509KeyPair(config.Cert, config.Key)
func NewServer(port int, cert []byte, key []byte, dbPath string, pebbleNotificationsEnabled bool) (*http.Server, error) {
serverCerts, err := tls.X509KeyPair(cert, key)
if err != nil {
return nil, err
}
db, err := certdb.NewCertificateRequestsRepository(config.DBPath, "CertificateRequests")
db, err := certdb.NewCertificateRequestsRepository(dbPath, "CertificateRequests")
if err != nil {
log.Fatalf("Couldn't connect to database: %s", err)
}

env := &Environment{}
env.DB = db
env.SendPebbleNotifications = config.PebbleNotificationsEnabled
env.SendPebbleNotifications = pebbleNotificationsEnabled
router := NewGoCertRouter(env)

s := &http.Server{
Addr: fmt.Sprintf(":%d", config.Port),
Addr: fmt.Sprintf(":%d", port),

ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
Expand Down
53 changes: 5 additions & 48 deletions internal/api/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,24 +85,6 @@ Q53tuiWQeoxNOjHiWstBPELxGbW6447JyVVbNYGUk+VFU7okzA6sRTJ/5Ysda4Sf
auNQc2hruhr/2plhFUYoZHPzGz7d5zUGKymhCoS8BsFVtD0WDL4srdtY/W2Us7TD
D7DC34n8CH9+avz9sCRwxpjxKnYW/BeyK0c4n9uZpjI8N4sOVqy6yWBUseww
-----END RSA PRIVATE KEY-----`
validConfig = `key_path: "./key_test.pem"
cert_path: "./cert_test.pem"
db_path: "./certs.db"
port: 8000`
wrongCertConfig = `key_path: "./key_test.pem"
cert_path: "./cert_test_wrong.pem"
db_path: "./certs.db"
port: 8000`
wrongKeyConfig = `key_path: "./key_test_wrong.pem"
cert_path: "./cert_test.pem"
db_path: "./certs.db"
port: 8000`
invalidYAMLConfig = `wrong: fields
every: where`
invalidFileConfig = `key_path: "./nokeyfile.pem"
cert_path: "./nocertfile.pem"
db_path: "./certs.db"
port: 8000`
)

func TestMain(m *testing.M) {
Expand Down Expand Up @@ -131,11 +113,7 @@ func TestMain(m *testing.M) {
}

func TestNewServerSuccess(t *testing.T) {
writeConfigErr := os.WriteFile("config.yaml", []byte(validConfig), 0644)
if writeConfigErr != nil {
log.Fatalf("Error writing config file")
}
s, err := server.NewServer("config.yaml")
s, err := server.NewServer(8000, []byte(validCert), []byte(validPK), "certs.db", false)
if err != nil {
t.Errorf("Error occured: %s", err)
}
Expand All @@ -144,30 +122,9 @@ func TestNewServerSuccess(t *testing.T) {
}
}

func TestNewServerFail(t *testing.T) {
testCases := []struct {
desc string
config string
}{
{
desc: "wrong certificate",
config: wrongCertConfig,
},
{
desc: "wrong key",
config: wrongKeyConfig,
},
}
for _, tC := range testCases {
writeConfigErr := os.WriteFile("config.yaml", []byte(tC.config), 0644)
if writeConfigErr != nil {
log.Fatalf("Error writing config file")
}
t.Run(tC.desc, func(t *testing.T) {
_, err := server.NewServer("config.yaml")
if err == nil {
t.Errorf("Expected error")
}
})
func TestInvalidKeyFailure(t *testing.T) {
_, err := server.NewServer(8000, []byte(validCert), []byte{}, "certs.db", false)
if err == nil {
t.Errorf("No error was thrown for invalid key")
}
}
73 changes: 73 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package config

import (
"errors"
"os"

"gopkg.in/yaml.v3"
)

type ConfigYAML struct {
KeyPath string `yaml:"key_path"`
CertPath string `yaml:"cert_path"`
DBPath string `yaml:"db_path"`
Port int `yaml:"port"`
Pebblenotificationsenabled bool `yaml:"pebble_notifications"`
}

type Config struct {
Key []byte
Cert []byte
DBPath string
Port int
PebbleNotificationsEnabled bool
}

// Validate opens and processes the given yaml file, and catches errors in the process
func Validate(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)
}
if c.CertPath == "" {
return config, errors.Join(validationErr, errors.New("`cert_path` is empty"))
}
cert, err := os.ReadFile(c.CertPath)
if err != nil {
return config, errors.Join(validationErr, err)
}
if c.KeyPath == "" {
return config, errors.Join(validationErr, errors.New("`key_path` is empty"))
}
key, err := os.ReadFile(c.KeyPath)
if err != nil {
return config, errors.Join(validationErr, err)
}
if c.DBPath == "" {
return config, errors.Join(validationErr, errors.New("`db_path` is empty"))
}
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)
}
if c.Port == 0 {
return config, errors.Join(validationErr, errors.New("`port` is empty"))
}

config.Cert = cert
config.Key = key
config.DBPath = c.DBPath
config.Port = c.Port
config.PebbleNotificationsEnabled = c.Pebblenotificationsenabled
return config, nil
}
Loading

0 comments on commit 01ca625

Please sign in to comment.