/
kits.go
482 lines (440 loc) · 13.7 KB
/
kits.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
/*************************************************************************
* Copyright 2021 Gravwell, Inc. All rights reserved.
* Contact: <legal@gravwell.io>
*
* This software may be modified and distributed under the terms of the
* BSD 2-clause license. See the LICENSE file for details.
**************************************************************************/
package types
import (
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"math/rand"
"strings"
"time"
"unicode"
"github.com/buger/jsonparser"
"github.com/google/uuid"
)
const (
kitIdBase string = `io.gravwell.user.`
kitIdRandLen int = 8
)
type KitConfigMacro struct {
MacroName string // The name of the macro which will be created
Description string // a verbose description of what this *does*
DefaultValue string // Should be defined at kit creation time
Value string // Set by the UI when preparing for installation
Type string // "TAG" or "OTHER"
InstalledByID string // if the macro already exists, the ID of the kit that installed it
}
// KitConfig represents rules, labels, and other configuration options used
// during kit installation.
type KitConfig struct {
OverwriteExisting bool `json:",omitempty"`
Global bool `json:",omitempty"`
AllowExternalResource bool `json:",omitempty"`
AllowUnsigned bool `json:",omitempty"`
InstallationGroup int32 `json:",omitempty"` // deprecated, use InstallationGroups instead
InstallationGroups []int32
InstallationWriteAccess Access
Labels []string `json:",omitempty"` // labels applied to each *item*
KitLabels []string `json:",omitempty"` // labels applied to the *kit* itself
ConfigMacros []KitConfigMacro
ScriptDeployRules map[string]ScriptDeployConfig // overrides for defaults
}
// Each item in a kit (dashboard, query, etc) is represented by a KitItem
// object.
type KitItem struct {
Name string
Type string
ID string `json:",omitempty"` //the UUID
AdditionalInfo json.RawMessage `json:",omitempty"`
Hash [sha256.Size]byte
}
// SourcedKitItem is wraps a KitItem with additional information regarding the
// kit's version and origin.
type SourcedKitItem struct {
KitItem
KitID string
KitVersion uint
KitName string
}
// The kit data type that is actually stored in the datastore
type KitState struct {
ID string
Name string
Description string
Readme string
UUID string
Signed bool
AdminRequired bool
MinVersion CanonicalVersion `json:",omitempty"`
MaxVersion CanonicalVersion `json:",omitempty"`
UID int32 `json:",omitempty"`
Version uint
Items []KitItem
Labels []string
Icon string //use for icon when in the context of a kit
Banner string //use for banner in a kit
Cover string //use for cover image on a kit
ModifiedItems []SourcedKitItem // Items which were installed by a previous version of the kit and have been modified by the user
ConflictingItems []KitItem // items which will overwrite a user-created object
RequiredDependencies []KitMetadata
Installed bool //true means everything was pushed in, false means it is JUST staged
InstallationTime time.Time // the time at which this kit was installed
InstallationVersion CanonicalVersion // the Gravwell version in use when this kit was installed
ConfigMacros []KitConfigMacro
Metadata json.RawMessage `json:",omitempty"`
}
// the type that handles the datastore system
type KitManifest struct {
UID int32
GIDs []int32
Global bool
WriteAccess Access
UUID uuid.UUID
Data []byte
WebserverID uuid.UUID // which webserver created this manifest: needed to manage staged manifests & on-disk kit files
Synced bool
}
// type that is used when sending back lists via a ADMIN request (show uid and gid)
type IdKitState struct {
UUID uuid.UUID
UID int32
GIDs []int32
Global bool
WriteAccess Access
KitState
}
type KitEmbeddedItem struct {
KitItem
Content []byte `json:",omitempty"` //the actual contents of the kit
}
// the type that is used to request a kit be built
type KitBuildRequest struct {
ID string
Name string
Description string
Readme string
Version uint
MinVersion CanonicalVersion `json:",omitempty"`
MaxVersion CanonicalVersion `json:",omitempty"`
Dashboards []uint64 `json:",omitempty"`
Templates []uuid.UUID `json:",omitempty"`
Pivots []uuid.UUID `json:",omitempty"`
Resources []string `json:",omitempty"`
ScheduledSearches []int32 `json:",omitempty"`
Flows []int32 `json:",omitempty"`
Macros []uint64 `json:",omitempty"`
Extractors []uuid.UUID `json:",omitempty"`
Files []uuid.UUID `json:",omitempty"`
SearchLibraries []uuid.UUID `json:",omitempty"`
Playbooks []uuid.UUID `json:",omitempty"`
Alerts []uuid.UUID `json:",omitempty"`
EmbeddedItems []KitEmbeddedItem `json:",omitempty"`
Icon string `json:",omitempty"`
Banner string `json:",omitempty"`
Cover string `json:",omitempty"`
Dependencies []KitDependency `json:",omitempty"`
ConfigMacros []KitConfigMacro
ScriptDeployRules map[int32]ScriptDeployConfig
}
// this is what we store in the datastore
type StoredBuildRequest struct {
UID int32
KitBuildRequest
BuildDate time.Time
}
type KitBuildResponse struct {
UUID string
Size int64
UID int32 `json:",omitempty"`
}
func (pm *KitManifest) Encode(v interface{}) (err error) {
pm.Data, err = json.Marshal(v)
return
}
func (pm *KitManifest) Decode(v interface{}) (err error) {
err = json.Unmarshal(pm.Data, v)
return
}
func (ps *KitState) UpdateItem(name, tp, id string) error {
for i := range ps.Items {
if ps.Items[i].Name == name && ps.Items[i].Type == tp {
ps.Items[i].ID = id
return nil
}
}
return errors.New("not found")
}
func (ps *KitState) AddItem(itm KitItem) error {
for i := range ps.Items {
if ps.Items[i].Name == itm.Name && ps.Items[i].Type == itm.Type {
return errors.New("already exists")
}
}
ps.Items = append(ps.Items, itm)
return nil
}
func (ps *KitState) GetItem(name, tp string) (KitItem, error) {
for i := range ps.Items {
if ps.Items[i].Name == name && ps.Items[i].Type == tp {
return ps.Items[i], nil
}
}
return KitItem{}, errors.New("not found")
}
func (ps *KitState) RemoveItem(name, tp string) error {
for i := range ps.Items {
if ps.Items[i].Name == name && ps.Items[i].Type == tp {
ps.Items = append(ps.Items[:i], ps.Items[i+1:]...)
return nil
}
}
return errors.New("not found")
}
func (pbr *KitBuildRequest) validateReferencedFile(val, name string) error {
guid, err := uuid.Parse(val)
if err != nil {
return fmt.Errorf("Invalid %s ID: %v", name, err)
}
//iterate through the files and make sure the file exists
var ok bool
for _, v := range pbr.Files {
if v == guid {
ok = true
break
}
}
if !ok {
return fmt.Errorf("The %s file ID %s is not included in the kit", name, guid)
}
return nil
}
func (pbr *KitBuildRequest) Validate() error {
if pbr.ID = strings.TrimSpace(pbr.ID); len(pbr.ID) == 0 {
pbr.ID = randKitId()
}
if !isLetterNumberPeriod(pbr.ID) {
return errors.New("Invalid ID")
}
if pbr.Name = strings.TrimSpace(pbr.Name); len(pbr.Name) == 0 {
return errors.New("empty Name")
}
if pbr.Version == 0 {
pbr.Version = 1
}
for i := range pbr.Dashboards {
if pbr.Dashboards[i] == 0 {
return errors.New("zero value dashboard id")
}
}
for i := range pbr.Resources {
pbr.Resources[i] = strings.TrimSpace(pbr.Resources[i]) //clean it
//attempt to parse it
if _, err := uuid.Parse(pbr.Resources[i]); err != nil {
return err
}
}
for i := range pbr.ScheduledSearches {
if pbr.ScheduledSearches[i] <= 0 {
return fmt.Errorf("Invalid scheduled search/script ID %d", pbr.ScheduledSearches[i])
}
}
for i := range pbr.Flows {
if pbr.Flows[i] <= 0 {
return fmt.Errorf("Invalid flow ID %d", pbr.Flows[i])
}
}
for i := range pbr.Macros {
if pbr.Macros[i] == 0 {
return errors.New("Invalid macro ID")
}
}
for i := range pbr.Templates {
if pbr.Templates[i] == uuid.Nil {
return errors.New("Zero UUID in templates list")
}
}
for i := range pbr.Pivots {
if pbr.Pivots[i] == uuid.Nil {
return errors.New("Zero UUID in pivots list")
}
}
for i := range pbr.Files {
if pbr.Files[i] == uuid.Nil {
return errors.New("Zero UUID in file list")
}
}
for i := range pbr.Playbooks {
if pbr.Playbooks[i] == uuid.Nil {
return errors.New("Zero UUID in playbook list")
}
}
for i := range pbr.Alerts {
if pbr.Alerts[i] == uuid.Nil {
return errors.New("Zero UUID in alert list")
}
}
if pbr.Icon != `` {
if err := pbr.validateReferencedFile(pbr.Icon, `Icon`); err != nil {
return err
}
}
if pbr.Banner != `` {
if err := pbr.validateReferencedFile(pbr.Banner, `Banner`); err != nil {
return err
}
}
if pbr.Cover != `` {
if err := pbr.validateReferencedFile(pbr.Cover, `Cover`); err != nil {
return err
}
}
idMp := map[KitDependency]es{}
for _, dp := range pbr.Dependencies {
if _, ok := idMp[dp]; ok {
return fmt.Errorf("Dependency %s %d is duplicated", dp.ID, dp.MinVersion)
}
idMp[dp] = empty
}
for _, emb := range pbr.EmbeddedItems {
if len(emb.Name) == 0 {
return errors.New("Missing name on embedded item")
} else if len(emb.Type) == 0 {
return errors.New("Embedded item must have a type")
} else if len(emb.Content) == 0 {
return errors.New("Embedded content items must not be empty")
}
}
kitItemCount := len(pbr.Dashboards) + len(pbr.Templates) + len(pbr.Pivots) + len(pbr.Resources) + len(pbr.ScheduledSearches) + len(pbr.Flows) + len(pbr.Macros) + len(pbr.Extractors) + len(pbr.Files) + len(pbr.SearchLibraries) + len(pbr.Playbooks) + len(pbr.Alerts)
if kitItemCount == 0 {
return errors.New("Build request does not contain any items")
}
return nil
}
func isLetterNumberPeriod(s string) bool {
for _, r := range s {
if !unicode.IsLetter(r) && !unicode.IsNumber(r) && r != '.' {
return false
}
}
return true
}
func randKitId() string {
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890")
b := make([]rune, kitIdRandLen)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := range b {
b[i] = letterRunes[r.Intn(len(letterRunes))]
}
return kitIdBase + string(b)
}
func (ki *KitItem) DescriptionString() (s string) {
//check if there is a Desc fild in the raw json
var err error
if s, err = jsonparser.GetString([]byte(ki.AdditionalInfo), `Description`); err != nil {
s = ``
}
return
}
func (ki KitItem) String() (r string) {
r = fmt.Sprintf("%s %s %s", ki.ID, ki.Type, ki.Name)
if v := ki.DescriptionString(); len(v) != 0 {
r += ` ` + v
}
return
}
// KitDependency declares a series of kits and minimum version requirements
type KitDependency struct {
ID string
MinVersion uint
}
// KitMetadata is a struct that is primarily served by the
// kit server, we use this to record info about a kit so the GUI
// and hint to users what kits they shoudld install.
type KitMetadata struct {
ID string
Name string
GUID string `json:",omitempty"` // DEPRECATED, TODO: remove
UUID string
Version uint
Description string
Readme string
Signed bool
AdminRequired bool
MinVersion CanonicalVersion
MaxVersion CanonicalVersion
Size int64
Created time.Time
Ingesters []string //ingesters associated with the kit
Tags []string //tags associated with the kit
Assets []KitMetadataAsset
Dependencies []KitDependency
Items []KitItem
ConfigMacros []KitConfigMacro
}
// KitMetadataAssets are items that might be associated with kits when hosting them
// we use these to enable pinning additional stuff to a kit.
type KitMetadataAsset struct {
Type string
Source string //URL
Legend string //some description about the asset
Featured bool //should be an image, will be used for cover image
Banner bool //should be an image, will be used for upper banner image
}
func (kma KitMetadataAsset) String() (s string) {
if kma.Featured {
s = `* `
}
s += fmt.Sprintf("%s (%s) %s", kma.Type, kma.Source, kma.Legend)
return
}
type InstallStatus struct {
Owner int32
Done bool
itemCount int
itemsDone int
Percentage float64
CurrentStep string
Error string
Log string
InstallID int32
Updated time.Time
}
func NewInstallStatus(itemcount int, installID int32, uid int32) *InstallStatus {
return &InstallStatus{itemCount: itemcount, Updated: time.Now(), InstallID: installID, Owner: uid}
}
func (i *InstallStatus) SetDone() {
i.Updated = time.Now()
i.Done = true
}
func (i *InstallStatus) ItemDone() {
i.Updated = time.Now()
if i.itemsDone < i.itemCount {
i.itemsDone++
}
i.Percentage = float64(i.itemsDone) / float64(i.itemCount)
}
func (i *InstallStatus) UpdateCurrentStep(step string) {
i.Updated = time.Now()
i.CurrentStep = step
i.Log = fmt.Sprintf("%v\n%v", i.Log, step)
}
func (i *InstallStatus) SetError(err error) {
i.Updated = time.Now()
i.Log = fmt.Sprintf("%v\n%v", i.Log, err)
i.Error = err.Error()
i.Done = true
}
type KitItemStatus struct {
Item KitItem
Error string
}
type KitModifyReport struct {
Statuses []KitItemStatus
WasError bool
}