diff --git a/pkg/security/generators/schemas/policy/main.go b/pkg/security/generators/schemas/policy/main.go new file mode 100644 index 00000000000000..e39d15ebb8a644 --- /dev/null +++ b/pkg/security/generators/schemas/policy/main.go @@ -0,0 +1,70 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:generate go run github.com/DataDog/datadog-agent/pkg/security/generators/schemas/policy -output ../../../secl/schemas/policy.schema.json + +// Package main holds main related files +package main + +import ( + "encoding/json" + "flag" + "os" + "reflect" + "time" + + "github.com/invopop/jsonschema" + + "github.com/DataDog/datadog-agent/pkg/security/secl/rules" +) + +func main() { + var output string + flag.StringVar(&output, "output", "", "output file") + flag.Parse() + + if output == "" { + panic("an output file argument is required") + } + + reflector := jsonschema.Reflector{ + ExpandedStruct: true, + Mapper: func(t reflect.Type) *jsonschema.Schema { + switch t { + case reflect.TypeOf(time.Duration(0)): + return &jsonschema.Schema{ + OneOf: []*jsonschema.Schema{ + { + Type: "string", + Format: "duration", + Description: "Duration in Go format (e.g. 1h30m, see https://pkg.go.dev/time#ParseDuration)", + }, + { + Type: "integer", + Description: "Duration in nanoseconds", + }, + }, + } + } + return nil + }, + } + + if err := reflector.AddGoComments("github.com/DataDog/datadog-agent/pkg/security/secl/rules/model.go", "../../../secl/rules"); err != nil { + panic(err) + } + + schema := reflector.Reflect(&rules.PolicyDef{}) + schema.ID = "https://github.com/DataDog/datadog-agent/tree/main/pkg/security/secl/rules" + + data, err := json.MarshalIndent(schema, "", " ") + if err != nil { + panic(err) + } + + if err := os.WriteFile(output, data, 0644); err != nil { + panic(err) + } +} diff --git a/pkg/security/secl/rules/policy_test.go b/pkg/security/secl/rules/policy_test.go index 150509cb2ac5b8..9f7d11e9668a26 100644 --- a/pkg/security/secl/rules/policy_test.go +++ b/pkg/security/secl/rules/policy_test.go @@ -10,19 +10,23 @@ package rules import ( "fmt" + "net/http" "os" "path/filepath" "strings" "syscall" "testing" + "time" "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" + "github.com/xeipuuv/gojsonschema" "github.com/Masterminds/semver/v3" "github.com/hashicorp/go-multierror" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" + yamlk8s "sigs.k8s.io/yaml" "github.com/DataDog/datadog-agent/pkg/security/secl/compiler/eval" "github.com/DataDog/datadog-agent/pkg/security/secl/model" @@ -77,14 +81,14 @@ func TestMacroMerge(t *testing.T) { } loader := NewPolicyLoader(provider) - evaluationSet, _ := newTestEvaluationSet([]eval.RuleSetTagValue{DefaultRuleSetTagValue}) - if errs := evaluationSet.LoadPolicies(loader, PolicyLoaderOpts{}); errs.ErrorOrNil() != nil { + rs := newRuleSet() + if errs := rs.LoadPolicies(loader, PolicyLoaderOpts{}); errs.ErrorOrNil() != nil { t.Error(err) } - macro := evaluationSet.RuleSets[DefaultRuleSetTagValue].evalOpts.MacroStore.Get("test_macro") + macro := rs.evalOpts.MacroStore.Get("test_macro") if macro == nil { - t.Fatalf("failed to find test_macro in ruleset: %+v", evaluationSet.RuleSets[DefaultRuleSetTagValue].evalOpts.MacroStore.List()) + t.Fatalf("failed to find test_macro in ruleset: %+v", rs.evalOpts.MacroStore.List()) } testPolicy2.Macros[0].Combine = "" @@ -93,25 +97,47 @@ func TestMacroMerge(t *testing.T) { t.Fatal(err) } - if err := evaluationSet.LoadPolicies(loader, PolicyLoaderOpts{}); err == nil { + if err := rs.LoadPolicies(loader, PolicyLoaderOpts{}); err == nil { t.Error("expected macro ID conflict") } } func TestRuleMerge(t *testing.T) { testPolicy := &PolicyDef{ - Rules: []*RuleDefinition{{ - ID: "test_rule", - Expression: `open.file.path == "/tmp/test"`, - }}, + Rules: []*RuleDefinition{ + { + ID: "test_rule", + Expression: `open.file.path == "/tmp/test"`, + }, + { + ID: "test_rule_foo", + Expression: `exec.file.name == "foo"`, + }, + { + ID: "test_rule_bar", + Expression: `exec.file.name == "bar"`, + Disabled: true, + }, + }, } testPolicy2 := &PolicyDef{ - Rules: []*RuleDefinition{{ - ID: "test_rule", - Expression: `open.file.path == "/tmp/test"`, - Combine: OverridePolicy, - }}, + Rules: []*RuleDefinition{ + { + ID: "test_rule", + Expression: `open.file.path == "/tmp/test"`, + Combine: OverridePolicy, + }, + { + ID: "test_rule_foo", + Expression: `exec.file.name == "foo"`, + Disabled: true, + }, + { + ID: "test_rule_bar", + Expression: `exec.file.name == "bar"`, + }, + }, } tmpDir := t.TempDir() @@ -130,25 +156,41 @@ func TestRuleMerge(t *testing.T) { } loader := NewPolicyLoader(provider) - evaluationSet, _ := newTestEvaluationSet([]eval.RuleSetTagValue{DefaultRuleSetTagValue}) - if errs := evaluationSet.LoadPolicies(loader, PolicyLoaderOpts{}); errs.ErrorOrNil() != nil { + rs := newRuleSet() + if errs := rs.LoadPolicies(loader, PolicyLoaderOpts{}); errs.ErrorOrNil() != nil { t.Error(err) } - rule := evaluationSet.RuleSets[DefaultRuleSetTagValue].GetRules()["test_rule"] - if rule == nil { - t.Fatal("failed to find test_rule in ruleset") - } + t.Run("override", func(t *testing.T) { + rule := rs.GetRules()["test_rule"] + if rule == nil { + t.Fatal("failed to find test_rule in ruleset") + } - testPolicy2.Rules[0].Combine = "" + testPolicy2.Rules[0].Combine = "" - if err := savePolicy(filepath.Join(tmpDir, "test2.policy"), testPolicy2); err != nil { - t.Fatal(err) - } + if err := savePolicy(filepath.Join(tmpDir, "test2.policy"), testPolicy2); err != nil { + t.Fatal(err) + } - if err := evaluationSet.LoadPolicies(loader, PolicyLoaderOpts{}); err == nil { - t.Error("expected rule ID conflict") - } + if err := rs.LoadPolicies(loader, PolicyLoaderOpts{}); err == nil { + t.Error("expected rule ID conflict") + } + }) + + t.Run("enabled-disabled", func(t *testing.T) { + rule := rs.GetRules()["test_rule_foo"] + if rule != nil { + t.Fatal("expected test_rule_foo to not be loaded") + } + }) + + t.Run("disabled-enabled", func(t *testing.T) { + rule := rs.GetRules()["test_rule_bar"] + if rule == nil { + t.Fatal("expected test_rule_bar to be loaded") + } + }) } func TestActionSetVariable(t *testing.T) { @@ -248,12 +290,12 @@ func TestActionSetVariable(t *testing.T) { } loader := NewPolicyLoader(provider) - evaluationSet, _ := newTestEvaluationSet([]eval.RuleSetTagValue{DefaultRuleSetTagValue}) - if errs := evaluationSet.LoadPolicies(loader, PolicyLoaderOpts{}); errs.ErrorOrNil() != nil { + rs := newRuleSet() + if err := rs.LoadPolicies(loader, PolicyLoaderOpts{}); err != nil { t.Error(err) } - rule := evaluationSet.RuleSets[DefaultRuleSetTagValue].GetRules()["test_rule"] + rule := rs.GetRules()["test_rule"] if rule == nil { t.Fatal("failed to find test_rule in ruleset") } @@ -266,28 +308,87 @@ func TestActionSetVariable(t *testing.T) { event.SetFieldValue("open.file.path", "/tmp/test2") event.SetFieldValue("open.flags", syscall.O_RDONLY) - if evaluationSet.RuleSets[DefaultRuleSetTagValue].Evaluate(event) { + if rs.Evaluate(event) { t.Errorf("Expected event to match no rule") } event.SetFieldValue("open.file.path", "/tmp/test") - if !evaluationSet.RuleSets[DefaultRuleSetTagValue].Evaluate(event) { + if !rs.Evaluate(event) { t.Errorf("Expected event to match rule") } event.SetFieldValue("open.file.path", "/tmp/test2") - if !evaluationSet.RuleSets[DefaultRuleSetTagValue].Evaluate(event) { + if !rs.Evaluate(event) { t.Errorf("Expected event to match rule") } - scopedVariables := evaluationSet.RuleSets[DefaultRuleSetTagValue].scopedVariables["process"].(*eval.ScopedVariables) + scopedVariables := rs.scopedVariables["process"].(*eval.ScopedVariables) assert.Equal(t, scopedVariables.Len(), 1) event.ProcessCacheEntry.Release() assert.Equal(t, scopedVariables.Len(), 0) } +func TestActionSetVariableTTL(t *testing.T) { + testPolicy := &PolicyDef{ + Rules: []*RuleDefinition{{ + ID: "test_rule", + Expression: `open.file.path == "/tmp/test"`, + Actions: []*ActionDefinition{{ + Set: &SetDefinition{ + Name: "var1", + Append: true, + Value: []string{"foo"}, + TTL: 1 * time.Second, + }, + }}, + }}, + } + + tmpDir := t.TempDir() + + if err := savePolicy(filepath.Join(tmpDir, "test.policy"), testPolicy); err != nil { + t.Fatal(err) + } + + provider, err := NewPoliciesDirProvider(tmpDir, false) + if err != nil { + t.Fatal(err) + } + loader := NewPolicyLoader(provider) + + rs := newRuleSet() + if err := rs.LoadPolicies(loader, PolicyLoaderOpts{}); err != nil { + t.Error(err) + } + + event := model.NewFakeEvent() + event.Type = uint32(model.FileOpenEventType) + processCacheEntry := &model.ProcessCacheEntry{} + processCacheEntry.Retain() + event.ProcessCacheEntry = processCacheEntry + event.SetFieldValue("open.file.path", "/tmp/test") + event.SetFieldValue("open.flags", syscall.O_RDONLY) + + if !rs.Evaluate(event) { + t.Errorf("Expected event to match rule") + } + + opts := rs.evalOpts + + existingVariable := opts.VariableStore.Get("var1") + assert.NotNil(t, existingVariable) + + stringArrayVar, ok := existingVariable.(*eval.MutableStringArrayVariable) + assert.NotNil(t, stringArrayVar) + assert.True(t, ok) + + assert.True(t, stringArrayVar.LRU.Has("foo")) + time.Sleep(time.Second + 100*time.Millisecond) + assert.False(t, stringArrayVar.LRU.Has("foo")) +} + func TestActionSetVariableConflict(t *testing.T) { testPolicy := &PolicyDef{ Rules: []*RuleDefinition{{ @@ -323,12 +424,31 @@ func TestActionSetVariableConflict(t *testing.T) { } loader := NewPolicyLoader(provider) - evaluationSet, _ := newTestEvaluationSet([]eval.RuleSetTagValue{DefaultRuleSetTagValue}) - if errs := evaluationSet.LoadPolicies(loader, PolicyLoaderOpts{}); errs.ErrorOrNil() == nil { + rs := newRuleSet() + if err := rs.LoadPolicies(loader, PolicyLoaderOpts{}); err == nil { t.Error("expected policy to fail to load") } } +func loadPolicy(t *testing.T, testPolicy *PolicyDef, policyOpts PolicyLoaderOpts) (*RuleSet, *multierror.Error) { + rs := newRuleSet() + + tmpDir := t.TempDir() + + if err := savePolicy(filepath.Join(tmpDir, "test.policy"), testPolicy); err != nil { + t.Fatal(err) + } + + provider, err := NewPoliciesDirProvider(tmpDir, false) + if err != nil { + t.Fatal(err) + } + + loader := NewPolicyLoader(provider) + + return rs, rs.LoadPolicies(loader, policyOpts) +} + func TestRuleErrorLoading(t *testing.T) { testPolicy := &PolicyDef{ Rules: []*RuleDefinition{ @@ -347,8 +467,7 @@ func TestRuleErrorLoading(t *testing.T) { }, } - es, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}) - rs := es.RuleSets[DefaultRuleSetTagValue] + rs, err := loadPolicy(t, testPolicy, PolicyLoaderOpts{}) assert.NotNil(t, err) assert.Len(t, err.Errors, 2) assert.ErrorContains(t, err.Errors[0], "rule `testA` error: multiple definition with the same ID") @@ -472,10 +591,10 @@ func TestRuleAgentConstraint(t *testing.T) { } agentVersion, err := semver.NewVersion("7.38") - assert.Nil(t, err) + assert.NoError(t, err) agentVersionFilter, err := NewAgentVersionFilter(agentVersion) - assert.Nil(t, err) + assert.NoError(t, err) policyOpts := PolicyLoaderOpts{ MacroFilters: []MacroFilter{ @@ -486,15 +605,9 @@ func TestRuleAgentConstraint(t *testing.T) { }, } - es, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, policyOpts) - rs := es.RuleSets[DefaultRuleSetTagValue] - - for _, err := range err.(*multierror.Error).Errors { - if rerr, ok := err.(*ErrRuleLoad); ok { - if rerr.Definition.ID != "basic" && rerr.Definition.ID != "range_not" { - t.Errorf("unexpected error: %v", rerr) - } - } + rs, rsErr := loadPolicy(t, testPolicy, policyOpts) + if rsErr != nil { + t.Fatalf("unexpected error: %v\n", rsErr) } for _, exp := range expected { @@ -524,7 +637,7 @@ func TestActionSetVariableInvalid(t *testing.T) { }}, } - if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil { + if _, err := loadPolicy(t, testPolicy, PolicyLoaderOpts{}); err == nil { t.Error("policy should fail to load") } else { t.Log(err) @@ -549,7 +662,7 @@ func TestActionSetVariableInvalid(t *testing.T) { }}, } - if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil { + if _, err := loadPolicy(t, testPolicy, PolicyLoaderOpts{}); err == nil { t.Error("expected policy to fail to load") } else { t.Log(err) @@ -574,7 +687,7 @@ func TestActionSetVariableInvalid(t *testing.T) { }}, } - if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil { + if _, err := loadPolicy(t, testPolicy, PolicyLoaderOpts{}); err == nil { t.Error("expected policy to fail to load") } else { t.Log(err) @@ -595,7 +708,7 @@ func TestActionSetVariableInvalid(t *testing.T) { }}, } - if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil { + if _, err := loadPolicy(t, testPolicy, PolicyLoaderOpts{}); err == nil { t.Error("expected policy to fail to load") } else { t.Log(err) @@ -627,7 +740,7 @@ func TestActionSetVariableInvalid(t *testing.T) { }}, } - if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil { + if _, err := loadPolicy(t, testPolicy, PolicyLoaderOpts{}); err == nil { t.Error("expected policy to fail to load") } else { t.Log(err) @@ -658,7 +771,7 @@ func TestActionSetVariableInvalid(t *testing.T) { }}, } - if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil { + if _, err := loadPolicy(t, testPolicy, PolicyLoaderOpts{}); err == nil { t.Error("expected policy to fail to load") } else { t.Log(err) @@ -690,7 +803,7 @@ func TestActionSetVariableInvalid(t *testing.T) { }}, } - if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil { + if _, err := loadPolicy(t, testPolicy, PolicyLoaderOpts{}); err == nil { t.Error("expected policy to fail to load") } else { t.Log(err) @@ -723,7 +836,7 @@ func TestLoadPolicy(t *testing.T) { ruleFilters: nil, }, want: nil, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + wantErr: func(t assert.TestingT, err error, _ ...interface{}) bool { return assert.EqualError(t, err, ErrPolicyLoad{Name: "myLocal.policy", Err: fmt.Errorf(`EOF`)}.Error()) }, }, @@ -738,7 +851,7 @@ func TestLoadPolicy(t *testing.T) { ruleFilters: nil, }, want: nil, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + wantErr: func(t assert.TestingT, err error, _ ...interface{}) bool { return assert.EqualError(t, err, ErrPolicyLoad{Name: "myLocal.policy", Err: fmt.Errorf(`EOF`)}.Error()) }, }, @@ -756,7 +869,8 @@ rules: want: &Policy{ Name: "myLocal.policy", Source: PolicyProviderTypeRC, - Rules: nil, + rules: map[string][]*PolicyRule{}, + macros: map[string][]*PolicyMacro{}, }, wantErr: assert.NoError, }, @@ -772,7 +886,7 @@ broken ruleFilters: nil, }, want: nil, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + wantErr: func(t assert.TestingT, err error, _ ...interface{}) bool { return assert.ErrorContains(t, err, ErrPolicyLoad{Name: "myLocal.policy", Err: fmt.Errorf(`yaml: unmarshal error`)}.Error()) }, }, @@ -788,21 +902,23 @@ broken macroFilters: nil, ruleFilters: nil, }, - want: &Policy{ + want: fixupRulesPolicy(&Policy{ Name: "myLocal.policy", Source: PolicyProviderTypeRC, - Rules: []*RuleDefinition{ - { - ID: "rule_test", - Expression: "", - Disabled: true, - Policy: &Policy{ - Name: "myLocal.policy", - Source: PolicyProviderTypeRC, + rules: map[string][]*PolicyRule{ + "rule_test": { + { + Def: &RuleDefinition{ + ID: "rule_test", + Expression: "", + Disabled: true, + }, + Accepted: true, }, }, }, - }, + macros: map[string][]*PolicyMacro{}, + }), wantErr: assert.NoError, }, { @@ -818,21 +934,23 @@ broken macroFilters: nil, ruleFilters: nil, }, - want: &Policy{ + want: fixupRulesPolicy(&Policy{ Name: "myLocal.policy", Source: PolicyProviderTypeRC, - Rules: []*RuleDefinition{ - { - ID: "rule_test", - Expression: "open.file.path == \"/etc/gshadow\"", - Combine: OverridePolicy, - Policy: &Policy{ - Name: "myLocal.policy", - Source: PolicyProviderTypeRC, + rules: map[string][]*PolicyRule{ + "rule_test": { + { + Def: &RuleDefinition{ + ID: "rule_test", + Expression: "open.file.path == \"/etc/gshadow\"", + Combine: OverridePolicy, + }, + Accepted: true, }, }, }, - }, + macros: map[string][]*PolicyMacro{}, + }), wantErr: assert.NoError, }, } @@ -846,9 +964,193 @@ broken return } - if !cmp.Equal(tt.want, got, cmpopts.IgnoreFields(RuleDefinition{}, "Policy")) { - t.Errorf("LoadPolicy(%v, %v, %v, %v, %v)", tt.args.name, tt.args.source, r, tt.args.macroFilters, tt.args.ruleFilters) + if !cmp.Equal(tt.want, got, policyCmpOpts...) { + t.Errorf("The loaded policies do not match the expected\nDiff:\n%s", cmp.Diff(tt.want, got, policyCmpOpts...)) } }) } } + +// go test -v github.com/DataDog/datadog-agent/pkg/security/secl/rules --run="TestPolicySchema" +func TestPolicySchema(t *testing.T) { + tests := []struct { + name string + policy string + schemaResultCb func(*testing.T, *gojsonschema.Result) + }{ + { + name: "valid", + policy: policyValid, + schemaResultCb: func(t *testing.T, result *gojsonschema.Result) { + if !assert.True(t, result.Valid(), "schema validation failed") { + for _, err := range result.Errors() { + t.Errorf("%s", err) + } + } + }, + }, + { + name: "missing required rule ID", + policy: policyWithMissingRequiredRuleID, + schemaResultCb: func(t *testing.T, result *gojsonschema.Result) { + require.False(t, result.Valid(), "schema validation should fail") + require.Len(t, result.Errors(), 1) + assert.Contains(t, result.Errors()[0].String(), "id is required") + }, + }, + { + name: "unknown field", + policy: policyWithUnknownField, + schemaResultCb: func(t *testing.T, result *gojsonschema.Result) { + require.False(t, result.Valid(), "schema validation should fail") + require.Len(t, result.Errors(), 1) + assert.Contains(t, result.Errors()[0].String(), "Additional property unknown_field is not allowed") + }, + }, + { + name: "invalid field type", + policy: policyWithInvalidFieldType, + schemaResultCb: func(t *testing.T, result *gojsonschema.Result) { + require.False(t, result.Valid(), "schema validation should fail") + require.Len(t, result.Errors(), 1) + assert.Contains(t, result.Errors()[0].String(), "Invalid type") + + }, + }, + { + name: "multiple actions", + policy: policyWithMultipleActions, + schemaResultCb: func(t *testing.T, result *gojsonschema.Result) { + require.False(t, result.Valid(), "schema validation should fail") + require.Len(t, result.Errors(), 1) + assert.Contains(t, result.Errors()[0].String(), "Must validate one and only one schema") + }, + }, + } + + fs := os.DirFS("../../../../pkg/security/secl/schemas") + schemaLoader := gojsonschema.NewReferenceLoaderFileSystem("file:///policy.schema.json", http.FS(fs)) + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + json, err := yamlk8s.YAMLToJSON([]byte(test.policy)) + require.NoErrorf(t, err, "failed to convert yaml to json: %v", err) + documentLoader := gojsonschema.NewBytesLoader(json) + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + require.NoErrorf(t, err, "failed to validate schema: %v", err) + test.schemaResultCb(t, result) + }) + } +} + +const policyValid = ` +version: 1.2.3 +rules: + - id: basic + expression: exec.file.name == "foo" + - id: with_tags + description: Rule with tags + expression: exec.file.name == "foo" + tags: + tagA: a + tagB: b + - id: disabled + description: Disabled rule + expression: exec.file.name == "foo" + disabled: true + - id: with_tags + description: Rule with combine + expression: exec.file.name == "bar" + combine: override + override_options: + fields: + - expression + - id: with_filters + description: Rule with a filter and agent_version field + expression: exec.file.name == "foo" + agent_version: ">= 7.38" + filters: + - os == "linux" + - id: with_every_silent_group_id + description: Rule with a silent/every/group_id field + expression: exec.file.name == "foo" + silent: true + every: 10s + group_id: "baz_group" + - id: with_set_action_with_field + description: Rule with a set action using an event field + expression: exec.file.name == "foo" + actions: + - set: + name: process_names + field: process.file.name + append: true + size: 10 + ttl: 10s + - id: with_set_action_with_value + description: Rule with a set action using a value + expression: exec.file.name == "foo" + actions: + - set: + name: global_var_set + value: true + - id: with_set_action_use + description: Rule using a variable set by a previous action + expression: open.file.path == "/tmp/bar" && ${global_var_set} + - id: with_kill_action + description: Rule with a kill action + expression: exec.file.name == "foo" + actions: + - kill: + signal: SIGKILL + scope: process + - id: with_coredump_action + description: Rule with a coredump action + expression: exec.file.name == "foo" + actions: + - coredump: + process: true + dentry: true + mount: true + no_compression: true + - id: with_hash_action + description: Rule with a hash action + expression: exec.file.name == "foo" + actions: + - hash: {} +` +const policyWithMissingRequiredRuleID = ` +version: 1.2.3 +rules: + - description: Rule with missing ID + expression: exec.file.name == "foo" +` + +const policyWithUnknownField = ` +version: 1.2.3 +rules: + - id: rule with unknown field + expression: exec.file.name == "foo" + unknown_field: "bar" +` + +const policyWithInvalidFieldType = ` +version: 1.2.3 +rules: + - id: 2 + expression: exec.file.name == "foo" +` + +const policyWithMultipleActions = ` +version: 1.2.3 +rules: + - id: rule with missing action + expression: exec.file.name == "foo" + actions: + - set: + name: global_var_set + value: true + kill: + signal: SIGKILL + scope: process +` diff --git a/pkg/security/tests/schemas/activity_dump.schema.json b/pkg/security/secl/schemas/activity_dump.schema.json similarity index 100% rename from pkg/security/tests/schemas/activity_dump.schema.json rename to pkg/security/secl/schemas/activity_dump.schema.json diff --git a/pkg/security/tests/schemas/activity_dump_proto.schema.json b/pkg/security/secl/schemas/activity_dump_proto.schema.json similarity index 100% rename from pkg/security/tests/schemas/activity_dump_proto.schema.json rename to pkg/security/secl/schemas/activity_dump_proto.schema.json diff --git a/pkg/security/secl/schemas/agent_context.schema.json b/pkg/security/secl/schemas/agent_context.schema.json new file mode 100644 index 00000000000000..f524a02c9986e1 --- /dev/null +++ b/pkg/security/secl/schemas/agent_context.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "agent_context.schema.json", + "type": "object", + "properties": { + "rule_id": { + "type": "string" + }, + "rule_version": { + "type": "string" + }, + "policy_name": { + "type": "string" + }, + "policy_version": { + "type": "string" + }, + "version": { + "type": "string" + }, + "os": { + "type": "string" + }, + "arch": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "rule_actions": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "kill.schema.json" + }, + { + "$ref": "hash.schema.json" + } + ] + } + } + }, + "required": [ + "rule_id", + "policy_name", + "policy_version", + "version", + "os", + "arch", + "origin" + ] +} \ No newline at end of file diff --git a/pkg/security/tests/schemas/bind.schema.json b/pkg/security/secl/schemas/bind.schema.json similarity index 86% rename from pkg/security/tests/schemas/bind.schema.json rename to pkg/security/secl/schemas/bind.schema.json index a0351a64a6c759..0e8503fa071c50 100644 --- a/pkg/security/tests/schemas/bind.schema.json +++ b/pkg/security/secl/schemas/bind.schema.json @@ -1,20 +1,20 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "bind.json", + "$id": "bind.schema.json", "type": "object", "allOf": [ { - "$ref": "/schemas/event.json" + "$ref": "event.schema.json" }, { - "$ref": "/schemas/usr.json" + "$ref": "usr.schema.json" }, { - "$ref": "/schemas/process_context.json" + "$ref": "process_context.schema.json" }, { "date": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" } }, { diff --git a/pkg/security/tests/schemas/bpf.schema.json b/pkg/security/secl/schemas/bpf.schema.json similarity index 91% rename from pkg/security/tests/schemas/bpf.schema.json rename to pkg/security/secl/schemas/bpf.schema.json index dd423ab1537710..c315002647f53f 100644 --- a/pkg/security/tests/schemas/bpf.schema.json +++ b/pkg/security/secl/schemas/bpf.schema.json @@ -1,20 +1,20 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "bpf.json", + "$id": "bpf.schema.json", "type": "object", "allOf": [ { - "$ref": "/schemas/event.json" + "$ref": "event.schema.json" }, { - "$ref": "/schemas/usr.json" + "$ref": "usr.schema.json" }, { - "$ref": "/schemas/process_context.json" + "$ref": "process_context.schema.json" }, { "date": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" } }, { diff --git a/pkg/security/tests/schemas/chmod.schema.json b/pkg/security/secl/schemas/chmod.schema.json similarity index 85% rename from pkg/security/tests/schemas/chmod.schema.json rename to pkg/security/secl/schemas/chmod.schema.json index cf56e011e0e3da..6bd85b45ae8b49 100644 --- a/pkg/security/tests/schemas/chmod.schema.json +++ b/pkg/security/secl/schemas/chmod.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "chmod.json", + "$id": "chmod.schema.json", "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ], "allOf": [ diff --git a/pkg/security/tests/schemas/chown.schema.json b/pkg/security/secl/schemas/chown.schema.json similarity index 78% rename from pkg/security/tests/schemas/chown.schema.json rename to pkg/security/secl/schemas/chown.schema.json index 9fc6f29e5996a5..6c8d52399f538c 100644 --- a/pkg/security/tests/schemas/chown.schema.json +++ b/pkg/security/secl/schemas/chown.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "chown.json", + "$id": "chown.schema.json", "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ], "allOf": [ diff --git a/pkg/security/secl/schemas/connect.schema.json b/pkg/security/secl/schemas/connect.schema.json new file mode 100644 index 00000000000000..add7097809aba7 --- /dev/null +++ b/pkg/security/secl/schemas/connect.schema.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "connect.schema.json", + "type": "object", + "allOf": [ + { + "$ref": "event.schema.json" + }, + { + "$ref": "usr.schema.json" + }, + { + "$ref": "process_context.schema.json" + }, + { + "date": { + "$ref": "datetime.schema.json" + } + }, + { + "properties": { + "connect": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "object", + "required": [ + "family", + "ip", + "port" + ], + "properties": { + "family": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "port": { + "type": "integer" + } + } + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/pkg/security/tests/schemas/container.json b/pkg/security/secl/schemas/container.schema.json similarity index 85% rename from pkg/security/tests/schemas/container.json rename to pkg/security/secl/schemas/container.schema.json index 42db4215b7d258..9ce795d204fd2c 100644 --- a/pkg/security/tests/schemas/container.json +++ b/pkg/security/secl/schemas/container.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "container.json", + "$id": "container.schema.json", "type": "object", "properties": { "id": { diff --git a/pkg/security/tests/schemas/container_context.json b/pkg/security/secl/schemas/container_context.schema.json similarity index 68% rename from pkg/security/tests/schemas/container_context.json rename to pkg/security/secl/schemas/container_context.schema.json index dc65196a9e48ab..5def87884bb87d 100644 --- a/pkg/security/tests/schemas/container_context.json +++ b/pkg/security/secl/schemas/container_context.schema.json @@ -1,10 +1,10 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "container_context.json", + "$id": "container_context.schema.json", "type": "object", "properties": { "container": { - "$ref": "/schemas/container.json" + "$ref": "container.schema.json" } }, "required": [ diff --git a/pkg/security/secl/schemas/container_event.schema.json b/pkg/security/secl/schemas/container_event.schema.json new file mode 100644 index 00000000000000..4485b5a35c4b7d --- /dev/null +++ b/pkg/security/secl/schemas/container_event.schema.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "container_event.schema.json", + "allOf": [ + { + "$ref": "host_event.schema.json" + }, + { + "$ref": "container_context.schema.json" + } + ] +} diff --git a/pkg/security/secl/schemas/container_event_no_file.schema.json b/pkg/security/secl/schemas/container_event_no_file.schema.json new file mode 100644 index 00000000000000..42481aac800b95 --- /dev/null +++ b/pkg/security/secl/schemas/container_event_no_file.schema.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "container_event_no_file.schema.json", + "allOf": [ + { + "$ref": "host_event_no_file.schema.json" + }, + { + "$ref": "container_context.schema.json" + } + ] +} diff --git a/pkg/security/tests/schemas/datetime.json b/pkg/security/secl/schemas/datetime.schema.json similarity index 90% rename from pkg/security/tests/schemas/datetime.json rename to pkg/security/secl/schemas/datetime.schema.json index 03cfc3d81cdc51..e0e6e8eed8b96b 100644 --- a/pkg/security/tests/schemas/datetime.json +++ b/pkg/security/secl/schemas/datetime.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "datetime.json", + "$id": "datetime.schema.json", "allOf": [ { "type": "string", diff --git a/pkg/security/tests/schemas/dns.schema.json b/pkg/security/secl/schemas/dns.schema.json similarity index 87% rename from pkg/security/tests/schemas/dns.schema.json rename to pkg/security/secl/schemas/dns.schema.json index d9d62483a5bc2c..3e697ead51f0af 100644 --- a/pkg/security/tests/schemas/dns.schema.json +++ b/pkg/security/secl/schemas/dns.schema.json @@ -1,23 +1,23 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "dns.json", + "$id": "dns.schema.json", "type": "object", "allOf": [ { - "$ref": "/schemas/event.json" + "$ref": "event.schema.json" }, { - "$ref": "/schemas/usr.json" + "$ref": "usr.schema.json" }, { - "$ref": "/schemas/process_context.json" + "$ref": "process_context.schema.json" }, { - "$ref": "/schemas/network.json" + "$ref": "network.schema.json" }, { "date": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" } }, { diff --git a/pkg/security/tests/schemas/event.json b/pkg/security/secl/schemas/event.schema.json similarity index 91% rename from pkg/security/tests/schemas/event.json rename to pkg/security/secl/schemas/event.schema.json index 16dcdc0fe6a60e..685f3af7ae28e3 100644 --- a/pkg/security/tests/schemas/event.json +++ b/pkg/security/secl/schemas/event.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "event.json", + "$id": "event.schema.json", "type": "object", "properties": { "evt": { @@ -29,7 +29,7 @@ ] }, "date": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" } }, "required": [ diff --git a/pkg/security/tests/schemas/exec.schema.json b/pkg/security/secl/schemas/exec.schema.json similarity index 81% rename from pkg/security/tests/schemas/exec.schema.json rename to pkg/security/secl/schemas/exec.schema.json index ba466e3327ad91..2f7d44b03cc5d8 100644 --- a/pkg/security/tests/schemas/exec.schema.json +++ b/pkg/security/secl/schemas/exec.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "exec.json", + "$id": "exec.schema.json", "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ], "allOf": [ diff --git a/pkg/security/tests/schemas/exit.schema.json b/pkg/security/secl/schemas/exit.schema.json similarity index 86% rename from pkg/security/tests/schemas/exit.schema.json rename to pkg/security/secl/schemas/exit.schema.json index d8e9820a804327..341107a3791855 100644 --- a/pkg/security/tests/schemas/exit.schema.json +++ b/pkg/security/secl/schemas/exit.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "exit.json", + "$id": "exit.schema.json", "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ], "allOf": [ diff --git a/pkg/security/tests/schemas/file.json b/pkg/security/secl/schemas/file.schema.json similarity index 90% rename from pkg/security/tests/schemas/file.json rename to pkg/security/secl/schemas/file.schema.json index c78c9fba93a0d6..770b748c4c2816 100644 --- a/pkg/security/tests/schemas/file.json +++ b/pkg/security/secl/schemas/file.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "file.json", + "$id": "file.schema.json", "type": "object", "properties": { "path": { @@ -29,10 +29,10 @@ "type": "string" }, "modification_time": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" }, "change_time": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" }, "flags": { "type": "array", diff --git a/pkg/security/secl/schemas/hash.schema.json b/pkg/security/secl/schemas/hash.schema.json new file mode 100644 index 00000000000000..229a60483fe20b --- /dev/null +++ b/pkg/security/secl/schemas/hash.schema.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "kill.schema.json", + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "path": { + "type": "string" + }, + "state": { + "type": "string" + }, + "trigger": { + "type": "string" + } + }, + "required": [ + "type", + "path", + "state", + "trigger" + ] +} \ No newline at end of file diff --git a/pkg/security/tests/schemas/heartbeat.schema.json b/pkg/security/secl/schemas/heartbeat.schema.json similarity index 92% rename from pkg/security/tests/schemas/heartbeat.schema.json rename to pkg/security/secl/schemas/heartbeat.schema.json index 038fb54411c5fa..45086fab8415ed 100644 --- a/pkg/security/tests/schemas/heartbeat.schema.json +++ b/pkg/security/secl/schemas/heartbeat.schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "$id": "heartbeat.json", + "$id": "heartbeat.schema.json", "type": "object", "properties": { "policy": { @@ -9,7 +9,7 @@ } }, "date": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" } }, "required": [ diff --git a/pkg/security/secl/schemas/host_event.schema.json b/pkg/security/secl/schemas/host_event.schema.json new file mode 100644 index 00000000000000..357955d548ce72 --- /dev/null +++ b/pkg/security/secl/schemas/host_event.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "host_event.schema.json", + "allOf": [ + { + "$ref": "event.schema.json" + }, + { + "file": { "$ref": "file.schema.json" } + }, + { + "$ref": "usr.schema.json" + }, + { + "$ref": "process_context.schema.json" + }, + { + "date": { + "$ref": "datetime.schema.json" + } + } + ] +} diff --git a/pkg/security/secl/schemas/host_event_no_file.schema.json b/pkg/security/secl/schemas/host_event_no_file.schema.json new file mode 100644 index 00000000000000..2d260efa55250e --- /dev/null +++ b/pkg/security/secl/schemas/host_event_no_file.schema.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "host_event_no_file.schema.json", + "allOf": [ + { + "$ref": "event.schema.json" + }, + { + "$ref": "usr.schema.json" + }, + { + "$ref": "process_context.schema.json" + }, + { + "date": { + "$ref": "datetime.schema.json" + } + } + ] +} diff --git a/pkg/security/secl/schemas/imds.schema.json b/pkg/security/secl/schemas/imds.schema.json new file mode 100644 index 00000000000000..a8fd8ed7bcb7be --- /dev/null +++ b/pkg/security/secl/schemas/imds.schema.json @@ -0,0 +1,93 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "imds.schema.json", + "type": "object", + "allOf": [ + { + "$ref": "event.schema.json" + }, + { + "$ref": "usr.schema.json" + }, + { + "$ref": "process_context.schema.json" + }, + { + "$ref": "network.schema.json" + }, + { + "date": { + "$ref": "datetime.schema.json" + } + }, + { + "properties": { + "imds": { + "type": "object", + "required": [ + "cloud_provider", + "type" + ], + "properties": { + "type": { + "type": "string" + }, + "cloud_provider": { + "type": "string" + }, + "host": { + "type": "string" + }, + "url": { + "type": "string" + }, + "user_agent": { + "type": "string" + }, + "server": { + "type": "string" + }, + "aws": { + "type": "object", + "required": [ + "is_imds_v2" + ], + "properties": { + "is_imds_v2": { + "type": "boolean" + }, + "security_credentials": { + "type": "object", + "required": [ + "code", + "type", + "access_key_id", + "last_updated", + "expiration" + ], + "properties": { + "code": { + "type": "string" + }, + "type": { + "type": "string" + }, + "access_key_id": { + "type": "string" + }, + "last_updated": { + "type": "string" + }, + "expiration": { + "type": "string" + } + } + } + } + } + } + } + } + } + ] +} diff --git a/pkg/security/secl/schemas/kill.schema.json b/pkg/security/secl/schemas/kill.schema.json new file mode 100644 index 00000000000000..7ad33dcf8a0be4 --- /dev/null +++ b/pkg/security/secl/schemas/kill.schema.json @@ -0,0 +1,88 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "kill.schema.json", + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "signal": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "created_at": { + "$ref": "datetime.schema.json" + }, + "detected_at": { + "$ref": "datetime.schema.json" + }, + "killed_at": { + "$ref": "datetime.schema.json" + }, + "exited_at": { + "$ref": "datetime.schema.json" + }, + "ttr": { + "type": "string" + } + }, + "anyOf": [ + { + "properties": { + "signal": { + "const": "SIGKILL" + }, + "status": { + "const": "performed" + } + }, + "required": [ + "type", + "signal", + "scope", + "status", + "created_at", + "detected_at", + "killed_at", + "exited_at", + "ttr" + ] + }, + { + "properties": { + "signal": { + "const": "SIGUSR2" + }, + "status": { + "const": "performed" + } + }, + "required": [ + "type", + "signal", + "scope", + "status", + "created_at", + "detected_at", + "killed_at" + ] + }, + { + "properties": { + "status": { + "const": "rule_disarmed" + } + }, + "required": [ + "type", + "signal", + "scope", + "status", + "created_at", + "detected_at" + ] + } + ] +} \ No newline at end of file diff --git a/pkg/security/tests/schemas/link.schema.json b/pkg/security/secl/schemas/link.schema.json similarity index 76% rename from pkg/security/tests/schemas/link.schema.json rename to pkg/security/secl/schemas/link.schema.json index 98b94a81f285cf..e618ebd5172fa2 100644 --- a/pkg/security/tests/schemas/link.schema.json +++ b/pkg/security/secl/schemas/link.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "link.json", + "$id": "link.schema.json", "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ], "allOf": [ @@ -20,7 +20,7 @@ ], "properties": { "destination": { - "$ref": "/schemas/file.json" + "$ref": "file.schema.json" } } } diff --git a/pkg/security/tests/schemas/load_module.schema.json b/pkg/security/secl/schemas/load_module.schema.json similarity index 84% rename from pkg/security/tests/schemas/load_module.schema.json rename to pkg/security/secl/schemas/load_module.schema.json index 4e86932fa111ce..2fc2d99e86ae3f 100644 --- a/pkg/security/tests/schemas/load_module.schema.json +++ b/pkg/security/secl/schemas/load_module.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "load_module.json", + "$id": "load_module.schema.json", "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ], "allOf": [ diff --git a/pkg/security/tests/schemas/load_module_no_file.schema.json b/pkg/security/secl/schemas/load_module_no_file.schema.json similarity index 82% rename from pkg/security/tests/schemas/load_module_no_file.schema.json rename to pkg/security/secl/schemas/load_module_no_file.schema.json index c878d0f3b39339..c3a1e7c1c1ddbc 100644 --- a/pkg/security/tests/schemas/load_module_no_file.schema.json +++ b/pkg/security/secl/schemas/load_module_no_file.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "load_module_no_file.json", + "$id": "load_module_no_file.schema.json", "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event_no_file.json" + "$ref": "container_event_no_file.schema.json" }, { - "$ref": "/schemas/host_event_no_file.json" + "$ref": "host_event_no_file.schema.json" } ], "allOf": [ diff --git a/pkg/security/secl/schemas/message.schema.json b/pkg/security/secl/schemas/message.schema.json new file mode 100644 index 00000000000000..f3d8d2534bc97a --- /dev/null +++ b/pkg/security/secl/schemas/message.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "message.schema.json", + "allOf": [ + { + "properties": { + "agent": { + "$ref": "agent_context.schema.json" + } + } + }, + { + "properties": { + "evt": { + "type": "object" + } + }, + "required": [ + "evt" + ] + } + ] +} \ No newline at end of file diff --git a/pkg/security/tests/schemas/mmap.schema.json b/pkg/security/secl/schemas/mmap.schema.json similarity index 82% rename from pkg/security/tests/schemas/mmap.schema.json rename to pkg/security/secl/schemas/mmap.schema.json index 21244fc74cc6e3..06090e86ed626c 100644 --- a/pkg/security/tests/schemas/mmap.schema.json +++ b/pkg/security/secl/schemas/mmap.schema.json @@ -1,20 +1,20 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "mmap.json", + "$id": "mmap.schema.json", "type": "object", "allOf": [ { - "$ref": "/schemas/event.json" + "$ref": "event.schema.json" }, { - "$ref": "/schemas/usr.json" + "$ref": "usr.schema.json" }, { - "$ref": "/schemas/process_context.json" + "$ref": "process_context.schema.json" }, { "date": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" } }, { diff --git a/pkg/security/tests/schemas/mount.schema.json b/pkg/security/secl/schemas/mount.schema.json similarity index 92% rename from pkg/security/tests/schemas/mount.schema.json rename to pkg/security/secl/schemas/mount.schema.json index 1b2570ea2e2b47..b4fa7741e516ad 100644 --- a/pkg/security/tests/schemas/mount.schema.json +++ b/pkg/security/secl/schemas/mount.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "mount.json", + "$id": "mount.schema.json", "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ], "allOf": [ diff --git a/pkg/security/tests/schemas/mprotect.schema.json b/pkg/security/secl/schemas/mprotect.schema.json similarity index 84% rename from pkg/security/tests/schemas/mprotect.schema.json rename to pkg/security/secl/schemas/mprotect.schema.json index 5d10f010f467ee..9d48937725a73a 100644 --- a/pkg/security/tests/schemas/mprotect.schema.json +++ b/pkg/security/secl/schemas/mprotect.schema.json @@ -1,20 +1,20 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "mprotect.json", + "$id": "mprotect.schema.json", "type": "object", "allOf": [ { - "$ref": "/schemas/event.json" + "$ref": "event.schema.json" }, { - "$ref": "/schemas/usr.json" + "$ref": "usr.schema.json" }, { - "$ref": "/schemas/process_context.json" + "$ref": "process_context.schema.json" }, { "date": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" } }, { diff --git a/pkg/security/tests/schemas/network.json b/pkg/security/secl/schemas/network.schema.json similarity index 98% rename from pkg/security/tests/schemas/network.json rename to pkg/security/secl/schemas/network.schema.json index 814f2ab76255a3..22d8c2374c802c 100644 --- a/pkg/security/tests/schemas/network.json +++ b/pkg/security/secl/schemas/network.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "network.json", + "$id": "network.schema.json", "type": "object", "properties": { "network": { diff --git a/pkg/security/tests/schemas/open.schema.json b/pkg/security/secl/schemas/open.schema.json similarity index 78% rename from pkg/security/tests/schemas/open.schema.json rename to pkg/security/secl/schemas/open.schema.json index 00d79202b53acb..96f8936906c084 100644 --- a/pkg/security/tests/schemas/open.schema.json +++ b/pkg/security/secl/schemas/open.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "open.json", + "$id": "open.schema.json", "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ], "allOf": [ diff --git a/pkg/security/secl/schemas/policy.schema.json b/pkg/security/secl/schemas/policy.schema.json new file mode 100644 index 00000000000000..5fceb63011b5b3 --- /dev/null +++ b/pkg/security/secl/schemas/policy.schema.json @@ -0,0 +1,457 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/DataDog/datadog-agent/tree/main/pkg/security/secl/rules", + "$defs": { + "ActionDefinition": { + "oneOf": [ + { + "required": [ + "set" + ], + "title": "SetAction" + }, + { + "required": [ + "kill" + ], + "title": "KillAction" + }, + { + "required": [ + "coredump" + ], + "title": "CoreDumpAction" + }, + { + "required": [ + "hash" + ], + "title": "HashAction" + } + ], + "properties": { + "filter": { + "type": "string" + }, + "set": { + "$ref": "#/$defs/SetDefinition" + }, + "kill": { + "$ref": "#/$defs/KillDefinition" + }, + "coredump": { + "$ref": "#/$defs/CoreDumpDefinition" + }, + "hash": { + "$ref": "#/$defs/HashDefinition" + } + }, + "additionalProperties": false, + "type": "object", + "description": "ActionDefinition describes a rule action section" + }, + "CoreDumpDefinition": { + "anyOf": [ + { + "required": [ + "process" + ], + "title": "CoreDumpWithProcess" + }, + { + "required": [ + "mount" + ], + "title": "CoreDumpWithMount" + }, + { + "required": [ + "dentry" + ], + "title": "CoreDumpWithDentry" + } + ], + "properties": { + "process": { + "type": "boolean" + }, + "mount": { + "type": "boolean" + }, + "dentry": { + "type": "boolean" + }, + "no_compression": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object", + "description": "CoreDumpDefinition describes the 'coredump' action" + }, + "HashDefinition": { + "properties": {}, + "additionalProperties": false, + "type": "object", + "description": "HashDefinition describes the 'hash' section of a rule action" + }, + "HookPointArg": { + "properties": { + "n": { + "type": "integer", + "description": "Zero-based argument index" + }, + "kind": { + "type": "string", + "enum": [ + "uint", + "null-terminated-string" + ] + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "n", + "kind" + ], + "description": "HookPointArg represents the definition of a hook point argument" + }, + "KillDefinition": { + "properties": { + "signal": { + "type": "string", + "description": "A valid signal name", + "examples": [ + "SIGKILL", + "SIGTERM" + ] + }, + "scope": { + "type": "string", + "enum": [ + "process", + "container" + ] + }, + "disarmer": { + "$ref": "#/$defs/KillDisarmerDefinition" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "signal" + ], + "description": "KillDefinition describes the 'kill' section of a rule action" + }, + "KillDisarmerDefinition": { + "properties": { + "container": { + "$ref": "#/$defs/KillDisarmerParamsDefinition" + }, + "executable": { + "$ref": "#/$defs/KillDisarmerParamsDefinition" + } + }, + "additionalProperties": false, + "type": "object", + "description": "KillDisarmerDefinition describes the 'disarmer' section of a kill action" + }, + "KillDisarmerParamsDefinition": { + "properties": { + "max_allowed": { + "type": "integer", + "description": "The maximum number of allowed kill actions within the period", + "examples": [ + 5 + ] + }, + "period": { + "oneOf": [ + { + "type": "string", + "format": "duration", + "description": "Duration in Go format (e.g. 1h30m, see https://pkg.go.dev/time#ParseDuration)" + }, + { + "type": "integer", + "description": "Duration in nanoseconds" + } + ], + "description": "The period of time during which the maximum number of allowed kill actions is calculated" + } + }, + "additionalProperties": false, + "type": "object", + "description": "KillDisarmerParamsDefinition describes the parameters of a kill action disarmer" + }, + "MacroDefinition": { + "oneOf": [ + { + "required": [ + "expression" + ], + "title": "MacroWithExpression" + }, + { + "required": [ + "values" + ], + "title": "MacroWithValues" + } + ], + "properties": { + "id": { + "type": "string" + }, + "expression": { + "type": "string" + }, + "description": { + "type": "string" + }, + "agent_version": { + "type": "string" + }, + "filters": { + "items": { + "type": "string" + }, + "type": "array" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + }, + "combine": { + "type": "string", + "enum": [ + "merge", + "override" + ] + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id" + ], + "description": "MacroDefinition holds the definition of a macro" + }, + "OnDemandHookPoint": { + "properties": { + "name": { + "type": "string" + }, + "syscall": { + "type": "boolean" + }, + "args": { + "items": { + "$ref": "#/$defs/HookPointArg" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ], + "description": "OnDemandHookPoint represents a hook point definition" + }, + "OverrideOptions": { + "properties": { + "fields": { + "items": { + "type": "string", + "enum": [ + "all", + "expression", + "actions", + "every", + "tags" + ] + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "fields" + ], + "description": "OverrideOptions defines combine options" + }, + "RuleDefinition": { + "properties": { + "id": { + "type": "string" + }, + "version": { + "type": "string" + }, + "expression": { + "type": "string" + }, + "description": { + "type": "string" + }, + "tags": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "agent_version": { + "type": "string" + }, + "filters": { + "items": { + "type": "string" + }, + "type": "array" + }, + "disabled": { + "type": "boolean" + }, + "combine": { + "type": "string", + "enum": [ + "override" + ] + }, + "override_options": { + "$ref": "#/$defs/OverrideOptions" + }, + "actions": { + "items": { + "$ref": "#/$defs/ActionDefinition" + }, + "type": "array" + }, + "every": { + "oneOf": [ + { + "type": "string", + "format": "duration", + "description": "Duration in Go format (e.g. 1h30m, see https://pkg.go.dev/time#ParseDuration)" + }, + { + "type": "integer", + "description": "Duration in nanoseconds" + } + ] + }, + "limiter_token": { + "items": { + "type": "string" + }, + "type": "array" + }, + "silent": { + "type": "boolean" + }, + "group_id": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id" + ], + "description": "RuleDefinition holds the definition of a rule" + }, + "SetDefinition": { + "oneOf": [ + { + "required": [ + "value" + ], + "title": "SetWithValue" + }, + { + "required": [ + "field" + ], + "title": "SetWithField" + } + ], + "properties": { + "name": { + "type": "string" + }, + "value": true, + "field": { + "type": "string" + }, + "append": { + "type": "boolean" + }, + "scope": { + "type": "string", + "enum": [ + "process", + "container" + ] + }, + "size": { + "type": "integer" + }, + "ttl": { + "oneOf": [ + { + "type": "string", + "format": "duration", + "description": "Duration in Go format (e.g. 1h30m, see https://pkg.go.dev/time#ParseDuration)" + }, + { + "type": "integer", + "description": "Duration in nanoseconds" + } + ] + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ], + "description": "SetDefinition describes the 'set' section of a rule action" + } + }, + "properties": { + "version": { + "type": "string" + }, + "macros": { + "items": { + "$ref": "#/$defs/MacroDefinition" + }, + "type": "array" + }, + "rules": { + "items": { + "$ref": "#/$defs/RuleDefinition" + }, + "type": "array" + }, + "hooks": { + "items": { + "$ref": "#/$defs/OnDemandHookPoint" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "version", + "rules" + ], + "description": "PolicyDef represents a policy file definition" +} \ No newline at end of file diff --git a/pkg/security/tests/schemas/process.json b/pkg/security/secl/schemas/process.schema.json similarity index 95% rename from pkg/security/tests/schemas/process.json rename to pkg/security/secl/schemas/process.schema.json index 8cd4e967e62a0b..a517391d001cb2 100644 --- a/pkg/security/tests/schemas/process.json +++ b/pkg/security/secl/schemas/process.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "process.json", + "$id": "process.schema.json", "type": "object", "properties": { "tid": { @@ -37,10 +37,10 @@ ] }, "fork_time": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" }, "exec_time": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" }, "is_kthread": { "type": "boolean" @@ -150,10 +150,10 @@ "type": "integer" }, "modification_time": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" }, "change_time": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" }, "envs": { "type": "array", @@ -176,7 +176,7 @@ ] }, "container": { - "$ref": "/schemas/container.json" + "$ref": "container.schema.json" } }, "oneOf": [ diff --git a/pkg/security/tests/schemas/process_context.json b/pkg/security/secl/schemas/process_context.schema.json similarity index 74% rename from pkg/security/tests/schemas/process_context.json rename to pkg/security/secl/schemas/process_context.schema.json index 3e60de7d63caa4..84147f66c81dcf 100644 --- a/pkg/security/tests/schemas/process_context.json +++ b/pkg/security/secl/schemas/process_context.schema.json @@ -1,26 +1,26 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "process_context.json", + "$id": "process_context.schema.json", "type": "object", "properties": { "process": { "allOf": [ { - "$ref": "/schemas/process.json" + "$ref": "process.schema.json" }, { "properties": { "parent": { - "$ref": "/schemas/process.json" + "$ref": "process.schema.json" }, "ancestors": { "type": "array", "items": { - "$ref": "/schemas/process.json" + "$ref": "process.schema.json" } }, "container": { - "$ref": "/schemas/container.json" + "$ref": "container.schema.json" } }, "required": [ diff --git a/pkg/security/tests/schemas/ptrace.schema.json b/pkg/security/secl/schemas/ptrace.schema.json similarity index 76% rename from pkg/security/tests/schemas/ptrace.schema.json rename to pkg/security/secl/schemas/ptrace.schema.json index a8a023197ea747..d30872b27e64cd 100644 --- a/pkg/security/tests/schemas/ptrace.schema.json +++ b/pkg/security/secl/schemas/ptrace.schema.json @@ -1,20 +1,20 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "ptrace.json", + "$id": "ptrace.schema.json", "type": "object", "allOf": [ { - "$ref": "/schemas/event.json" + "$ref": "event.schema.json" }, { - "$ref": "/schemas/usr.json" + "$ref": "usr.schema.json" }, { - "$ref": "/schemas/process_context.json" + "$ref": "process_context.schema.json" }, { "date": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" } }, { @@ -32,7 +32,7 @@ "type": "string" }, "tracee": { - "$ref": "/schemas/process.json" + "$ref": "process.schema.json" } } } diff --git a/pkg/security/tests/schemas/rename.schema.json b/pkg/security/secl/schemas/rename.schema.json similarity index 76% rename from pkg/security/tests/schemas/rename.schema.json rename to pkg/security/secl/schemas/rename.schema.json index a972cfd8692b9b..d7dc7f38d4c8aa 100644 --- a/pkg/security/tests/schemas/rename.schema.json +++ b/pkg/security/secl/schemas/rename.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "rename.json", + "$id": "rename.schema.json", "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ], "allOf": [ @@ -20,7 +20,7 @@ ], "properties": { "destination": { - "$ref": "/schemas/file.json" + "$ref": "file.schema.json" } } } diff --git a/pkg/security/tests/schemas/ruleset_loaded.schema.json b/pkg/security/secl/schemas/ruleset_loaded.schema.json similarity index 96% rename from pkg/security/tests/schemas/ruleset_loaded.schema.json rename to pkg/security/secl/schemas/ruleset_loaded.schema.json index 92777b10cba2f8..68b252a9e77274 100644 --- a/pkg/security/tests/schemas/ruleset_loaded.schema.json +++ b/pkg/security/secl/schemas/ruleset_loaded.schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "$id": "ruleset_loaded.json", + "$id": "ruleset_loaded.schema.json", "type": "object", "properties": { "policies": { @@ -10,7 +10,7 @@ } }, "date": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" } }, "required": [ diff --git a/pkg/security/secl/schemas/schemas.go b/pkg/security/secl/schemas/schemas.go new file mode 100644 index 00000000000000..8c3866583f1a84 --- /dev/null +++ b/pkg/security/secl/schemas/schemas.go @@ -0,0 +1,16 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +// Package schemas holds JSON schemas validation code +package schemas + +import ( + "embed" +) + +// AssetFS holds the embedded JSON schemas +// +//go:embed *.schema.json +var AssetFS embed.FS diff --git a/pkg/security/tests/schemas/self_test_schema.json b/pkg/security/secl/schemas/self_test_schema.json similarity index 83% rename from pkg/security/tests/schemas/self_test_schema.json rename to pkg/security/secl/schemas/self_test_schema.json index 506b8395c5fa00..4c2604c21cd7e9 100644 --- a/pkg/security/tests/schemas/self_test_schema.json +++ b/pkg/security/secl/schemas/self_test_schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "self_test.json", + "$id": "self_test.schema.json", "type": "object", "properties": { "agent": { @@ -16,7 +16,7 @@ "required": ["rule_id", "version"] }, "date": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" }, "hostname": { "type": "string" @@ -49,13 +49,16 @@ "type": "object", "properties": { "datadog_agent_cws_self_test_rule_open": { - "$ref": "/schemas/open.json" + "$ref": "open.schema.json" }, "datadog_agent_cws_self_test_rule_chmod": { - "$ref": "/schemas/chmod.json" + "$ref": "chmod.schema.json" }, "datadog_agent_cws_self_test_rule_chown": { - "$ref": "/schemas/chown.json" + "$ref": "chown.schema.json" + }, + "datadog_agent_cws_self_test_rule_exec": { + "$ref": "exec.schema.json" } } } diff --git a/pkg/security/tests/schemas/selinux.schema.json b/pkg/security/secl/schemas/selinux.schema.json similarity index 95% rename from pkg/security/tests/schemas/selinux.schema.json rename to pkg/security/secl/schemas/selinux.schema.json index 5cff42b712ef4b..54619cf41f6503 100644 --- a/pkg/security/tests/schemas/selinux.schema.json +++ b/pkg/security/secl/schemas/selinux.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "selinux.json", + "$id": "selinux.schema.json", "definitions": { "bool": { "type": "object", @@ -74,10 +74,10 @@ "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ], "properties": { diff --git a/pkg/security/tests/schemas/signal.schema.json b/pkg/security/secl/schemas/signal.schema.json similarity index 76% rename from pkg/security/tests/schemas/signal.schema.json rename to pkg/security/secl/schemas/signal.schema.json index a1c0fdff431919..ec9851a087e374 100644 --- a/pkg/security/tests/schemas/signal.schema.json +++ b/pkg/security/secl/schemas/signal.schema.json @@ -1,20 +1,20 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "signal.json", + "$id": "signal.schema.json", "type": "object", "allOf": [ { - "$ref": "/schemas/event.json" + "$ref": "event.schema.json" }, { - "$ref": "/schemas/usr.json" + "$ref": "usr.schema.json" }, { - "$ref": "/schemas/process_context.json" + "$ref": "process_context.schema.json" }, { "date": { - "$ref": "/schemas/datetime.json" + "$ref": "datetime.schema.json" } }, { @@ -34,7 +34,7 @@ "type": "integer" }, "target": { - "$ref": "/schemas/process.json" + "$ref": "process.schema.json" } } } diff --git a/pkg/security/tests/schemas/span.schema.json b/pkg/security/secl/schemas/span.schema.json similarity index 57% rename from pkg/security/tests/schemas/span.schema.json rename to pkg/security/secl/schemas/span.schema.json index 8ef452ac1bb6fb..f12ab4c05b96b8 100644 --- a/pkg/security/tests/schemas/span.schema.json +++ b/pkg/security/secl/schemas/span.schema.json @@ -1,20 +1,20 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "span.json", + "$id": "span.schema.json", "type": "object", "allOf": [ { "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ] }, { - "$ref": "/schemas/span_context.json" + "$ref": "span_context.schema.json" } ] } \ No newline at end of file diff --git a/pkg/security/tests/schemas/span_context.json b/pkg/security/secl/schemas/span_context.schema.json similarity index 91% rename from pkg/security/tests/schemas/span_context.json rename to pkg/security/secl/schemas/span_context.schema.json index eb259271fa4639..149b85c48a6a64 100644 --- a/pkg/security/tests/schemas/span_context.json +++ b/pkg/security/secl/schemas/span_context.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "span_context.json", + "$id": "span_context.schema.json", "type": "object", "properties": { "dd": { diff --git a/pkg/security/tests/schemas/splice.schema.json b/pkg/security/secl/schemas/splice.schema.json similarity index 86% rename from pkg/security/tests/schemas/splice.schema.json rename to pkg/security/secl/schemas/splice.schema.json index 36220d5e69a8ba..aaf433f77915f7 100644 --- a/pkg/security/tests/schemas/splice.schema.json +++ b/pkg/security/secl/schemas/splice.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "splice.json", + "$id": "splice.schema.json", "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ], "allOf": [ diff --git a/pkg/security/tests/schemas/unload_module.schema.json b/pkg/security/secl/schemas/unload_module.schema.json similarity index 79% rename from pkg/security/tests/schemas/unload_module.schema.json rename to pkg/security/secl/schemas/unload_module.schema.json index fbec0b070d9514..d026c73578ca34 100644 --- a/pkg/security/tests/schemas/unload_module.schema.json +++ b/pkg/security/secl/schemas/unload_module.schema.json @@ -1,13 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "unload_module.json", + "$id": "unload_module.schema.json", "type": "object", "anyOf": [ { - "$ref": "/schemas/container_event_no_file.json" + "$ref": "container_event_no_file.schema.json" }, { - "$ref": "/schemas/host_event_no_file.json" + "$ref": "host_event_no_file.schema.json" } ], "allOf": [ diff --git a/pkg/security/tests/schemas/user_session.schema.json b/pkg/security/secl/schemas/user_session.schema.json similarity index 56% rename from pkg/security/tests/schemas/user_session.schema.json rename to pkg/security/secl/schemas/user_session.schema.json index 8f7ba10082fb94..9fd49e1fb5ac91 100644 --- a/pkg/security/tests/schemas/user_session.schema.json +++ b/pkg/security/secl/schemas/user_session.schema.json @@ -1,20 +1,20 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "user_session.json", + "$id": "user_session.schema.json", "type": "object", "allOf": [ { "anyOf": [ { - "$ref": "/schemas/container_event.json" + "$ref": "container_event.schema.json" }, { - "$ref": "/schemas/host_event.json" + "$ref": "host_event.schema.json" } ] }, { - "$ref": "/schemas/user_session_context.json" + "$ref": "user_session_context.schema.json" } ] } diff --git a/pkg/security/tests/schemas/user_session_context.json b/pkg/security/secl/schemas/user_session_context.schema.json similarity index 95% rename from pkg/security/tests/schemas/user_session_context.json rename to pkg/security/secl/schemas/user_session_context.schema.json index 4e380a3c30d43a..04c03f4233d66a 100644 --- a/pkg/security/tests/schemas/user_session_context.json +++ b/pkg/security/secl/schemas/user_session_context.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "user_session_context.json", + "$id": "user_session_context.schema.json", "type": "object", "properties": { "process": { diff --git a/pkg/security/tests/schemas/usr.json b/pkg/security/secl/schemas/usr.schema.json similarity index 92% rename from pkg/security/tests/schemas/usr.json rename to pkg/security/secl/schemas/usr.schema.json index 132e029f6aaa30..4279c1430aa6ab 100644 --- a/pkg/security/tests/schemas/usr.json +++ b/pkg/security/secl/schemas/usr.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "usr.json", + "$id": "usr.schema.json", "type": "object", "properties": { "usr": { diff --git a/pkg/security/tests/schemas.go b/pkg/security/tests/schemas.go index 5e062fdefa7d80..0ee7d7767b9e7b 100644 --- a/pkg/security/tests/schemas.go +++ b/pkg/security/tests/schemas.go @@ -9,75 +9,43 @@ package tests import ( - "embed" + "fmt" "math/big" "net/http" + "os" "testing" - "github.com/xeipuuv/gojsonschema" - "github.com/DataDog/datadog-agent/pkg/security/events" "github.com/DataDog/datadog-agent/pkg/security/resolvers/dentry" "github.com/DataDog/datadog-agent/pkg/security/secl/model" + "github.com/DataDog/datadog-agent/pkg/security/secl/schemas" "github.com/DataDog/datadog-agent/pkg/security/serializers" + "github.com/xeipuuv/gojsonschema" ) -//nolint:deadcode,unused -//go:embed schemas -var schemaAssetFS embed.FS +func getUpstreamEventSchema() string { + sha, _ := os.LookupEnv("CI_COMMIT_SHA") + if sha == "" { + sha = "main" + } + return fmt.Sprintf("https://raw.githubusercontent.com/DataDog/datadog-agent/%s/docs/cloud-workload-security/backend_linux.schema.json", sha) +} -// ValidInodeFormatChecker defines the format inode checker -// -//nolint:deadcode,unused -type ValidInodeFormatChecker struct{} +var upstreamEventSchema = getUpstreamEventSchema() -// IsFormat check inode format -// //nolint:deadcode,unused -func (v ValidInodeFormatChecker) IsFormat(input interface{}) bool { - - var inode uint64 - switch t := input.(type) { - case float64: - inode = uint64(t) - case *big.Int: - inode = t.Uint64() - case *big.Float: - inode, _ = t.Uint64() - case *big.Rat: - f, _ := t.Float64() - inode = uint64(f) - default: - return false - } - return !dentry.IsFakeInode(inode) +func validateActivityDumpProtoSchema(t *testing.T, ad string) bool { + t.Helper() + return validateUrlSchema(t, ad, "file:///activity_dump_proto.schema.json") } //nolint:deadcode,unused -func validateStringSchema(t *testing.T, json string, path string) bool { +func validateMessageSchema(t *testing.T, msg string) bool { t.Helper() - - fs := http.FS(schemaAssetFS) - gojsonschema.FormatCheckers.Add("ValidInode", ValidInodeFormatChecker{}) - - documentLoader := gojsonschema.NewStringLoader(json) - schemaLoader := gojsonschema.NewReferenceLoaderFileSystem(path, fs) - - result, err := gojsonschema.Validate(schemaLoader, documentLoader) - if err != nil { - t.Error(err) + if !validateUrlSchema(t, msg, "file:///message.schema.json") { return false } - - if !result.Valid() { - for _, desc := range result.Errors() { - t.Errorf("%s", desc) - } - t.Error(json) - return false - } - - return true + return validateUrlSchema(t, msg, upstreamEventSchema) } //nolint:deadcode,unused @@ -96,7 +64,7 @@ func (tm *testModule) validateEventSchema(t *testing.T, event *model.Event, path //nolint:deadcode,unused func (tm *testModule) validateExecSchema(t *testing.T, event *model.Event) bool { t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/exec.schema.json") + return tm.validateEventSchema(t, event, "file:///exec.schema.json") } //nolint:deadcode,unused @@ -106,7 +74,7 @@ func (tm *testModule) validateExitSchema(t *testing.T, event *model.Event) bool } t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/exit.schema.json") + return tm.validateEventSchema(t, event, "file:///exit.schema.json") } //nolint:deadcode,unused @@ -116,7 +84,7 @@ func (tm *testModule) validateOpenSchema(t *testing.T, event *model.Event) bool } t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/open.schema.json") + return tm.validateEventSchema(t, event, "file:///open.schema.json") } //nolint:deadcode,unused @@ -126,7 +94,7 @@ func (tm *testModule) validateRenameSchema(t *testing.T, event *model.Event) boo } t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/rename.schema.json") + return tm.validateEventSchema(t, event, "file:///rename.schema.json") } //nolint:deadcode,unused @@ -136,7 +104,7 @@ func (tm *testModule) validateChmodSchema(t *testing.T, event *model.Event) bool } t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/chmod.schema.json") + return tm.validateEventSchema(t, event, "file:///chmod.schema.json") } //nolint:deadcode,unused @@ -146,13 +114,13 @@ func (tm *testModule) validateChownSchema(t *testing.T, event *model.Event) bool } t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/chown.schema.json") + return tm.validateEventSchema(t, event, "file:///chown.schema.json") } //nolint:deadcode,unused func (tm *testModule) validateSELinuxSchema(t *testing.T, event *model.Event) bool { t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/selinux.schema.json") + return tm.validateEventSchema(t, event, "file:///selinux.schema.json") } //nolint:deadcode,unused @@ -162,43 +130,47 @@ func (tm *testModule) validateLinkSchema(t *testing.T, event *model.Event) bool } t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/link.schema.json") + return tm.validateEventSchema(t, event, "file:///link.schema.json") } //nolint:deadcode,unused func (tm *testModule) validateSpanSchema(t *testing.T, event *model.Event) bool { + if ebpfLessEnabled { + return true + } + t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/span.schema.json") + return tm.validateEventSchema(t, event, "file:///span.schema.json") } //nolint:deadcode,unused func (tm *testModule) validateUserSessionSchema(t *testing.T, event *model.Event) bool { t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/user_session.schema.json") + return tm.validateEventSchema(t, event, "file:///user_session.schema.json") } //nolint:deadcode,unused func (tm *testModule) validateBPFSchema(t *testing.T, event *model.Event) bool { t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/bpf.schema.json") + return tm.validateEventSchema(t, event, "file:///bpf.schema.json") } //nolint:deadcode,unused func (tm *testModule) validateMMapSchema(t *testing.T, event *model.Event) bool { t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/mmap.schema.json") + return tm.validateEventSchema(t, event, "file:///mmap.schema.json") } //nolint:deadcode,unused func (tm *testModule) validateMProtectSchema(t *testing.T, event *model.Event) bool { t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/mprotect.schema.json") + return tm.validateEventSchema(t, event, "file:///mprotect.schema.json") } //nolint:deadcode,unused func (tm *testModule) validatePTraceSchema(t *testing.T, event *model.Event) bool { t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/ptrace.schema.json") + return tm.validateEventSchema(t, event, "file:///ptrace.schema.json") } //nolint:deadcode,unused @@ -208,7 +180,7 @@ func (tm *testModule) validateLoadModuleSchema(t *testing.T, event *model.Event) } t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/load_module.schema.json") + return tm.validateEventSchema(t, event, "file:///load_module.schema.json") } //nolint:deadcode,unused @@ -218,7 +190,7 @@ func (tm *testModule) validateLoadModuleNoFileSchema(t *testing.T, event *model. } t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/load_module_no_file.schema.json") + return tm.validateEventSchema(t, event, "file:///load_module_no_file.schema.json") } //nolint:deadcode,unused @@ -228,37 +200,53 @@ func (tm *testModule) validateUnloadModuleSchema(t *testing.T, event *model.Even } t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/unload_module.schema.json") + return tm.validateEventSchema(t, event, "file:///unload_module.schema.json") } //nolint:deadcode,unused func (tm *testModule) validateSignalSchema(t *testing.T, event *model.Event) bool { t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/signal.schema.json") + return tm.validateEventSchema(t, event, "file:///signal.schema.json") } //nolint:deadcode,unused func (tm *testModule) validateSpliceSchema(t *testing.T, event *model.Event) bool { t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/splice.schema.json") + return tm.validateEventSchema(t, event, "file:///splice.schema.json") } //nolint:deadcode,unused func (tm *testModule) validateDNSSchema(t *testing.T, event *model.Event) bool { t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/dns.schema.json") + return tm.validateEventSchema(t, event, "file:///dns.schema.json") +} + +//nolint:deadcode,unused +func (tm *testModule) validateIMDSSchema(t *testing.T, event *model.Event) bool { + t.Helper() + return tm.validateEventSchema(t, event, "file:///imds.schema.json") } //nolint:deadcode,unused func (tm *testModule) validateBindSchema(t *testing.T, event *model.Event) bool { t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/bind.schema.json") + return tm.validateEventSchema(t, event, "file:///bind.schema.json") +} + +//nolint:deadcode,unused +func (tm *testModule) validateConnectSchema(t *testing.T, event *model.Event) bool { + t.Helper() + return tm.validateEventSchema(t, event, "file:///connect.schema.json") } //nolint:deadcode,unused func (tm *testModule) validateMountSchema(t *testing.T, event *model.Event) bool { + if ebpfLessEnabled { + return true + } + t.Helper() - return tm.validateEventSchema(t, event, "file:///schemas/mount.schema.json") + return tm.validateEventSchema(t, event, "file:///mount.schema.json") } //nolint:deadcode,unused @@ -271,7 +259,7 @@ func validateRuleSetLoadedSchema(t *testing.T, event *events.CustomEvent) bool { return false } - return validateStringSchema(t, string(eventJSON), "file:///schemas/ruleset_loaded.schema.json") + return validateUrlSchema(t, string(eventJSON), "file:///ruleset_loaded.schema.json") } //nolint:deadcode,unused @@ -284,11 +272,88 @@ func validateHeartbeatSchema(t *testing.T, event *events.CustomEvent) bool { return false } - return validateStringSchema(t, string(eventJSON), "file:///schemas/heartbeat.schema.json") + return validateUrlSchema(t, string(eventJSON), "file:///heartbeat.schema.json") } +// ValidInodeFormatChecker defines the format inode checker +// //nolint:deadcode,unused -func validateActivityDumpProtoSchema(t *testing.T, ad string) bool { +type ValidInodeFormatChecker struct{} + +// IsFormat check inode format +// +//nolint:deadcode,unused +func (v ValidInodeFormatChecker) IsFormat(input interface{}) bool { + + var inode uint64 + switch t := input.(type) { + case float64: + inode = uint64(t) + case *big.Int: + inode = t.Uint64() + case *big.Float: + inode, _ = t.Uint64() + case *big.Rat: + f, _ := t.Float64() + inode = uint64(f) + default: + return false + } + return !dentry.IsFakeInode(inode) +} + +func validateSchema(t *testing.T, schemaLoader gojsonschema.JSONLoader, documentLoader gojsonschema.JSONLoader) bool { + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + if err != nil { + t.Error(err) + return false + } + + success := true + + if !result.Valid() { + for _, err := range result.Errors() { + // allow addition properties + if err.Type() == "additional_property_not_allowed" { + continue + } + + t.Error(err) + success = false + } + } + return success +} + +//nolint:deadcode,unused +func validateStringSchema(t *testing.T, json string, path string) bool { + t.Helper() + + fs := http.FS(schemas.AssetFS) + gojsonschema.FormatCheckers.Add("ValidInode", ValidInodeFormatChecker{}) + + documentLoader := gojsonschema.NewStringLoader(json) + schemaLoader := gojsonschema.NewReferenceLoaderFileSystem(path, fs) + + if !validateSchema(t, schemaLoader, documentLoader) { + t.Error(json) + return false + } + + return true +} + +//nolint:deadcode,unused +func validateUrlSchema(t *testing.T, json string, url string) bool { t.Helper() - return validateStringSchema(t, ad, "file:///schemas/activity_dump_proto.schema.json") + + documentLoader := gojsonschema.NewStringLoader(json) + schemaLoader := gojsonschema.NewReferenceLoader(url) + + if !validateSchema(t, schemaLoader, documentLoader) { + t.Error(json) + return false + } + + return true } diff --git a/pkg/security/tests/schemas/container_event.json b/pkg/security/tests/schemas/container_event.json deleted file mode 100644 index 8da9674f17f6b3..00000000000000 --- a/pkg/security/tests/schemas/container_event.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "container_event.json", - "allOf": [ - { - "$ref": "/schemas/host_event.json" - }, - { - "$ref": "/schemas/container_context.json" - } - ] -} diff --git a/pkg/security/tests/schemas/container_event_no_file.json b/pkg/security/tests/schemas/container_event_no_file.json deleted file mode 100644 index 6adba61b6bd40c..00000000000000 --- a/pkg/security/tests/schemas/container_event_no_file.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "container_event_no_file.json", - "allOf": [ - { - "$ref": "/schemas/host_event_no_file.json" - }, - { - "$ref": "/schemas/container_context.json" - } - ] -} diff --git a/pkg/security/tests/schemas/host_event.json b/pkg/security/tests/schemas/host_event.json deleted file mode 100644 index 9971c36e7c6195..00000000000000 --- a/pkg/security/tests/schemas/host_event.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "host_event.json", - "allOf": [ - { - "$ref": "/schemas/event.json" - }, - { - "file": { "$ref": "/schemas/file.json" } - }, - { - "$ref": "/schemas/usr.json" - }, - { - "$ref": "/schemas/process_context.json" - }, - { - "date": { - "$ref": "/schemas/datetime.json" - } - } - ] -} diff --git a/pkg/security/tests/schemas/host_event_no_file.json b/pkg/security/tests/schemas/host_event_no_file.json deleted file mode 100644 index eb83c82682b75a..00000000000000 --- a/pkg/security/tests/schemas/host_event_no_file.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "host_event_no_file.json", - "allOf": [ - { - "$ref": "/schemas/event.json" - }, - { - "$ref": "/schemas/usr.json" - }, - { - "$ref": "/schemas/process_context.json" - }, - { - "date": { - "$ref": "/schemas/datetime.json" - } - } - ] -}