github.com/DataDog/datadog-agent/pkg/security/secl@v0.55.0-devel.0.20240517055856-10c4965fea94/rules/policy_loader_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  // Package rules holds rules related files
     7  package rules
     8  
     9  import (
    10  	"fmt"
    11  	"testing"
    12  
    13  	"github.com/hashicorp/go-multierror"
    14  	"github.com/stretchr/testify/assert"
    15  )
    16  
    17  // go test -v github.com/DataDog/datadog-agent/pkg/security/secl/rules --run="TestPolicyLoader_LoadPolicies"
    18  func TestPolicyLoader_LoadPolicies(t *testing.T) {
    19  	type fields struct {
    20  		Providers []PolicyProvider
    21  	}
    22  	type args struct {
    23  		opts PolicyLoaderOpts
    24  	}
    25  	tests := []struct {
    26  		name    string
    27  		fields  fields
    28  		args    args
    29  		want    func(t assert.TestingT, fields fields, got []*Policy, msgs ...interface{}) bool
    30  		wantErr func(t assert.TestingT, err *multierror.Error, msgs ...interface{}) bool
    31  	}{
    32  		{
    33  			name: "RC Default replaces Local Default, and RC Custom overrides Local Custom",
    34  			fields: fields{
    35  				Providers: []PolicyProvider{
    36  					dummyDirProvider{
    37  						dummyLoadPoliciesFunc: func() ([]*Policy, *multierror.Error) {
    38  							return []*Policy{{
    39  								Name:   "myLocal.policy",
    40  								Source: PolicyProviderTypeDir,
    41  								Rules: []*RuleDefinition{
    42  									{
    43  										ID:         "foo",
    44  										Expression: "open.file.path == \"/etc/local-custom/foo\"",
    45  									},
    46  									{
    47  										ID:         "bar",
    48  										Expression: "open.file.path == \"/etc/local-custom/bar\"",
    49  									},
    50  								},
    51  							}, {
    52  								Name:   DefaultPolicyName,
    53  								Source: PolicyProviderTypeDir,
    54  								Rules: []*RuleDefinition{
    55  									{
    56  										ID:         "foo",
    57  										Expression: "open.file.path == \"/etc/local-default/foo\"",
    58  									},
    59  									{
    60  										ID:         "baz",
    61  										Expression: "open.file.path == \"/etc/local-default/baz\"",
    62  									},
    63  								},
    64  							}}, nil
    65  						},
    66  					},
    67  					dummyRCProvider{
    68  						dummyLoadPoliciesFunc: func() ([]*Policy, *multierror.Error) {
    69  							return []*Policy{{
    70  								Name:   "myRC.policy",
    71  								Source: PolicyProviderTypeRC,
    72  								Rules: []*RuleDefinition{
    73  									{
    74  										ID:         "foo",
    75  										Expression: "open.file.path == \"/etc/rc-custom/foo\"",
    76  									},
    77  									{
    78  										ID:         "alpha",
    79  										Expression: "open.file.path == \"/etc/rc-custom/alpha\"",
    80  									},
    81  								},
    82  							}, {
    83  								Name:   DefaultPolicyName,
    84  								Source: PolicyProviderTypeRC,
    85  								Rules: []*RuleDefinition{
    86  									{
    87  										ID:         "foo",
    88  										Expression: "open.file.path == \"/etc/rc-default/foo\"",
    89  									},
    90  									{
    91  										ID:         "bravo",
    92  										Expression: "open.file.path == \"/etc/rc-default/bravo\"",
    93  									},
    94  								},
    95  							}}, nil
    96  						},
    97  					},
    98  				},
    99  			},
   100  			want: func(t assert.TestingT, fields fields, got []*Policy, msgs ...interface{}) bool {
   101  				expectedLoadedPolicies := []*Policy{
   102  					{
   103  						Name:   DefaultPolicyName,
   104  						Source: PolicyProviderTypeRC,
   105  						Rules: []*RuleDefinition{
   106  							{
   107  								ID:         "foo",
   108  								Expression: "open.file.path == \"/etc/rc-default/foo\"",
   109  							},
   110  							{
   111  								ID:         "bravo",
   112  								Expression: "open.file.path == \"/etc/rc-default/bravo\"",
   113  							},
   114  						},
   115  					},
   116  					{
   117  						Name:   "myRC.policy",
   118  						Source: PolicyProviderTypeRC,
   119  						Rules: []*RuleDefinition{
   120  							{
   121  								ID:         "foo",
   122  								Expression: "open.file.path == \"/etc/rc-custom/foo\"",
   123  							},
   124  							{
   125  								ID:         "alpha",
   126  								Expression: "open.file.path == \"/etc/rc-custom/alpha\"",
   127  							},
   128  						},
   129  					},
   130  					{
   131  						Name:    "myLocal.policy",
   132  						Source:  PolicyProviderTypeDir,
   133  						Version: "",
   134  						Rules: []*RuleDefinition{
   135  							{
   136  								ID:         "foo",
   137  								Expression: "open.file.path == \"/etc/local-custom/foo\"",
   138  							},
   139  							{
   140  								ID:         "bar",
   141  								Expression: "open.file.path == \"/etc/local-custom/bar\"",
   142  							},
   143  						},
   144  						Macros: nil,
   145  					},
   146  				}
   147  
   148  				defaultPolicyCount, lastSeenDefaultPolicyIdx := numAndLastIdxOfDefaultPolicies(expectedLoadedPolicies)
   149  
   150  				assert.Equalf(t, 1, defaultPolicyCount, "There are more than 1 default policies")
   151  				assert.Equalf(t, PolicyProviderTypeRC, got[lastSeenDefaultPolicyIdx].Source, "The default policy is not from RC")
   152  
   153  				return assert.Equalf(t, expectedLoadedPolicies, got, "The loaded policies do not match the expected")
   154  			},
   155  			wantErr: func(t assert.TestingT, err *multierror.Error, msgs ...interface{}) bool {
   156  				return assert.Nil(t, err, "Expected no errors but got %+v", err)
   157  			},
   158  		},
   159  		{
   160  			name: "No default policy",
   161  			fields: fields{
   162  				Providers: []PolicyProvider{
   163  					dummyDirProvider{
   164  						dummyLoadPoliciesFunc: func() ([]*Policy, *multierror.Error) {
   165  							return []*Policy{{
   166  								Name:   "myLocal.policy",
   167  								Source: PolicyProviderTypeDir,
   168  								Rules: []*RuleDefinition{
   169  									{
   170  										ID:         "foo",
   171  										Expression: "open.file.path == \"/etc/local-custom/foo\"",
   172  									},
   173  									{
   174  										ID:         "bar",
   175  										Expression: "open.file.path == \"/etc/local-custom/bar\"",
   176  									},
   177  								},
   178  							}}, nil
   179  						},
   180  					},
   181  					dummyRCProvider{
   182  						dummyLoadPoliciesFunc: func() ([]*Policy, *multierror.Error) {
   183  							return []*Policy{{
   184  								Name:   "myRC.policy",
   185  								Source: PolicyProviderTypeRC,
   186  								Rules: []*RuleDefinition{
   187  									{
   188  										ID:         "foo",
   189  										Expression: "open.file.path == \"/etc/rc-custom/foo\"",
   190  									},
   191  									{
   192  										ID:         "bar3",
   193  										Expression: "open.file.path == \"/etc/rc-custom/bar\"",
   194  									},
   195  								},
   196  							}}, nil
   197  						},
   198  					},
   199  				},
   200  			},
   201  			want: func(t assert.TestingT, fields fields, got []*Policy, msgs ...interface{}) bool {
   202  				expectedLoadedPolicies := []*Policy{
   203  					{
   204  						Name:   "myRC.policy",
   205  						Source: PolicyProviderTypeRC,
   206  						Rules: []*RuleDefinition{
   207  							{
   208  								ID:         "foo",
   209  								Expression: "open.file.path == \"/etc/rc-custom/foo\"",
   210  							},
   211  							{
   212  								ID:         "bar3",
   213  								Expression: "open.file.path == \"/etc/rc-custom/bar\"",
   214  							},
   215  						},
   216  					},
   217  					{
   218  						Name:   "myLocal.policy",
   219  						Source: PolicyProviderTypeDir,
   220  						Rules: []*RuleDefinition{
   221  							{
   222  								ID:         "foo",
   223  								Expression: "open.file.path == \"/etc/local-custom/foo\"",
   224  							},
   225  							{
   226  								ID:         "bar",
   227  								Expression: "open.file.path == \"/etc/local-custom/bar\"",
   228  							},
   229  						},
   230  					},
   231  				}
   232  
   233  				defaultPolicyCount, _ := numAndLastIdxOfDefaultPolicies(expectedLoadedPolicies)
   234  
   235  				assert.Equalf(t, 0, defaultPolicyCount, "The count of default policies do not match")
   236  				return assert.Equalf(t, expectedLoadedPolicies, got, "The loaded policies do not match the expected")
   237  			},
   238  			wantErr: func(t assert.TestingT, err *multierror.Error, msgs ...interface{}) bool {
   239  				return assert.Nil(t, err, "Expected no errors but got %+v", err)
   240  			},
   241  		},
   242  		{
   243  			name: "Broken policy yaml file from RC → packaged policy",
   244  			fields: fields{
   245  				Providers: []PolicyProvider{
   246  					dummyDirProvider{
   247  						dummyLoadPoliciesFunc: func() ([]*Policy, *multierror.Error) {
   248  							return []*Policy{{
   249  								Name:    "myLocal.policy",
   250  								Source:  PolicyProviderTypeDir,
   251  								Version: "",
   252  								Rules: []*RuleDefinition{
   253  									{
   254  										ID:         "foo",
   255  										Expression: "open.file.path == \"/etc/local-custom/foo\"",
   256  									},
   257  									{
   258  										ID:         "bar",
   259  										Expression: "open.file.path == \"/etc/local-custom/bar\"",
   260  									},
   261  								},
   262  								Macros: nil,
   263  							}}, nil
   264  						},
   265  					},
   266  					dummyRCProvider{
   267  						dummyLoadPoliciesFunc: func() ([]*Policy, *multierror.Error) {
   268  							var errs *multierror.Error
   269  
   270  							errs = multierror.Append(errs, &ErrPolicyLoad{Name: "myRC.policy", Err: fmt.Errorf(`yaml: unmarshal error`)})
   271  							return nil, errs
   272  						},
   273  					},
   274  				},
   275  			},
   276  			want: func(t assert.TestingT, fields fields, got []*Policy, msgs ...interface{}) bool {
   277  				expectedLoadedPolicies := []*Policy{
   278  					{
   279  						Name:    "myLocal.policy",
   280  						Source:  PolicyProviderTypeDir,
   281  						Version: "",
   282  						Rules: []*RuleDefinition{
   283  							{
   284  								ID:         "foo",
   285  								Expression: "open.file.path == \"/etc/local-custom/foo\"",
   286  							},
   287  							{
   288  								ID:         "bar",
   289  								Expression: "open.file.path == \"/etc/local-custom/bar\"",
   290  							},
   291  						},
   292  						Macros: nil,
   293  					},
   294  				}
   295  
   296  				defaultPolicyCount, _ := numAndLastIdxOfDefaultPolicies(expectedLoadedPolicies)
   297  
   298  				assert.Equalf(t, 0, defaultPolicyCount, "The count of default policies do not match")
   299  				return assert.Equalf(t, expectedLoadedPolicies, got, "The loaded policies do not match the expected")
   300  			},
   301  			wantErr: func(t assert.TestingT, err *multierror.Error, msgs ...interface{}) bool {
   302  				return assert.Equal(t, err, &multierror.Error{Errors: []error{
   303  					&ErrPolicyLoad{Name: "myRC.policy", Err: fmt.Errorf(`yaml: unmarshal error`)},
   304  				}}, "Expected no errors but got %+v", err)
   305  			},
   306  		},
   307  		{
   308  			name: "Empty RC policy yaml file → local policy",
   309  			fields: fields{
   310  				Providers: []PolicyProvider{
   311  					dummyDirProvider{
   312  						dummyLoadPoliciesFunc: func() ([]*Policy, *multierror.Error) {
   313  							return []*Policy{{
   314  								Name:    "myLocal.policy",
   315  								Source:  PolicyProviderTypeDir,
   316  								Version: "",
   317  								Rules: []*RuleDefinition{
   318  									{
   319  										ID:         "foo",
   320  										Expression: "open.file.path == \"/etc/local-custom/foo\"",
   321  									},
   322  									{
   323  										ID:         "bar",
   324  										Expression: "open.file.path == \"/etc/local-custom/bar\"",
   325  									},
   326  								},
   327  								Macros: nil,
   328  							}}, nil
   329  						},
   330  					},
   331  					dummyRCProvider{
   332  						dummyLoadPoliciesFunc: func() ([]*Policy, *multierror.Error) {
   333  							var errs *multierror.Error
   334  
   335  							errs = multierror.Append(errs, &ErrPolicyLoad{Name: "myRC.policy", Err: fmt.Errorf(`EOF`)})
   336  							return nil, errs
   337  						},
   338  					},
   339  				},
   340  			},
   341  			want: func(t assert.TestingT, fields fields, got []*Policy, msgs ...interface{}) bool {
   342  				expectedLoadedPolicies := []*Policy{
   343  					{
   344  						Name:    "myLocal.policy",
   345  						Source:  PolicyProviderTypeDir,
   346  						Version: "",
   347  						Rules: []*RuleDefinition{
   348  							{
   349  								ID:         "foo",
   350  								Expression: "open.file.path == \"/etc/local-custom/foo\"",
   351  							},
   352  							{
   353  								ID:         "bar",
   354  								Expression: "open.file.path == \"/etc/local-custom/bar\"",
   355  							},
   356  						},
   357  						Macros: nil,
   358  					},
   359  				}
   360  
   361  				return assert.Equalf(t, expectedLoadedPolicies, got, "The loaded policies do not match the expected")
   362  			},
   363  			wantErr: func(t assert.TestingT, err *multierror.Error, msgs ...interface{}) bool {
   364  				return assert.Equal(t, err, &multierror.Error{
   365  					Errors: []error{
   366  						&ErrPolicyLoad{Name: "myRC.policy", Err: fmt.Errorf(`EOF`)},
   367  					}})
   368  			},
   369  		},
   370  		{
   371  			name: "Empty rules → packaged policy",
   372  			fields: fields{
   373  				Providers: []PolicyProvider{
   374  					dummyDirProvider{
   375  						dummyLoadPoliciesFunc: func() ([]*Policy, *multierror.Error) {
   376  							return []*Policy{{
   377  								Name:    "myLocal.policy",
   378  								Source:  PolicyProviderTypeDir,
   379  								Version: "",
   380  								Rules: []*RuleDefinition{
   381  									{
   382  										ID:         "foo",
   383  										Expression: "open.file.path == \"/etc/local-custom/foo\"",
   384  									},
   385  									{
   386  										ID:         "bar",
   387  										Expression: "open.file.path == \"/etc/local-custom/bar\"",
   388  									},
   389  								},
   390  								Macros: nil,
   391  							}}, nil
   392  						},
   393  					},
   394  					dummyRCProvider{
   395  						dummyLoadPoliciesFunc: func() ([]*Policy, *multierror.Error) {
   396  							return []*Policy{{
   397  								Name:   "myRC.policy",
   398  								Source: PolicyProviderTypeRC,
   399  								Rules:  nil,
   400  							}}, nil
   401  						},
   402  					},
   403  				},
   404  			},
   405  			want: func(t assert.TestingT, fields fields, got []*Policy, msgs ...interface{}) bool {
   406  				expectedLoadedPolicies := []*Policy{
   407  					{
   408  						Name:   "myRC.policy",
   409  						Source: PolicyProviderTypeRC,
   410  						Rules:  nil, // TODO: Ensure this doesn't cause a problem with loading rules
   411  					},
   412  					{
   413  						Name:    "myLocal.policy",
   414  						Source:  PolicyProviderTypeDir,
   415  						Version: "",
   416  						Rules: []*RuleDefinition{
   417  							{
   418  								ID:         "foo",
   419  								Expression: "open.file.path == \"/etc/local-custom/foo\"",
   420  							},
   421  							{
   422  								ID:         "bar",
   423  								Expression: "open.file.path == \"/etc/local-custom/bar\"",
   424  							},
   425  						},
   426  						Macros: nil,
   427  					},
   428  				}
   429  
   430  				return assert.Equalf(t, expectedLoadedPolicies, got, "The loaded policies do not match the expected")
   431  			},
   432  			wantErr: func(t assert.TestingT, err *multierror.Error, msgs ...interface{}) bool {
   433  				return assert.Nil(t, err, "Expected no errors but got %+v", err)
   434  			},
   435  		},
   436  	}
   437  
   438  	for _, tt := range tests {
   439  		t.Run(tt.name, func(t *testing.T) {
   440  			p := &PolicyLoader{
   441  				Providers: tt.fields.Providers,
   442  			}
   443  			loadedPolicies, errs := p.LoadPolicies(tt.args.opts)
   444  
   445  			tt.want(t, tt.fields, loadedPolicies)
   446  			tt.wantErr(t, errs)
   447  		})
   448  	}
   449  }
   450  
   451  // Utils
   452  
   453  func numAndLastIdxOfDefaultPolicies(policies []*Policy) (int, int) {
   454  	var defaultPolicyCount int
   455  	var lastSeenDefaultPolicyIdx int
   456  	for idx, policy := range policies {
   457  		if policy.Name == DefaultPolicyName {
   458  			defaultPolicyCount++
   459  			lastSeenDefaultPolicyIdx = idx
   460  		}
   461  	}
   462  
   463  	return defaultPolicyCount, lastSeenDefaultPolicyIdx
   464  }
   465  
   466  type dummyDirProvider struct {
   467  	dummyLoadPoliciesFunc func() ([]*Policy, *multierror.Error)
   468  }
   469  
   470  func (d dummyDirProvider) LoadPolicies(_ []MacroFilter, _ []RuleFilter) ([]*Policy, *multierror.Error) {
   471  	return d.dummyLoadPoliciesFunc()
   472  }
   473  
   474  func (dummyDirProvider) SetOnNewPoliciesReadyCb(_ func()) {}
   475  
   476  func (dummyDirProvider) Start() {}
   477  
   478  func (dummyDirProvider) Close() error {
   479  	return nil
   480  }
   481  
   482  func (dummyDirProvider) Type() string {
   483  	return PolicyProviderTypeDir
   484  }
   485  
   486  type dummyRCProvider struct {
   487  	dummyLoadPoliciesFunc func() ([]*Policy, *multierror.Error)
   488  }
   489  
   490  func (d dummyRCProvider) LoadPolicies(_ []MacroFilter, _ []RuleFilter) ([]*Policy, *multierror.Error) {
   491  	return d.dummyLoadPoliciesFunc()
   492  }
   493  
   494  func (dummyRCProvider) SetOnNewPoliciesReadyCb(_ func()) {}
   495  
   496  func (dummyRCProvider) Start() {}
   497  
   498  func (dummyRCProvider) Close() error {
   499  	return nil
   500  }
   501  
   502  func (dummyRCProvider) Type() string {
   503  	return PolicyProviderTypeRC
   504  }