github.com/DataDog/datadog-agent/pkg/security/secl@v0.55.0-devel.0.20240517055856-10c4965fea94/rules/policy_dir.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 "context" 11 "os" 12 "path/filepath" 13 "sort" 14 15 "github.com/fsnotify/fsnotify" 16 "github.com/hashicorp/go-multierror" 17 ) 18 19 const ( 20 policyExtension = ".policy" 21 ) 22 23 var _ PolicyProvider = (*PoliciesDirProvider)(nil) 24 25 // PoliciesDirProvider defines a new policy dir provider 26 type PoliciesDirProvider struct { 27 PoliciesDir string 28 29 onNewPoliciesReadyCb func() 30 cancelFnc func() 31 watcher *fsnotify.Watcher 32 watchedFiles []string 33 } 34 35 // SetOnNewPoliciesReadyCb implements the policy provider interface 36 func (p *PoliciesDirProvider) SetOnNewPoliciesReadyCb(cb func()) { 37 p.onNewPoliciesReadyCb = cb 38 } 39 40 // Start starts the policy dir provider 41 func (p *PoliciesDirProvider) Start() {} 42 43 func (p *PoliciesDirProvider) loadPolicy(filename string, macroFilters []MacroFilter, ruleFilters []RuleFilter) (*Policy, error) { 44 f, err := os.Open(filename) 45 if err != nil { 46 return nil, &ErrPolicyLoad{Name: filename, Err: err} 47 } 48 defer f.Close() 49 50 name := filepath.Base(filename) 51 52 return LoadPolicy(name, PolicyProviderTypeDir, f, macroFilters, ruleFilters) 53 } 54 55 func (p *PoliciesDirProvider) getPolicyFiles() ([]string, error) { 56 files, err := os.ReadDir(p.PoliciesDir) 57 if err != nil { 58 return nil, err 59 } 60 sort.Slice(files, func(i, j int) bool { 61 switch { 62 case files[i].Name() == DefaultPolicyName: 63 return true 64 case files[j].Name() == DefaultPolicyName: 65 return false 66 default: 67 return files[i].Name() < files[j].Name() 68 } 69 }) 70 71 var policyFiles []string 72 for _, policyPath := range files { 73 name := policyPath.Name() 74 75 if filepath.Ext(name) == policyExtension { 76 filename := filepath.Join(p.PoliciesDir, name) 77 policyFiles = append(policyFiles, filename) 78 } 79 } 80 81 return policyFiles, nil 82 } 83 84 // LoadPolicies implements the policy provider interface 85 func (p *PoliciesDirProvider) LoadPolicies(macroFilters []MacroFilter, ruleFilters []RuleFilter) ([]*Policy, *multierror.Error) { 86 var errs *multierror.Error 87 88 var policies []*Policy 89 90 policyFiles, err := p.getPolicyFiles() 91 if err != nil { 92 errs = multierror.Append(errs, err) 93 } 94 95 // remove oldest watched files 96 if p.watcher != nil { 97 for _, watched := range p.watchedFiles { 98 _ = p.watcher.Remove(watched) 99 } 100 p.watchedFiles = p.watchedFiles[0:0] 101 } 102 103 // Load and parse policies 104 for _, filename := range policyFiles { 105 policy, err := p.loadPolicy(filename, macroFilters, ruleFilters) 106 if err != nil { 107 errs = multierror.Append(errs, err) 108 } 109 110 if policy == nil { 111 continue 112 } 113 114 policies = append(policies, policy) 115 116 if p.watcher != nil { 117 if err := p.watcher.Add(filename); err != nil { 118 errs = multierror.Append(errs, err) 119 } else { 120 p.watchedFiles = append(p.watchedFiles, filename) 121 } 122 } 123 } 124 125 return policies, errs 126 } 127 128 // Close stops policy provider interface 129 func (p *PoliciesDirProvider) Close() error { 130 if p.cancelFnc != nil { 131 p.cancelFnc() 132 } 133 134 if p.watcher != nil { 135 p.watcher.Close() 136 } 137 return nil 138 } 139 140 func filesEqual(a []string, b []string) bool { 141 if len(a) != len(b) { 142 return false 143 } 144 for i, v := range a { 145 if v != b[i] { 146 return false 147 } 148 } 149 return true 150 } 151 152 func (p *PoliciesDirProvider) watch(ctx context.Context) { 153 go func() { 154 for { 155 select { 156 case <-ctx.Done(): 157 return 158 case event, ok := <-p.watcher.Events: 159 if !ok { 160 return 161 } 162 163 if event.Op&(fsnotify.Create|fsnotify.Remove) > 0 { 164 files, _ := p.getPolicyFiles() 165 if !filesEqual(files, p.watchedFiles) { 166 p.onNewPoliciesReadyCb() 167 } 168 } else if event.Op&fsnotify.Write > 0 && filepath.Ext(event.Name) == policyExtension { 169 p.onNewPoliciesReadyCb() 170 } 171 case _, ok := <-p.watcher.Errors: 172 if !ok { 173 return 174 } 175 } 176 } 177 }() 178 } 179 180 // NewPoliciesDirProvider returns providers for the given policies dir 181 func NewPoliciesDirProvider(policiesDir string, watch bool) (*PoliciesDirProvider, error) { 182 p := &PoliciesDirProvider{ 183 PoliciesDir: policiesDir, 184 } 185 186 if watch { 187 var err error 188 if p.watcher, err = fsnotify.NewWatcher(); err != nil { 189 return nil, err 190 } 191 192 if err := p.watcher.Add(policiesDir); err != nil { 193 p.watcher.Close() 194 return nil, err 195 } 196 197 var ctx context.Context 198 ctx, p.cancelFnc = context.WithCancel(context.Background()) 199 go p.watch(ctx) 200 } 201 202 return p, nil 203 } 204 205 // Type returns the type of policy dir provider 206 func (p *PoliciesDirProvider) Type() string { 207 return PolicyProviderTypeDir 208 }