Skip to content

Commit

Permalink
Implemented an MRU for recently viewed clients. (Velocidex#1167)
Browse files Browse the repository at this point in the history
  • Loading branch information
scudette authored Jul 29, 2021
1 parent 1057361 commit 8fefba8
Show file tree
Hide file tree
Showing 20 changed files with 613 additions and 420 deletions.
67 changes: 2 additions & 65 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ import (
config_proto "www.velocidex.com/golang/velociraptor/config/proto"
"www.velocidex.com/golang/velociraptor/constants"
crypto_utils "www.velocidex.com/golang/velociraptor/crypto/utils"
"www.velocidex.com/golang/velociraptor/datastore"
"www.velocidex.com/golang/velociraptor/flows"
flows_proto "www.velocidex.com/golang/velociraptor/flows/proto"
"www.velocidex.com/golang/velociraptor/grpc_client"
"www.velocidex.com/golang/velociraptor/logging"
"www.velocidex.com/golang/velociraptor/search"
"www.velocidex.com/golang/velociraptor/server"
"www.velocidex.com/golang/velociraptor/services"
users "www.velocidex.com/golang/velociraptor/users"
Expand Down Expand Up @@ -256,70 +256,7 @@ func (self *ApiServer) ListClients(
"User is not allowed to view clients.")
}

db, err := datastore.GetDB(self.config)
if err != nil {
return nil, err
}

limit := uint64(50)
if in.Limit > 0 {
limit = in.Limit
}

query_type := ""
if in.Type == api_proto.SearchClientsRequest_KEY {
query_type = "key"
}

sort_direction := datastore.UNSORTED
switch in.Sort {
case api_proto.SearchClientsRequest_SORT_UP:
sort_direction = datastore.SORT_UP
case api_proto.SearchClientsRequest_SORT_DOWN:
sort_direction = datastore.SORT_DOWN
}

// If the output is filtered, we need to retrieve as many
// clients as possible because we may eliminate them with the
// filter.
if in.Filter != api_proto.SearchClientsRequest_UNFILTERED {
limit = 100000
}

// Microseconds
now := uint64(time.Now().UnixNano() / 1000)

result := &api_proto.SearchClientsResponse{}
for _, client_id := range db.SearchClients(
self.config, constants.CLIENT_INDEX_URN,
in.Query, query_type, in.Offset, 0, sort_direction) {
if in.NameOnly || query_type == "key" {
result.Names = append(result.Names, client_id)
} else {
api_client, err := GetApiClient(
ctx, self.config,
self.server_obj, client_id,
false /* detailed */)
if err != nil {
continue
}

// Skip clients that are offline
if in.Filter == api_proto.SearchClientsRequest_ONLINE &&
now > api_client.LastSeenAt &&
now-api_client.LastSeenAt > 1000000*60*15 {
continue
}

result.Items = append(result.Items, api_client)

if uint64(len(result.Items)) >= limit {
break
}
}
}

return result, nil
return search.SearchClients(ctx, self.config, in, user_name)
}

func (self *ApiServer) NotifyClients(
Expand Down
123 changes: 31 additions & 92 deletions api/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,104 +28,16 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"www.velocidex.com/golang/velociraptor/acls"
actions_proto "www.velocidex.com/golang/velociraptor/actions/proto"
api_proto "www.velocidex.com/golang/velociraptor/api/proto"
config_proto "www.velocidex.com/golang/velociraptor/config/proto"
crypto_proto "www.velocidex.com/golang/velociraptor/crypto/proto"
"www.velocidex.com/golang/velociraptor/datastore"
"www.velocidex.com/golang/velociraptor/flows"
flows_proto "www.velocidex.com/golang/velociraptor/flows/proto"
"www.velocidex.com/golang/velociraptor/paths"
"www.velocidex.com/golang/velociraptor/server"
"www.velocidex.com/golang/velociraptor/search"
"www.velocidex.com/golang/velociraptor/services"
)

