2016-09-04 05:13:42 +00:00
|
|
|
package kolide
|
|
|
|
|
2017-03-15 15:55:30 +00:00
|
|
|
import (
|
|
|
|
"context"
|
2020-12-15 02:13:34 +00:00
|
|
|
"fmt"
|
|
|
|
"regexp"
|
2018-01-03 19:18:05 +00:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/ghodss/yaml"
|
2018-10-11 19:37:18 +00:00
|
|
|
"github.com/pkg/errors"
|
2017-03-15 15:55:30 +00:00
|
|
|
)
|
2016-09-04 05:13:42 +00:00
|
|
|
|
|
|
|
type QueryStore interface {
|
2018-01-03 19:18:05 +00:00
|
|
|
// ApplyQueries applies a list of queries (likely from a yaml file) to
|
|
|
|
// the datastore. Existing queries are updated, and new queries are
|
|
|
|
// created.
|
|
|
|
ApplyQueries(authorID uint, queries []*Query) error
|
|
|
|
|
2016-12-06 18:22:28 +00:00
|
|
|
// NewQuery creates a new query object in thie datastore. The returned
|
|
|
|
// query should have the ID updated.
|
2017-05-30 19:42:00 +00:00
|
|
|
NewQuery(query *Query, opts ...OptionalArg) (*Query, error)
|
2016-12-06 18:22:28 +00:00
|
|
|
// SaveQuery saves changes to an existing query object.
|
2016-09-04 05:13:42 +00:00
|
|
|
SaveQuery(query *Query) error
|
2018-05-04 18:05:55 +00:00
|
|
|
// DeleteQuery deletes an existing query object.
|
|
|
|
DeleteQuery(name string) error
|
2020-10-22 17:51:26 +00:00
|
|
|
// DeleteQueries deletes the existing query objects with the provided IDs.
|
|
|
|
// The number of deleted queries is returned along with any error.
|
2016-12-09 17:12:45 +00:00
|
|
|
DeleteQueries(ids []uint) (uint, error)
|
2016-12-06 18:22:28 +00:00
|
|
|
// Query returns the query associated with the provided ID. Associated
|
|
|
|
// packs should also be loaded.
|
2016-09-04 05:13:42 +00:00
|
|
|
Query(id uint) (*Query, error)
|
2016-12-06 18:22:28 +00:00
|
|
|
// ListQueries returns a list of queries with the provided sorting and
|
|
|
|
// paging options. Associated packs should also be loaded.
|
2016-10-14 15:59:27 +00:00
|
|
|
ListQueries(opt ListOptions) ([]*Query, error)
|
2018-05-09 23:54:42 +00:00
|
|
|
// QueryByName looks up a query by name.
|
|
|
|
QueryByName(name string, opts ...OptionalArg) (*Query, error)
|
2016-09-04 05:13:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type QueryService interface {
|
2018-01-03 19:18:05 +00:00
|
|
|
// ApplyQuerySpecs applies a list of queries (creating or updating
|
|
|
|
// them as necessary)
|
|
|
|
ApplyQuerySpecs(ctx context.Context, specs []*QuerySpec) error
|
|
|
|
// GetQuerySpecs gets the YAML file representing all the stored queries.
|
|
|
|
GetQuerySpecs(ctx context.Context) ([]*QuerySpec, error)
|
2018-05-08 01:54:29 +00:00
|
|
|
// GetQuerySpec gets the spec for the query with the given name.
|
|
|
|
GetQuerySpec(ctx context.Context, name string) (*QuerySpec, error)
|
2018-01-03 19:18:05 +00:00
|
|
|
|
2016-12-06 18:16:04 +00:00
|
|
|
// ListQueries returns a list of saved queries. Note only saved queries
|
|
|
|
// should be returned (those that are created for distributed queries
|
|
|
|
// but not saved should not be returned).
|
2016-10-13 18:21:47 +00:00
|
|
|
ListQueries(ctx context.Context, opt ListOptions) ([]*Query, error)
|
2016-09-04 05:13:42 +00:00
|
|
|
GetQuery(ctx context.Context, id uint) (*Query, error)
|
|
|
|
NewQuery(ctx context.Context, p QueryPayload) (*Query, error)
|
|
|
|
ModifyQuery(ctx context.Context, id uint, p QueryPayload) (*Query, error)
|
2018-05-04 18:05:55 +00:00
|
|
|
DeleteQuery(ctx context.Context, name string) error
|
2018-06-15 14:13:11 +00:00
|
|
|
// For backwards compatibility with UI
|
|
|
|
DeleteQueryByID(ctx context.Context, id uint) error
|
2020-10-22 17:51:26 +00:00
|
|
|
// DeleteQueries deletes the existing query objects with the provided IDs.
|
|
|
|
// The number of deleted queries is returned along with any error.
|
2016-12-09 17:12:45 +00:00
|
|
|
DeleteQueries(ctx context.Context, ids []uint) (uint, error)
|
2016-09-04 05:13:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type QueryPayload struct {
|
2016-12-13 22:22:05 +00:00
|
|
|
Name *string
|
|
|
|
Description *string
|
|
|
|
Query *string
|
2016-09-04 05:13:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Query struct {
|
2016-11-16 13:47:49 +00:00
|
|
|
UpdateCreateTimestamps
|
2016-12-13 22:22:05 +00:00
|
|
|
ID uint `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
Query string `json:"query"`
|
|
|
|
Saved bool `json:"saved"`
|
2018-10-11 19:37:18 +00:00
|
|
|
AuthorID *uint `json:"author_id" db:"author_id"`
|
2016-12-07 20:22:31 +00:00
|
|
|
// AuthorName is retrieved with a join to the users table in the MySQL
|
|
|
|
// backend (using AuthorID)
|
|
|
|
AuthorName string `json:"author_name" db:"author_name"`
|
2016-12-06 18:22:28 +00:00
|
|
|
// Packs is loaded when retrieving queries, but is stored in a join
|
|
|
|
// table in the MySQL backend.
|
|
|
|
Packs []Pack `json:"packs" db:"-"`
|
2016-09-04 05:13:42 +00:00
|
|
|
}
|
2018-01-03 19:18:05 +00:00
|
|
|
|
2020-12-15 02:13:34 +00:00
|
|
|
var (
|
|
|
|
validateSQLRegexp = regexp.MustCompile(`(?i)attach[^\w]+.*[^\w]+as[^\w]+`)
|
|
|
|
)
|
|
|
|
|
|
|
|
// ValidateSQL performs security validations on the input query. It does not
|
|
|
|
// actually determine whether the query is well formed.
|
|
|
|
func (q Query) ValidateSQL() error {
|
|
|
|
if validateSQLRegexp.MatchString(q.Query) {
|
|
|
|
return fmt.Errorf("ATTACH not allowed in queries")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-01-03 19:18:05 +00:00
|
|
|
const (
|
2020-10-08 17:16:07 +00:00
|
|
|
QueryKind = "query"
|
2018-01-03 19:18:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type QueryObject struct {
|
|
|
|
ObjectMetadata
|
|
|
|
Spec QuerySpec `json:"spec"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type QuerySpec struct {
|
|
|
|
Name string `json:"name"`
|
2018-05-21 16:26:22 +00:00
|
|
|
Description string `json:"description,omitempty"`
|
2018-01-03 19:18:05 +00:00
|
|
|
Query string `json:"query"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func LoadQueriesFromYaml(yml string) ([]*Query, error) {
|
|
|
|
queries := []*Query{}
|
|
|
|
for _, s := range strings.Split(yml, "---") {
|
|
|
|
s = strings.TrimSpace(s)
|
|
|
|
if len(s) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var q QueryObject
|
|
|
|
err := yaml.Unmarshal([]byte(s), &q)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "unmarshal yaml")
|
|
|
|
}
|
|
|
|
queries = append(queries,
|
|
|
|
&Query{Name: q.Spec.Name, Description: q.Spec.Description, Query: q.Spec.Query},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return queries, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func WriteQueriesToYaml(queries []*Query) (string, error) {
|
|
|
|
ymlStrings := []string{}
|
|
|
|
for _, q := range queries {
|
|
|
|
qYaml := QueryObject{
|
|
|
|
ObjectMetadata: ObjectMetadata{
|
|
|
|
ApiVersion: ApiVersion,
|
|
|
|
Kind: QueryKind,
|
|
|
|
},
|
|
|
|
Spec: QuerySpec{
|
|
|
|
Name: q.Name,
|
|
|
|
Description: q.Description,
|
|
|
|
Query: q.Query,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
yml, err := yaml.Marshal(qYaml)
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "marshal YAML")
|
|
|
|
}
|
|
|
|
ymlStrings = append(ymlStrings, string(yml))
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Join(ymlStrings, "---\n"), nil
|
|
|
|
}
|