Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ee/agent-smith/pkg/config/config.go
2501 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package config
6
7
import (
8
"encoding/json"
9
"fmt"
10
"io/ioutil"
11
"strings"
12
"time"
13
14
"github.com/gitpod-io/gitpod/agent-smith/pkg/classifier"
15
"github.com/gitpod-io/gitpod/agent-smith/pkg/common"
16
"golang.org/x/xerrors"
17
)
18
19
func GetConfig(cfgFile string) (*ServiceConfig, error) {
20
if cfgFile == "" {
21
return nil, xerrors.Errorf("missing --config")
22
}
23
24
fc, err := ioutil.ReadFile(cfgFile)
25
if err != nil {
26
return nil, xerrors.Errorf("cannot read config: %v", err)
27
}
28
29
var cfg ServiceConfig
30
err = json.Unmarshal(fc, &cfg)
31
if err != nil {
32
return nil, xerrors.Errorf("cannot unmarshal config: %v", err)
33
}
34
35
if cfg.ProbePath == "" {
36
cfg.ProbePath = "/app/probe.o"
37
}
38
39
return &cfg, nil
40
}
41
42
// ServiceConfig is the struct holding the configuration for agent-smith
43
// if you are considering changing this struct, remember
44
// to update the config schema using:
45
// $ go run main.go config-schema > config-schema.json
46
// And also update the examples accordingly.
47
type ServiceConfig struct {
48
Config
49
50
Namespace string `json:"namespace,omitempty"`
51
52
PProfAddr string `json:"pprofAddr,omitempty"`
53
PrometheusAddr string `json:"prometheusAddr,omitempty"`
54
55
// We have had memory leak issues with agent smith in the past due to experimental gRPC use.
56
// This upper limit causes agent smith to stop itself should it go above this limit.
57
MaxSysMemMib uint64 `json:"systemMemoryLimitMib,omitempty"`
58
}
59
60
type Enforcement struct {
61
Default *EnforcementRules `json:"default,omitempty"`
62
PerRepo map[string]EnforcementRules `json:"perRepo,omitempty"`
63
CPULimitPenalty string `json:"cpuLimitPenalty,omitempty"`
64
}
65
66
// EnforcementRules matches a infringement with a particular penalty
67
type EnforcementRules map[GradedInfringementKind]PenaltyKind
68
69
// Validate returns an error if the enforcement rules are invalid for some reason
70
func (er EnforcementRules) Validate() error {
71
for k := range er {
72
if _, err := k.Kind(); err != nil {
73
return xerrors.Errorf("%s: %w", k, err)
74
}
75
}
76
77
validPenalties := map[PenaltyKind]struct{}{
78
PenaltyLimitCPU: {},
79
PenaltyNone: {},
80
PenaltyStopWorkspace: {},
81
PenaltyStopWorkspaceAndBlockUser: {},
82
}
83
for _, v := range er {
84
if _, ok := validPenalties[v]; !ok {
85
return xerrors.Errorf("%s: unknown penalty", v)
86
}
87
}
88
89
return nil
90
}
91
92
// InfringementKind describes the kind of infringement
93
type InfringementKind string
94
95
const (
96
// InfringementExec means a user executed a blocklisted executable
97
InfringementExec InfringementKind = "blocklisted executable"
98
)
99
100
// PenaltyKind describes a kind of penalty for a violating workspace
101
type PenaltyKind string
102
103
const (
104
// PenaltyNone means there's no penalty for a particular infringement
105
PenaltyNone PenaltyKind = ""
106
// PenaltyStopWorkspace stops a workspace hard
107
PenaltyStopWorkspace PenaltyKind = "stop workspace"
108
// PenaltyLimitCPU permanently limits the CPU a workspace can use
109
PenaltyLimitCPU PenaltyKind = "limit CPU"
110
// PenaltyLimitCPU permanently limits the CPU a workspace can use
111
PenaltyStopWorkspaceAndBlockUser PenaltyKind = "stop workspace and block user"
112
)
113
114
// GradedInfringementKind is a combination of infringement kind and severity
115
type GradedInfringementKind string
116
117
// GradeKind produces a graded infringement kind from severity and kind
118
func GradeKind(kind InfringementKind, severity common.Severity) GradedInfringementKind {
119
if len(severity) == 0 {
120
return GradedInfringementKind(kind)
121
}
122
return GradedInfringementKind(fmt.Sprintf("%s %s", severity, kind))
123
}
124
125
// Severity returns the severity of the graded infringement kind
126
func (g GradedInfringementKind) Severity() common.Severity {
127
for _, pfx := range []common.Severity{common.SeverityBarely, common.SeverityVery} {
128
if strings.HasPrefix(string(g), string(pfx)) {
129
return pfx
130
}
131
}
132
133
return common.SeverityAudit
134
}
135
136
// Kind returns the infringement kind
137
func (g GradedInfringementKind) Kind() (InfringementKind, error) {
138
wopfx := strings.TrimSpace(strings.TrimPrefix(string(g), string(g.Severity())))
139
140
validKinds := []InfringementKind{
141
InfringementExec,
142
}
143
for _, k := range validKinds {
144
if string(k) == wopfx {
145
return k, nil
146
}
147
}
148
149
return "", xerrors.Errorf("unknown kind")
150
}
151
152
type ExcessiveCPUCheck struct {
153
Threshold float32 `json:"threshold"`
154
AverageOver int `json:"averageOverMinutes"`
155
}
156
157
type GitpodAPI struct {
158
HostURL string `json:"hostURL"`
159
APIToken string `json:"apiToken"`
160
}
161
162
type Kubernetes struct {
163
Enabled bool `json:"enabled"`
164
Kubeconfig string `json:"kubeconfig,omitempty"`
165
}
166
167
// Config configures Agent Smith
168
type Config struct {
169
WorkspaceManager WorkspaceManagerConfig `json:"wsman"`
170
GitpodAPI GitpodAPI `json:"gitpodAPI"`
171
KubernetesNamespace string `json:"namespace"`
172
173
Blocklists *Blocklists `json:"blocklists,omitempty"`
174
175
Enforcement Enforcement `json:"enforcement,omitempty"`
176
ExcessiveCPUCheck *ExcessiveCPUCheck `json:"excessiveCPUCheck,omitempty"`
177
Kubernetes Kubernetes `json:"kubernetes"`
178
FilesystemScanning *FilesystemScanning `json:"filesystemScanning,omitempty"`
179
180
ProbePath string `json:"probePath,omitempty"`
181
}
182
183
type TLS struct {
184
Authority string `json:"ca"`
185
Certificate string `json:"crt"`
186
PrivateKey string `json:"key"`
187
}
188
189
type WorkspaceManagerConfig struct {
190
Address string `json:"address"`
191
TLS TLS `json:"tls,omitempty"`
192
}
193
194
// FilesystemScanning configures filesystem signature scanning
195
type FilesystemScanning struct {
196
Enabled bool `json:"enabled"`
197
ScanInterval Duration `json:"scanInterval"`
198
MaxFileSize int64 `json:"maxFileSize"`
199
WorkingArea string `json:"workingArea"`
200
}
201
202
// Duration wraps time.Duration to provide JSON marshaling/unmarshaling
203
type Duration struct {
204
time.Duration
205
}
206
207
// UnmarshalJSON implements json.Unmarshaler interface
208
func (d *Duration) UnmarshalJSON(data []byte) error {
209
var s string
210
if err := json.Unmarshal(data, &s); err != nil {
211
return err
212
}
213
214
duration, err := time.ParseDuration(s)
215
if err != nil {
216
return err
217
}
218
219
d.Duration = duration
220
return nil
221
}
222
223
// MarshalJSON implements json.Marshaler interface
224
func (d Duration) MarshalJSON() ([]byte, error) {
225
return json.Marshal(d.Duration.String())
226
}
227
228
// Slackwebhooks holds slack notification configuration for different levels of penalty severity
229
type SlackWebhooks struct {
230
Audit string `json:"audit,omitempty"`
231
Warning string `json:"warning,omitempty"`
232
}
233
234
// Blocklists list s/signature blocklists for various levels of infringement
235
type Blocklists struct {
236
Barely *PerLevelBlocklist `json:"barely,omitempty"`
237
Audit *PerLevelBlocklist `json:"audit,omitempty"`
238
Very *PerLevelBlocklist `json:"very,omitempty"`
239
}
240
241
func (b *Blocklists) Classifier() (res classifier.ProcessClassifier, err error) {
242
defer func() {
243
if res == nil {
244
return
245
}
246
res = classifier.NewCountingMetricsClassifier("all", res)
247
}()
248
249
if b == nil {
250
return classifier.NewCommandlineClassifier("empty", classifier.LevelAudit, nil, nil)
251
}
252
253
gres := make(classifier.GradedClassifier)
254
for level, bl := range b.Levels() {
255
lvl := classifier.Level(level)
256
gres[lvl], err = bl.Classifier(string(level), lvl)
257
if err != nil {
258
return nil, err
259
}
260
}
261
return gres, nil
262
}
263
264
// FileClassifier creates a classifier specifically for filesystem scanning
265
// This extracts only filesystem signatures from all blocklist levels and creates
266
// a clean classifier without any CountingMetricsClassifier wrapper
267
func (b *Blocklists) FileClassifier() (classifier.FileClassifier, error) {
268
if b == nil {
269
// Return a classifier with no signatures - will match nothing
270
return classifier.NewSignatureMatchClassifier("filesystem-empty", classifier.LevelAudit, nil), nil
271
}
272
273
// Collect all filesystem signatures from all levels
274
var allFilesystemSignatures []*classifier.Signature
275
276
for _, bl := range b.Levels() {
277
if bl == nil || bl.Signatures == nil {
278
continue
279
}
280
281
for _, sig := range bl.Signatures {
282
if sig.Domain == classifier.DomainFileSystem {
283
fsSig := &classifier.Signature{
284
Name: sig.Name,
285
Domain: sig.Domain,
286
Pattern: sig.Pattern,
287
Filename: sig.Filename,
288
Regexp: sig.Regexp,
289
}
290
allFilesystemSignatures = append(allFilesystemSignatures, fsSig)
291
}
292
}
293
}
294
295
// Create a single SignatureMatchClassifier with all filesystem signatures
296
// Use LevelAudit as default - individual signatures can still have their own severity
297
return classifier.NewSignatureMatchClassifier("filesystem", classifier.LevelAudit, allFilesystemSignatures), nil
298
}
299
300
func (b *Blocklists) Levels() map[common.Severity]*PerLevelBlocklist {
301
res := make(map[common.Severity]*PerLevelBlocklist)
302
if b.Barely != nil {
303
res[common.SeverityBarely] = b.Barely
304
}
305
if b.Audit != nil {
306
res[common.SeverityAudit] = b.Audit
307
}
308
if b.Very != nil {
309
res[common.SeverityVery] = b.Very
310
}
311
return res
312
}
313
314
// AllowList configures a list of commands that should not be blocked.
315
// The command could be the full path to the executable or a regular expression
316
type AllowList struct {
317
Commands []string `json:"commands,omitempty"`
318
}
319
320
// PerLevelBlocklist lists blacklists for level of infringement
321
type PerLevelBlocklist struct {
322
Binaries []string `json:"binaries,omitempty"`
323
AllowList []string `json:"allowlist,omitempty"`
324
Signatures []*classifier.Signature `json:"signatures,omitempty"`
325
}
326
327
func (p *PerLevelBlocklist) Classifier(name string, level classifier.Level) (classifier.ProcessClassifier, error) {
328
if p == nil {
329
return classifier.CompositeClassifier{}, nil
330
}
331
332
cmdl, err := classifier.NewCommandlineClassifier(name, level, p.AllowList, p.Binaries)
333
if err != nil {
334
return nil, err
335
}
336
cmdlc := classifier.NewCountingMetricsClassifier("cmd_"+name, cmdl)
337
338
sigsc := classifier.NewCountingMetricsClassifier("sig_"+name,
339
classifier.NewSignatureMatchClassifier(name, level, p.Signatures),
340
)
341
342
return classifier.CompositeClassifier{cmdlc, sigsc}, nil
343
}
344
345