From 941b1b0c44b1705a919002b7542c17e298fd1df8 Mon Sep 17 00:00:00 2001 From: Guillaume Belanger Date: Fri, 14 Jun 2024 15:02:46 -0400 Subject: [PATCH 1/7] chore: use skopeo from rockcraft (#25) --- .github/workflows/build-rock.yaml | 10 +++++----- .github/workflows/publish-rock.yaml | 7 ++++--- .github/workflows/scan-rock.yaml | 6 +++--- CONTRIBUTING.md | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-rock.yaml b/.github/workflows/build-rock.yaml index 6f2148f..1e3d0ba 100644 --- a/.github/workflows/build-rock.yaml +++ b/.github/workflows/build-rock.yaml @@ -11,13 +11,13 @@ jobs: uses: actions/checkout@v4 - uses: canonical/craft-actions/rockcraft-pack@main id: rockcraft - - - name: Install Skopeo - run: | - sudo snap install skopeo --edge --devmode + with: + rockcraft-channel: edge + - name: Import the image to Docker registry run: | - sudo skopeo --insecure-policy copy oci-archive:${{ steps.rockcraft.outputs.rock }} docker-daemon:gocert:latest + 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 diff --git a/.github/workflows/publish-rock.yaml b/.github/workflows/publish-rock.yaml index c0820eb..e7c3c6d 100644 --- a/.github/workflows/publish-rock.yaml +++ b/.github/workflows/publish-rock.yaml @@ -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 @@ -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}" \ diff --git a/.github/workflows/scan-rock.yaml b/.github/workflows/scan-rock.yaml index 3cb0efc..2dd7a8c 100644 --- a/.github/workflows/scan-rock.yaml +++ b/.github/workflows/scan-rock.yaml @@ -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: | @@ -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}" \ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e5e098d..e534ccf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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} ``` From 160db0dae663245b654c9d5f60c7fea14c4ebdf4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 19:08:36 +0000 Subject: [PATCH 2/7] chore: bump braces from 3.0.2 to 3.0.3 in /ui (#26) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ui/package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index a378531..e2c8cf9 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -2064,11 +2064,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -3309,9 +3309,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, From e6db72b4e3364a1506ef15896b47823eaf35282f Mon Sep 17 00:00:00 2001 From: Guillaume Belanger Date: Fri, 14 Jun 2024 15:30:33 -0400 Subject: [PATCH 3/7] fix: add key missing in config validation (#24) --- cmd/gocert/main.go | 10 ++- cmd/gocert/main_test.go | 2 +- internal/api/server.go | 69 ++----------------- internal/api/server_test.go | 53 ++------------ internal/config/config.go | 73 ++++++++++++++++++++ internal/config/config_test.go | 122 +++++++++++++++++++++++++++++++++ 6 files changed, 213 insertions(+), 116 deletions(-) create mode 100644 internal/config/config.go create mode 100644 internal/config/config_test.go diff --git a/cmd/gocert/main.go b/cmd/gocert/main.go index 5c43364..115b665 100644 --- a/cmd/gocert/main.go +++ b/cmd/gocert/main.go @@ -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) } diff --git a/cmd/gocert/main_test.go b/cmd/gocert/main_test.go index 58d37d7..3b62a2e 100644 --- a/cmd/gocert/main_test.go +++ b/cmd/gocert/main_test.go @@ -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:"}, } diff --git a/internal/api/server.go b/internal/api/server.go index 5910d76..ee2ca8b 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -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 { @@ -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, diff --git a/internal/api/server_test.go b/internal/api/server_test.go index 8a6caea..535ac71 100644 --- a/internal/api/server_test.go +++ b/internal/api/server_test.go @@ -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) { @@ -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) } @@ -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") } } diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..0adc8a0 --- /dev/null +++ b/internal/config/config.go @@ -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 +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..0aecd23 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,122 @@ +package config_test + +import ( + "log" + "os" + "strings" + "testing" + + "github.com/canonical/gocert/internal/config" +) + +const ( + validCert = `Whatever cert content` + validPK = `Whatever key content` + validConfig = `key_path: "./key_test.pem" +cert_path: "./cert_test.pem" +db_path: "./certs.db" +port: 8000` + noCertPathConfig = `key_path: "./key_test.pem" +db_path: "./certs.db" +port: 8000` + noKeyPathConfig = `cert_path: "./cert_test.pem" +db_path: "./certs.db" +port: 8000` + noDBPathConfig = `key_path: "./key_test.pem" +cert_path: "./cert_test.pem" +port: 8000` + wrongCertPathConfig = `key_path: "./key_test.pem" +cert_path: "./cert_test_wrong.pem" +db_path: "./certs.db" +port: 8000` + wrongKeyPathConfig = `key_path: "./key_test_wrong.pem" +cert_path: "./cert_test.pem" +db_path: "./certs.db" +port: 8000` + invalidYAMLConfig = `just_an=invalid +yaml.here` +) + +func TestMain(m *testing.M) { + 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 TestGoodConfigSuccess(t *testing.T) { + writeConfigErr := os.WriteFile("config.yaml", []byte(validConfig), 0644) + if writeConfigErr != nil { + t.Fatalf("Error writing config file") + } + conf, err := config.Validate("config.yaml") + if err != nil { + t.Fatalf("Error occured: %s", err) + } + + if conf.Cert == nil { + t.Fatalf("No certificates were configured for server") + } + + if conf.Key == nil { + t.Fatalf("No key was configured for server") + } + + if conf.DBPath == "" { + t.Fatalf("No database path was configured for server") + } + + if conf.Port != 8000 { + t.Fatalf("Port was not configured correctly") + } + +} + +func TestBadConfigFail(t *testing.T) { + cases := []struct { + Name string + ConfigYAML string + ExpectedError string + }{ + {"no cert path", noCertPathConfig, "`cert_path` is empty"}, + {"no key path", noKeyPathConfig, "`key_path` is empty"}, + {"no db path", noDBPathConfig, "`db_path` is empty"}, + {"wrong cert path", wrongCertPathConfig, "no such file or directory"}, + {"wrong key path", wrongKeyPathConfig, "no such file or directory"}, + {"invalid yaml", invalidYAMLConfig, "unmarshal errors"}, + } + + for _, tc := range cases { + writeConfigErr := os.WriteFile("config.yaml", []byte(tc.ConfigYAML), 0644) + if writeConfigErr != nil { + t.Errorf("Failed writing config file") + } + _, err := config.Validate("config.yaml") + if err == nil { + t.Errorf("Expected error, got nil") + } + + if !strings.Contains(err.Error(), tc.ExpectedError) { + t.Errorf("Expected error not found: %s", err) + } + + } +} From d6705229bfff406d85ff1a03a4bade0e6ca83adf Mon Sep 17 00:00:00 2001 From: Guillaume Belanger Date: Mon, 17 Jun 2024 09:38:35 -0400 Subject: [PATCH 4/7] fix: display accurate status code in logs (#27) --- internal/api/middleware.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/internal/api/middleware.go b/internal/api/middleware.go index 8e8e3d1..1a978b0 100644 --- a/internal/api/middleware.go +++ b/internal/api/middleware.go @@ -3,6 +3,7 @@ package server import ( "log" "net/http" + "strings" "github.com/canonical/gocert/internal/metrics" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -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 }) } } From 533b78cbf246cd0ff28af219b506cb26a21dbb06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 02:46:35 +0000 Subject: [PATCH 5/7] chore: bump ws from 8.17.0 to 8.17.1 in /ui (#28) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ui/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index e2c8cf9..af2fb43 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -6700,9 +6700,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" From 604f3cd35a118b6ce7459c943568aa02525ee310 Mon Sep 17 00:00:00 2001 From: Guillaume Belanger Date: Wed, 19 Jun 2024 10:45:59 -0400 Subject: [PATCH 6/7] chore: remove .html suffix from url (#29) --- internal/api/handlers.go | 14 +++++++++++++- ui/src/app/nav.tsx | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 965ead5..21368a6 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -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 diff --git a/ui/src/app/nav.tsx b/ui/src/app/nav.tsx index 762a329..d905608 100644 --- a/ui/src/app/nav.tsx +++ b/ui/src/app/nav.tsx @@ -81,7 +81,7 @@ export function SideBar({ sidebarVisible, setSidebarVisible }: { sidebarVisible: