github.com/DataDog/datadog-agent/pkg/security/secl@v0.55.0-devel.0.20240517055856-10c4965fea94/rules/policy_test.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the Apache License Version 2.0.
     3  // This product includes software developed at Datadog (https://www.datadoghq.com/).
     4  // Copyright 2016-present Datadog, Inc.
     5  
     6  //go:build linux
     7  
     8  // Package rules holds rules related files
     9  package rules
    10  
    11  import (
    12  	"fmt"
    13  	"os"
    14  	"path/filepath"
    15  	"strings"
    16  	"syscall"
    17  	"testing"
    18  
    19  	"github.com/google/go-cmp/cmp"
    20  	"github.com/google/go-cmp/cmp/cmpopts"
    21  
    22  	"github.com/Masterminds/semver/v3"
    23  	"github.com/hashicorp/go-multierror"
    24  	"github.com/stretchr/testify/assert"
    25  	"gopkg.in/yaml.v3"
    26  
    27  	"github.com/DataDog/datadog-agent/pkg/security/secl/compiler/eval"
    28  	"github.com/DataDog/datadog-agent/pkg/security/secl/model"
    29  )
    30  
    31  func savePolicy(filename string, testPolicy *PolicyDef) error {
    32  	yamlBytes, err := yaml.Marshal(testPolicy)
    33  	if err != nil {
    34  		return err
    35  	}
    36  
    37  	return os.WriteFile(filename, yamlBytes, 0700)
    38  }
    39  
    40  func TestMacroMerge(t *testing.T) {
    41  	testPolicy := &PolicyDef{
    42  		Rules: []*RuleDefinition{{
    43  			ID:         "test_rule",
    44  			Expression: `open.file.path == "/tmp/test" && process.name == "/usr/bin/vim"`,
    45  		}},
    46  		Macros: []*MacroDefinition{{
    47  			ID:     "test_macro",
    48  			Values: []string{"/usr/bin/vi"},
    49  		}},
    50  	}
    51  
    52  	testPolicy2 := &PolicyDef{
    53  		Macros: []*MacroDefinition{{
    54  			ID:      "test_macro",
    55  			Values:  []string{"/usr/bin/vim"},
    56  			Combine: MergePolicy,
    57  		}},
    58  	}
    59  
    60  	tmpDir := t.TempDir()
    61  
    62  	if err := savePolicy(filepath.Join(tmpDir, "test.policy"), testPolicy); err != nil {
    63  		t.Fatal(err)
    64  	}
    65  
    66  	if err := savePolicy(filepath.Join(tmpDir, "test2.policy"), testPolicy2); err != nil {
    67  		t.Fatal(err)
    68  	}
    69  
    70  	event := model.NewFakeEvent()
    71  	event.SetFieldValue("open.file.path", "/tmp/test")
    72  	event.SetFieldValue("process.comm", "/usr/bin/vi")
    73  
    74  	provider, err := NewPoliciesDirProvider(tmpDir, false)
    75  	if err != nil {
    76  		t.Fatal(err)
    77  	}
    78  	loader := NewPolicyLoader(provider)
    79  
    80  	evaluationSet, _ := newTestEvaluationSet([]eval.RuleSetTagValue{DefaultRuleSetTagValue})
    81  	if errs := evaluationSet.LoadPolicies(loader, PolicyLoaderOpts{}); errs.ErrorOrNil() != nil {
    82  		t.Error(err)
    83  	}
    84  
    85  	macro := evaluationSet.RuleSets[DefaultRuleSetTagValue].evalOpts.MacroStore.Get("test_macro")
    86  	if macro == nil {
    87  		t.Fatalf("failed to find test_macro in ruleset: %+v", evaluationSet.RuleSets[DefaultRuleSetTagValue].evalOpts.MacroStore.List())
    88  	}
    89  
    90  	testPolicy2.Macros[0].Combine = ""
    91  
    92  	if err := savePolicy(filepath.Join(tmpDir, "test2.policy"), testPolicy2); err != nil {
    93  		t.Fatal(err)
    94  	}
    95  
    96  	if err := evaluationSet.LoadPolicies(loader, PolicyLoaderOpts{}); err == nil {
    97  		t.Error("expected macro ID conflict")
    98  	}
    99  }
   100  
   101  func TestRuleMerge(t *testing.T) {
   102  	testPolicy := &PolicyDef{
   103  		Rules: []*RuleDefinition{{
   104  			ID:         "test_rule",
   105  			Expression: `open.file.path == "/tmp/test"`,
   106  		}},
   107  	}
   108  
   109  	testPolicy2 := &PolicyDef{
   110  		Rules: []*RuleDefinition{{
   111  			ID:         "test_rule",
   112  			Expression: `open.file.path == "/tmp/test"`,
   113  			Combine:    OverridePolicy,
   114  		}},
   115  	}
   116  
   117  	tmpDir := t.TempDir()
   118  
   119  	if err := savePolicy(filepath.Join(tmpDir, "test.policy"), testPolicy); err != nil {
   120  		t.Fatal(err)
   121  	}
   122  
   123  	if err := savePolicy(filepath.Join(tmpDir, "test2.policy"), testPolicy2); err != nil {
   124  		t.Fatal(err)
   125  	}
   126  
   127  	provider, err := NewPoliciesDirProvider(tmpDir, false)
   128  	if err != nil {
   129  		t.Fatal(err)
   130  	}
   131  	loader := NewPolicyLoader(provider)
   132  
   133  	evaluationSet, _ := newTestEvaluationSet([]eval.RuleSetTagValue{DefaultRuleSetTagValue})
   134  	if errs := evaluationSet.LoadPolicies(loader, PolicyLoaderOpts{}); errs.ErrorOrNil() != nil {
   135  		t.Error(err)
   136  	}
   137  
   138  	rule := evaluationSet.RuleSets[DefaultRuleSetTagValue].GetRules()["test_rule"]
   139  	if rule == nil {
   140  		t.Fatal("failed to find test_rule in ruleset")
   141  	}
   142  
   143  	testPolicy2.Rules[0].Combine = ""
   144  
   145  	if err := savePolicy(filepath.Join(tmpDir, "test2.policy"), testPolicy2); err != nil {
   146  		t.Fatal(err)
   147  	}
   148  
   149  	if err := evaluationSet.LoadPolicies(loader, PolicyLoaderOpts{}); err == nil {
   150  		t.Error("expected rule ID conflict")
   151  	}
   152  }
   153  
   154  func TestActionSetVariable(t *testing.T) {
   155  	testPolicy := &PolicyDef{
   156  		Rules: []*RuleDefinition{{
   157  			ID:         "test_rule",
   158  			Expression: `open.file.path == "/tmp/test"`,
   159  			Actions: []*ActionDefinition{{
   160  				Set: &SetDefinition{
   161  					Name:  "var1",
   162  					Value: true,
   163  				},
   164  			}, {
   165  				Set: &SetDefinition{
   166  					Name:  "var2",
   167  					Value: "value",
   168  				},
   169  			}, {
   170  				Set: &SetDefinition{
   171  					Name:  "var3",
   172  					Value: 123,
   173  				},
   174  			}, {
   175  				Set: &SetDefinition{
   176  					Name:  "var4",
   177  					Value: 123,
   178  					Scope: "process",
   179  				},
   180  			}, {
   181  				Set: &SetDefinition{
   182  					Name: "var5",
   183  					Value: []string{
   184  						"val1",
   185  					},
   186  				},
   187  			}, {
   188  				Set: &SetDefinition{
   189  					Name: "var6",
   190  					Value: []int{
   191  						123,
   192  					},
   193  				},
   194  			}, {
   195  				Set: &SetDefinition{
   196  					Name:   "var7",
   197  					Append: true,
   198  					Value: []string{
   199  						"aaa",
   200  					},
   201  				},
   202  			}, {
   203  				Set: &SetDefinition{
   204  					Name:   "var8",
   205  					Append: true,
   206  					Value: []int{
   207  						123,
   208  					},
   209  				},
   210  			}, {
   211  				Set: &SetDefinition{
   212  					Name:  "var9",
   213  					Field: "open.file.path",
   214  				},
   215  			}, {
   216  				Set: &SetDefinition{
   217  					Name:   "var10",
   218  					Field:  "open.file.path",
   219  					Append: true,
   220  				},
   221  			}},
   222  		}, {
   223  			ID: "test_rule2",
   224  			Expression: `open.file.path == "/tmp/test2" && ` +
   225  				`${var1} == true && ` +
   226  				`"${var2}" == "value" && ` +
   227  				`${var2} == "value" && ` +
   228  				`${var3} == 123 && ` +
   229  				`${process.var4} == 123 && ` +
   230  				`"val1" in ${var5} && ` +
   231  				`123 in ${var6} && ` +
   232  				`"aaa" in ${var7} && ` +
   233  				`123 in ${var8} && ` +
   234  				`${var9} == "/tmp/test" && ` +
   235  				`"/tmp/test" in ${var10}`,
   236  		}},
   237  	}
   238  
   239  	tmpDir := t.TempDir()
   240  
   241  	if err := savePolicy(filepath.Join(tmpDir, "test.policy"), testPolicy); err != nil {
   242  		t.Fatal(err)
   243  	}
   244  
   245  	provider, err := NewPoliciesDirProvider(tmpDir, false)
   246  	if err != nil {
   247  		t.Fatal(err)
   248  	}
   249  	loader := NewPolicyLoader(provider)
   250  
   251  	evaluationSet, _ := newTestEvaluationSet([]eval.RuleSetTagValue{DefaultRuleSetTagValue})
   252  	if errs := evaluationSet.LoadPolicies(loader, PolicyLoaderOpts{}); errs.ErrorOrNil() != nil {
   253  		t.Error(err)
   254  	}
   255  
   256  	rule := evaluationSet.RuleSets[DefaultRuleSetTagValue].GetRules()["test_rule"]
   257  	if rule == nil {
   258  		t.Fatal("failed to find test_rule in ruleset")
   259  	}
   260  
   261  	event := model.NewFakeEvent()
   262  	event.Type = uint32(model.FileOpenEventType)
   263  	processCacheEntry := &model.ProcessCacheEntry{}
   264  	processCacheEntry.Retain()
   265  	event.ProcessCacheEntry = processCacheEntry
   266  	event.SetFieldValue("open.file.path", "/tmp/test2")
   267  	event.SetFieldValue("open.flags", syscall.O_RDONLY)
   268  
   269  	if evaluationSet.RuleSets[DefaultRuleSetTagValue].Evaluate(event) {
   270  		t.Errorf("Expected event to match no rule")
   271  	}
   272  
   273  	event.SetFieldValue("open.file.path", "/tmp/test")
   274  
   275  	if !evaluationSet.RuleSets[DefaultRuleSetTagValue].Evaluate(event) {
   276  		t.Errorf("Expected event to match rule")
   277  	}
   278  
   279  	event.SetFieldValue("open.file.path", "/tmp/test2")
   280  	if !evaluationSet.RuleSets[DefaultRuleSetTagValue].Evaluate(event) {
   281  		t.Errorf("Expected event to match rule")
   282  	}
   283  
   284  	scopedVariables := evaluationSet.RuleSets[DefaultRuleSetTagValue].scopedVariables["process"].(*eval.ScopedVariables)
   285  
   286  	assert.Equal(t, scopedVariables.Len(), 1)
   287  	event.ProcessCacheEntry.Release()
   288  	assert.Equal(t, scopedVariables.Len(), 0)
   289  }
   290  
   291  func TestActionSetVariableConflict(t *testing.T) {
   292  	testPolicy := &PolicyDef{
   293  		Rules: []*RuleDefinition{{
   294  			ID:         "test_rule",
   295  			Expression: `open.file.path == "/tmp/test"`,
   296  			Actions: []*ActionDefinition{{
   297  				Set: &SetDefinition{
   298  					Name:  "var1",
   299  					Value: true,
   300  				},
   301  			}, {
   302  				Set: &SetDefinition{
   303  					Name:  "var1",
   304  					Value: "value",
   305  				},
   306  			}},
   307  		}, {
   308  			ID: "test_rule2",
   309  			Expression: `open.file.path == "/tmp/test2" && ` +
   310  				`${var1} == true`,
   311  		}},
   312  	}
   313  
   314  	tmpDir := t.TempDir()
   315  
   316  	if err := savePolicy(filepath.Join(tmpDir, "test.policy"), testPolicy); err != nil {
   317  		t.Fatal(err)
   318  	}
   319  
   320  	provider, err := NewPoliciesDirProvider(tmpDir, false)
   321  	if err != nil {
   322  		t.Fatal(err)
   323  	}
   324  	loader := NewPolicyLoader(provider)
   325  
   326  	evaluationSet, _ := newTestEvaluationSet([]eval.RuleSetTagValue{DefaultRuleSetTagValue})
   327  	if errs := evaluationSet.LoadPolicies(loader, PolicyLoaderOpts{}); errs.ErrorOrNil() == nil {
   328  		t.Error("expected policy to fail to load")
   329  	}
   330  }
   331  
   332  func TestRuleErrorLoading(t *testing.T) {
   333  	testPolicy := &PolicyDef{
   334  		Rules: []*RuleDefinition{
   335  			{
   336  				ID:         "testA",
   337  				Expression: `open.file.path == "/tmp/test"`,
   338  			},
   339  			{
   340  				ID:         "testB",
   341  				Expression: `open.file.path =-= "/tmp/test"`,
   342  			},
   343  			{
   344  				ID:         "testA",
   345  				Expression: `open.file.path == "/tmp/toto"`,
   346  			},
   347  		},
   348  	}
   349  
   350  	es, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{})
   351  	rs := es.RuleSets[DefaultRuleSetTagValue]
   352  	assert.NotNil(t, err)
   353  	assert.Len(t, err.Errors, 2)
   354  	assert.ErrorContains(t, err.Errors[0], "rule `testA` error: multiple definition with the same ID")
   355  	assert.ErrorContains(t, err.Errors[1], "rule `testB` error: syntax error `1:17: unexpected token \"-\" (expected \"~\")`")
   356  
   357  	assert.Contains(t, rs.rules, "testA")
   358  	assert.NotContains(t, rs.rules, "testB")
   359  }
   360  
   361  func TestRuleAgentConstraint(t *testing.T) {
   362  	testPolicy := &PolicyDef{
   363  		Macros: []*MacroDefinition{
   364  			{
   365  				ID:         "macro1",
   366  				Expression: `[1, 2]`,
   367  			},
   368  			{
   369  				ID:                     "macro2",
   370  				Expression:             `[3, 4]`,
   371  				AgentVersionConstraint: ">= 7.37, < 7.38",
   372  			},
   373  			{
   374  				ID:                     "macro2",
   375  				Expression:             `[3, 4, 5]`,
   376  				AgentVersionConstraint: ">= 7.38",
   377  			},
   378  		},
   379  		Rules: []*RuleDefinition{
   380  			{
   381  				ID:         "no_constraint",
   382  				Expression: `open.file.path == "/tmp/test"`,
   383  			},
   384  			{
   385  				ID:                     "conflict",
   386  				Expression:             `open.file.path == "/tmp/test1"`,
   387  				AgentVersionConstraint: "< 7.37",
   388  			},
   389  			{
   390  				ID:                     "conflict",
   391  				Expression:             `open.file.path == "/tmp/test2"`,
   392  				AgentVersionConstraint: ">= 7.37",
   393  			},
   394  			{
   395  				ID:                     "basic",
   396  				Expression:             `open.file.path == "/tmp/test"`,
   397  				AgentVersionConstraint: "< 7.37",
   398  			},
   399  			{
   400  				ID:                     "basic2",
   401  				Expression:             `open.file.path == "/tmp/test"`,
   402  				AgentVersionConstraint: "> 7.37",
   403  			},
   404  			{
   405  				ID:                     "range",
   406  				Expression:             `open.file.path == "/tmp/test"`,
   407  				AgentVersionConstraint: ">= 7.30, < 7.39",
   408  			},
   409  			{
   410  				ID:                     "range_not",
   411  				Expression:             `open.file.path == "/tmp/test"`,
   412  				AgentVersionConstraint: ">= 7.30, < 7.39, != 7.38",
   413  			},
   414  			{
   415  				ID:                     "rc_prerelease",
   416  				Expression:             `open.file.path == "/tmp/test"`,
   417  				AgentVersionConstraint: ">= 7.38",
   418  			},
   419  			{
   420  				ID:                     "with_macro1",
   421  				Expression:             `open.file.path == "/tmp/test" && open.mode in macro1`,
   422  				AgentVersionConstraint: ">= 7.38",
   423  			},
   424  			{
   425  				ID:                     "with_macro2",
   426  				Expression:             `open.file.path == "/tmp/test" && open.mode in macro2`,
   427  				AgentVersionConstraint: ">= 7.38",
   428  			},
   429  		},
   430  	}
   431  
   432  	expected := []struct {
   433  		ruleID       string
   434  		expectedLoad bool
   435  	}{
   436  		{
   437  			ruleID:       "no_constraint",
   438  			expectedLoad: true,
   439  		},
   440  		{
   441  			ruleID:       "conflict",
   442  			expectedLoad: true,
   443  		},
   444  		{
   445  			ruleID:       "basic",
   446  			expectedLoad: false,
   447  		},
   448  		{
   449  			ruleID:       "basic2",
   450  			expectedLoad: true,
   451  		},
   452  		{
   453  			ruleID:       "range",
   454  			expectedLoad: true,
   455  		},
   456  		{
   457  			ruleID:       "range_not",
   458  			expectedLoad: false,
   459  		},
   460  		{
   461  			ruleID:       "rc_prerelease",
   462  			expectedLoad: true,
   463  		},
   464  		{
   465  			ruleID:       "with_macro1",
   466  			expectedLoad: true,
   467  		},
   468  		{
   469  			ruleID:       "with_macro2",
   470  			expectedLoad: true,
   471  		},
   472  	}
   473  
   474  	agentVersion, err := semver.NewVersion("7.38")
   475  	assert.NoError(t, err)
   476  
   477  	agentVersionFilter, err := NewAgentVersionFilter(agentVersion)
   478  	assert.NoError(t, err)
   479  
   480  	policyOpts := PolicyLoaderOpts{
   481  		MacroFilters: []MacroFilter{
   482  			agentVersionFilter,
   483  		},
   484  		RuleFilters: []RuleFilter{
   485  			agentVersionFilter,
   486  		},
   487  	}
   488  
   489  	es, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, policyOpts)
   490  	rs := es.RuleSets[DefaultRuleSetTagValue]
   491  
   492  	for _, err := range err.(*multierror.Error).Errors {
   493  		if rerr, ok := err.(*ErrRuleLoad); ok {
   494  			if rerr.Definition.ID != "basic" && rerr.Definition.ID != "range_not" {
   495  				t.Errorf("unexpected error: %v", rerr)
   496  			}
   497  		}
   498  	}
   499  
   500  	for _, exp := range expected {
   501  		t.Run(exp.ruleID, func(t *testing.T) {
   502  			if exp.expectedLoad {
   503  				assert.Contains(t, rs.rules, exp.ruleID)
   504  			} else {
   505  				assert.NotContains(t, rs.rules, exp.ruleID)
   506  			}
   507  		})
   508  	}
   509  }
   510  
   511  func TestActionSetVariableInvalid(t *testing.T) {
   512  	t.Run("both-field-and-value", func(t *testing.T) {
   513  		testPolicy := &PolicyDef{
   514  			Rules: []*RuleDefinition{{
   515  				ID:         "test_rule",
   516  				Expression: `open.file.path == "/tmp/test"`,
   517  				Actions: []*ActionDefinition{{
   518  					Set: &SetDefinition{
   519  						Name:  "var1",
   520  						Value: []string{"abc"},
   521  						Field: "open.file.path",
   522  					},
   523  				}},
   524  			}},
   525  		}
   526  
   527  		if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil {
   528  			t.Error("policy should fail to load")
   529  		} else {
   530  			t.Log(err)
   531  		}
   532  	})
   533  
   534  	t.Run("bool-array", func(t *testing.T) {
   535  		testPolicy := &PolicyDef{
   536  			Rules: []*RuleDefinition{{
   537  				ID:         "test_rule",
   538  				Expression: `open.file.path == "/tmp/test"`,
   539  				Actions: []*ActionDefinition{{
   540  					Set: &SetDefinition{
   541  						Name:  "var1",
   542  						Value: []bool{true},
   543  					},
   544  				}},
   545  			}, {
   546  				ID: "test_rule2",
   547  				Expression: `open.file.path == "/tmp/test2" && ` +
   548  					`${var1} == true`,
   549  			}},
   550  		}
   551  
   552  		if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil {
   553  			t.Error("expected policy to fail to load")
   554  		} else {
   555  			t.Log(err)
   556  		}
   557  	})
   558  
   559  	t.Run("heterogeneous-array", func(t *testing.T) {
   560  		testPolicy := &PolicyDef{
   561  			Rules: []*RuleDefinition{{
   562  				ID:         "test_rule",
   563  				Expression: `open.file.path == "/tmp/test"`,
   564  				Actions: []*ActionDefinition{{
   565  					Set: &SetDefinition{
   566  						Name:  "var1",
   567  						Value: []interface{}{"string", true},
   568  					},
   569  				}},
   570  			}, {
   571  				ID: "test_rule2",
   572  				Expression: `open.file.path == "/tmp/test2" && ` +
   573  					`${var1} == true`,
   574  			}},
   575  		}
   576  
   577  		if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil {
   578  			t.Error("expected policy to fail to load")
   579  		} else {
   580  			t.Log(err)
   581  		}
   582  	})
   583  
   584  	t.Run("nil-values", func(t *testing.T) {
   585  		testPolicy := &PolicyDef{
   586  			Rules: []*RuleDefinition{{
   587  				ID:         "test_rule",
   588  				Expression: `open.file.path == "/tmp/test"`,
   589  				Actions: []*ActionDefinition{{
   590  					Set: &SetDefinition{
   591  						Name:  "var1",
   592  						Value: nil,
   593  					},
   594  				}},
   595  			}},
   596  		}
   597  
   598  		if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil {
   599  			t.Error("expected policy to fail to load")
   600  		} else {
   601  			t.Log(err)
   602  		}
   603  	})
   604  
   605  	t.Run("append-array", func(t *testing.T) {
   606  		testPolicy := &PolicyDef{
   607  			Rules: []*RuleDefinition{{
   608  				ID:         "test_rule",
   609  				Expression: `open.file.path == "/tmp/test"`,
   610  				Actions: []*ActionDefinition{{
   611  					Set: &SetDefinition{
   612  						Name:   "var1",
   613  						Value:  []string{"abc"},
   614  						Append: true,
   615  					},
   616  				}, {
   617  					Set: &SetDefinition{
   618  						Name:   "var1",
   619  						Value:  true,
   620  						Append: true,
   621  					},
   622  				}},
   623  			}, {
   624  				ID: "test_rule2",
   625  				Expression: `open.file.path == "/tmp/test2" && ` +
   626  					`${var1} == true`,
   627  			}},
   628  		}
   629  
   630  		if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil {
   631  			t.Error("expected policy to fail to load")
   632  		} else {
   633  			t.Log(err)
   634  		}
   635  	})
   636  
   637  	t.Run("conflicting-field-type", func(t *testing.T) {
   638  		testPolicy := &PolicyDef{
   639  			Rules: []*RuleDefinition{{
   640  				ID:         "test_rule",
   641  				Expression: `open.file.path == "/tmp/test"`,
   642  				Actions: []*ActionDefinition{{
   643  					Set: &SetDefinition{
   644  						Name:  "var1",
   645  						Field: "open.file.path",
   646  					},
   647  				}, {
   648  					Set: &SetDefinition{
   649  						Name:   "var1",
   650  						Value:  true,
   651  						Append: true,
   652  					},
   653  				}},
   654  			}, {
   655  				ID: "test_rule2",
   656  				Expression: `open.file.path == "/tmp/test2" && ` +
   657  					`${var1} == "true"`,
   658  			}},
   659  		}
   660  
   661  		if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil {
   662  			t.Error("expected policy to fail to load")
   663  		} else {
   664  			t.Log(err)
   665  		}
   666  	})
   667  
   668  	t.Run("conflicting-field-type", func(t *testing.T) {
   669  		testPolicy := &PolicyDef{
   670  			Rules: []*RuleDefinition{{
   671  				ID:         "test_rule",
   672  				Expression: `open.file.path == "/tmp/test"`,
   673  				Actions: []*ActionDefinition{{
   674  					Set: &SetDefinition{
   675  						Name:   "var1",
   676  						Field:  "open.file.path",
   677  						Append: true,
   678  					},
   679  				}, {
   680  					Set: &SetDefinition{
   681  						Name:   "var1",
   682  						Field:  "process.is_root",
   683  						Append: true,
   684  					},
   685  				}},
   686  			}, {
   687  				ID: "test_rule2",
   688  				Expression: `open.file.path == "/tmp/test2" && ` +
   689  					`${var1} == "true"`,
   690  			}},
   691  		}
   692  
   693  		if _, err := loadPolicyIntoProbeEvaluationRuleSet(t, testPolicy, PolicyLoaderOpts{}); err == nil {
   694  			t.Error("expected policy to fail to load")
   695  		} else {
   696  			t.Log(err)
   697  		}
   698  	})
   699  }
   700  
   701  // go test -v github.com/DataDog/datadog-agent/pkg/security/secl/rules --run="TestLoadPolicy"
   702  func TestLoadPolicy(t *testing.T) {
   703  	type args struct {
   704  		name         string
   705  		source       string
   706  		fileContent  string
   707  		macroFilters []MacroFilter
   708  		ruleFilters  []RuleFilter
   709  	}
   710  	tests := []struct {
   711  		name    string
   712  		args    args
   713  		want    *Policy
   714  		wantErr assert.ErrorAssertionFunc
   715  	}{
   716  		{
   717  			name: "empty yaml file",
   718  			args: args{
   719  				name:         "myLocal.policy",
   720  				source:       PolicyProviderTypeRC,
   721  				fileContent:  ``,
   722  				macroFilters: nil,
   723  				ruleFilters:  nil,
   724  			},
   725  			want: nil,
   726  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   727  				return assert.EqualError(t, err, ErrPolicyLoad{Name: "myLocal.policy", Err: fmt.Errorf(`EOF`)}.Error())
   728  			},
   729  		},
   730  		{
   731  			name: "empty yaml file with new line char",
   732  			args: args{
   733  				name:   "myLocal.policy",
   734  				source: PolicyProviderTypeRC,
   735  				fileContent: `
   736  `,
   737  				macroFilters: nil,
   738  				ruleFilters:  nil,
   739  			},
   740  			want: nil,
   741  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   742  				return assert.EqualError(t, err, ErrPolicyLoad{Name: "myLocal.policy", Err: fmt.Errorf(`EOF`)}.Error())
   743  			},
   744  		},
   745  		{
   746  			name: "no rules in yaml file",
   747  			args: args{
   748  				name:   "myLocal.policy",
   749  				source: PolicyProviderTypeRC,
   750  				fileContent: `
   751  rules:
   752  `,
   753  				macroFilters: nil,
   754  				ruleFilters:  nil,
   755  			},
   756  			want: &Policy{
   757  				Name:   "myLocal.policy",
   758  				Source: PolicyProviderTypeRC,
   759  				Rules:  nil,
   760  			},
   761  			wantErr: assert.NoError,
   762  		},
   763  		{
   764  			name: "broken yaml file",
   765  			args: args{
   766  				name:   "myLocal.policy",
   767  				source: PolicyProviderTypeRC,
   768  				fileContent: `
   769  broken
   770  `,
   771  				macroFilters: nil,
   772  				ruleFilters:  nil,
   773  			},
   774  			want: nil,
   775  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   776  				return assert.ErrorContains(t, err, ErrPolicyLoad{Name: "myLocal.policy", Err: fmt.Errorf(`yaml: unmarshal error`)}.Error())
   777  			},
   778  		},
   779  		{
   780  			name: "disabled tag",
   781  			args: args{
   782  				name:   "myLocal.policy",
   783  				source: PolicyProviderTypeRC,
   784  				fileContent: `rules:
   785   - id: rule_test
   786     disabled: true
   787  `,
   788  				macroFilters: nil,
   789  				ruleFilters:  nil,
   790  			},
   791  			want: &Policy{
   792  				Name:   "myLocal.policy",
   793  				Source: PolicyProviderTypeRC,
   794  				Rules: []*RuleDefinition{
   795  					{
   796  						ID:         "rule_test",
   797  						Expression: "",
   798  						Disabled:   true,
   799  						Policy: &Policy{
   800  							Name:   "myLocal.policy",
   801  							Source: PolicyProviderTypeRC,
   802  						},
   803  					},
   804  				},
   805  			},
   806  			wantErr: assert.NoError,
   807  		},
   808  		{
   809  			name: "combine:override tag",
   810  			args: args{
   811  				name:   "myLocal.policy",
   812  				source: PolicyProviderTypeRC,
   813  				fileContent: `rules:
   814   - id: rule_test
   815     expression: open.file.path == "/etc/gshadow"
   816     combine: override
   817  `,
   818  				macroFilters: nil,
   819  				ruleFilters:  nil,
   820  			},
   821  			want: &Policy{
   822  				Name:   "myLocal.policy",
   823  				Source: PolicyProviderTypeRC,
   824  				Rules: []*RuleDefinition{
   825  					{
   826  						ID:         "rule_test",
   827  						Expression: "open.file.path == \"/etc/gshadow\"",
   828  						Combine:    OverridePolicy,
   829  						Policy: &Policy{
   830  							Name:   "myLocal.policy",
   831  							Source: PolicyProviderTypeRC,
   832  						},
   833  					},
   834  				},
   835  			},
   836  			wantErr: assert.NoError,
   837  		},
   838  	}
   839  	for _, tt := range tests {
   840  		t.Run(tt.name, func(t *testing.T) {
   841  			r := strings.NewReader(tt.args.fileContent)
   842  
   843  			got, err := LoadPolicy(tt.args.name, tt.args.source, r, tt.args.macroFilters, tt.args.ruleFilters)
   844  
   845  			if !tt.wantErr(t, err, fmt.Sprintf("LoadPolicy(%v, %v, %v, %v, %v)", tt.args.name, tt.args.source, r, tt.args.macroFilters, tt.args.ruleFilters)) {
   846  				return
   847  			}
   848  
   849  			if !cmp.Equal(tt.want, got, cmpopts.IgnoreFields(RuleDefinition{}, "Policy")) {
   850  				t.Errorf("LoadPolicy(%v, %v, %v, %v, %v)", tt.args.name, tt.args.source, r, tt.args.macroFilters, tt.args.ruleFilters)
   851  			}
   852  		})
   853  	}
   854  }