Path: blob/main/components/ee/agent-smith/pkg/config/config.go
2501 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package config56import (7"encoding/json"8"fmt"9"io/ioutil"10"strings"11"time"1213"github.com/gitpod-io/gitpod/agent-smith/pkg/classifier"14"github.com/gitpod-io/gitpod/agent-smith/pkg/common"15"golang.org/x/xerrors"16)1718func GetConfig(cfgFile string) (*ServiceConfig, error) {19if cfgFile == "" {20return nil, xerrors.Errorf("missing --config")21}2223fc, err := ioutil.ReadFile(cfgFile)24if err != nil {25return nil, xerrors.Errorf("cannot read config: %v", err)26}2728var cfg ServiceConfig29err = json.Unmarshal(fc, &cfg)30if err != nil {31return nil, xerrors.Errorf("cannot unmarshal config: %v", err)32}3334if cfg.ProbePath == "" {35cfg.ProbePath = "/app/probe.o"36}3738return &cfg, nil39}4041// ServiceConfig is the struct holding the configuration for agent-smith42// if you are considering changing this struct, remember43// to update the config schema using:44// $ go run main.go config-schema > config-schema.json45// And also update the examples accordingly.46type ServiceConfig struct {47Config4849Namespace string `json:"namespace,omitempty"`5051PProfAddr string `json:"pprofAddr,omitempty"`52PrometheusAddr string `json:"prometheusAddr,omitempty"`5354// We have had memory leak issues with agent smith in the past due to experimental gRPC use.55// This upper limit causes agent smith to stop itself should it go above this limit.56MaxSysMemMib uint64 `json:"systemMemoryLimitMib,omitempty"`57}5859type Enforcement struct {60Default *EnforcementRules `json:"default,omitempty"`61PerRepo map[string]EnforcementRules `json:"perRepo,omitempty"`62CPULimitPenalty string `json:"cpuLimitPenalty,omitempty"`63}6465// EnforcementRules matches a infringement with a particular penalty66type EnforcementRules map[GradedInfringementKind]PenaltyKind6768// Validate returns an error if the enforcement rules are invalid for some reason69func (er EnforcementRules) Validate() error {70for k := range er {71if _, err := k.Kind(); err != nil {72return xerrors.Errorf("%s: %w", k, err)73}74}7576validPenalties := map[PenaltyKind]struct{}{77PenaltyLimitCPU: {},78PenaltyNone: {},79PenaltyStopWorkspace: {},80PenaltyStopWorkspaceAndBlockUser: {},81}82for _, v := range er {83if _, ok := validPenalties[v]; !ok {84return xerrors.Errorf("%s: unknown penalty", v)85}86}8788return nil89}9091// InfringementKind describes the kind of infringement92type InfringementKind string9394const (95// InfringementExec means a user executed a blocklisted executable96InfringementExec InfringementKind = "blocklisted executable"97)9899// PenaltyKind describes a kind of penalty for a violating workspace100type PenaltyKind string101102const (103// PenaltyNone means there's no penalty for a particular infringement104PenaltyNone PenaltyKind = ""105// PenaltyStopWorkspace stops a workspace hard106PenaltyStopWorkspace PenaltyKind = "stop workspace"107// PenaltyLimitCPU permanently limits the CPU a workspace can use108PenaltyLimitCPU PenaltyKind = "limit CPU"109// PenaltyLimitCPU permanently limits the CPU a workspace can use110PenaltyStopWorkspaceAndBlockUser PenaltyKind = "stop workspace and block user"111)112113// GradedInfringementKind is a combination of infringement kind and severity114type GradedInfringementKind string115116// GradeKind produces a graded infringement kind from severity and kind117func GradeKind(kind InfringementKind, severity common.Severity) GradedInfringementKind {118if len(severity) == 0 {119return GradedInfringementKind(kind)120}121return GradedInfringementKind(fmt.Sprintf("%s %s", severity, kind))122}123124// Severity returns the severity of the graded infringement kind125func (g GradedInfringementKind) Severity() common.Severity {126for _, pfx := range []common.Severity{common.SeverityBarely, common.SeverityVery} {127if strings.HasPrefix(string(g), string(pfx)) {128return pfx129}130}131132return common.SeverityAudit133}134135// Kind returns the infringement kind136func (g GradedInfringementKind) Kind() (InfringementKind, error) {137wopfx := strings.TrimSpace(strings.TrimPrefix(string(g), string(g.Severity())))138139validKinds := []InfringementKind{140InfringementExec,141}142for _, k := range validKinds {143if string(k) == wopfx {144return k, nil145}146}147148return "", xerrors.Errorf("unknown kind")149}150151type ExcessiveCPUCheck struct {152Threshold float32 `json:"threshold"`153AverageOver int `json:"averageOverMinutes"`154}155156type GitpodAPI struct {157HostURL string `json:"hostURL"`158APIToken string `json:"apiToken"`159}160161type Kubernetes struct {162Enabled bool `json:"enabled"`163Kubeconfig string `json:"kubeconfig,omitempty"`164}165166// Config configures Agent Smith167type Config struct {168WorkspaceManager WorkspaceManagerConfig `json:"wsman"`169GitpodAPI GitpodAPI `json:"gitpodAPI"`170KubernetesNamespace string `json:"namespace"`171172Blocklists *Blocklists `json:"blocklists,omitempty"`173174Enforcement Enforcement `json:"enforcement,omitempty"`175ExcessiveCPUCheck *ExcessiveCPUCheck `json:"excessiveCPUCheck,omitempty"`176Kubernetes Kubernetes `json:"kubernetes"`177FilesystemScanning *FilesystemScanning `json:"filesystemScanning,omitempty"`178179ProbePath string `json:"probePath,omitempty"`180}181182type TLS struct {183Authority string `json:"ca"`184Certificate string `json:"crt"`185PrivateKey string `json:"key"`186}187188type WorkspaceManagerConfig struct {189Address string `json:"address"`190TLS TLS `json:"tls,omitempty"`191}192193// FilesystemScanning configures filesystem signature scanning194type FilesystemScanning struct {195Enabled bool `json:"enabled"`196ScanInterval Duration `json:"scanInterval"`197MaxFileSize int64 `json:"maxFileSize"`198WorkingArea string `json:"workingArea"`199}200201// Duration wraps time.Duration to provide JSON marshaling/unmarshaling202type Duration struct {203time.Duration204}205206// UnmarshalJSON implements json.Unmarshaler interface207func (d *Duration) UnmarshalJSON(data []byte) error {208var s string209if err := json.Unmarshal(data, &s); err != nil {210return err211}212213duration, err := time.ParseDuration(s)214if err != nil {215return err216}217218d.Duration = duration219return nil220}221222// MarshalJSON implements json.Marshaler interface223func (d Duration) MarshalJSON() ([]byte, error) {224return json.Marshal(d.Duration.String())225}226227// Slackwebhooks holds slack notification configuration for different levels of penalty severity228type SlackWebhooks struct {229Audit string `json:"audit,omitempty"`230Warning string `json:"warning,omitempty"`231}232233// Blocklists list s/signature blocklists for various levels of infringement234type Blocklists struct {235Barely *PerLevelBlocklist `json:"barely,omitempty"`236Audit *PerLevelBlocklist `json:"audit,omitempty"`237Very *PerLevelBlocklist `json:"very,omitempty"`238}239240func (b *Blocklists) Classifier() (res classifier.ProcessClassifier, err error) {241defer func() {242if res == nil {243return244}245res = classifier.NewCountingMetricsClassifier("all", res)246}()247248if b == nil {249return classifier.NewCommandlineClassifier("empty", classifier.LevelAudit, nil, nil)250}251252gres := make(classifier.GradedClassifier)253for level, bl := range b.Levels() {254lvl := classifier.Level(level)255gres[lvl], err = bl.Classifier(string(level), lvl)256if err != nil {257return nil, err258}259}260return gres, nil261}262263// FileClassifier creates a classifier specifically for filesystem scanning264// This extracts only filesystem signatures from all blocklist levels and creates265// a clean classifier without any CountingMetricsClassifier wrapper266func (b *Blocklists) FileClassifier() (classifier.FileClassifier, error) {267if b == nil {268// Return a classifier with no signatures - will match nothing269return classifier.NewSignatureMatchClassifier("filesystem-empty", classifier.LevelAudit, nil), nil270}271272// Collect all filesystem signatures from all levels273var allFilesystemSignatures []*classifier.Signature274275for _, bl := range b.Levels() {276if bl == nil || bl.Signatures == nil {277continue278}279280for _, sig := range bl.Signatures {281if sig.Domain == classifier.DomainFileSystem {282fsSig := &classifier.Signature{283Name: sig.Name,284Domain: sig.Domain,285Pattern: sig.Pattern,286Filename: sig.Filename,287Regexp: sig.Regexp,288}289allFilesystemSignatures = append(allFilesystemSignatures, fsSig)290}291}292}293294// Create a single SignatureMatchClassifier with all filesystem signatures295// Use LevelAudit as default - individual signatures can still have their own severity296return classifier.NewSignatureMatchClassifier("filesystem", classifier.LevelAudit, allFilesystemSignatures), nil297}298299func (b *Blocklists) Levels() map[common.Severity]*PerLevelBlocklist {300res := make(map[common.Severity]*PerLevelBlocklist)301if b.Barely != nil {302res[common.SeverityBarely] = b.Barely303}304if b.Audit != nil {305res[common.SeverityAudit] = b.Audit306}307if b.Very != nil {308res[common.SeverityVery] = b.Very309}310return res311}312313// AllowList configures a list of commands that should not be blocked.314// The command could be the full path to the executable or a regular expression315type AllowList struct {316Commands []string `json:"commands,omitempty"`317}318319// PerLevelBlocklist lists blacklists for level of infringement320type PerLevelBlocklist struct {321Binaries []string `json:"binaries,omitempty"`322AllowList []string `json:"allowlist,omitempty"`323Signatures []*classifier.Signature `json:"signatures,omitempty"`324}325326func (p *PerLevelBlocklist) Classifier(name string, level classifier.Level) (classifier.ProcessClassifier, error) {327if p == nil {328return classifier.CompositeClassifier{}, nil329}330331cmdl, err := classifier.NewCommandlineClassifier(name, level, p.AllowList, p.Binaries)332if err != nil {333return nil, err334}335cmdlc := classifier.NewCountingMetricsClassifier("cmd_"+name, cmdl)336337sigsc := classifier.NewCountingMetricsClassifier("sig_"+name,338classifier.NewSignatureMatchClassifier(name, level, p.Signatures),339)340341return classifier.CompositeClassifier{cmdlc, sigsc}, nil342}343344345