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  }