mirror of
https://github.com/empayre/fleet.git
synced 2024-11-08 01:38:57 +00:00
c89cd370d5
This adds the option to set up an S3 bucket as the storage backend for file carving (partially solving #111). It works by using the multipart upload capabilities of S3 to maintain compatibility with the "upload in blocks" protocol that osquery uses. It does this basically replacing the carve_blocks table while still maintaining the metadata in the original place (it would probably be possible to rely completely on S3 by using object tagging at the cost of listing performance). To make this pluggable, I created a new field in the service struct dedicated to the CarveStore which, if no configuration for S3 is set up will be just a reference to the standard datastore, otherwise it will point to the S3 one (effectively this separation will allow in the future to add more backends).
262 lines
5.3 KiB
Go
262 lines
5.3 KiB
Go
package mysql
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/server/kolide"
|
|
"github.com/jmoiron/sqlx"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func (d *Datastore) NewCarve(metadata *kolide.CarveMetadata) (*kolide.CarveMetadata, error) {
|
|
stmt := `INSERT INTO carve_metadata (
|
|
host_id,
|
|
created_at,
|
|
name,
|
|
block_count,
|
|
block_size,
|
|
carve_size,
|
|
carve_id,
|
|
request_id,
|
|
session_id
|
|
) VALUES (
|
|
?,
|
|
?,
|
|
?,
|
|
?,
|
|
?,
|
|
?,
|
|
?,
|
|
?,
|
|
?
|
|
)`
|
|
|
|
result, err := d.db.Exec(
|
|
stmt,
|
|
metadata.HostId,
|
|
metadata.CreatedAt.Format(mySQLTimestampFormat),
|
|
metadata.Name,
|
|
metadata.BlockCount,
|
|
metadata.BlockSize,
|
|
metadata.CarveSize,
|
|
metadata.CarveId,
|
|
metadata.RequestId,
|
|
metadata.SessionId,
|
|
)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "insert carve metadata")
|
|
}
|
|
|
|
id, _ := result.LastInsertId()
|
|
metadata.ID = id
|
|
|
|
return metadata, nil
|
|
}
|
|
|
|
// UpdateCarve updates the carve metadata in database
|
|
// Only max_block and expired are updatable
|
|
func (d *Datastore) UpdateCarve(metadata *kolide.CarveMetadata) error {
|
|
stmt := `
|
|
UPDATE carve_metadata SET
|
|
max_block = ?,
|
|
expired = ?
|
|
WHERE id = ?
|
|
`
|
|
_, err := d.db.Exec(
|
|
stmt,
|
|
metadata.MaxBlock,
|
|
metadata.Expired,
|
|
metadata.ID,
|
|
)
|
|
if err != nil {
|
|
return errors.Wrap(err, "update carve metadata")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Datastore) CleanupCarves(now time.Time) (int, error) {
|
|
var countExpired int
|
|
err := d.withRetryTxx(func(tx *sqlx.Tx) error {
|
|
// Get IDs of carves to expire
|
|
stmt := `
|
|
SELECT id
|
|
FROM carve_metadata
|
|
WHERE expired = 0 AND created_at < (? - INTERVAL 24 HOUR)
|
|
LIMIT 50000
|
|
`
|
|
var expiredCarves []int64
|
|
if err := tx.Select(&expiredCarves, stmt, now); err != nil {
|
|
return errors.Wrap(err, "get expired carves")
|
|
}
|
|
|
|
countExpired = len(expiredCarves)
|
|
|
|
if len(expiredCarves) == 0 {
|
|
// Nothing to do
|
|
return nil
|
|
}
|
|
|
|
// Delete carve block data
|
|
stmt = `
|
|
DELETE FROM carve_blocks
|
|
WHERE metadata_id IN (?)
|
|
`
|
|
stmt, args, err := sqlx.In(stmt, expiredCarves)
|
|
if err != nil {
|
|
return errors.Wrap(err, "IN for DELETE FROM carve_blocks")
|
|
}
|
|
stmt = tx.Rebind(stmt)
|
|
if _, err := d.db.Exec(stmt, args...); err != nil {
|
|
return errors.Wrap(err, "delete carve blocks")
|
|
}
|
|
|
|
// Mark metadata expired
|
|
stmt = `
|
|
UPDATE carve_metadata
|
|
SET expired = 1
|
|
WHERE id IN (?)
|
|
`
|
|
stmt, args, err = sqlx.In(stmt, expiredCarves)
|
|
if err != nil {
|
|
return errors.Wrap(err, "IN for UPDATE carve_metadata")
|
|
}
|
|
stmt = tx.Rebind(stmt)
|
|
if _, err := d.db.Exec(stmt, args...); err != nil {
|
|
return errors.Wrap(err, "update carve_metadtata")
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return countExpired, nil
|
|
|
|
}
|
|
|
|
// Selecting max_block should be very efficient because MySQL is able to use
|
|
// the index metadata and optimizes away the SELECT.
|
|
const carveSelectFields = `
|
|
id,
|
|
host_id,
|
|
created_at,
|
|
name,
|
|
block_count,
|
|
block_size,
|
|
carve_size,
|
|
carve_id,
|
|
request_id,
|
|
session_id,
|
|
expired,
|
|
max_block
|
|
`
|
|
|
|
func (d *Datastore) Carve(carveId int64) (*kolide.CarveMetadata, error) {
|
|
stmt := fmt.Sprintf(`
|
|
SELECT %s
|
|
FROM carve_metadata
|
|
WHERE id = ?`,
|
|
carveSelectFields,
|
|
)
|
|
|
|
var metadata kolide.CarveMetadata
|
|
if err := d.db.Get(&metadata, stmt, carveId); err != nil {
|
|
return nil, errors.Wrap(err, "get carve by ID")
|
|
}
|
|
|
|
return &metadata, nil
|
|
}
|
|
|
|
func (d *Datastore) CarveBySessionId(sessionId string) (*kolide.CarveMetadata, error) {
|
|
stmt := fmt.Sprintf(`
|
|
SELECT %s
|
|
FROM carve_metadata
|
|
WHERE session_id = ?`,
|
|
carveSelectFields,
|
|
)
|
|
|
|
var metadata kolide.CarveMetadata
|
|
if err := d.db.Get(&metadata, stmt, sessionId); err != nil {
|
|
return nil, errors.Wrap(err, "get carve by session ID")
|
|
}
|
|
|
|
return &metadata, nil
|
|
}
|
|
|
|
func (d *Datastore) CarveByName(name string) (*kolide.CarveMetadata, error) {
|
|
stmt := fmt.Sprintf(`
|
|
SELECT %s
|
|
FROM carve_metadata
|
|
WHERE name = ?`,
|
|
carveSelectFields,
|
|
)
|
|
|
|
var metadata kolide.CarveMetadata
|
|
if err := d.db.Get(&metadata, stmt, name); err != nil {
|
|
return nil, errors.Wrap(err, "get carve by name")
|
|
}
|
|
|
|
return &metadata, nil
|
|
}
|
|
|
|
func (d *Datastore) ListCarves(opt kolide.CarveListOptions) ([]*kolide.CarveMetadata, error) {
|
|
stmt := fmt.Sprintf(`
|
|
SELECT %s
|
|
FROM carve_metadata`,
|
|
carveSelectFields,
|
|
)
|
|
if !opt.Expired {
|
|
stmt += ` WHERE NOT expired `
|
|
}
|
|
stmt = appendListOptionsToSQL(stmt, opt.ListOptions)
|
|
carves := []*kolide.CarveMetadata{}
|
|
if err := d.db.Select(&carves, stmt); err != nil && err != sql.ErrNoRows {
|
|
return nil, errors.Wrap(err, "list carves")
|
|
}
|
|
|
|
return carves, nil
|
|
}
|
|
|
|
func (d *Datastore) NewBlock(metadata *kolide.CarveMetadata, blockId int64, data []byte) error {
|
|
stmt := `
|
|
INSERT INTO carve_blocks (
|
|
metadata_id,
|
|
block_id,
|
|
data
|
|
) VALUES (
|
|
?,
|
|
?,
|
|
?
|
|
)`
|
|
if _, err := d.db.Exec(stmt, metadata.ID, blockId, data); err != nil {
|
|
return errors.Wrap(err, "insert carve block")
|
|
}
|
|
|
|
if metadata.MaxBlock < blockId {
|
|
// Update max_block
|
|
metadata.MaxBlock = blockId
|
|
if err := d.UpdateCarve(metadata); err != nil {
|
|
return errors.Wrap(err, "insert carve block")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Datastore) GetBlock(metadata *kolide.CarveMetadata, blockId int64) ([]byte, error) {
|
|
stmt := `
|
|
SELECT data
|
|
FROM carve_blocks
|
|
WHERE metadata_id = ? AND block_id = ?
|
|
`
|
|
var data []byte
|
|
if err := d.db.Get(&data, stmt, metadata.ID, blockId); err != nil {
|
|
return nil, errors.Wrap(err, "select data")
|
|
}
|
|
|
|
return data, nil
|
|
}
|