fleet/server/service/service_import_config.go
2017-02-01 10:20:50 -07:00

422 lines
12 KiB
Go

package service
import (
"errors"
"strconv"
"strings"
"github.com/kolide/kolide/server/contexts/viewer"
"github.com/kolide/kolide/server/kolide"
"golang.org/x/net/context"
)
func (svc service) ImportConfig(ctx context.Context, cfg *kolide.ImportConfig) (*kolide.ImportConfigResponse, error) {
resp := &kolide.ImportConfigResponse{
ImportStatusBySection: make(map[kolide.ImportSection]*kolide.ImportStatus),
}
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, errors.New("internal error, unable to fetch user")
}
if err := svc.importOptions(cfg.Options, resp); err != nil {
return nil, err
}
if err := svc.importPacks(vc.UserID(), cfg, resp); err != nil {
return nil, err
}
if err := svc.importScheduledQueries(vc.UserID(), cfg, resp); err != nil {
return nil, err
}
if err := svc.importDecorators(cfg, resp); err != nil {
return nil, err
}
if err := svc.importFIMSections(cfg, resp); err != nil {
return nil, err
}
return resp, nil
}
func (svc service) importYARA(cfg *kolide.ImportConfig, resp *kolide.ImportConfigResponse) error {
if cfg.YARA != nil {
for sig, paths := range cfg.YARA.Signatures {
ysg := &kolide.YARASignatureGroup{
SignatureName: sig,
Paths: paths,
}
_, err := svc.ds.NewYARASignatureGroup(ysg)
if err != nil {
return err
}
resp.Status(kolide.YARASigSection).ImportCount++
resp.Status(kolide.YARASigSection).Message("imported '%s'", sig)
}
for section, sigs := range cfg.YARA.FilePaths {
for _, sig := range sigs {
err := svc.ds.NewYARAFilePath(section, sig)
if err != nil {
return err
}
}
resp.Status(kolide.YARAFileSection).ImportCount++
resp.Status(kolide.YARAFileSection).Message("imported '%s'", section)
}
}
return nil
}
func (svc service) importFIMSections(cfg *kolide.ImportConfig, resp *kolide.ImportConfigResponse) error {
if cfg.FileIntegrityMonitoring != nil {
for sectionName, paths := range cfg.FileIntegrityMonitoring {
fp := &kolide.FIMSection{
SectionName: sectionName,
Description: "imported",
Paths: paths,
}
_, err := svc.ds.NewFIMSection(fp)
if err != nil {
return err
}
resp.Status(kolide.FilePathsSection).ImportCount++
resp.Status(kolide.FilePathsSection).Message("imported '%s'", sectionName)
}
}
// this has to happen AFTER fim section, because it requires file paths
return svc.importYARA(cfg, resp)
}
func (svc service) importDecorators(cfg *kolide.ImportConfig, resp *kolide.ImportConfigResponse) error {
if cfg.Decorators != nil {
for _, query := range cfg.Decorators.Load {
decorator := &kolide.Decorator{
Query: query,
Type: kolide.DecoratorLoad,
}
_, err := svc.ds.NewDecorator(decorator)
if err != nil {
return err
}
resp.Status(kolide.DecoratorsSection).ImportCount++
resp.Status(kolide.DecoratorsSection).Message("imported load '%s'", query)
}
for _, query := range cfg.Decorators.Always {
decorator := &kolide.Decorator{
Query: query,
Type: kolide.DecoratorAlways,
}
_, err := svc.ds.NewDecorator(decorator)
if err != nil {
return err
}
resp.Status(kolide.DecoratorsSection).ImportCount++
resp.Status(kolide.DecoratorsSection).Message("imported always '%s'", query)
}
for key, queries := range cfg.Decorators.Interval {
for _, query := range queries {
interval, err := strconv.ParseInt(key, 10, 32)
if err != nil {
return err
}
decorator := &kolide.Decorator{
Query: query,
Type: kolide.DecoratorInterval,
Interval: uint(interval),
}
_, err = svc.ds.NewDecorator(decorator)
if err != nil {
return err
}
resp.Status(kolide.DecoratorsSection).ImportCount++
resp.Status(kolide.DecoratorsSection).Message("imported interval %d '%s'", interval, query)
}
}
}
return nil
}
func (svc service) importScheduledQueries(uid uint, cfg *kolide.ImportConfig, resp *kolide.ImportConfigResponse) error {
_, ok, err := svc.ds.PackByName(kolide.ImportPackName)
if ok {
resp.Status(kolide.PacksSection).Warning(
kolide.PackDuplicate, "skipped '%s' already exists", kolide.ImportPackName,
)
resp.Status(kolide.PacksSection).SkipCount++
return nil
}
// create import pack to hold imported scheduled queries
pack := &kolide.Pack{
Name: kolide.ImportPackName,
Description: "holds imported scheduled queries",
CreatedBy: uid,
Disabled: false,
}
pack, err = svc.ds.NewPack(pack)
if err != nil {
return err
}
resp.Status(kolide.PacksSection).ImportCount++
resp.Status(kolide.PacksSection).Message("created import pack")
for queryName, queryDetails := range cfg.Schedule {
var query *kolide.Query
query, ok, err = svc.ds.QueryByName(queryName)
// if we find the query check to see if the import query matches the
// query we have, if it doesn't skip it
if ok {
if hashQuery("", query.Query) != hashQuery("", queryDetails.Query) {
resp.Status(kolide.PacksSection).Warning(
kolide.DifferentQuerySameName,
"queries named '%s' have different statements and won't be added to '%s'",
queryName,
pack.Name,
)
continue
}
resp.Status(kolide.QueriesSection).Warning(
kolide.QueryDuplicate, "skipped '%s' different query of same name already exists", queryName,
)
resp.Status(kolide.QueriesSection).SkipCount++
} else {
// if query doesn't exist, create it
query = &kolide.Query{
Name: queryName,
Description: "imported",
Query: queryDetails.Query,
Saved: true,
AuthorID: uid,
}
query, err = svc.ds.NewQuery(query)
if err != nil {
return err
}
resp.Status(kolide.QueriesSection).ImportCount++
resp.Status(kolide.QueriesSection).Message(
"imported scheduled query '%s'", query.Name,
)
}
sq := &kolide.ScheduledQuery{
PackID: pack.ID,
QueryID: query.ID,
Interval: queryDetails.Interval,
Snapshot: queryDetails.Snapshot,
Removed: queryDetails.Removed,
Platform: queryDetails.Platform,
Version: queryDetails.Version,
Shard: queryDetails.Shard,
}
_, err = svc.ds.NewScheduledQuery(sq)
if err != nil {
return nil
}
resp.Status(kolide.PacksSection).Message(
"added query '%s' to '%s'", query.Name, pack.Name,
)
}
return nil
}
func (svc service) importPacks(uid uint, cfg *kolide.ImportConfig, resp *kolide.ImportConfigResponse) error {
labelCache := map[string]*kolide.Label{}
packs, err := cfg.CollectPacks()
if err != nil {
return err
}
for packName, packDetails := range packs {
_, ok, err := svc.ds.PackByName(packName)
if err != nil {
return err
}
if ok {
resp.Status(kolide.PacksSection).Warning(
kolide.PackDuplicate, "skipped '%s' already exists", packName,
)
resp.Status(kolide.PacksSection).SkipCount++
continue
}
// import new pack
if packDetails.Shard != nil {
resp.Status(kolide.PacksSection).Warning(
kolide.Unsupported,
"shard for pack '%s'",
packName,
)
}
if packDetails.Version != nil {
resp.Status(kolide.PacksSection).Warning(
kolide.Unsupported,
"version for pack '%s'",
packName,
)
}
pack := &kolide.Pack{
Name: packName,
Description: "Imported pack",
Platform: packDetails.Platform,
}
pack, err = svc.ds.NewPack(pack)
if err != nil {
return err
}
err = svc.createLabelsForPack(pack, &packDetails, labelCache, resp)
if err != nil {
return err
}
err = svc.createQueriesForPack(uid, pack, &packDetails, resp)
if err != nil {
return err
}
resp.Status(kolide.PacksSection).ImportCount++
resp.Status(kolide.PacksSection).Message("imported '%s'", packName)
}
return nil
}
func hashQuery(platform, query string) string {
s := strings.Replace(query, " ", "", -1)
s = strings.Replace(s, "\t", "", -1)
s = strings.Replace(s, "\n", "", -1)
s = strings.Trim(s, ";")
s = platform + s
return strings.ToLower(s)
}
func uniqueImportName() (string, error) {
random, err := kolide.RandomText(12)
if err != nil {
return "", err
}
return "import_" + random, nil
}
func (svc service) createQueriesForPack(uid uint, pack *kolide.Pack, details *kolide.PackDetails,
resp *kolide.ImportConfigResponse) error {
for queryName, queryDetails := range details.Queries {
query, ok, err := svc.ds.QueryByName(queryName)
if err != nil {
return err
}
// if the query isn't already in the database, create it
if !ok {
query = &kolide.Query{
Name: queryName,
Description: "imported",
Query: queryDetails.Query,
Saved: true,
AuthorID: uid,
}
query, err = svc.ds.NewQuery(query)
if err != nil {
return err
}
resp.Status(kolide.QueriesSection).Message(
"created '%s' as part of pack '%s'", queryName, pack.Name,
)
resp.Status(kolide.QueriesSection).ImportCount++
}
// associate query with pack
scheduledQuery := &kolide.ScheduledQuery{
PackID: pack.ID,
QueryID: query.ID,
Interval: queryDetails.Interval,
Platform: queryDetails.Platform,
Snapshot: queryDetails.Snapshot,
Removed: queryDetails.Removed,
Version: queryDetails.Version,
Shard: queryDetails.Shard,
}
_, err = svc.ds.NewScheduledQuery(scheduledQuery)
if err != nil {
return nil
}
resp.Status(kolide.PacksSection).Message("added query '%s'", query.Name)
}
return nil
}
// createLabelsForPack Iterates through discover queries, creates a label for
// each query and assigns it to the pack passed as an argument. Once a Label is created we cache
// it for reuse.
func (svc service) createLabelsForPack(pack *kolide.Pack, details *kolide.PackDetails,
cache map[string]*kolide.Label, resp *kolide.ImportConfigResponse) error {
for _, query := range details.Discovery {
hash := hashQuery(details.Platform, query)
label, ok := cache[hash]
// add existing label to pack
if ok {
err := svc.ds.AddLabelToPack(label.ID, pack.ID)
if err != nil {
return err
}
resp.Status(kolide.PacksSection).Message(
"added label '%s' to pack '%s'", label.Name, pack.Name,
)
continue
}
// create new label and add it to pack
labelName, err := uniqueImportName()
if err != nil {
return err
}
label = &kolide.Label{
Name: labelName,
Query: query,
Description: "imported",
LabelType: kolide.LabelTypeRegular,
Platform: details.Platform,
}
label, err = svc.ds.NewLabel(label)
if err != nil {
return err
}
// hang on to label so we can reuse it for other packs if needed
cache[hash] = label
err = svc.ds.AddLabelToPack(label.ID, pack.ID)
if err != nil {
return err
}
resp.Status(kolide.PacksSection).Message(
"added label '%s' to '%s'", label.Name, pack.Name,
)
}
return nil
}
func (svc service) importOptions(opts kolide.OptionNameToValueMap, resp *kolide.ImportConfigResponse) error {
var updateOptions []kolide.Option
for optName, optValue := range opts {
opt, err := svc.ds.OptionByName(optName)
if err != nil {
resp.Status(kolide.OptionsSection).Warning(
kolide.OptionUnknown, "skipped '%s' can't find option", optName,
)
resp.Status(kolide.OptionsSection).SkipCount++
continue
}
if opt.ReadOnly {
resp.Status(kolide.OptionsSection).Warning(
kolide.OptionReadonly, "skipped '%s' can't change read only option", optName,
)
resp.Status(kolide.OptionsSection).SkipCount++
continue
}
if opt.OptionSet() {
resp.Status(kolide.OptionsSection).Warning(
kolide.OptionAlreadySet, "skipped '%s' can't change option that is already set", optName,
)
resp.Status(kolide.OptionsSection).SkipCount++
continue
}
opt.SetValue(optValue)
resp.Status(kolide.OptionsSection).Message("set %s value to %v", optName, optValue)
resp.Status(kolide.OptionsSection).ImportCount++
updateOptions = append(updateOptions, *opt)
}
if len(updateOptions) > 0 {
if err := svc.ds.SaveOptions(updateOptions); err != nil {
return err
}
}
return nil
}