2016-11-16 13:47:49 +00:00
package mysql
import (
2021-09-14 12:11:07 +00:00
"context"
2016-11-16 13:47:49 +00:00
"database/sql"
2021-11-15 14:11:38 +00:00
"errors"
2016-12-01 17:00:00 +00:00
"fmt"
2021-10-06 17:17:33 +00:00
"os"
2021-05-07 04:05:09 +00:00
"strings"
2016-11-16 13:47:49 +00:00
"time"
2020-12-10 19:04:58 +00:00
"github.com/cenkalti/backoff/v4"
2021-11-12 11:18:25 +00:00
"github.com/doug-martin/goqu/v9"
2021-11-09 14:35:36 +00:00
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
2021-06-26 04:46:51 +00:00
"github.com/fleetdm/fleet/v4/server/fleet"
2020-12-10 19:04:58 +00:00
"github.com/jmoiron/sqlx"
2016-11-16 13:47:49 +00:00
)
2021-06-24 00:32:19 +00:00
var hostSearchColumns = [ ] string { "hostname" , "uuid" , "hardware_serial" , "primary_ip" }
2021-02-18 20:52:43 +00:00
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) NewHost ( ctx context . Context , host * fleet . Host ) ( * fleet . Host , error ) {
2016-11-16 13:47:49 +00:00
sqlStatement := `
INSERT INTO hosts (
2016-12-06 19:51:11 +00:00
osquery_host_id ,
2021-06-24 00:32:19 +00:00
detail_updated_at ,
label_updated_at ,
2021-09-27 19:27:38 +00:00
policy_updated_at ,
2016-11-16 13:47:49 +00:00
node_key ,
2021-06-24 00:32:19 +00:00
hostname ,
2016-11-16 13:47:49 +00:00
uuid ,
platform ,
osquery_version ,
os_version ,
uptime ,
2021-06-24 00:32:19 +00:00
memory ,
2021-05-27 20:18:00 +00:00
team_id
2016-11-16 13:47:49 +00:00
)
2021-11-08 14:42:37 +00:00
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )
2016-11-16 13:47:49 +00:00
`
2021-09-14 14:44:02 +00:00
result , err := d . writer . ExecContext (
ctx ,
2020-04-07 01:10:20 +00:00
sqlStatement ,
host . OsqueryHostID ,
2021-06-24 00:32:19 +00:00
host . DetailUpdatedAt ,
host . LabelUpdatedAt ,
2021-09-27 19:27:38 +00:00
host . PolicyUpdatedAt ,
2020-04-07 01:10:20 +00:00
host . NodeKey ,
2021-06-24 00:32:19 +00:00
host . Hostname ,
2020-04-07 01:10:20 +00:00
host . UUID ,
host . Platform ,
host . OsqueryVersion ,
host . OSVersion ,
host . Uptime ,
2021-06-24 00:32:19 +00:00
host . Memory ,
2021-05-27 20:18:00 +00:00
host . TeamID ,
2020-04-07 01:10:20 +00:00
)
2016-11-16 13:47:49 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "new host" )
2016-11-16 13:47:49 +00:00
}
id , _ := result . LastInsertId ( )
host . ID = uint ( id )
2021-11-08 14:42:37 +00:00
_ , err = d . writer . ExecContext ( ctx , ` INSERT INTO host_seen_times (host_id, seen_time) VALUES (?,?) ` , host . ID , host . SeenTime )
if err != nil {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "new host seen time" )
2021-11-08 14:42:37 +00:00
}
2016-11-16 13:47:49 +00:00
return host , nil
}
2021-11-08 14:42:37 +00:00
func ( d * Datastore ) SerialSaveHost ( ctx context . Context , host * fleet . Host ) error {
errCh := make ( chan error , 1 )
defer close ( errCh )
select {
case <- ctx . Done ( ) :
return ctx . Err ( )
case d . writeCh <- itemToWrite {
ctx : ctx ,
errCh : errCh ,
item : host ,
} :
return <- errCh
}
}
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) SaveHost ( ctx context . Context , host * fleet . Host ) error {
2021-09-14 14:44:02 +00:00
return d . withRetryTxx ( ctx , func ( tx sqlx . ExtContext ) error {
2021-09-08 18:43:22 +00:00
sqlStatement := `
2016-11-16 13:47:49 +00:00
UPDATE hosts SET
2021-06-24 00:32:19 +00:00
detail_updated_at = ? ,
label_updated_at = ? ,
2021-09-27 19:27:38 +00:00
policy_updated_at = ? ,
2016-11-16 13:47:49 +00:00
node_key = ? ,
2021-06-24 00:32:19 +00:00
hostname = ? ,
2016-11-16 13:47:49 +00:00
uuid = ? ,
platform = ? ,
osquery_version = ? ,
os_version = ? ,
uptime = ? ,
2021-06-24 00:32:19 +00:00
memory = ? ,
2016-12-01 17:00:00 +00:00
cpu_type = ? ,
cpu_subtype = ? ,
cpu_brand = ? ,
cpu_physical_cores = ? ,
hardware_vendor = ? ,
hardware_model = ? ,
hardware_version = ? ,
hardware_serial = ? ,
computer_name = ? ,
build = ? ,
platform_like = ? ,
code_name = ? ,
2017-01-04 21:16:17 +00:00
cpu_logical_cores = ? ,
2017-04-06 18:55:24 +00:00
distributed_interval = ? ,
config_tls_refresh = ? ,
2020-05-21 15:36:00 +00:00
logger_tls_period = ? ,
2021-05-31 16:02:05 +00:00
team_id = ? ,
2020-03-11 20:38:44 +00:00
primary_ip = ? ,
2021-05-13 20:09:22 +00:00
primary_mac = ? ,
2021-08-20 17:57:37 +00:00
refetch_requested = ? ,
gigs_disk_space_available = ? ,
percent_disk_space_available = ?
2016-11-16 13:47:49 +00:00
WHERE id = ?
`
2021-09-14 14:44:02 +00:00
_ , err := tx . ExecContext ( ctx , sqlStatement ,
2021-09-08 18:43:22 +00:00
host . DetailUpdatedAt ,
host . LabelUpdatedAt ,
2021-09-27 19:27:38 +00:00
host . PolicyUpdatedAt ,
2021-09-08 18:43:22 +00:00
host . NodeKey ,
host . Hostname ,
host . UUID ,
host . Platform ,
host . OsqueryVersion ,
host . OSVersion ,
host . Uptime ,
host . Memory ,
host . CPUType ,
host . CPUSubtype ,
host . CPUBrand ,
host . CPUPhysicalCores ,
host . HardwareVendor ,
host . HardwareModel ,
host . HardwareVersion ,
host . HardwareSerial ,
host . ComputerName ,
host . Build ,
host . PlatformLike ,
host . CodeName ,
host . CPULogicalCores ,
host . DistributedInterval ,
host . ConfigTLSRefresh ,
host . LoggerTLSPeriod ,
host . TeamID ,
host . PrimaryIP ,
host . PrimaryMac ,
host . RefetchRequested ,
host . GigsDiskSpaceAvailable ,
host . PercentDiskSpaceAvailable ,
host . ID ,
)
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrapf ( ctx , err , "save host with id %d" , host . ID )
2021-05-07 04:05:09 +00:00
}
2021-09-08 18:43:22 +00:00
// Save host pack stats only if it is non-nil. Empty stats should be
// represented by an empty slice.
if host . PackStats != nil {
2021-09-14 14:44:02 +00:00
if err := saveHostPackStatsDB ( ctx , tx , host ) ; err != nil {
2021-09-08 18:43:22 +00:00
return err
}
2021-07-13 20:15:38 +00:00
}
2021-09-18 14:33:36 +00:00
ac , err := d . AppConfig ( ctx )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "failed to get app config to see if we need to update host users and inventory" )
2021-09-18 14:33:36 +00:00
}
2021-10-06 17:17:33 +00:00
softwareInventoryEnabled := os . Getenv ( "FLEET_BETA_SOFTWARE_INVENTORY" ) != "" || ac . HostSettings . EnableSoftwareInventory
if host . HostSoftware . Modified && softwareInventoryEnabled && len ( host . HostSoftware . Software ) > 0 {
2021-09-14 14:44:02 +00:00
if err := saveHostSoftwareDB ( ctx , tx , host ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "failed to save host software" )
2021-09-08 18:43:22 +00:00
}
2021-07-13 20:15:38 +00:00
}
2021-09-08 18:43:22 +00:00
if host . Modified {
2021-10-01 21:27:57 +00:00
if host . Additional != nil {
if err := saveHostAdditionalDB ( ctx , tx , host ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "failed to save host additional" )
2021-10-01 21:27:57 +00:00
}
2021-09-08 18:43:22 +00:00
}
2021-07-13 20:15:38 +00:00
2021-10-01 21:27:57 +00:00
if ac . HostSettings . EnableHostUsers && len ( host . Users ) > 0 {
2021-09-18 14:33:36 +00:00
if err := saveHostUsersDB ( ctx , tx , host ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "failed to save host users" )
2021-09-18 14:33:36 +00:00
}
2021-09-08 18:43:22 +00:00
}
}
2021-07-13 20:15:38 +00:00
2021-09-08 18:43:22 +00:00
host . Modified = false
return nil
} )
2021-05-07 04:05:09 +00:00
}
2021-09-14 14:44:02 +00:00
func saveHostPackStatsDB ( ctx context . Context , db sqlx . ExecerContext , host * fleet . Host ) error {
2021-09-08 18:43:22 +00:00
// Bulk insert software entries
var args [ ] interface { }
queryCount := 0
for _ , pack := range host . PackStats {
for _ , query := range pack . QueryStats {
queryCount ++
args = append ( args ,
query . PackName ,
query . ScheduledQueryName ,
host . ID ,
query . AverageMemory ,
query . Denylisted ,
query . Executions ,
query . Interval ,
query . LastExecuted ,
query . OutputSize ,
query . SystemTime ,
query . UserTime ,
query . WallTime ,
)
2021-05-07 04:05:09 +00:00
}
2021-09-08 18:43:22 +00:00
}
2021-05-07 04:05:09 +00:00
2021-09-08 18:43:22 +00:00
if queryCount == 0 {
return nil
}
2021-05-07 04:05:09 +00:00
2021-09-08 18:43:22 +00:00
values := strings . TrimSuffix ( strings . Repeat ( "((SELECT sq.id FROM scheduled_queries sq JOIN packs p ON (sq.pack_id = p.id) WHERE p.name = ? AND sq.name = ?),?,?,?,?,?,?,?,?,?,?)," , queryCount ) , "," )
sql := fmt . Sprintf ( `
2021-05-07 04:05:09 +00:00
INSERT IGNORE INTO scheduled_query_stats (
scheduled_query_id ,
host_id ,
average_memory ,
denylisted ,
executions ,
schedule_interval ,
last_executed ,
output_size ,
system_time ,
user_time ,
wall_time
)
2021-09-02 20:39:08 +00:00
VALUES % s ON DUPLICATE KEY UPDATE
scheduled_query_id = VALUES ( scheduled_query_id ) ,
host_id = VALUES ( host_id ) ,
average_memory = VALUES ( average_memory ) ,
denylisted = VALUES ( denylisted ) ,
executions = VALUES ( executions ) ,
schedule_interval = VALUES ( schedule_interval ) ,
last_executed = VALUES ( last_executed ) ,
output_size = VALUES ( output_size ) ,
system_time = VALUES ( system_time ) ,
user_time = VALUES ( user_time ) ,
wall_time = VALUES ( wall_time )
2021-05-07 04:05:09 +00:00
` , values )
2021-09-14 14:44:02 +00:00
if _ , err := db . ExecContext ( ctx , sql , args ... ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "insert pack stats" )
2021-05-07 04:05:09 +00:00
}
return nil
}
2021-11-17 22:03:30 +00:00
// schQueriesPlatformFromHost converts the platform from a Host.Platform
// string to a scheduled query platform string.
func schQueryPlatformFromHost ( hostPlatform string ) string {
switch hostPlatform {
case "ubuntu" , "rhel" , "debian" :
return "linux"
default : // darwin, windows
return hostPlatform
}
}
2021-11-12 11:18:25 +00:00
// loadhostPacksStatsDB will load all the pack stats for the given host. The scheduled
// queries that haven't run yet are returned with zero values.
2021-11-17 22:03:30 +00:00
func loadHostPackStatsDB ( ctx context . Context , db sqlx . QueryerContext , hid uint , hostPlatform string ) ( [ ] fleet . PackStats , error ) {
2021-11-12 11:18:25 +00:00
packs , err := listPacksForHost ( ctx , db , hid )
if err != nil {
return nil , ctxerr . Wrapf ( ctx , err , "list packs for host: %d" , hid )
}
if len ( packs ) == 0 {
return nil , nil
}
packIDs := make ( [ ] uint , len ( packs ) )
packTypes := make ( map [ uint ] * string )
for i := range packs {
packIDs [ i ] = packs [ i ] . ID
packTypes [ packs [ i ] . ID ] = packs [ i ] . Type
}
ds := dialect . From ( goqu . I ( "scheduled_queries" ) . As ( "sq" ) ) . Select (
goqu . I ( "sq.name" ) . As ( "scheduled_query_name" ) ,
goqu . I ( "sq.id" ) . As ( "scheduled_query_id" ) ,
goqu . I ( "sq.query_name" ) . As ( "query_name" ) ,
goqu . I ( "q.description" ) . As ( "description" ) ,
goqu . I ( "p.name" ) . As ( "pack_name" ) ,
goqu . I ( "p.id" ) . As ( "pack_id" ) ,
goqu . COALESCE ( goqu . I ( "sqs.average_memory" ) , 0 ) . As ( "average_memory" ) ,
goqu . COALESCE ( goqu . I ( "sqs.denylisted" ) , false ) . As ( "denylisted" ) ,
goqu . COALESCE ( goqu . I ( "sqs.executions" ) , 0 ) . As ( "executions" ) ,
goqu . I ( "sq.interval" ) . As ( "schedule_interval" ) ,
goqu . COALESCE ( goqu . I ( "sqs.last_executed" ) , goqu . L ( "timestamp(0)" ) ) . As ( "last_executed" ) ,
goqu . COALESCE ( goqu . I ( "sqs.output_size" ) , 0 ) . As ( "output_size" ) ,
goqu . COALESCE ( goqu . I ( "sqs.system_time" ) , 0 ) . As ( "system_time" ) ,
goqu . COALESCE ( goqu . I ( "sqs.user_time" ) , 0 ) . As ( "user_time" ) ,
goqu . COALESCE ( goqu . I ( "sqs.wall_time" ) , 0 ) . As ( "wall_time" ) ,
) . Join (
dialect . From ( "packs" ) . As ( "p" ) . Select (
goqu . I ( "id" ) ,
goqu . I ( "name" ) ,
) . Where ( goqu . I ( "id" ) . In ( packIDs ) ) ,
goqu . On ( goqu . I ( "sq.pack_id" ) . Eq ( goqu . I ( "p.id" ) ) ) ,
) . Join (
goqu . I ( "queries" ) . As ( "q" ) ,
goqu . On ( goqu . I ( "sq.query_name" ) . Eq ( goqu . I ( "q.name" ) ) ) ,
) . LeftJoin (
2021-11-17 22:03:30 +00:00
dialect . From ( "scheduled_query_stats" ) . As ( "sqs" ) . Where (
goqu . I ( "host_id" ) . Eq ( hid ) ,
) ,
2021-11-12 11:18:25 +00:00
goqu . On ( goqu . I ( "sqs.scheduled_query_id" ) . Eq ( goqu . I ( "sq.id" ) ) ) ,
2021-11-17 22:03:30 +00:00
) . Where (
goqu . Or (
// sq.platform empty or NULL means the scheduled query is set to
// run on all hosts.
goqu . I ( "sq.platform" ) . Eq ( "" ) ,
goqu . I ( "sq.platform" ) . IsNull ( ) ,
// scheduled_queries.platform can be a comma-separated list of
// platforms, e.g. "darwin,windows".
goqu . L ( "FIND_IN_SET(?, sq.platform)" , schQueryPlatformFromHost ( hostPlatform ) ) . Neq ( 0 ) ,
) ,
2021-11-12 11:18:25 +00:00
)
sql , args , err := ds . ToSQL ( )
if err != nil {
return nil , ctxerr . Wrap ( ctx , err , "sql build" )
}
2021-06-06 22:07:29 +00:00
var stats [ ] fleet . ScheduledQueryStats
2021-11-12 11:18:25 +00:00
if err := sqlx . SelectContext ( ctx , db , & stats , sql , args ... ) ; err != nil {
return nil , ctxerr . Wrap ( ctx , err , "load pack stats" )
2021-05-07 04:05:09 +00:00
}
2021-11-12 11:18:25 +00:00
packStats := map [ uint ] fleet . PackStats { }
2021-05-07 04:05:09 +00:00
for _ , query := range stats {
2021-11-12 11:18:25 +00:00
pack := packStats [ query . PackID ]
2021-05-07 04:05:09 +00:00
pack . PackName = query . PackName
pack . PackID = query . PackID
2021-11-12 11:18:25 +00:00
pack . Type = getPackTypeFromDBField ( packTypes [ pack . PackID ] )
2021-05-07 04:05:09 +00:00
pack . QueryStats = append ( pack . QueryStats , query )
2021-11-12 11:18:25 +00:00
packStats [ pack . PackID ] = pack
2021-05-07 04:05:09 +00:00
}
2021-11-12 11:18:25 +00:00
var ps [ ] fleet . PackStats
for _ , pack := range packStats {
ps = append ( ps , pack )
2021-05-07 04:05:09 +00:00
}
2021-11-12 11:18:25 +00:00
return ps , nil
}
2021-05-07 04:05:09 +00:00
2021-11-12 11:18:25 +00:00
func getPackTypeFromDBField ( t * string ) string {
if t == nil {
return "pack"
}
return * t
2016-11-16 13:47:49 +00:00
}
2021-09-14 14:44:02 +00:00
func loadHostUsersDB ( ctx context . Context , db sqlx . QueryerContext , host * fleet . Host ) error {
2021-11-11 17:26:03 +00:00
sql := ` SELECT username, groupname, uid, user_type, shell FROM host_users WHERE host_id = ? and removed_at IS NULL `
2021-09-14 14:44:02 +00:00
if err := sqlx . SelectContext ( ctx , db , & host . Users , sql , host . ID ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "load host users" )
2021-07-13 20:15:38 +00:00
}
return nil
}
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) DeleteHost ( ctx context . Context , hid uint ) error {
2021-09-20 17:47:06 +00:00
err := d . deleteEntity ( ctx , hostsTable , hid )
2017-01-20 17:22:33 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrapf ( ctx , err , "deleting host with id %d" , hid )
2017-01-20 17:22:33 +00:00
}
2021-11-08 14:42:37 +00:00
_ , err = d . writer . ExecContext ( ctx , ` DELETE FROM host_seen_times WHERE host_id=? ` , hid )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "deleting host seen times" )
2021-11-08 14:42:37 +00:00
}
_ , err = d . writer . ExecContext ( ctx , ` DELETE FROM host_software WHERE host_id=? ` , hid )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "deleting host seen times" )
2021-11-08 14:42:37 +00:00
}
2017-01-20 17:22:33 +00:00
return nil
2016-11-16 13:47:49 +00:00
}
2021-11-18 17:36:35 +00:00
func ( d * Datastore ) Host ( ctx context . Context , id uint , skipLoadingExtras bool ) ( * fleet . Host , error ) {
policiesColumns := ` ,
coalesce ( failing_policies . count , 0 ) as failing_policies_count ,
coalesce ( failing_policies . count , 0 ) as total_issues_count `
policiesJoin := `
JOIN (
SELECT count ( * ) as count FROM policy_membership WHERE passes = 0 AND host_id = ?
) failing_policies `
args := [ ] interface { } { id , id }
if skipLoadingExtras {
policiesColumns = ""
policiesJoin = ""
args = [ ] interface { } { id }
}
sqlStatement := fmt . Sprintf ( `
2021-11-09 14:35:36 +00:00
SELECT
2021-11-08 14:42:37 +00:00
h . * ,
hst . seen_time ,
2021-11-09 14:35:36 +00:00
t . name AS team_name ,
2021-11-18 17:36:35 +00:00
( SELECT additional FROM host_additional WHERE host_id = h . id ) AS additional
% s
2021-10-15 10:34:30 +00:00
FROM hosts h
LEFT JOIN teams t ON ( h . team_id = t . id )
2021-11-08 14:42:37 +00:00
LEFT JOIN host_seen_times hst ON ( h . id = hst . host_id )
2021-11-18 17:36:35 +00:00
% s
2021-05-26 23:24:12 +00:00
WHERE h . id = ?
2021-11-18 17:36:35 +00:00
LIMIT 1 ` , policiesColumns , policiesJoin )
2021-06-06 22:07:29 +00:00
host := & fleet . Host { }
2021-11-18 17:36:35 +00:00
err := sqlx . GetContext ( ctx , d . reader , host , sqlStatement , args ... )
2016-11-16 13:47:49 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "get host by id" )
2021-05-07 04:05:09 +00:00
}
2021-11-12 11:18:25 +00:00
2021-11-17 22:03:30 +00:00
packStats , err := loadHostPackStatsDB ( ctx , d . reader , host . ID , host . Platform )
2021-11-12 11:18:25 +00:00
if err != nil {
2021-05-07 04:05:09 +00:00
return nil , err
2016-11-16 13:47:49 +00:00
}
2021-11-12 11:18:25 +00:00
host . PackStats = packStats
2021-09-14 14:44:02 +00:00
if err := loadHostUsersDB ( ctx , d . reader , host ) ; err != nil {
2021-07-13 20:15:38 +00:00
return nil , err
}
2016-11-16 13:47:49 +00:00
return host , nil
}
2021-09-08 18:43:22 +00:00
func amountEnrolledHostsDB ( db sqlx . Queryer ) ( int , error ) {
2021-07-20 21:39:50 +00:00
var amount int
2021-09-08 18:43:22 +00:00
err := sqlx . Get ( db , & amount , ` SELECT count(*) FROM hosts ` )
2021-07-20 21:39:50 +00:00
if err != nil {
return 0 , err
}
return amount , nil
}
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) ListHosts ( ctx context . Context , filter fleet . TeamFilter , opt fleet . HostListOptions ) ( [ ] * fleet . Host , error ) {
2021-05-18 00:52:59 +00:00
sql := ` SELECT
2021-07-02 15:59:42 +00:00
h . * ,
2021-11-08 14:42:37 +00:00
hst . seen_time ,
2021-11-29 21:04:33 +00:00
t . name AS team_name
`
failingPoliciesSelect := ` ,
2021-10-15 10:34:30 +00:00
coalesce ( failing_policies . count , 0 ) as failing_policies_count ,
coalesce ( failing_policies . count , 0 ) as total_issues_count
2021-11-29 21:04:33 +00:00
`
if opt . DisableFailingPolicies {
failingPoliciesSelect = ""
}
sql += failingPoliciesSelect
Add host additional info filters (#28)
This change adds the ability to filter additional host info via the list hosts endpoint; a continuation from [here](https://github.com/kolide/fleet/pull/2330), but now filtering is accomplished via SQL.
Additional object without filter:
```
curl 'https://localhost:8080/api/v1/kolide/hosts'
...
"additional": {
"macs": [
{
"mac": "00:00:00:00:00:00"
},
{
"mac": "02:42:c0:a8:10:05"
}
],
"time": [
{
"day": "13",
"hour": "3",
"year": "2020",
"month": "10",
"minutes": "43",
"seconds": "11",
"weekday": "Tuesday",
"datetime": "2020-10-13T03:43:11Z",
"iso_8601": "2020-10-13T03:43:11Z",
"timezone": "GMT",
"timestamp": "Tue Oct 13 03:43:11 2020 UTC",
"unix_time": "1602560591",
"local_time": "1602560591",
"local_timezone": "UTC"
}
},
...
```
Additional object with filter:
```
curl 'https://localhost:8080/api/v1/kolide/hosts?additional_info_filters=macs,notreal'
...
"additional": {
"macs": [
{
"mac": "00:00:00:00:00:00"
},
{
"mac": "02:42:c0:a8:10:05"
}
],
"notreal": null
},
...
```
2020-11-14 00:33:25 +00:00
2020-03-30 02:19:54 +00:00
var params [ ] interface { }
Add host additional info filters (#28)
This change adds the ability to filter additional host info via the list hosts endpoint; a continuation from [here](https://github.com/kolide/fleet/pull/2330), but now filtering is accomplished via SQL.
Additional object without filter:
```
curl 'https://localhost:8080/api/v1/kolide/hosts'
...
"additional": {
"macs": [
{
"mac": "00:00:00:00:00:00"
},
{
"mac": "02:42:c0:a8:10:05"
}
],
"time": [
{
"day": "13",
"hour": "3",
"year": "2020",
"month": "10",
"minutes": "43",
"seconds": "11",
"weekday": "Tuesday",
"datetime": "2020-10-13T03:43:11Z",
"iso_8601": "2020-10-13T03:43:11Z",
"timezone": "GMT",
"timestamp": "Tue Oct 13 03:43:11 2020 UTC",
"unix_time": "1602560591",
"local_time": "1602560591",
"local_timezone": "UTC"
}
},
...
```
Additional object with filter:
```
curl 'https://localhost:8080/api/v1/kolide/hosts?additional_info_filters=macs,notreal'
...
"additional": {
"macs": [
{
"mac": "00:00:00:00:00:00"
},
{
"mac": "02:42:c0:a8:10:05"
}
],
"notreal": null
},
...
```
2020-11-14 00:33:25 +00:00
2021-05-26 23:24:12 +00:00
// Only include "additional" if filter provided.
if len ( opt . AdditionalFilters ) == 1 && opt . AdditionalFilters [ 0 ] == "*" {
// All info requested.
sql += `
, ( SELECT additional FROM host_additional WHERE host_id = h . id ) AS additional
`
} else if len ( opt . AdditionalFilters ) > 0 {
// Filter specific columns.
sql += ` , ( SELECT JSON_OBJECT (
Add host additional info filters (#28)
This change adds the ability to filter additional host info via the list hosts endpoint; a continuation from [here](https://github.com/kolide/fleet/pull/2330), but now filtering is accomplished via SQL.
Additional object without filter:
```
curl 'https://localhost:8080/api/v1/kolide/hosts'
...
"additional": {
"macs": [
{
"mac": "00:00:00:00:00:00"
},
{
"mac": "02:42:c0:a8:10:05"
}
],
"time": [
{
"day": "13",
"hour": "3",
"year": "2020",
"month": "10",
"minutes": "43",
"seconds": "11",
"weekday": "Tuesday",
"datetime": "2020-10-13T03:43:11Z",
"iso_8601": "2020-10-13T03:43:11Z",
"timezone": "GMT",
"timestamp": "Tue Oct 13 03:43:11 2020 UTC",
"unix_time": "1602560591",
"local_time": "1602560591",
"local_timezone": "UTC"
}
},
...
```
Additional object with filter:
```
curl 'https://localhost:8080/api/v1/kolide/hosts?additional_info_filters=macs,notreal'
...
"additional": {
"macs": [
{
"mac": "00:00:00:00:00:00"
},
{
"mac": "02:42:c0:a8:10:05"
}
],
"notreal": null
},
...
```
2020-11-14 00:33:25 +00:00
`
for _ , field := range opt . AdditionalFilters {
2021-05-17 17:29:50 +00:00
sql += ` ?, JSON_EXTRACT(additional, ?), `
Add host additional info filters (#28)
This change adds the ability to filter additional host info via the list hosts endpoint; a continuation from [here](https://github.com/kolide/fleet/pull/2330), but now filtering is accomplished via SQL.
Additional object without filter:
```
curl 'https://localhost:8080/api/v1/kolide/hosts'
...
"additional": {
"macs": [
{
"mac": "00:00:00:00:00:00"
},
{
"mac": "02:42:c0:a8:10:05"
}
],
"time": [
{
"day": "13",
"hour": "3",
"year": "2020",
"month": "10",
"minutes": "43",
"seconds": "11",
"weekday": "Tuesday",
"datetime": "2020-10-13T03:43:11Z",
"iso_8601": "2020-10-13T03:43:11Z",
"timezone": "GMT",
"timestamp": "Tue Oct 13 03:43:11 2020 UTC",
"unix_time": "1602560591",
"local_time": "1602560591",
"local_timezone": "UTC"
}
},
...
```
Additional object with filter:
```
curl 'https://localhost:8080/api/v1/kolide/hosts?additional_info_filters=macs,notreal'
...
"additional": {
"macs": [
{
"mac": "00:00:00:00:00:00"
},
{
"mac": "02:42:c0:a8:10:05"
}
],
"notreal": null
},
...
```
2020-11-14 00:33:25 +00:00
params = append ( params , field , fmt . Sprintf ( ` $."%s" ` , field ) )
}
sql = sql [ : len ( sql ) - 2 ]
sql += `
2021-05-26 23:24:12 +00:00
) FROM host_additional WHERE host_id = h . id ) AS additional
Add host additional info filters (#28)
This change adds the ability to filter additional host info via the list hosts endpoint; a continuation from [here](https://github.com/kolide/fleet/pull/2330), but now filtering is accomplished via SQL.
Additional object without filter:
```
curl 'https://localhost:8080/api/v1/kolide/hosts'
...
"additional": {
"macs": [
{
"mac": "00:00:00:00:00:00"
},
{
"mac": "02:42:c0:a8:10:05"
}
],
"time": [
{
"day": "13",
"hour": "3",
"year": "2020",
"month": "10",
"minutes": "43",
"seconds": "11",
"weekday": "Tuesday",
"datetime": "2020-10-13T03:43:11Z",
"iso_8601": "2020-10-13T03:43:11Z",
"timezone": "GMT",
"timestamp": "Tue Oct 13 03:43:11 2020 UTC",
"unix_time": "1602560591",
"local_time": "1602560591",
"local_timezone": "UTC"
}
},
...
```
Additional object with filter:
```
curl 'https://localhost:8080/api/v1/kolide/hosts?additional_info_filters=macs,notreal'
...
"additional": {
"macs": [
{
"mac": "00:00:00:00:00:00"
},
{
"mac": "02:42:c0:a8:10:05"
}
],
"notreal": null
},
...
```
2020-11-14 00:33:25 +00:00
`
}
2021-10-07 11:25:35 +00:00
sql , params = d . applyHostFilters ( opt , sql , filter , params )
hosts := [ ] * fleet . Host { }
if err := sqlx . SelectContext ( ctx , d . reader , & hosts , sql , params ... ) ; err != nil {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "list hosts" )
2021-10-07 11:25:35 +00:00
}
return hosts , nil
}
func ( d * Datastore ) applyHostFilters ( opt fleet . HostListOptions , sql string , filter fleet . TeamFilter , params [ ] interface { } ) ( string , [ ] interface { } ) {
2021-08-24 20:24:52 +00:00
policyMembershipJoin := "JOIN policy_membership pm ON (h.id=pm.host_id)"
if opt . PolicyIDFilter == nil {
policyMembershipJoin = ""
} else if opt . PolicyResponseFilter == nil {
policyMembershipJoin = "LEFT " + policyMembershipJoin
}
2021-10-12 14:38:12 +00:00
softwareFilter := "TRUE"
if opt . SoftwareIDFilter != nil {
softwareFilter = "EXISTS (SELECT 1 FROM host_software hs WHERE hs.host_id=h.id AND hs.software_id=?)"
params = append ( params , opt . SoftwareIDFilter )
}
2021-11-29 21:04:33 +00:00
failingPoliciesJoin := ` LEFT JOIN (
SELECT host_id , count ( * ) as count FROM policy_membership WHERE passes = 0
GROUP BY host_id
) as failing_policies ON ( h . id = failing_policies . host_id ) `
if opt . DisableFailingPolicies {
failingPoliciesJoin = ""
}
2021-11-08 14:42:37 +00:00
sql += fmt . Sprintf ( ` FROM hosts h
LEFT JOIN host_seen_times hst ON ( h . id = hst . host_id )
LEFT JOIN teams t ON ( h . team_id = t . id )
2021-11-29 21:04:33 +00:00
% s
2021-08-24 20:24:52 +00:00
% s
2021-10-12 14:38:12 +00:00
WHERE TRUE AND % s AND % s
2021-11-29 21:04:33 +00:00
` , policyMembershipJoin , failingPoliciesJoin , d . whereFilterHostsByTeams ( filter , "h" ) , softwareFilter ,
2021-06-04 01:53:43 +00:00
)
2021-08-05 17:56:29 +00:00
sql , params = filterHostsByStatus ( sql , opt , params )
2021-08-11 14:40:56 +00:00
sql , params = filterHostsByTeam ( sql , opt , params )
2021-08-24 20:24:52 +00:00
sql , params = filterHostsByPolicy ( sql , opt , params )
2021-08-05 17:56:29 +00:00
sql , params = searchLike ( sql , params , opt . MatchQuery , hostSearchColumns ... )
2021-11-29 18:06:00 +00:00
sql , params = appendListOptionsWithCursorToSQL ( sql , params , opt . ListOptions )
2021-08-05 17:56:29 +00:00
2021-10-07 11:25:35 +00:00
return sql , params
2021-08-05 17:56:29 +00:00
}
2021-08-11 14:40:56 +00:00
func filterHostsByTeam ( sql string , opt fleet . HostListOptions , params [ ] interface { } ) ( string , [ ] interface { } ) {
if opt . TeamFilter != nil {
sql += ` AND h.team_id = ? `
params = append ( params , * opt . TeamFilter )
}
return sql , params
}
2021-08-24 20:24:52 +00:00
func filterHostsByPolicy ( sql string , opt fleet . HostListOptions , params [ ] interface { } ) ( string , [ ] interface { } ) {
if opt . PolicyIDFilter != nil && opt . PolicyResponseFilter != nil {
sql += ` AND pm.policy_id = ? AND pm.passes = ? `
params = append ( params , * opt . PolicyIDFilter , * opt . PolicyResponseFilter )
} else if opt . PolicyIDFilter != nil && opt . PolicyResponseFilter == nil {
sql += ` AND (pm.policy_id = ? OR pm.policy_id IS NULL) AND pm.passes IS NULL `
params = append ( params , * opt . PolicyIDFilter )
}
return sql , params
}
2021-08-05 17:56:29 +00:00
func filterHostsByStatus ( sql string , opt fleet . HostListOptions , params [ ] interface { } ) ( string , [ ] interface { } ) {
2020-03-30 02:19:54 +00:00
switch opt . StatusFilter {
case "new" :
2021-05-18 00:52:59 +00:00
sql += "AND DATE_ADD(h.created_at, INTERVAL 1 DAY) >= ?"
2020-03-30 02:19:54 +00:00
params = append ( params , time . Now ( ) )
case "online" :
2021-11-08 14:42:37 +00:00
sql += fmt . Sprintf ( "AND DATE_ADD(hst.seen_time, INTERVAL LEAST(h.distributed_interval, h.config_tls_refresh) + %d SECOND) > ?" , fleet . OnlineIntervalBuffer )
2020-03-30 02:19:54 +00:00
params = append ( params , time . Now ( ) )
case "offline" :
2021-11-08 14:42:37 +00:00
sql += fmt . Sprintf ( "AND DATE_ADD(hst.seen_time, INTERVAL LEAST(h.distributed_interval, h.config_tls_refresh) + %d SECOND) <= ? AND DATE_ADD(hst.seen_time, INTERVAL 30 DAY) >= ?" , fleet . OnlineIntervalBuffer )
2020-03-30 02:19:54 +00:00
params = append ( params , time . Now ( ) , time . Now ( ) )
case "mia" :
2021-11-08 14:42:37 +00:00
sql += "AND DATE_ADD(hst.seen_time, INTERVAL 30 DAY) <= ?"
2020-03-30 02:19:54 +00:00
params = append ( params , time . Now ( ) )
}
2021-08-05 17:56:29 +00:00
return sql , params
2016-12-06 19:51:11 +00:00
}
2021-10-07 11:25:35 +00:00
func ( d * Datastore ) CountHosts ( ctx context . Context , filter fleet . TeamFilter , opt fleet . HostListOptions ) ( int , error ) {
sql := ` SELECT count(*) `
2021-10-12 14:38:12 +00:00
// ignore pagination in count
opt . Page = 0
opt . PerPage = 0
2021-10-07 11:25:35 +00:00
var params [ ] interface { }
sql , params = d . applyHostFilters ( opt , sql , filter , params )
var count int
if err := sqlx . GetContext ( ctx , d . reader , & count , sql , params ... ) ; err != nil {
2021-11-15 14:11:38 +00:00
return 0 , ctxerr . Wrap ( ctx , err , "count hosts" )
2021-10-07 11:25:35 +00:00
}
return count , nil
}
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) CleanupIncomingHosts ( ctx context . Context , now time . Time ) error {
2019-04-09 18:11:11 +00:00
sqlStatement := `
DELETE FROM hosts
2021-06-24 00:32:19 +00:00
WHERE hostname = ' ' AND osquery_version = ' '
2019-04-09 18:11:11 +00:00
AND created_at < ( ? - INTERVAL 5 MINUTE )
`
2021-09-14 14:44:02 +00:00
if _ , err := d . writer . ExecContext ( ctx , sqlStatement , now ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "cleanup incoming hosts" )
2019-04-09 18:11:11 +00:00
}
return nil
}
2021-11-09 14:35:36 +00:00
func ( d * Datastore ) GenerateHostStatusStatistics ( ctx context . Context , filter fleet . TeamFilter , now time . Time ) ( * fleet . HostSummary , error ) {
2017-04-18 17:39:50 +00:00
// The logic in this function should remain synchronized with
2021-11-09 14:35:36 +00:00
// host.Status and CountHostsInTargets - that is, the intervals associated
// with each status must be the same.
2017-04-18 17:39:50 +00:00
2021-11-09 14:35:36 +00:00
whereClause := d . whereFilterHostsByTeams ( filter , "h" )
2017-04-18 17:39:50 +00:00
sqlStatement := fmt . Sprintf ( `
2021-06-04 01:53:43 +00:00
SELECT
2021-11-09 14:35:36 +00:00
COUNT ( * ) total ,
2021-11-08 14:42:37 +00:00
COALESCE ( SUM ( CASE WHEN DATE_ADD ( hst . seen_time , INTERVAL 30 DAY ) <= ? THEN 1 ELSE 0 END ) , 0 ) mia ,
COALESCE ( SUM ( CASE WHEN DATE_ADD ( hst . seen_time , INTERVAL LEAST ( distributed_interval , config_tls_refresh ) + % d SECOND ) <= ? AND DATE_ADD ( hst . seen_time , INTERVAL 30 DAY ) >= ? THEN 1 ELSE 0 END ) , 0 ) offline ,
COALESCE ( SUM ( CASE WHEN DATE_ADD ( hst . seen_time , INTERVAL LEAST ( distributed_interval , config_tls_refresh ) + % d SECOND ) > ? THEN 1 ELSE 0 END ) , 0 ) online ,
2021-06-04 01:53:43 +00:00
COALESCE ( SUM ( CASE WHEN DATE_ADD ( created_at , INTERVAL 1 DAY ) >= ? THEN 1 ELSE 0 END ) , 0 ) new
2021-11-08 14:42:37 +00:00
FROM hosts h LEFT JOIN host_seen_times hst ON ( h . id = hst . host_id ) WHERE % s
2021-06-04 01:53:43 +00:00
LIMIT 1 ;
2021-11-09 14:35:36 +00:00
` , fleet . OnlineIntervalBuffer , fleet . OnlineIntervalBuffer , whereClause )
2017-01-04 21:16:17 +00:00
2021-11-15 14:56:13 +00:00
summary := fleet . HostSummary { TeamID : filter . TeamID }
2021-11-09 14:35:36 +00:00
err := sqlx . GetContext ( ctx , d . reader , & summary , sqlStatement , now , now , now , now , now )
2017-01-16 19:52:03 +00:00
if err != nil && err != sql . ErrNoRows {
2021-11-09 14:35:36 +00:00
return nil , ctxerr . Wrap ( ctx , err , "generating host statistics" )
}
// get the counts per platform, the `h` alias for hosts is required so that
// reusing the whereClause is ok.
sqlStatement = fmt . Sprintf ( `
SELECT
COUNT ( * ) total ,
h . platform
FROM hosts h
WHERE % s
GROUP BY h . platform
` , whereClause )
var platforms [ ] * fleet . HostSummaryPlatform
err = sqlx . SelectContext ( ctx , d . reader , & platforms , sqlStatement )
if err != nil {
return nil , ctxerr . Wrap ( ctx , err , "generating host platforms statistics" )
2017-01-04 21:16:17 +00:00
}
2021-11-09 14:35:36 +00:00
summary . Platforms = platforms
2017-01-04 21:16:17 +00:00
2021-11-09 14:35:36 +00:00
return & summary , nil
2017-01-04 21:16:17 +00:00
}
2016-11-16 13:47:49 +00:00
// EnrollHost enrolls a host
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) EnrollHost ( ctx context . Context , osqueryHostID , nodeKey string , teamID * uint , cooldown time . Duration ) ( * fleet . Host , error ) {
2016-12-06 19:51:11 +00:00
if osqueryHostID == "" {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . New ( ctx , "missing osquery host identifier" )
2016-11-16 13:47:49 +00:00
}
2016-12-06 19:51:11 +00:00
2021-06-06 22:07:29 +00:00
var host fleet . Host
2021-09-14 14:44:02 +00:00
err := d . withRetryTxx ( ctx , func ( tx sqlx . ExtContext ) error {
2020-12-10 19:04:58 +00:00
zeroTime := time . Unix ( 0 , 0 ) . Add ( 24 * time . Hour )
2016-11-16 13:47:49 +00:00
2020-12-10 19:04:58 +00:00
var id int64
2021-09-14 14:44:02 +00:00
err := sqlx . GetContext ( ctx , tx , & host , ` SELECT id, last_enrolled_at FROM hosts WHERE osquery_host_id = ? ` , osqueryHostID )
2021-01-19 22:45:58 +00:00
switch {
case err != nil && ! errors . Is ( err , sql . ErrNoRows ) :
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "check existing" )
2021-01-19 22:45:58 +00:00
case errors . Is ( err , sql . ErrNoRows ) :
2020-12-10 19:04:58 +00:00
// Create new host record
sqlInsert := `
INSERT INTO hosts (
2021-06-24 00:32:19 +00:00
detail_updated_at ,
label_updated_at ,
2021-09-27 19:27:38 +00:00
policy_updated_at ,
2020-12-10 19:04:58 +00:00
osquery_host_id ,
node_key ,
2021-05-31 16:02:05 +00:00
team_id
2021-11-08 14:42:37 +00:00
) VALUES ( ? , ? , ? , ? , ? , ? )
2020-12-10 19:04:58 +00:00
`
2021-11-08 14:42:37 +00:00
result , err := tx . ExecContext ( ctx , sqlInsert , zeroTime , zeroTime , zeroTime , osqueryHostID , nodeKey , teamID )
2020-12-10 19:04:58 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "insert host" )
2020-12-10 19:04:58 +00:00
}
2016-11-16 13:47:49 +00:00
2020-12-10 19:04:58 +00:00
id , _ = result . LastInsertId ( )
2021-01-19 22:45:58 +00:00
2021-11-08 14:42:37 +00:00
_ , err = d . writer . ExecContext ( ctx , ` INSERT INTO host_seen_times (host_id, seen_time) VALUES (?,?) ` , id , time . Now ( ) . UTC ( ) )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "new host seen time" )
2021-11-08 14:42:37 +00:00
}
2021-01-19 22:45:58 +00:00
default :
2020-12-10 19:04:58 +00:00
// Prevent hosts from enrolling too often with the same identifier.
// Prior to adding this we saw many hosts (probably VMs) with the
// same identifier competing for enrollment and causing perf issues.
2021-06-24 00:32:19 +00:00
if cooldown > 0 && time . Since ( host . LastEnrolledAt ) < cooldown {
2021-11-15 14:11:38 +00:00
return backoff . Permanent ( ctxerr . Errorf ( ctx , "host identified by %s enrolling too often" , osqueryHostID ) )
2020-12-10 19:04:58 +00:00
}
id = int64 ( host . ID )
// Update existing host record
sqlUpdate := `
UPDATE hosts
SET node_key = ? ,
2021-05-31 16:02:05 +00:00
team_id = ? ,
2021-06-24 00:32:19 +00:00
last_enrolled_at = NOW ( )
2020-12-10 19:04:58 +00:00
WHERE osquery_host_id = ?
`
2021-09-14 14:44:02 +00:00
_ , err := tx . ExecContext ( ctx , sqlUpdate , nodeKey , teamID , osqueryHostID )
2020-12-10 19:04:58 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "update host" )
2020-12-10 19:04:58 +00:00
}
}
2020-04-07 01:10:20 +00:00
2020-12-10 19:04:58 +00:00
sqlSelect := `
SELECT * FROM hosts WHERE id = ? LIMIT 1
`
2021-09-14 14:44:02 +00:00
err = sqlx . GetContext ( ctx , tx , & host , sqlSelect , id )
2020-12-10 19:04:58 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "getting the host to return" )
2020-12-10 19:04:58 +00:00
}
2021-09-14 14:44:02 +00:00
_ , err = tx . ExecContext ( ctx , ` INSERT IGNORE INTO label_membership (host_id, label_id) VALUES (?, (SELECT id FROM labels WHERE name = 'All Hosts' AND label_type = 1)) ` , id )
2020-12-10 19:04:58 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "insert new host into all hosts label" )
2020-12-10 19:04:58 +00:00
}
2016-11-16 13:47:49 +00:00
2020-12-10 19:04:58 +00:00
return nil
} )
if err != nil {
return nil , err
}
return & host , nil
2016-11-16 13:47:49 +00:00
}
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) AuthenticateHost ( ctx context . Context , nodeKey string ) ( * fleet . Host , error ) {
2021-03-09 17:01:26 +00:00
// Select everything besides `additional`
2021-10-06 12:27:53 +00:00
sqlStatement := ` SELECT * FROM hosts WHERE node_key = ? LIMIT 1 `
2016-12-06 19:51:11 +00:00
2021-06-06 22:07:29 +00:00
host := & fleet . Host { }
2021-09-14 14:44:02 +00:00
if err := sqlx . GetContext ( ctx , d . reader , host , sqlStatement , nodeKey ) ; err != nil {
2016-11-16 13:47:49 +00:00
switch err {
case sql . ErrNoRows :
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , notFound ( "Host" ) )
2016-11-16 13:47:49 +00:00
default :
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "find host" )
2016-11-16 13:47:49 +00:00
}
}
2016-12-06 19:51:11 +00:00
return host , nil
2016-11-16 13:47:49 +00:00
}
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) MarkHostSeen ( ctx context . Context , host * fleet . Host , t time . Time ) error {
2021-11-08 14:42:37 +00:00
sqlStatement := ` UPDATE host_seen_times SET seen_time = ? WHERE host_id=? `
2016-11-16 23:12:59 +00:00
2021-11-08 14:42:37 +00:00
_ , err := d . writer . ExecContext ( ctx , sqlStatement , t , host . ID )
2016-11-16 23:12:59 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "marking host seen" )
2016-11-16 23:12:59 +00:00
}
host . UpdatedAt = t
return nil
2016-11-16 13:47:49 +00:00
}
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) MarkHostsSeen ( ctx context . Context , hostIDs [ ] uint , t time . Time ) error {
2021-04-12 23:22:22 +00:00
if len ( hostIDs ) == 0 {
return nil
}
2021-09-14 14:44:02 +00:00
if err := d . withRetryTxx ( ctx , func ( tx sqlx . ExtContext ) error {
2021-11-08 14:42:37 +00:00
query := ` UPDATE host_seen_times SET seen_time = ? WHERE host_id IN (?) `
2021-04-12 23:22:22 +00:00
query , args , err := sqlx . In ( query , t , hostIDs )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "sqlx in" )
2021-04-12 23:22:22 +00:00
}
2021-08-13 17:59:31 +00:00
query = tx . Rebind ( query )
2021-09-14 14:44:02 +00:00
if _ , err := tx . ExecContext ( ctx , query , args ... ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "exec update" )
2021-04-12 23:22:22 +00:00
}
return nil
} ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "MarkHostsSeen transaction" )
2021-04-12 23:22:22 +00:00
}
return nil
}
2021-10-21 20:46:21 +00:00
// SearchHosts performs a search on the hosts table using the following criteria:
// - Use the provided team filter.
// - Full-text search with the "query" argument (if query == "", then no fulltext matching is executed).
// Full-text search is used even if "query" is a short or stopword.
// (what defines a short word is the "ft_min_word_len" VARIABLE, set to 4 by default in Fleet deployments).
// - An optional list of IDs to omit from the search.
func ( d * Datastore ) SearchHosts ( ctx context . Context , filter fleet . TeamFilter , query string , omit ... uint ) ( [ ] * fleet . Host , error ) {
var sqlb strings . Builder
2021-11-08 14:42:37 +00:00
sqlb . WriteString ( "SELECT h.*, hst.seen_time FROM hosts h LEFT JOIN host_seen_times hst ON (h.id=hst.host_id) WHERE" )
2016-12-01 17:00:00 +00:00
2021-10-21 20:46:21 +00:00
var args [ ] interface { }
if len ( query ) > 0 {
sqlb . WriteString ( ` (
2021-06-24 00:32:19 +00:00
MATCH ( hostname , uuid ) AGAINST ( ? IN BOOLEAN MODE )
2021-05-25 04:34:08 +00:00
OR MATCH ( primary_ip , primary_mac ) AGAINST ( ? IN BOOLEAN MODE )
2021-10-21 20:46:21 +00:00
) AND ` )
// Transform query argument and append the truncation operator "*" for MATCH.
// From Oracle docs: "If a word is specified with the truncation operator, it is not
// stripped from a boolean query, even if it is too short or a stopword."
hostQuery := transformQueryWithSuffix ( query , "*" )
// Needs quotes to avoid each "." marking a word boundary.
// TODO(lucas): Currently matching the primary_mac doesn't work, see #1959.
ipQuery := ` " ` + query + ` " `
args = append ( args , hostQuery , ipQuery )
2016-11-16 13:47:49 +00:00
}
2017-01-17 14:51:04 +00:00
var in interface { }
2021-10-21 20:46:21 +00:00
// use -1 if there are no values to omit.
// Avoids empty args error for `sqlx.In`
in = omit
if len ( omit ) == 0 {
in = - 1
}
args = append ( args , in )
sqlb . WriteString ( " id NOT IN (?) AND " )
2021-11-10 17:24:19 +00:00
sqlb . WriteString ( d . whereFilterHostsByTeams ( filter , "h" ) )
2021-11-08 14:42:37 +00:00
sqlb . WriteString ( ` ORDER BY hst.seen_time DESC LIMIT 10 ` )
2021-10-21 20:46:21 +00:00
sql , args , err := sqlx . In ( sqlb . String ( ) , args ... )
2017-01-17 14:51:04 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "searching default hosts" )
2017-01-17 14:51:04 +00:00
}
2021-09-01 19:50:52 +00:00
sql = d . reader . Rebind ( sql )
2021-06-06 22:07:29 +00:00
hosts := [ ] * fleet . Host { }
2021-10-21 20:46:21 +00:00
if err := sqlx . SelectContext ( ctx , d . reader , & hosts , sql , args ... ) ; err != nil {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "searching hosts" )
2016-11-16 13:47:49 +00:00
}
return hosts , nil
}
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) HostIDsByName ( ctx context . Context , filter fleet . TeamFilter , hostnames [ ] string ) ( [ ] uint , error ) {
2020-01-24 05:27:20 +00:00
if len ( hostnames ) == 0 {
return [ ] uint { } , nil
}
2021-06-04 01:53:43 +00:00
sqlStatement := fmt . Sprintf ( `
SELECT id FROM hosts
2021-06-24 00:32:19 +00:00
WHERE hostname IN ( ? ) AND % s
2021-06-04 01:53:43 +00:00
` , d . whereFilterHostsByTeams ( filter , "hosts" ) ,
)
2018-05-17 22:54:34 +00:00
sql , args , err := sqlx . In ( sqlStatement , hostnames )
if err != nil {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "building query to get host IDs" )
2018-05-17 22:54:34 +00:00
}
var hostIDs [ ] uint
2021-09-14 14:44:02 +00:00
if err := sqlx . SelectContext ( ctx , d . reader , & hostIDs , sql , args ... ) ; err != nil {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "get host IDs" )
2018-05-17 22:54:34 +00:00
}
return hostIDs , nil
}
2020-04-22 20:54:32 +00:00
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) HostByIdentifier ( ctx context . Context , identifier string ) ( * fleet . Host , error ) {
2020-04-22 20:54:32 +00:00
sql := `
SELECT * FROM hosts
2021-06-24 00:32:19 +00:00
WHERE ? IN ( hostname , osquery_host_id , node_key , uuid )
2020-04-22 20:54:32 +00:00
LIMIT 1
`
2021-06-06 22:07:29 +00:00
host := & fleet . Host { }
2021-09-14 14:44:02 +00:00
err := sqlx . GetContext ( ctx , d . reader , host , sql , identifier )
2020-04-22 20:54:32 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "get host by identifier" )
2020-04-22 20:54:32 +00:00
}
2021-11-17 22:03:30 +00:00
packStats , err := loadHostPackStatsDB ( ctx , d . reader , host . ID , host . Platform )
2021-11-12 11:18:25 +00:00
if err != nil {
2021-05-07 04:05:09 +00:00
return nil , err
}
2021-11-12 11:18:25 +00:00
host . PackStats = packStats
2021-05-07 04:05:09 +00:00
2020-04-22 20:54:32 +00:00
return host , nil
}
2021-05-17 19:23:21 +00:00
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) AddHostsToTeam ( ctx context . Context , teamID * uint , hostIDs [ ] uint ) error {
2021-05-17 19:23:21 +00:00
if len ( hostIDs ) == 0 {
return nil
}
2021-10-05 18:48:26 +00:00
return d . withRetryTxx ( ctx , func ( tx sqlx . ExtContext ) error {
// hosts can only be in one team, so if there's a policy that has a team id and a result from one of our hosts
// it can only be from the previous team they are being transferred from
2021-11-09 14:35:36 +00:00
query , args , err := sqlx . In ( ` DELETE FROM policy_membership_history
2021-10-05 18:48:26 +00:00
WHERE policy_id IN ( SELECT id FROM policies WHERE team_id IS NOT NULL ) AND host_id IN ( ? ) ` , hostIDs )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "add host to team sqlx in" )
2021-10-05 18:48:26 +00:00
}
if _ , err := tx . ExecContext ( ctx , query , args ... ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "exec AddHostsToTeam delete policy membership history" )
2021-10-05 18:48:26 +00:00
}
2021-05-17 19:23:21 +00:00
2021-10-05 18:48:26 +00:00
query , args , err = sqlx . In ( ` UPDATE hosts SET team_id = ? WHERE id IN (?) ` , teamID , hostIDs )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "sqlx.In AddHostsToTeam" )
2021-10-05 18:48:26 +00:00
}
2021-05-17 19:23:21 +00:00
2021-10-05 18:48:26 +00:00
if _ , err := tx . ExecContext ( ctx , query , args ... ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "exec AddHostsToTeam" )
2021-10-05 18:48:26 +00:00
}
return nil
} )
2021-05-17 19:23:21 +00:00
}
2021-05-31 17:56:50 +00:00
2021-09-14 14:44:02 +00:00
func saveHostAdditionalDB ( ctx context . Context , exec sqlx . ExecerContext , host * fleet . Host ) error {
2021-05-26 23:24:12 +00:00
sql := `
INSERT INTO host_additional ( host_id , additional )
VALUES ( ? , ? )
ON DUPLICATE KEY UPDATE additional = VALUES ( additional )
`
2021-09-14 14:44:02 +00:00
if _ , err := exec . ExecContext ( ctx , sql , host . ID , host . Additional ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "insert additional" )
2021-05-26 23:24:12 +00:00
}
2021-05-31 17:56:50 +00:00
2021-05-26 23:24:12 +00:00
return nil
}
2021-07-13 20:15:38 +00:00
2021-09-14 14:44:02 +00:00
func saveHostUsersDB ( ctx context . Context , tx sqlx . ExtContext , host * fleet . Host ) error {
2021-07-13 20:15:38 +00:00
currentHost := & fleet . Host { ID : host . ID }
2021-09-14 14:44:02 +00:00
if err := loadHostUsersDB ( ctx , tx , currentHost ) ; err != nil {
2021-07-13 20:15:38 +00:00
return err
}
2021-09-07 14:02:35 +00:00
keyForUser := func ( u * fleet . HostUser ) string { return fmt . Sprintf ( "%d\x00%s" , u . Uid , u . Username ) }
incomingUsers := make ( map [ string ] bool )
var insertArgs [ ] interface { }
2021-07-13 20:15:38 +00:00
for _ , u := range host . Users {
2021-11-11 17:26:03 +00:00
insertArgs = append ( insertArgs , host . ID , u . Uid , u . Username , u . Type , u . GroupName , u . Shell )
2021-09-07 14:02:35 +00:00
incomingUsers [ keyForUser ( & u ) ] = true
2021-07-13 20:15:38 +00:00
}
var removedArgs [ ] interface { }
for _ , u := range currentHost . Users {
2021-09-07 14:02:35 +00:00
if _ , ok := incomingUsers [ keyForUser ( & u ) ] ; ! ok {
removedArgs = append ( removedArgs , u . Username )
2021-07-13 20:15:38 +00:00
}
}
2021-11-11 17:26:03 +00:00
insertValues := strings . TrimSuffix ( strings . Repeat ( "(?, ?, ?, ?, ?, ?)," , len ( host . Users ) ) , "," )
2021-09-07 14:02:35 +00:00
insertSql := fmt . Sprintf (
2021-11-23 13:23:12 +00:00
` INSERT INTO host_users ( host_id , uid , username , user_type , groupname , shell )
VALUES % s
ON DUPLICATE KEY UPDATE
user_type = VALUES ( user_type ) ,
groupname = VALUES ( groupname ) ,
shell = VALUES ( shell ) ,
removed_at = NULL ` ,
2021-09-07 14:02:35 +00:00
insertValues ,
)
2021-09-14 14:44:02 +00:00
if _ , err := tx . ExecContext ( ctx , insertSql , insertArgs ... ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "insert users" )
2021-09-07 14:02:35 +00:00
}
2021-07-13 20:15:38 +00:00
if len ( removedArgs ) == 0 {
return nil
}
removedValues := strings . TrimSuffix ( strings . Repeat ( "?," , len ( removedArgs ) ) , "," )
removedSql := fmt . Sprintf (
2021-09-07 14:02:35 +00:00
` UPDATE host_users SET removed_at = CURRENT_TIMESTAMP WHERE host_id = ? and username IN (%s) ` ,
2021-07-13 20:15:38 +00:00
removedValues ,
)
2021-09-14 14:44:02 +00:00
if _ , err := tx . ExecContext ( ctx , removedSql , append ( [ ] interface { } { host . ID } , removedArgs ... ) ... ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "mark users as removed" )
2021-07-13 20:15:38 +00:00
}
return nil
}
2021-08-27 14:15:36 +00:00
2021-09-14 12:11:07 +00:00
func ( d * Datastore ) TotalAndUnseenHostsSince ( ctx context . Context , daysCount int ) ( int , int , error ) {
2021-08-27 14:15:36 +00:00
var totalCount , unseenCount int
2021-09-14 14:44:02 +00:00
err := sqlx . GetContext ( ctx , d . reader , & totalCount , "SELECT count(*) FROM hosts" )
2021-08-27 14:15:36 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return 0 , 0 , ctxerr . Wrap ( ctx , err , "getting total host count" )
2021-08-27 14:15:36 +00:00
}
2021-09-14 14:44:02 +00:00
err = sqlx . GetContext ( ctx , d . reader , & unseenCount ,
2021-11-08 14:42:37 +00:00
"SELECT count(*) FROM host_seen_times WHERE DATEDIFF(CURRENT_DATE, seen_time) >= ?" ,
2021-08-27 14:15:36 +00:00
daysCount ,
2021-09-01 19:50:52 +00:00
)
2021-08-27 14:15:36 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return 0 , 0 , ctxerr . Wrap ( ctx , err , "getting unseen host count" )
2021-08-27 14:15:36 +00:00
}
return totalCount , unseenCount , nil
}
2021-09-29 16:13:23 +00:00
func ( d * Datastore ) DeleteHosts ( ctx context . Context , ids [ ] uint ) error {
_ , err := d . deleteEntities ( ctx , hostsTable , ids )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "deleting hosts" )
2021-09-29 16:13:23 +00:00
}
2021-11-08 14:42:37 +00:00
query , args , err := sqlx . In ( ` DELETE FROM host_seen_times WHERE host_id in (?) ` , ids )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrapf ( ctx , err , "building delete host_seen_times query" )
2021-11-08 14:42:37 +00:00
}
_ , err = d . writer . ExecContext ( ctx , query , args ... )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "deleting host seen times" )
2021-11-08 14:42:37 +00:00
}
2021-09-29 16:13:23 +00:00
return nil
}
2021-10-07 11:11:10 +00:00
func ( d * Datastore ) ListPoliciesForHost ( ctx context . Context , hid uint ) ( packs [ ] * fleet . HostPolicy , err error ) {
// instead of using policy_membership, we use the same query but with `where host_id=?` in the subquery
// if we don't do this, the subquery does a full table scan because the where at the end doesn't affect it
2021-11-24 17:16:42 +00:00
query := ` SELECT p . * ,
COALESCE ( u . name , ' < deleted > ' ) AS author_name ,
COALESCE ( u . email , ' ' ) AS author_email ,
2021-10-07 11:11:10 +00:00
CASE
2021-11-09 14:35:36 +00:00
WHEN pm . passes = 1 THEN ' pass '
WHEN pm . passes = 0 THEN ' fail '
ELSE ' '
2021-10-20 15:07:16 +00:00
END AS response ,
2021-10-21 18:53:23 +00:00
coalesce ( p . resolution , ' ' ) as resolution
2021-11-11 11:40:32 +00:00
FROM policies p
LEFT JOIN (
2021-10-07 11:11:10 +00:00
SELECT * FROM policy_membership_history WHERE id IN (
SELECT max ( id ) AS id FROM policy_membership_history WHERE host_id = ? GROUP BY host_id , policy_id
)
2021-11-11 11:40:32 +00:00
) as pm ON ( p . id = pm . policy_id )
2021-11-24 21:17:44 +00:00
LEFT JOIN users u ON p . author_id = u . id
WHERE p . team_id IS NULL OR p . team_id = ( select team_id from hosts WHERE id = ? ) `
2021-10-07 11:11:10 +00:00
var policies [ ] * fleet . HostPolicy
2021-11-24 21:17:44 +00:00
if err := sqlx . SelectContext ( ctx , d . reader , & policies , query , hid , hid ) ; err != nil {
2021-11-15 14:11:38 +00:00
return nil , ctxerr . Wrap ( ctx , err , "get host policies" )
2021-10-07 11:11:10 +00:00
}
return policies , nil
}
2021-10-19 20:47:37 +00:00
func ( d * Datastore ) CleanupExpiredHosts ( ctx context . Context ) error {
ac , err := appConfigDB ( ctx , d . reader )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "getting app config" )
2021-10-19 20:47:37 +00:00
}
if ! ac . HostExpirySettings . HostExpiryEnabled {
return nil
}
2021-11-08 14:42:37 +00:00
// Usual clean up queries used to be like this:
// DELETE FROM hosts WHERE id in (SELECT host_id FROM host_seen_times WHERE seen_time < DATE_SUB(NOW(), INTERVAL ? DAY))
// This means a full table scan for hosts, and for big deployments, that's not ideal
// so instead, we get the ids one by one and delete things one by one
// it might take longer, but it should lock only the row we need
rows , err := d . writer . QueryContext (
ctx ,
` SELECT host_id FROM host_seen_times WHERE seen_time < DATE_SUB(NOW(), INTERVAL ? DAY) ` ,
ac . HostExpirySettings . HostExpiryWindow ,
)
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "getting expired host ids" )
2021-11-08 14:42:37 +00:00
}
defer rows . Close ( )
for rows . Next ( ) {
var id uint
err := rows . Scan ( & id )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "scanning expired host id" )
2021-11-08 14:42:37 +00:00
}
_ , err = d . writer . ExecContext ( ctx , ` DELETE FROM hosts WHERE id = ? ` , id )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "deleting expired hosts" )
2021-11-08 14:42:37 +00:00
}
_ , err = d . writer . ExecContext ( ctx , ` DELETE FROM host_software WHERE host_id = ? ` , id )
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "deleting expired host software" )
2021-11-08 14:42:37 +00:00
}
}
if err := rows . Err ( ) ; err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "expired hosts, row err" )
2021-11-08 14:42:37 +00:00
}
_ , err = d . writer . ExecContext ( ctx , ` DELETE FROM host_seen_times WHERE seen_time < DATE_SUB(NOW(), INTERVAL ? DAY) ` , ac . HostExpirySettings . HostExpiryWindow )
2021-10-19 20:47:37 +00:00
if err != nil {
2021-11-15 14:11:38 +00:00
return ctxerr . Wrap ( ctx , err , "deleting expired host seen times" )
2021-10-19 20:47:37 +00:00
}
return nil
}