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 }