From 69c3a0b99bf1d85b5cb3fb953b9d23b268b48224 Mon Sep 17 00:00:00 2001 From: Andrei Aaron Date: Thu, 27 Nov 2025 11:30:14 +0200 Subject: [PATCH] feat: explicitly log if each authentication method is enabled (#3599) feat: explicitly log if each autentication methods is enabled When the server starts or when the config is reloaded. See the discussion in: https://github.com/project-zot/zot/pull/3577#issuecomment-3568505810 Signed-off-by: Andrei Aaron --- pkg/api/controller.go | 19 +++++- pkg/cli/server/config_reloader_test.go | 92 ++++++++++++++++++++++++++ pkg/cli/server/root_test.go | 70 ++++++++++++++++++++ 3 files changed, 179 insertions(+), 2 deletions(-) diff --git a/pkg/api/controller.go b/pkg/api/controller.go index 524d3be6..7580540b 100644 --- a/pkg/api/controller.go +++ b/pkg/api/controller.go @@ -283,6 +283,15 @@ func (c *Controller) Init() error { // print the current configuration, but strip secrets c.Log.Info().Interface("params", c.Config.Sanitize()).Msg("configuration settings") + // log authentication methods status + authConfig := c.Config.CopyAuthConfig() + c.Log.Info().Bool("enabled", authConfig.IsBearerAuthEnabled()).Msg("bearer authentication") + c.Log.Info().Bool("enabled", authConfig.IsHtpasswdAuthEnabled()).Msg("basic authentication (htpasswd)") + c.Log.Info().Bool("enabled", authConfig.IsLdapAuthEnabled()).Msg("basic authentication (LDAP)") + c.Log.Info().Bool("enabled", authConfig.IsAPIKeyEnabled()).Msg("basic authentication (API key)") + c.Log.Info().Bool("enabled", authConfig.IsOpenIDAuthEnabled()).Msg("OpenID authentication") + c.Log.Info().Bool("enabled", c.Config.IsMTLSAuthEnabled()).Msg("mutual TLS authentication") + // print the current runtime environment DumpRuntimeParams(c.Log) @@ -310,8 +319,6 @@ func (c *Controller) Init() error { c.InitCVEInfo() c.Healthz.Started() - // Get auth config safely - authConfig := c.Config.CopyAuthConfig() if authConfig.IsHtpasswdAuthEnabled() { err := c.HTPasswdWatcher.ChangeFile(authConfig.HTPasswd.Path) if err != nil { @@ -435,6 +442,14 @@ func (c *Controller) LoadNewConfig(newConfig *config.Config) { c.Log.Info().Interface("reloaded params", c.Config.Sanitize()). Msg("loaded new configuration settings") + + // log authentication methods status + c.Log.Info().Bool("enabled", authConfig.IsBearerAuthEnabled()).Msg("bearer authentication") + c.Log.Info().Bool("enabled", authConfig.IsHtpasswdAuthEnabled()).Msg("basic authentication (htpasswd)") + c.Log.Info().Bool("enabled", authConfig.IsLdapAuthEnabled()).Msg("basic authentication (LDAP)") + c.Log.Info().Bool("enabled", authConfig.IsAPIKeyEnabled()).Msg("basic authentication (API key)") + c.Log.Info().Bool("enabled", authConfig.IsOpenIDAuthEnabled()).Msg("OpenID authentication") + c.Log.Info().Bool("enabled", c.Config.IsMTLSAuthEnabled()).Msg("mutual TLS authentication") } func (c *Controller) Shutdown() { diff --git a/pkg/cli/server/config_reloader_test.go b/pkg/cli/server/config_reloader_test.go index 3dad274b..3b0c681b 100644 --- a/pkg/cli/server/config_reloader_test.go +++ b/pkg/cli/server/config_reloader_test.go @@ -94,6 +94,20 @@ func TestConfigReloader(t *testing.T) { test.WaitTillServerReady(baseURL) + // verify initial startup authentication logs + initialData, err := os.ReadFile(logFile.Name()) + So(err, ShouldBeNil) + So(string(initialData), ShouldContainSubstring, "configuration settings") + // verify authentication methods status messages are present in initial startup + verifyAuthenticationLogs(initialData, map[string]bool{ + "bearer authentication": false, + "basic authentication (htpasswd)": true, + "basic authentication (LDAP)": false, + "basic authentication (API key)": false, + "OpenID authentication": false, + "mutual TLS authentication": false, + }) + content = fmt.Sprintf(`{ "distSpecVersion": "1.1.1", "storage": { @@ -156,6 +170,15 @@ func TestConfigReloader(t *testing.T) { So(string(data), ShouldContainSubstring, "loaded new configuration settings") So(string(data), ShouldContainSubstring, "\"Users\":[\"alice\"]") So(string(data), ShouldContainSubstring, "\"Actions\":[\"read\",\"create\",\"update\",\"delete\"]") + // verify authentication methods status messages are present + verifyAuthenticationLogs(data, map[string]bool{ + "bearer authentication": false, + "basic authentication (htpasswd)": true, + "basic authentication (LDAP)": false, + "basic authentication (API key)": false, + "OpenID authentication": false, + "mutual TLS authentication": false, + }) }) Convey("reload gc config", t, func(c C) { @@ -211,6 +234,20 @@ func TestConfigReloader(t *testing.T) { test.WaitTillServerReady(baseURL) + // verify initial startup authentication logs (no auth configured) + initialData, err := os.ReadFile(logFile.Name()) + So(err, ShouldBeNil) + So(string(initialData), ShouldContainSubstring, "configuration settings") + // verify authentication methods status messages are present in initial startup + verifyAuthenticationLogs(initialData, map[string]bool{ + "bearer authentication": false, + "basic authentication (htpasswd)": false, + "basic authentication (LDAP)": false, + "basic authentication (API key)": false, + "OpenID authentication": false, + "mutual TLS authentication": false, + }) + content = fmt.Sprintf(`{ "distSpecVersion": "1.1.1", "storage": { @@ -263,6 +300,15 @@ func TestConfigReloader(t *testing.T) { So(string(data), ShouldContainSubstring, "\"Dedupe\":true") So(string(data), ShouldNotContainSubstring, "\"GC\":false") So(string(data), ShouldNotContainSubstring, "\"Dedupe\":false") + // verify authentication methods status messages are present + verifyAuthenticationLogs(data, map[string]bool{ + "bearer authentication": false, + "basic authentication (htpasswd)": false, + "basic authentication (LDAP)": false, + "basic authentication (API key)": false, + "OpenID authentication": false, + "mutual TLS authentication": false, + }) }) Convey("reload sync config", t, func(c C) { @@ -330,6 +376,20 @@ func TestConfigReloader(t *testing.T) { test.WaitTillServerReady(baseURL) + // verify initial startup authentication logs (no auth configured) + initialData, err := os.ReadFile(logFile.Name()) + So(err, ShouldBeNil) + So(string(initialData), ShouldContainSubstring, "configuration settings") + // verify authentication methods status messages are present in initial startup + verifyAuthenticationLogs(initialData, map[string]bool{ + "bearer authentication": false, + "basic authentication (htpasswd)": false, + "basic authentication (LDAP)": false, + "basic authentication (API key)": false, + "OpenID authentication": false, + "mutual TLS authentication": false, + }) + content = fmt.Sprintf(`{ "distSpecVersion": "1.1.1", "storage": { @@ -396,6 +456,15 @@ func TestConfigReloader(t *testing.T) { So(string(data), ShouldContainSubstring, "\"Prefix\":\"zot-cve-test\"") So(string(data), ShouldContainSubstring, "\"Regex\":\"tag\"") So(string(data), ShouldContainSubstring, "\"Semver\":false") + // verify authentication methods status messages are present + verifyAuthenticationLogs(data, map[string]bool{ + "bearer authentication": false, + "basic authentication (htpasswd)": false, + "basic authentication (LDAP)": false, + "basic authentication (API key)": false, + "OpenID authentication": false, + "mutual TLS authentication": false, + }) }) Convey("reload scrub and CVE config", t, func(c C) { @@ -453,6 +522,20 @@ func TestConfigReloader(t *testing.T) { test.WaitTillServerReady(baseURL) + // verify initial startup authentication logs (no auth configured) + initialData, err := os.ReadFile(logFile.Name()) + So(err, ShouldBeNil) + So(string(initialData), ShouldContainSubstring, "configuration settings") + // verify authentication methods status messages are present in initial startup + verifyAuthenticationLogs(initialData, map[string]bool{ + "bearer authentication": false, + "basic authentication (htpasswd)": false, + "basic authentication (LDAP)": false, + "basic authentication (API key)": false, + "OpenID authentication": false, + "mutual TLS authentication": false, + }) + content = fmt.Sprintf(`{ "distSpecVersion": "1.1.1", "storage": { @@ -509,6 +592,15 @@ func TestConfigReloader(t *testing.T) { So(string(data), ShouldContainSubstring, "\"UpdateInterval\":18000000000000") So(string(data), ShouldContainSubstring, "\"Scrub\":null") So(string(data), ShouldContainSubstring, "\"DBRepository\":\"another/unreachable/trivy/url2\"") + // verify authentication methods status messages are present + verifyAuthenticationLogs(data, map[string]bool{ + "bearer authentication": false, + "basic authentication (htpasswd)": false, + "basic authentication (LDAP)": false, + "basic authentication (API key)": false, + "OpenID authentication": false, + "mutual TLS authentication": false, + }) // Just verify the new URL appears in the logs to confirm config reload worked and ignore // the order of json message formatting that can change independent of this functional diff --git a/pkg/cli/server/root_test.go b/pkg/cli/server/root_test.go index f1d9a57d..ca4d4130 100644 --- a/pkg/cli/server/root_test.go +++ b/pkg/cli/server/root_test.go @@ -6,6 +6,7 @@ import ( "os" "path" "path/filepath" + "strings" "testing" "time" @@ -19,6 +20,53 @@ import ( . "zotregistry.dev/zot/v2/pkg/test/common" ) +// checkAuthLogEntry checks if a log entry with the given message has the expected enabled value. +func checkAuthLogEntry(logData []byte, message string, expectedEnabled bool) bool { + //nolint:modernize // strings.Split is compatible with older Go versions + for _, line := range strings.Split(string(logData), "\n") { + if line == "" { + continue + } + + var logEntry map[string]any + if err := json.Unmarshal([]byte(line), &logEntry); err != nil { + continue + } + + if msg, ok := logEntry["message"].(string); ok && msg == message { + if enabled, ok := logEntry["enabled"].(bool); ok { + return enabled == expectedEnabled + } + } + } + + return false +} + +// verifyAuthenticationLogs verifies that all authentication method log messages are present +// and that each method has the expected enabled status. +// expectedAuth maps authentication method names to their expected enabled status (true/false). +func verifyAuthenticationLogs(data []byte, expectedAuth map[string]bool) { + authMethods := []string{ + "bearer authentication", + "basic authentication (htpasswd)", + "basic authentication (LDAP)", + "basic authentication (API key)", + "OpenID authentication", + "mutual TLS authentication", + } + + // Verify all authentication method messages are present + for _, method := range authMethods { + So(string(data), ShouldContainSubstring, method) + } + + // Verify each authentication method has the expected enabled status + for method, expectedEnabled := range expectedAuth { + So(checkAuthLogEntry(data, method, expectedEnabled), ShouldBeTrue) + } +} + func TestServerUsage(t *testing.T) { oldArgs := os.Args @@ -2031,6 +2079,17 @@ func TestServeAPIKey(t *testing.T) { defer os.Remove(logPath) // clean up So(string(data), ShouldContainSubstring, "\"APIKey\":true") + // verify configuration settings message is present + So(string(data), ShouldContainSubstring, "configuration settings") + // verify authentication methods status messages are present + verifyAuthenticationLogs(data, map[string]bool{ + "bearer authentication": false, + "basic authentication (htpasswd)": false, + "basic authentication (LDAP)": false, + "basic authentication (API key)": true, + "OpenID authentication": false, + "mutual TLS authentication": false, + }) }) Convey("apikey disabled", t, func(c C) { @@ -2058,6 +2117,17 @@ func TestServeAPIKey(t *testing.T) { defer os.Remove(logPath) // clean up So(string(data), ShouldContainSubstring, "\"APIKey\":false") + // verify configuration settings message is present + So(string(data), ShouldContainSubstring, "configuration settings") + // verify authentication methods status messages are present + verifyAuthenticationLogs(data, map[string]bool{ + "bearer authentication": false, + "basic authentication (htpasswd)": false, + "basic authentication (LDAP)": false, + "basic authentication (API key)": false, + "OpenID authentication": false, + "mutual TLS authentication": false, + }) }) }