func GetApiClient(
ctx context.Context,
config_obj *config_proto.Config,
server_obj *server.Server,
client_id string, detailed bool) (
*api_proto.ApiClient, error) {

if config_obj.GUI == nil {
return nil, errors.New("GUI not configured")
}

result := &api_proto.ApiClient{
ClientId: client_id,
}

// Special well known client id.
if client_id == "server" {
return result, nil
}

if client_id == "" || client_id[0] != 'C' {
return nil, errors.New("client_id must start with C")
}

client_path_manager := paths.NewClientPathManager(client_id)
db, err := datastore.GetDB(config_obj)
if err != nil {
return nil, err
}

labeler := services.GetLabeler()
if labeler == nil {
return nil, errors.New("Labeler not ready")
}

result.Labels = labeler.GetClientLabels(config_obj, client_id)

client_info := &actions_proto.ClientInfo{}
err = db.GetSubject(config_obj, client_path_manager.Path(), client_info)
if err != nil {
return nil, err
}

result.LastInterrogateFlowId = client_info.LastInterrogateFlowId
result.AgentInformation = &api_proto.AgentInformation{
Version: client_info.ClientVersion,
Name: client_info.ClientName,
}

result.OsInfo = &api_proto.Uname{
System: client_info.System,
Release: client_info.Release,
Machine: client_info.Architecture,
Fqdn: client_info.Fqdn,
}

public_key_info := &crypto_proto.PublicKey{}
err = db.GetSubject(config_obj, client_path_manager.Key().Path(),
public_key_info)
if err != nil {
// Offline clients do not have public key files, so
// this is not actually an error.
}

result.FirstSeenAt = public_key_info.EnrollTime

err = db.GetSubject(config_obj, client_path_manager.Ping().Path(),
client_info)
if err != nil {
// Offline clients do not have public key files, so
// this is not actually an error.
}

result.LastSeenAt = client_info.Ping
result.LastIp = client_info.IpAddress

if server_obj != nil && detailed &&
// Wait up to 2 seconds to find out if clients are connected.
services.GetNotifier().IsClientConnected(ctx,
config_obj, client_id, 2) {
result.LastSeenAt = uint64(time.Now().UnixNano() / 1000)
}

return result, nil
}

func (self *ApiServer) GetClientMetadata(
ctx context.Context,
in *api_proto.GetClientRequest) (*api_proto.ClientMetadata, error) {
Expand Down Expand Up @@ -191,16 +103,29 @@ func (self *ApiServer) GetClient(
"User is not allowed to view clients.")
}

api_client, err := GetApiClient(ctx,
if in.UpdateMru {
err = updateMRU(self.config, user_name, in.ClientId)
if err != nil {
return nil, err
}
}

api_client, err := search.GetApiClient(ctx,
self.config,
self.server_obj,
in.ClientId,
!in.Lightweight, // Detailed
)
if err != nil {
return nil, err
}

if self.server_obj != nil && !in.Lightweight &&
// Wait up to 2 seconds to find out if clients are connected.
services.GetNotifier().IsClientConnected(ctx,
self.config, in.ClientId, 2) {
api_client.LastSeenAt = uint64(time.Now().UnixNano() / 1000)
}

return api_client, nil
}

Expand Down Expand Up @@ -239,7 +164,21 @@ func (self *ApiServer) GetClientFlows(
return false
}
}

return flows.GetFlows(self.config, in.ClientId,
in.IncludeArchived, filter, in.Offset, in.Count)
}

func updateMRU(
config_obj *config_proto.Config,
user_name string, client_id string) error {
path_manager := &paths.UserPathManager{user_name}
db, err := datastore.GetDB(config_obj)
if err != nil {
return err
}

err = db.SetIndex(config_obj, path_manager.MRU(),
client_id, []string{"mru"})

return err
}
Loading

0 comments on commit 8fefba8

Please sign in to comment.