fleet/server/kolide/import_config.go
2017-12-21 18:37:32 -08:00

239 lines
8.3 KiB
Go

package kolide
import (
"bytes"
"context"
"errors"
"fmt"
"strconv"
"strings"
)
type ImportConfigService interface {
// ImportConfig create packs, queries, options etc based on imported
// osquery configuration.
ImportConfig(ctx context.Context, cfg *ImportConfig) (*ImportConfigResponse, error)
}
// ImportSection is used to categorize information associated with the import
// of a particular section of an imported osquery configuration file.
type ImportSection string
const (
OptionsSection ImportSection = "options"
PacksSection = "packs"
QueriesSection = "queries"
DecoratorsSection = "decorators"
FilePathsSection = "file_paths"
YARASigSection = "yara_signature_group"
YARAFileSection = "yara_file_group"
)
// WarningType is used to group associated warnings for options, packs etc
// when importing on osquery configuration file.
type WarningType string
const (
PackDuplicate WarningType = "duplicate_pack"
DifferentQuerySameName = "different_query_same_name"
OptionAlreadySet = "option_already_set"
OptionReadonly = "option_readonly"
OptionUnknown = "option_unknown"
QueryDuplicate = "duplicate_query"
FIMDuplicate = "duplicate_fim"
YARADuplicate = "duplicate_yara"
Unsupported = "unsupported"
)
// ImportStatus contains information pertaining to the import of a section
// of an osquery configuration file.
type ImportStatus struct {
// Title human readable name of the section of the import file that this
// status pertains to.
Title string `json:"title"`
// ImportCount count of items successfully imported.
ImportCount int `json:"import_count"`
// SkipCount count of items that are skipped. The reasons for the omissions
// can be found in Warnings.
SkipCount int `json:"skip_count"`
// Warnings groups categories of warnings with one or more detail messages.
Warnings map[WarningType][]string `json:"warnings"`
// Messages contains an entry for each import attempt.
Messages []string `json:"messages"`
}
// Warning is used to add a warning message to ImportStatus.
func (is *ImportStatus) Warning(warnType WarningType, fmtMsg string, fmtArgs ...interface{}) {
is.Warnings[warnType] = append(is.Warnings[warnType], fmt.Sprintf(fmtMsg, fmtArgs...))
}
// Message is used to add a general message to ImportStatus, usually indicating
// what was changed in a successful import.
func (is *ImportStatus) Message(fmtMsg string, args ...interface{}) {
is.Messages = append(is.Messages, fmt.Sprintf(fmtMsg, args...))
}
// ImportConfigResponse contains information about the import of an osquery
// configuration file.
type ImportConfigResponse struct {
ImportStatusBySection map[ImportSection]*ImportStatus `json:"import_status"`
}
// Status returns a structure that contains information about the import
// of a particular section of an osquery configuration file.
func (ic *ImportConfigResponse) Status(section ImportSection) (status *ImportStatus) {
var ok bool
if status, ok = ic.ImportStatusBySection[section]; !ok {
status = new(ImportStatus)
status.Title = strings.Title(string(section))
status.Warnings = make(map[WarningType][]string)
ic.ImportStatusBySection[section] = status
}
return status
}
const (
GlobPacks = "*"
// ImportPackName is a custom pack name used for a pack we create to
// hold imported scheduled queries.
ImportPackName = "imported"
)
// QueryDetails represents the query objects used in the packs and the
// schedule section of an osquery configuration.
type QueryDetails struct {
Query string `json:"query"`
Interval OsQueryConfigInt `json:"interval"`
// Optional fields
Removed *bool `json:"removed"`
Platform *string `json:"platform"`
Version *string `json:"version"`
Shard *OsQueryConfigInt `json:"shard"`
Snapshot *bool `json:"snapshot"`
}
// PackDetails represents the "packs" section of an osquery configuration
// file.
type PackDetails struct {
Queries QueryNameToQueryDetailsMap `json:"queries"`
Shard *OsQueryConfigInt `json:"shard"`
Version *string `json:"version"`
Platform string `json:"platform"`
Discovery []string `json:"discovery"`
}
// YARAConfig yara configuration maps keys to lists of files.
// See https://osquery.readthedocs.io/en/stable/deployment/yara/
type YARAConfig struct {
Signatures map[string][]string `json:"signatures"`
FilePaths map[string][]string `json:"file_paths"`
}
// Decorator section of osquery config each section contains rows of decorator
// queries.
type DecoratorConfig struct {
Load []string `json:"load"`
Always []string `json:"always"`
/*
Interval maps a string representation of a numeric interval to a set
of decorator queries.
{
"interval": {
"3600": [
"SELECT total_seconds FROM uptime;"
]
}
}
*/
Interval map[string][]string `json:"interval"`
}
type OptionNameToValueMap map[string]interface{}
type QueryNameToQueryDetailsMap map[string]QueryDetails
type PackNameMap map[string]interface{}
type FIMCategoryToPaths map[string][]string
type PackNameToPackDetails map[string]PackDetails
// ImportConfig is a representation of an Osquery configuration. Osquery
// documentation has further details.
// See https://osquery.readthedocs.io/en/stable/deployment/configuration/
type ImportConfig struct {
// DryRun if true an import will be attempted, and if successful will be completely rolled back
DryRun bool
// Options is a map of option name to a value which can be an int,
// bool, or string.
Options OptionNameToValueMap `json:"options"`
// Schedule is a map of query names to details
Schedule QueryNameToQueryDetailsMap `json:"schedule"`
// Packs is a map of pack names to either PackDetails, or a string
// containing a file path with a pack config. If a string, we expect
// PackDetails to be stored in ExternalPacks.
Packs PackNameMap `json:"packs"`
// FileIntegrityMonitoring file integrity monitoring information.
// See https://osquery.readthedocs.io/en/stable/deployment/file-integrity-monitoring/
FileIntegrityMonitoring FIMCategoryToPaths `json:"file_paths"`
// YARA configuration
YARA *YARAConfig `json:"yara"`
Decorators *DecoratorConfig `json:"decorators"`
// ExternalPacks are packs referenced when an item in the Packs map references
// an external file. The PackName here must match the PackName in the Packs map.
ExternalPacks PackNameToPackDetails `json:"-"`
// GlobPackNames lists pack names that are globbed.
GlobPackNames []string `json:"glob"`
}
func (ic *ImportConfig) fetchGlobPacks(packs *PackNameToPackDetails) error {
for _, packName := range ic.GlobPackNames {
pack, ok := ic.ExternalPacks[packName]
if !ok {
return fmt.Errorf("glob pack '%s' details not found", packName)
}
(*packs)[packName] = pack
}
return nil
}
// CollectPacks consolidates packs, globbed packs and external packs.
func (ic *ImportConfig) CollectPacks() (PackNameToPackDetails, error) {
result := make(PackNameToPackDetails)
for packName, packContent := range ic.Packs {
// special case handling for Globbed packs
if packName == GlobPacks {
if err := ic.fetchGlobPacks(&result); err != nil {
return nil, err
}
continue
}
// content can either be a file path, in which case we expect to find
// pack in ExternalPacks, or pack details
switch content := packContent.(type) {
case string:
pack, ok := ic.ExternalPacks[packName]
if !ok {
return nil, fmt.Errorf("external pack '%s' details not found", packName)
}
result[packName] = pack
case PackDetails:
result[packName] = content
default:
return nil, errors.New("unexpected pack content")
}
}
return result, nil
}
// OsQueryConfigInt is provided becase integers in the osquery config file may
// be represented as strings in the json. If we know a particular field is
// supposed to be an Integer, we convert from string to int if we can.
type OsQueryConfigInt uint
func (c *OsQueryConfigInt) UnmarshalJSON(b []byte) error {
stripped := bytes.Trim(b, `"`)
v, err := strconv.ParseUint(string(stripped), 10, 64)
if err != nil {
return err
}
*c = OsQueryConfigInt(v)
return nil
}