From 42e0f6a1ab295f53cfbbd7ec634d02c04593c6ae Mon Sep 17 00:00:00 2001 From: Chris Hager Date: Thu, 14 Nov 2024 17:31:36 +0100 Subject: [PATCH] cleanup --- .gitignore | 3 ++- README.md | 22 ++++++++++++++-------- systemapi-config.toml | 5 +++-- systemapi/config.go | 9 +++++---- systemapi/server.go | 20 +++++++++++++++----- systemapi/server_test.go | 21 ++++++++++++++++----- 6 files changed, 55 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index d19c6ea..508c1a8 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ /build /cert.pem /key.pem -/pipe.fifo \ No newline at end of file +/pipe.fifo +/basic-auth-secret.txt \ No newline at end of file diff --git a/README.md b/README.md index 3dfb73c..6ad9dce 100644 --- a/README.md +++ b/README.md @@ -106,19 +106,25 @@ The config file ([systemapi-config.toml](./systemapi-config.toml)) includes a `b - If the file exists and is not empty, then the APIs are authenticated for passwords that match the hash in this file. ```bash -# Set `basic_auth_secret_path` in the config file and create it empty -touch .basic-auth-secret +# Create an empty secrets file and enable `basic_auth_secret_path` in the config file +touch basic-auth-secret.txt vi systemapi-config.toml # Start the server, -$ go run cmd/system-api/main.go --config systemapi-config.toml +go run cmd/system-api/main.go --config systemapi-config.toml # Initially, requests are unauthenticated -$ curl localhost:3535/livez +curl -v localhost:3535/livez + +# Set the basic auth secret. From here on, authentication is required for all API requests. +curl -d "foobar" localhost:3535/api/v1/set-basic-auth + +# Fails with '401 Unauthorized' because no basic auth credentials are provided +curl -v localhost:3535/livez -# Set the basic auth secret -$ curl -d "foobar" localhost:3535/api/v1/set-basic-auth +# Works if correct basic auth credentials are provided +curl -v -u admin:foobar localhost:3535/livez -# Now requests are authenticated -$ curl -u admin:foobar -v localhost:3535/livez +# The update also shows up in the logs: +curl -u admin:foobar localhost:3535/logs ``` diff --git a/systemapi-config.toml b/systemapi-config.toml index 7ba7d00..612dcaa 100644 --- a/systemapi-config.toml +++ b/systemapi-config.toml @@ -1,11 +1,12 @@ [general] listen_addr = "0.0.0.0:3535" pipe_file = "pipe.fifo" +pprof = true log_json = false log_debug = true -# The path to the secret file used for basic auth -basic_auth_secret_path = "/tmp/basic_auth_secret" +# Enable HTTP Basic auth by setting a file for the hashed secret +basic_auth_secret_path = "basic-auth-secret.txt" [actions] # reboot = "reboot" diff --git a/systemapi/config.go b/systemapi/config.go index 1a1f351..ff02b41 100644 --- a/systemapi/config.go +++ b/systemapi/config.go @@ -7,10 +7,11 @@ import ( ) type systemAPIConfigGeneral struct { - ListenAddr string `toml:"listen_addr"` - PipeFile string `toml:"pipe_file"` - LogJSON bool `toml:"log_json"` - LogDebug bool `toml:"log_debug"` + ListenAddr string `toml:"listen_addr"` + PipeFile string `toml:"pipe_file"` + LogJSON bool `toml:"log_json"` + LogDebug bool `toml:"log_debug"` + EnablePprof bool `toml:"pprof"` // Enables pprof endpoints BasicAuthSecretPath string `toml:"basic_auth_secret_path"` } diff --git a/systemapi/server.go b/systemapi/server.go index 560443f..4bd8145 100644 --- a/systemapi/server.go +++ b/systemapi/server.go @@ -24,9 +24,8 @@ import ( ) type HTTPServerConfig struct { - Config *SystemAPIConfig - Log *httplog.Logger - EnablePprof bool + Config *SystemAPIConfig + Log *httplog.Logger DrainDuration time.Duration GracefulShutdownDuration time.Duration @@ -104,22 +103,32 @@ func (s *Server) getRouter() http.Handler { mux.Use(httplog.RequestLogger(s.log)) mux.Use(middleware.Recoverer) + + // Enable a custom HTTP Basic Auth middleware mux.Use(BasicAuth("system-api", s.getBasicAuthHashedCredentials)) + // Common APIs mux.Get("/", s.handleLivenessCheck) mux.Get("/livez", s.handleLivenessCheck) + + // Event (log) APIs mux.Get("/api/v1/new_event", s.handleNewEvent) mux.Get("/api/v1/events", s.handleGetEvents) mux.Get("/logs", s.handleGetLogs) + // API to set the basic auth secret mux.Post("/api/v1/set-basic-auth", s.handleSetBasicAuthCreds) + // API to trigger an action mux.Get("/api/v1/actions/{action}", s.handleAction) + + // API to upload a file mux.Post("/api/v1/file-upload/{file}", s.handleFileUpload) - if s.cfg.EnablePprof { - s.log.Info("pprof API enabled") + // Optionally, pprof + if s.cfg.Config.General.EnablePprof { mux.Mount("/debug", middleware.Profiler()) + s.log.Info("pprof API enabled: /debug/pprof/") } return mux @@ -353,5 +362,6 @@ func (s *Server) handleSetBasicAuthCreds(w http.ResponseWriter, r *http.Request) s.basicAuthHash = secretHash s.log.Info("Basic auth secret updated") + s.addInternalEvent(fmt.Sprintf("basic auth secret updated. new hash: %s", secretHash)) w.WriteHeader(http.StatusOK) } diff --git a/systemapi/server_test.go b/systemapi/server_test.go index 287afbe..cd478c9 100644 --- a/systemapi/server_test.go +++ b/systemapi/server_test.go @@ -51,8 +51,8 @@ func execRequest(t *testing.T, router http.Handler, method, url string, requestB return execRequestAuth(t, router, method, url, requestBody, "", "") } -// Helper to create prepared executors for specific API endpoints -func makeRequestExecutor(t *testing.T, router http.Handler, method, url string) func(basicAuthUser, basicAuthPass string, requestBody io.Reader) (statusCode int, responsePayload []byte) { +// Helper to create prepared test runners for specific API endpoints +func createRequestRunner(t *testing.T, router http.Handler, method, url string) func(basicAuthUser, basicAuthPass string, requestBody io.Reader) (statusCode int, responsePayload []byte) { t.Helper() return func(basicAuthUser, basicAuthPass string, requestBody io.Reader) (statusCode int, responsePayload []byte) { return execRequestAuth(t, router, method, url, requestBody, basicAuthUser, basicAuthPass) @@ -69,7 +69,7 @@ func TestGeneralHandlers(t *testing.T) { code, _ := execRequest(t, router, http.MethodGet, "/livez", nil) require.Equal(t, http.StatusOK, code) - // Test /api/v1/events + // /api/v1/events is initially empty code, respBody := execRequest(t, router, http.MethodGet, "/api/v1/events", nil) require.Equal(t, http.StatusOK, code) require.Equal(t, "[]\n", string(respBody)) @@ -78,6 +78,17 @@ func TestGeneralHandlers(t *testing.T) { code, _ = execRequest(t, router, http.MethodGet, "/api/v1/new_event?message=foo", nil) require.Equal(t, http.StatusOK, code) require.Len(t, srv.events, 1) + require.Equal(t, "foo", srv.events[0].Message) + + // /api/v1/events now has an entry + code, respBody = execRequest(t, router, http.MethodGet, "/api/v1/events", nil) + require.Equal(t, http.StatusOK, code) + require.Contains(t, string(respBody), "foo") + + // /logs should also work + code, respBody = execRequest(t, router, http.MethodGet, "/logs", nil) + require.Equal(t, http.StatusOK, code) + require.Contains(t, string(respBody), "foo\n") } func TestBasicAuth(t *testing.T) { @@ -107,8 +118,8 @@ func TestBasicAuth(t *testing.T) { router := srv.getRouter() // Prepare request helpers - reqGetLiveZ := makeRequestExecutor(t, router, http.MethodGet, "/livez") - reqSetBasicAuthSecret := makeRequestExecutor(t, router, http.MethodPost, "/api/v1/set-basic-auth") + reqGetLiveZ := createRequestRunner(t, router, http.MethodGet, "/livez") + reqSetBasicAuthSecret := createRequestRunner(t, router, http.MethodPost, "/api/v1/set-basic-auth") // Initially, /livez should work without basic auth code, _ := reqGetLiveZ("", "", nil)