Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
308 changes: 308 additions & 0 deletions internals/daemon/daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ import (
"crypto/ed25519"
"crypto/rand"
"crypto/sha512"
"crypto/tls"
"crypto/x509"
"encoding/base32"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
Expand All @@ -38,6 +41,7 @@ import (
"testing"
"time"

"github.com/GehirnInc/crypt/sha512_crypt"
"github.com/gorilla/mux"
. "gopkg.in/check.v1"

Expand Down Expand Up @@ -1879,3 +1883,307 @@ 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.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.Assert(*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.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) {
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.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) {
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.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) {
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.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) {
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.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) {
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.Assert(capturedUser, NotNil)
c.Assert(capturedUser.Username, Equals, "certuser1")
c.Assert(capturedUser.Access, Equals, state.AdminAccess)
c.Assert(capturedUser.UID, IsNil)
}
Loading