github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/server/db/store.go (about)

     1  // Copyright 2017 Google Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package db defines the interface that fleetspeak expects from its persistence
    16  // layer. Each installation will need to choose and configure a Store
    17  // implementation. An example implementation meant for testing and small scale
    18  // deployments is in the server/sqlite directory.
    19  //
    20  // It also includes some utility methods and types meant for use by Store
    21  // implementations.
    22  //
    23  // SECURITY NOTE:
    24  //
    25  // The endpoints provide much of the data passed through this interface.
    26  // Implementations are responsible for using safe coding practices to prevent
    27  // SQL injection and similar attacks.
    28  package db
    29  
    30  import (
    31  	"context"
    32  	"fmt"
    33  	"io"
    34  	"math"
    35  	"time"
    36  
    37  	"github.com/google/fleetspeak/fleetspeak/src/common"
    38  	"github.com/google/fleetspeak/fleetspeak/src/server/ids"
    39  
    40  	"google.golang.org/protobuf/proto"
    41  
    42  	fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak"
    43  	mpb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak_monitoring"
    44  	spb "github.com/google/fleetspeak/fleetspeak/src/server/proto/fleetspeak_server"
    45  	tspb "google.golang.org/protobuf/types/known/timestamppb"
    46  )
    47  
    48  // A Store describes the full persistence mechanism required by the base
    49  // fleetspeak system. These operations must be thread safe. These must also be
    50  // all-or-nothing, fully committed on success, and are otherwise trusted to be
    51  // individually transactional.
    52  type Store interface {
    53  	MessageStore
    54  	ClientStore
    55  	BroadcastStore
    56  	FileStore
    57  
    58  	// IsNotFound returns whether an error returned by the Datastore indicates that
    59  	// a record was not found.
    60  	IsNotFound(error) bool
    61  
    62  	// Close shuts down the Store, releasing any held resources.
    63  	Close() error
    64  }
    65  
    66  // ClientData contains basic data about a client.
    67  type ClientData struct {
    68  	Key    []byte        // The der encoded public key for the client.
    69  	Labels []*fspb.Label // The client's labels.
    70  
    71  	// Whether the client_id has been blacklisted. Once blacklisted any contact
    72  	// from this client_id will result in an rekey request.
    73  	Blacklisted bool
    74  }
    75  
    76  // Clone returns a deep copy.
    77  func (d *ClientData) Clone() *ClientData {
    78  	ret := &ClientData{
    79  		Blacklisted: d.Blacklisted,
    80  	}
    81  	ret.Key = append(ret.Key, d.Key...)
    82  
    83  	ret.Labels = make([]*fspb.Label, 0, len(d.Labels))
    84  	for _, l := range d.Labels {
    85  		ret.Labels = append(ret.Labels, proto.Clone(l).(*fspb.Label))
    86  	}
    87  	return ret
    88  }
    89  
    90  // A ContactID identifies a communication with a client. The form is determined
    91  // by the Datastore implementation - it is treated as an opaque string by the
    92  // rest of the FS system.
    93  type ContactID string
    94  
    95  // MessageStore provides methods to store and query messages.
    96  //
    97  // Notionally, a MessageStore is backed by a table where each row is a fspb.Message record,
    98  // along with with one of the following:
    99  //
   100  // 1) due time and retry count - If the message is not processed or delivered
   101  // before due time, it will be tried again. A count of the number of retries is
   102  // maintained and used to compute the next due time.
   103  //
   104  // 2) completion time - When a record is processed or acknowledged by a client, the
   105  // message is marked as completed by saving a completion time.
   106  //
   107  // Furthermore it is possible to register a MessageProcessor with each
   108  // MessageStore which then receives notifications that server messages are ready
   109  // for processing. In multi-server installations, the datastore should attempt
   110  // to provide eactly one notification to some Fleetspeak server each time the
   111  // message becomes overdue.
   112  type MessageStore interface {
   113  	// StoreMessages records msgs. If contact is not the empty string, it attaches
   114  	// them to the associated contact.
   115  	//
   116  	// It is not an error for a message to already exist. In this case a success result
   117  	// will overwrite any result already present, and a failed result will overwrite
   118  	// an empty result. Otherwise the operation is silently dropped.
   119  	//
   120  	// A message is eligible to be returned by ClientMessagesForProcessing or the
   121  	// registered MessageProcessor iff it does not yet have a Result. Also,
   122  	// setting a Result will delete the message's Data field payload.
   123  	StoreMessages(ctx context.Context, msgs []*fspb.Message, contact ContactID) error
   124  
   125  	// DeletePendingMessages removes all pending messages for given clients.
   126  	DeletePendingMessages(ctx context.Context, ids []common.ClientID) error
   127  
   128  	// GetPendingMessageCount returns the number of pending messages for the given clients.
   129  	GetPendingMessageCount(ctx context.Context, ids []common.ClientID) (uint64, error)
   130  
   131  	// GetPendingMessages returns pending messages for given clients.
   132  	// If wantData is true, message data is retrieved as well.
   133  	// offset and limit are optional, set them to 0 to retrieve all pending messages.
   134  	GetPendingMessages(ctx context.Context, ids []common.ClientID, offset uint64, limit uint64, wantData bool) ([]*fspb.Message, error)
   135  
   136  	// ClientMessagesForProcessing returns messages that are due to be
   137  	// processed by a client. It also increments the time at which the
   138  	// messages will again become overdue using rp.
   139  	//
   140  	// The lim parameter indicates the maximum number of messages to to
   141  	// retrieve. If non-nil, serviceLimits further limits the number returned
   142  	// for each service.
   143  	//
   144  	// Note that if an error occurs partway through the loading of messages,
   145  	// the already loaded messages may be returned along with the error. In
   146  	// particular, datastore implementations might do this if the ctx times
   147  	// out before all messages are found and updated. Any such are message
   148  	// is valid and ready for processing.
   149  	ClientMessagesForProcessing(ctx context.Context, id common.ClientID, lim uint64, serviceLimits map[string]uint64) ([]*fspb.Message, error)
   150  
   151  	// GetMessages retrieves specific messages.
   152  	GetMessages(ctx context.Context, ids []common.MessageID, wantData bool) ([]*fspb.Message, error)
   153  
   154  	// GetMessageResult retrieves the current status of a message.
   155  	GetMessageResult(ctx context.Context, id common.MessageID) (*fspb.MessageResult, error)
   156  
   157  	// SetMessageResult retrieves the current status of a message. The dest
   158  	// parameter identifies the destination client, and must be the empty id for
   159  	// messages addressed to the server.
   160  	SetMessageResult(ctx context.Context, dest common.ClientID, id common.MessageID, res *fspb.MessageResult) error
   161  
   162  	// RegisterMessageProcessor installs a MessageProcessor which will be
   163  	// called when a message is overdue for processing.
   164  	RegisterMessageProcessor(mp MessageProcessor)
   165  
   166  	// StopMessageProcessor causes the datastore to stop making calls to the
   167  	// registered MessageProcessor. It only returns once all existing calls
   168  	// to MessageProcessor have completed.
   169  	StopMessageProcessor()
   170  }
   171  
   172  // A MessageProcessor receives messages that are overdue and should be reprocessing.
   173  type MessageProcessor interface {
   174  	// ProcessMessage is called by the Datastore to indicate that the
   175  	// provided message is overdue and that processing should be attempted
   176  	// again.
   177  	//
   178  	// This call will be repeated until MarkMessage(Processed|Failed) is
   179  	// successfully called on m.
   180  	ProcessMessages(msgs []*fspb.Message)
   181  }
   182  
   183  // ContactData provides basic information about a client's contact with a FS
   184  // server.
   185  type ContactData struct {
   186  	ClientID                 common.ClientID // ID of the client.
   187  	NonceSent, NonceReceived uint64          // Nonce sent to the client and received from the client.
   188  	Addr                     string          // Observed client network address.
   189  	ClientClock              *tspb.Timestamp // Client's report of its current clock setting.
   190  
   191  	// If non-empty, indicates that the contact is or was a streaming contact to
   192  	// the listed FS server. (As defined by notifications module being used.)
   193  	StreamingTo string
   194  }
   195  
   196  // ClientStore provides methods to store and retrieve information about clients.
   197  type ClientStore interface {
   198  	// ListClients returns basic information about clients. If ids is empty, it
   199  	// returns all clients.
   200  	ListClients(ctx context.Context, ids []common.ClientID) ([]*spb.Client, error)
   201  
   202  	// StreamClientIds streams the IDs of all available clients, optionally with last contact after a given timestamp.
   203  	StreamClientIds(ctx context.Context, includeBlacklisted bool, lastContactAfter *time.Time, callback func(common.ClientID) error) error
   204  
   205  	// GetClientData retrieves the current data about the client identified
   206  	// by id.
   207  	GetClientData(ctx context.Context, id common.ClientID) (*ClientData, error)
   208  
   209  	// AddClient creates a new client.
   210  	AddClient(ctx context.Context, id common.ClientID, data *ClientData) error
   211  
   212  	// AddClientLabel records that a client now has a label.
   213  	AddClientLabel(ctx context.Context, id common.ClientID, l *fspb.Label) error
   214  
   215  	// RemoveLabel records that a client no longer has a label.
   216  	RemoveClientLabel(ctx context.Context, id common.ClientID, l *fspb.Label) error
   217  
   218  	// BlacklistClient records that a client_id is no longer trusted and should be
   219  	// recreated.
   220  	BlacklistClient(ctx context.Context, id common.ClientID) error
   221  
   222  	// RecordClientContact records an authenticated contact with a
   223  	// client. On success provides a contact id - an opaque string which can
   224  	// be used to link messages to a contact.
   225  	RecordClientContact(ctx context.Context, data ContactData) (ContactID, error)
   226  
   227  	// ListClientContacts lists all of the contacts in the database for a given
   228  	// client.
   229  	//
   230  	// NOTE: This method is explicitly permitted to return data up to 30 seconds
   231  	// stale. Also, it is normal (and expected) for a datastore to delete contact
   232  	// older than a few weeks.
   233  	ListClientContacts(ctx context.Context, id common.ClientID) ([]*spb.ClientContact, error)
   234  
   235  	// StreamClientContacts is a streaming version of ListClientContacts.
   236  	StreamClientContacts(ctx context.Context, id common.ClientID, callback func(*spb.ClientContact) error) error
   237  
   238  	// LinkMessagesToContact associates messages with a contact - it records
   239  	// that they were sent or received during the given contact.
   240  	LinkMessagesToContact(ctx context.Context, contact ContactID, msgs []common.MessageID) error
   241  
   242  	// Writes resource-usage data received from a client to the data-store.
   243  	RecordResourceUsageData(ctx context.Context, id common.ClientID, rud *mpb.ResourceUsageData) error
   244  
   245  	// Fetches resource-usage records for a given client and time range from the data-store.
   246  	FetchResourceUsageRecords(ctx context.Context, id common.ClientID, startTimestamp, endTimestamp *tspb.Timestamp) ([]*spb.ClientResourceUsageRecord, error)
   247  }
   248  
   249  // Broadcast limits with special meaning.
   250  const (
   251  	BroadcastDisabled  = uint64(0)
   252  	BroadcastUnlimited = uint64(math.MaxInt64) // The sqlite datastore's uint64 doesn't support full uint64 range.
   253  )
   254  
   255  // A BroadcastInfo describes a broadcast and contains the static broadcast
   256  // information, plus the current limit and count of messages sent.
   257  type BroadcastInfo struct {
   258  	Broadcast *spb.Broadcast
   259  	Sent      uint64
   260  	Limit     uint64
   261  }
   262  
   263  // An AllocationInfo describes an allocation. An allocation is the right to send
   264  // some broadcast to up to Limit machines before Expiry.
   265  type AllocationInfo struct {
   266  	ID     ids.AllocationID
   267  	Limit  uint64
   268  	Expiry time.Time
   269  }
   270  
   271  // ComputeBroadcastAllocation computes how large a new allocation should be. It
   272  // is meant to be used by implementations of Broadcaststore.
   273  //
   274  // It takes the allocation's current message limit, also the number already
   275  // sent, the number already allocated, and the target fraction of the allocation
   276  // to claim. It returns the number of messages that should be allocated to a new
   277  // allocation, also the new total number of messages allocated.
   278  func ComputeBroadcastAllocation(messageLimit, allocated, sent uint64, frac float32) (toAllocate, newAllocated uint64) {
   279  	// Allocations for unlimited broadcasts don't count; such allocations are only used
   280  	// to keep tract of the number sent.
   281  	if messageLimit == BroadcastUnlimited {
   282  		return BroadcastUnlimited, allocated
   283  	}
   284  	a := allocated
   285  	if sent > a {
   286  		a = sent
   287  	}
   288  	if a > messageLimit {
   289  		return 0, allocated
   290  	}
   291  	toAllocate = uint64(float32(messageLimit-a) * frac)
   292  	if toAllocate == 0 {
   293  		toAllocate = 1
   294  	}
   295  	if toAllocate > messageLimit-a {
   296  		toAllocate = messageLimit - a
   297  	}
   298  	newAllocated = toAllocate + allocated
   299  	return
   300  }
   301  
   302  // ComputeBroadcastAllocationCleanup computes the new number of messages
   303  // allocated when cleaning up an allocation. It takes the number of messages
   304  // that were allocated to the allocation, also the current total number of messages
   305  // allocated from the broadcast.
   306  func ComputeBroadcastAllocationCleanup(allocationLimit, allocated uint64) (uint64, error) {
   307  	if allocationLimit == BroadcastUnlimited {
   308  		return allocated, nil
   309  	}
   310  	if allocationLimit > allocated {
   311  		return 0, fmt.Errorf("allocationLimit = %v, which is larger than allocated = %v", allocationLimit, allocated)
   312  	}
   313  	return allocated - allocationLimit, nil
   314  }
   315  
   316  // BroadcastStore provides methods to store and retrieve information about broadcasts.
   317  type BroadcastStore interface {
   318  	// CreateBroadcast stores a new broadcast message.
   319  	CreateBroadcast(ctx context.Context, b *spb.Broadcast, limit uint64) error
   320  
   321  	// SetBroadcastLimit adjusts the limit of an existing broadcast.
   322  	SetBroadcastLimit(ctx context.Context, id ids.BroadcastID, limit uint64) error
   323  
   324  	// SaveBroadcastMessage saves a new broadcast message.
   325  	SaveBroadcastMessage(ctx context.Context, msg *fspb.Message, bid ids.BroadcastID, cid common.ClientID, aid ids.AllocationID) error
   326  
   327  	// ListActiveBroadcasts lists broadcasts which could be sent to some
   328  	// client.
   329  	ListActiveBroadcasts(ctx context.Context) ([]*BroadcastInfo, error)
   330  
   331  	// ListSentBroadcasts returns identifiers for those broadcasts which have already been sent to a client.
   332  	ListSentBroadcasts(ctx context.Context, id common.ClientID) ([]ids.BroadcastID, error)
   333  
   334  	// CreateAllocation creates an allocation for a given Broadcast,
   335  	// reserving frac of the unallocated broadcast limit until
   336  	// expiry. Return nil if there is no message allocation available.
   337  	CreateAllocation(ctx context.Context, id ids.BroadcastID, frac float32, expiry time.Time) (*AllocationInfo, error)
   338  
   339  	// CleanupAllocation deletes the identified allocation record and
   340  	// updates the broadcast sent count according to the number that were
   341  	// actually sent under the given allocation.
   342  	CleanupAllocation(ctx context.Context, bid ids.BroadcastID, aid ids.AllocationID) error
   343  }
   344  
   345  // ReadSeekerCloser groups io.ReadSeeker and io.Closer.
   346  type ReadSeekerCloser interface {
   347  	io.ReadSeeker
   348  	io.Closer
   349  }
   350  
   351  // NOOPCloser wraps an io.ReadSeeker to trivially turn it into a ReadSeekerCloser.
   352  type NOOPCloser struct {
   353  	io.ReadSeeker
   354  }
   355  
   356  // Close implements io.Closer.
   357  func (c NOOPCloser) Close() error {
   358  	return nil
   359  }
   360  
   361  // FileStore provides methods to store and retrieve files. Files are keyed by an associated
   362  // service and name.
   363  //
   364  // SECURITY NOTES:
   365  //
   366  // Fleetspeak doesn't provide any ACL support for files - all files are readable
   367  // by any client.
   368  //
   369  // Implementations are responsible for validating and/or sanitizing the
   370  // identifiers provided. For example, an implementation backed by a filesystem
   371  // would need to protect against path traversal vulnerabilities.
   372  type FileStore interface {
   373  	// StoreFile stores data into the Filestore, organized by service and name.
   374  	StoreFile(ctx context.Context, service, name string, data io.Reader) error
   375  
   376  	// StatFile returns the modification time of a file previously stored by
   377  	// StoreFile. Returns ErrNotFound if not found.
   378  	StatFile(ctx context.Context, servce, name string) (time.Time, error)
   379  
   380  	// ReadFile returns the data and modification time of file previously
   381  	// stored by StoreFile.  Caller is responsible for closing data.
   382  	//
   383  	// Note: Calls to data are permitted to fail if ctx is canceled or expired.
   384  	ReadFile(ctx context.Context, service, name string) (data ReadSeekerCloser, modtime time.Time, err error)
   385  }