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 }