From 2b5c590e2dd14fe2dc166e1b8e067109000c87ba Mon Sep 17 00:00:00 2001 From: Fred Lotter Date: Wed, 22 Oct 2025 14:38:46 +0200 Subject: [PATCH 1/2] chore(daemon): improve userstate test coverage --- internals/daemon/daemon_test.go | 297 ++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) diff --git a/internals/daemon/daemon_test.go b/internals/daemon/daemon_test.go index a7efb3eb..1c87f90d 100644 --- a/internals/daemon/daemon_test.go +++ b/internals/daemon/daemon_test.go @@ -20,8 +20,11 @@ import ( "crypto/ed25519" "crypto/rand" "crypto/sha512" + "crypto/tls" + "crypto/x509" "encoding/base32" "encoding/json" + "encoding/pem" "errors" "fmt" "io" @@ -38,6 +41,7 @@ import ( "testing" "time" + "github.com/GehirnInc/crypt/sha512_crypt" "github.com/gorilla/mux" . "gopkg.in/check.v1" @@ -1879,3 +1883,296 @@ func (t *transportTypeSuite) TestTransportTypeContext(c *C) { c.Assert(transport.IsConcealed(), Equals, true) c.Assert(transport.String(), Equals, "https") } + +func (s *daemonSuite) TestServeHTTPUserStateLocal(c *C) { + d := s.newDaemon(c) + + // Set up a Local identity. + d.state.Lock() + err := d.state.AddIdentities(map[string]*state.Identity{ + "localuser": { + Access: state.AdminAccess, + Local: &state.LocalIdentity{UserID: 1000}, + }, + }) + d.state.Unlock() + c.Assert(err, IsNil) + + // Capture the UserState passed to the response function. + var capturedUser *UserState + cmd := &Command{ + d: d, + GET: func(c *Command, r *http.Request, user *UserState) Response { + capturedUser = user + return SyncResponse(true) + }, + ReadAccess: UserAccess{}, + } + + // Make request with the Local user's UID. + ctx := context.WithValue(context.Background(), TransportTypeKey{}, TransportTypeUnixSocket) + req, err := http.NewRequestWithContext(ctx, "GET", "", nil) + c.Assert(err, IsNil) + req.RemoteAddr = "pid=100;uid=1000;socket=;" + + rec := httptest.NewRecorder() + cmd.ServeHTTP(rec, req) + c.Check(rec.Code, Equals, http.StatusOK) + + // Verify UserState for Local identity. + c.Check(capturedUser.Username, Equals, "localuser") + c.Check(capturedUser.Access, Equals, state.AdminAccess) + + // This specific expectation is only a temporary workaround to + // support code not yet supporting named identities, such as + // notices. UID should really be nil in this case. + c.Check(*capturedUser.UID, Equals, uint32(1000)) +} + +func (s *daemonSuite) TestServeHTTPUserStateUIDOnly(c *C) { + d := s.newDaemon(c) + // Don't set up any named identities, so it falls back to UID-only + + // Capture the UserState passed to the response function. + var capturedUser *UserState + cmd := &Command{ + d: d, + GET: func(c *Command, r *http.Request, user *UserState) Response { + capturedUser = user + return SyncResponse(true) + }, + ReadAccess: UserAccess{}, + } + + // Make request with just a UID (not root, not daemon UID) + ctx := context.WithValue(context.Background(), TransportTypeKey{}, TransportTypeUnixSocket) + req, err := http.NewRequestWithContext(ctx, "GET", "", nil) + c.Assert(err, IsNil) + req.RemoteAddr = "pid=100;uid=5000;socket=;" + + rec := httptest.NewRecorder() + cmd.ServeHTTP(rec, req) + c.Check(rec.Code, Equals, http.StatusOK) + + // Verify UserState for UID-only (no named identity) + c.Check(capturedUser.Username, Equals, "") + c.Check(capturedUser.Access, Equals, state.ReadAccess) + c.Check(*capturedUser.UID, Equals, uint32(5000)) +} + +func (s *daemonSuite) TestServeHTTPUserStateUIDOnlyRoot(c *C) { + d := s.newDaemon(c) + // Don't set up any named identities, so it falls back to UID-only + + // Capture the UserState passed to the response function. + var capturedUser *UserState + cmd := &Command{ + d: d, + GET: func(c *Command, r *http.Request, user *UserState) Response { + capturedUser = user + return SyncResponse(true) + }, + ReadAccess: UserAccess{}, + } + + // Make request as root (UID 0). + ctx := context.WithValue(context.Background(), TransportTypeKey{}, TransportTypeUnixSocket) + req, err := http.NewRequestWithContext(ctx, "GET", "", nil) + c.Assert(err, IsNil) + req.RemoteAddr = "pid=100;uid=0;socket=;" + + rec := httptest.NewRecorder() + cmd.ServeHTTP(rec, req) + c.Check(rec.Code, Equals, http.StatusOK) + + // Verify UserState for root UID. + c.Check(capturedUser.Username, Equals, "") + c.Check(capturedUser.Access, Equals, state.AdminAccess) + c.Check(*capturedUser.UID, Equals, uint32(0)) +} + +func (s *daemonSuite) TestServeHTTPUserStateUIDOnlyDaemonUID(c *C) { + d := s.newDaemon(c) + // Don't set up any named identities, so it falls back to UID-only + + // Capture the UserState passed to the response function. + var capturedUser *UserState + cmd := &Command{ + d: d, + GET: func(c *Command, r *http.Request, user *UserState) Response { + capturedUser = user + return SyncResponse(true) + }, + ReadAccess: UserAccess{}, + } + + // Make request as the daemon's UID. + daemonUID := uint32(os.Getuid()) + ctx := context.WithValue(context.Background(), TransportTypeKey{}, TransportTypeUnixSocket) + req, err := http.NewRequestWithContext(ctx, "GET", "", nil) + c.Assert(err, IsNil) + req.RemoteAddr = fmt.Sprintf("pid=100;uid=%d;socket=;", daemonUID) + + rec := httptest.NewRecorder() + cmd.ServeHTTP(rec, req) + c.Check(rec.Code, Equals, http.StatusOK) + + // Verify UserState for daemon UID. + c.Check(capturedUser.Username, Equals, "") + c.Check(capturedUser.Access, Equals, state.AdminAccess) + c.Check(*capturedUser.UID, Equals, daemonUID) +} + +func (s *daemonSuite) TestServeHTTPUserStateBasicUnixSocket(c *C) { + d := s.newDaemon(c) + + // Set up a Basic auth identity with a hashed password. + // Generate sha512-crypt hash for password "test". + crypt := sha512_crypt.New() + hashedPassword, err := crypt.Generate([]byte("test"), nil) + c.Assert(err, IsNil) + + d.state.Lock() + err = d.state.AddIdentities(map[string]*state.Identity{ + "basicuser": { + Access: state.ReadAccess, + Basic: &state.BasicIdentity{Password: hashedPassword}, + }, + }) + d.state.Unlock() + c.Assert(err, IsNil) + + // Capture the UserState passed to the response function. + var capturedUser *UserState + cmd := &Command{ + d: d, + GET: func(c *Command, r *http.Request, user *UserState) Response { + capturedUser = user + return SyncResponse(true) + }, + ReadAccess: UserAccess{}, + } + + // Make request with Basic auth credentials over Unix Socket. + ctx := context.WithValue(context.Background(), TransportTypeKey{}, TransportTypeUnixSocket) + req, err := http.NewRequestWithContext(ctx, "GET", "", nil) + c.Assert(err, IsNil) + req.SetBasicAuth("basicuser", "test") + req.RemoteAddr = "pid=100;uid=1000;socket=;" + + rec := httptest.NewRecorder() + cmd.ServeHTTP(rec, req) + c.Check(rec.Code, Equals, http.StatusOK) + + // Verify UserState for Basic identity over Unix Socket. + c.Check(capturedUser.Username, Equals, "basicuser") + c.Check(capturedUser.Access, Equals, state.ReadAccess) + c.Check(capturedUser.UID, IsNil) +} + +func (s *daemonSuite) TestServeHTTPUserStateBasicHTTP(c *C) { + d := s.newDaemon(c) + + // Set up a Basic auth identity with a hashed password. + // Generate sha512-crypt hash for password "test". + crypt := sha512_crypt.New() + hashedPassword, err := crypt.Generate([]byte("test"), nil) + c.Assert(err, IsNil) + + d.state.Lock() + err = d.state.AddIdentities(map[string]*state.Identity{ + "basicuser": { + Access: state.MetricsAccess, + Basic: &state.BasicIdentity{Password: hashedPassword}, + }, + }) + d.state.Unlock() + c.Assert(err, IsNil) + + // Capture the UserState passed to the response function. + var capturedUser *UserState + cmd := &Command{ + d: d, + GET: func(c *Command, r *http.Request, user *UserState) Response { + capturedUser = user + return SyncResponse(true) + }, + ReadAccess: MetricsAccess{}, + } + + // Make request with Basic auth credentials over HTTP (not HTTPS). + ctx := context.WithValue(context.Background(), TransportTypeKey{}, TransportTypeHTTP) + req, err := http.NewRequestWithContext(ctx, "GET", "", nil) + c.Assert(err, IsNil) + req.SetBasicAuth("basicuser", "test") + req.RemoteAddr = "192.168.1.100:8888" + + rec := httptest.NewRecorder() + cmd.ServeHTTP(rec, req) + c.Check(rec.Code, Equals, http.StatusOK) + + // Verify UserState for Basic identity over HTTP. + c.Check(capturedUser.Username, Equals, "basicuser") + c.Check(capturedUser.Access, Equals, state.MetricsAccess) + c.Check(capturedUser.UID, IsNil) +} + +func (s *daemonSuite) TestServeHTTPUserStateCert(c *C) { + d := s.newDaemon(c) + + // Create a test certificate. + certPEM := `-----BEGIN CERTIFICATE----- +MIIBRDCB96ADAgECAhROTkdEcgeil5/5NUNTq1ZRPDLiPTAFBgMrZXAwGDEWMBQG +A1UEAwwNY2Fub25pY2FsLmNvbTAeFw0yNTA5MDgxNTI2NTJaFw0zNTA5MDYxNTI2 +NTJaMBgxFjAUBgNVBAMMDWNhbm9uaWNhbC5jb20wKjAFBgMrZXADIQDtxRqb9EMe +ffcoJ0jNn9ys8uDFeHnQ6JRxgNFvomDTHqNTMFEwHQYDVR0OBBYEFI/oHjhG1A7F +3HM7McXP7w7CxtrwMB8GA1UdIwQYMBaAFI/oHjhG1A7F3HM7McXP7w7CxtrwMA8G +A1UdEwEB/wQFMAMBAf8wBQYDK2VwA0EA40v4eckaV7RBXyRb0sfcCcgCAGYtiCSD +jwXVTUH4HLpbhK0RAaEPOL4h5jm36CrWTkxzpbdCrIu4NgPLQKJ6Cw== +-----END CERTIFICATE-----` + + block, _ := pem.Decode([]byte(certPEM)) + c.Assert(block, NotNil) + cert, err := x509.ParseCertificate(block.Bytes) + c.Assert(err, IsNil) + + // Set up a Cert identity. + d.state.Lock() + err = d.state.AddIdentities(map[string]*state.Identity{ + "certuser1": { + Access: state.AdminAccess, + Cert: &state.CertIdentity{X509: cert}, + }, + }) + d.state.Unlock() + c.Assert(err, IsNil) + + // Capture the UserState passed to the response function. + var capturedUser *UserState + cmd := &Command{ + d: d, + GET: func(c *Command, r *http.Request, user *UserState) Response { + capturedUser = user + return SyncResponse(true) + }, + ReadAccess: UserAccess{}, + } + + // Make request with mTLS client certificate. + ctx := context.WithValue(context.Background(), TransportTypeKey{}, TransportTypeHTTPS) + req, err := http.NewRequestWithContext(ctx, "GET", "", nil) + c.Assert(err, IsNil) + req.RemoteAddr = "192.168.1.100:8443" + req.TLS = &tls.ConnectionState{ + PeerCertificates: []*x509.Certificate{cert}, + } + + rec := httptest.NewRecorder() + cmd.ServeHTTP(rec, req) + c.Check(rec.Code, Equals, http.StatusOK) + + // Verify UserState for Cert identity. + c.Check(capturedUser.Username, Equals, "certuser1") + c.Check(capturedUser.Access, Equals, state.AdminAccess) + c.Check(capturedUser.UID, IsNil) +} From 9fa62d5161b617377c3bec334da1819f0a7cc92a Mon Sep 17 00:00:00 2001 From: Fred Lotter Date: Thu, 23 Oct 2025 09:47:39 +0200 Subject: [PATCH 2/2] Code Review changes group 1 --- internals/daemon/daemon_test.go | 53 ++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/internals/daemon/daemon_test.go b/internals/daemon/daemon_test.go index 1c87f90d..830ae688 100644 --- a/internals/daemon/daemon_test.go +++ b/internals/daemon/daemon_test.go @@ -1920,13 +1920,15 @@ func (s *daemonSuite) TestServeHTTPUserStateLocal(c *C) { c.Check(rec.Code, Equals, http.StatusOK) // Verify UserState for Local identity. - c.Check(capturedUser.Username, Equals, "localuser") - c.Check(capturedUser.Access, Equals, state.AdminAccess) + c.Assert(capturedUser, NotNil) + c.Assert(capturedUser.Username, Equals, "localuser") + c.Assert(capturedUser.Access, Equals, state.AdminAccess) + c.Assert(capturedUser.UID, NotNil) // This specific expectation is only a temporary workaround to // support code not yet supporting named identities, such as // notices. UID should really be nil in this case. - c.Check(*capturedUser.UID, Equals, uint32(1000)) + c.Assert(*capturedUser.UID, Equals, uint32(1000)) } func (s *daemonSuite) TestServeHTTPUserStateUIDOnly(c *C) { @@ -1955,9 +1957,11 @@ func (s *daemonSuite) TestServeHTTPUserStateUIDOnly(c *C) { c.Check(rec.Code, Equals, http.StatusOK) // Verify UserState for UID-only (no named identity) - c.Check(capturedUser.Username, Equals, "") - c.Check(capturedUser.Access, Equals, state.ReadAccess) - c.Check(*capturedUser.UID, Equals, uint32(5000)) + c.Assert(capturedUser, NotNil) + c.Assert(capturedUser.Username, Equals, "") + c.Assert(capturedUser.Access, Equals, state.ReadAccess) + c.Assert(capturedUser.UID, NotNil) + c.Assert(*capturedUser.UID, Equals, uint32(5000)) } func (s *daemonSuite) TestServeHTTPUserStateUIDOnlyRoot(c *C) { @@ -1986,9 +1990,11 @@ func (s *daemonSuite) TestServeHTTPUserStateUIDOnlyRoot(c *C) { c.Check(rec.Code, Equals, http.StatusOK) // Verify UserState for root UID. - c.Check(capturedUser.Username, Equals, "") - c.Check(capturedUser.Access, Equals, state.AdminAccess) - c.Check(*capturedUser.UID, Equals, uint32(0)) + c.Assert(capturedUser, NotNil) + c.Assert(capturedUser.Username, Equals, "") + c.Assert(capturedUser.Access, Equals, state.AdminAccess) + c.Assert(capturedUser.UID, NotNil) + c.Assert(*capturedUser.UID, Equals, uint32(0)) } func (s *daemonSuite) TestServeHTTPUserStateUIDOnlyDaemonUID(c *C) { @@ -2018,9 +2024,11 @@ func (s *daemonSuite) TestServeHTTPUserStateUIDOnlyDaemonUID(c *C) { c.Check(rec.Code, Equals, http.StatusOK) // Verify UserState for daemon UID. - c.Check(capturedUser.Username, Equals, "") - c.Check(capturedUser.Access, Equals, state.AdminAccess) - c.Check(*capturedUser.UID, Equals, daemonUID) + c.Assert(capturedUser, NotNil) + c.Assert(capturedUser.Username, Equals, "") + c.Assert(capturedUser.Access, Equals, state.AdminAccess) + c.Assert(capturedUser.UID, NotNil) + c.Assert(*capturedUser.UID, Equals, daemonUID) } func (s *daemonSuite) TestServeHTTPUserStateBasicUnixSocket(c *C) { @@ -2065,9 +2073,10 @@ func (s *daemonSuite) TestServeHTTPUserStateBasicUnixSocket(c *C) { c.Check(rec.Code, Equals, http.StatusOK) // Verify UserState for Basic identity over Unix Socket. - c.Check(capturedUser.Username, Equals, "basicuser") - c.Check(capturedUser.Access, Equals, state.ReadAccess) - c.Check(capturedUser.UID, IsNil) + c.Assert(capturedUser, NotNil) + c.Assert(capturedUser.Username, Equals, "basicuser") + c.Assert(capturedUser.Access, Equals, state.ReadAccess) + c.Assert(capturedUser.UID, IsNil) } func (s *daemonSuite) TestServeHTTPUserStateBasicHTTP(c *C) { @@ -2112,9 +2121,10 @@ func (s *daemonSuite) TestServeHTTPUserStateBasicHTTP(c *C) { c.Check(rec.Code, Equals, http.StatusOK) // Verify UserState for Basic identity over HTTP. - c.Check(capturedUser.Username, Equals, "basicuser") - c.Check(capturedUser.Access, Equals, state.MetricsAccess) - c.Check(capturedUser.UID, IsNil) + c.Assert(capturedUser, NotNil) + c.Assert(capturedUser.Username, Equals, "basicuser") + c.Assert(capturedUser.Access, Equals, state.MetricsAccess) + c.Assert(capturedUser.UID, IsNil) } func (s *daemonSuite) TestServeHTTPUserStateCert(c *C) { @@ -2172,7 +2182,8 @@ jwXVTUH4HLpbhK0RAaEPOL4h5jm36CrWTkxzpbdCrIu4NgPLQKJ6Cw== c.Check(rec.Code, Equals, http.StatusOK) // Verify UserState for Cert identity. - c.Check(capturedUser.Username, Equals, "certuser1") - c.Check(capturedUser.Access, Equals, state.AdminAccess) - c.Check(capturedUser.UID, IsNil) + c.Assert(capturedUser, NotNil) + c.Assert(capturedUser.Username, Equals, "certuser1") + c.Assert(capturedUser.Access, Equals, state.AdminAccess) + c.Assert(capturedUser.UID, IsNil) }