Fix: address policy checker review feedback

This commit is contained in:
Ritesh
2026-03-24 17:59:48 +05:30
parent 1707fb5b02
commit 13a7a18e17
8 changed files with 313 additions and 57 deletions

View File

@@ -22,6 +22,7 @@ type Config struct {
Actions []string
Enabled bool
DebugLogging bool
FetchTimeout time.Duration
IsBundle bool
RefreshInterval time.Duration // 0 = disabled
RuntimeConfig map[string]string
@@ -34,12 +35,14 @@ var knownKeys = map[string]bool{
"actions": true,
"enabled": true,
"debugLogging": true,
"fetchTimeoutSeconds": true,
"refreshIntervalSeconds": true,
}
func DefaultConfig() *Config {
return &Config{
Enabled: true,
FetchTimeout: defaultPolicyFetchTimeout,
RuntimeConfig: make(map[string]string),
}
}
@@ -105,6 +108,14 @@ func ParseConfig(cfg map[string]string) (*Config, error) {
config.DebugLogging = debug == "true" || debug == "1"
}
if fts, ok := cfg["fetchTimeoutSeconds"]; ok && fts != "" {
secs, err := strconv.Atoi(fts)
if err != nil || secs <= 0 {
return nil, fmt.Errorf("'fetchTimeoutSeconds' must be a positive integer, got %q", fts)
}
config.FetchTimeout = time.Duration(secs) * time.Second
}
if ris, ok := cfg["refreshIntervalSeconds"]; ok && ris != "" {
secs, err := strconv.Atoi(ris)
if err != nil || secs < 0 {
@@ -136,9 +147,11 @@ func (c *Config) IsActionEnabled(action string) bool {
// PolicyEnforcer evaluates beckn messages against OPA policies and NACKs non-compliant messages.
type PolicyEnforcer struct {
config *Config
evaluator *Evaluator
evaluatorMu sync.RWMutex
config *Config
evaluator *Evaluator
evaluatorMu sync.RWMutex
closeOnce sync.Once
done chan struct{}
}
// getEvaluator safely returns the current evaluator under a read lock.
@@ -162,18 +175,24 @@ func New(ctx context.Context, cfg map[string]string) (*PolicyEnforcer, error) {
return nil, fmt.Errorf("opapolicychecker: config error: %w", err)
}
evaluator, err := NewEvaluator(config.PolicyPaths, config.Query, config.RuntimeConfig, config.IsBundle)
enforcer := &PolicyEnforcer{
config: config,
done: make(chan struct{}),
}
if !config.Enabled {
log.Warnf(ctx, "OPAPolicyChecker is disabled via config; policy enforcement will be skipped")
return enforcer, nil
}
evaluator, err := NewEvaluator(config.PolicyPaths, config.Query, config.RuntimeConfig, config.IsBundle, config.FetchTimeout)
if err != nil {
return nil, fmt.Errorf("opapolicychecker: failed to initialize OPA evaluator: %w", err)
}
enforcer.evaluator = evaluator
log.Infof(ctx, "OPAPolicyChecker initialized (actions=%v, query=%s, policies=%v, isBundle=%v, debugLogging=%v, refreshInterval=%s)",
config.Actions, config.Query, evaluator.ModuleNames(), config.IsBundle, config.DebugLogging, config.RefreshInterval)
enforcer := &PolicyEnforcer{
config: config,
evaluator: evaluator,
}
log.Infof(ctx, "OPAPolicyChecker initialized (actions=%v, query=%s, policies=%v, isBundle=%v, debugLogging=%v, fetchTimeout=%s, refreshInterval=%s)",
config.Actions, config.Query, evaluator.ModuleNames(), config.IsBundle, config.DebugLogging, config.FetchTimeout, config.RefreshInterval)
if config.RefreshInterval > 0 {
go enforcer.refreshLoop(ctx)
@@ -193,6 +212,9 @@ func (e *PolicyEnforcer) refreshLoop(ctx context.Context) {
case <-ctx.Done():
log.Infof(ctx, "OPAPolicyChecker: refresh loop stopped")
return
case <-e.done:
log.Infof(ctx, "OPAPolicyChecker: refresh loop stopped by Close()")
return
case <-ticker.C:
e.reloadPolicies(ctx)
}
@@ -208,6 +230,7 @@ func (e *PolicyEnforcer) reloadPolicies(ctx context.Context) {
e.config.Query,
e.config.RuntimeConfig,
e.config.IsBundle,
e.config.FetchTimeout,
)
if err != nil {
log.Errorf(ctx, err, "OPAPolicyChecker: policy reload failed (keeping previous policies): %v", err)
@@ -237,6 +260,9 @@ func (e *PolicyEnforcer) CheckPolicy(ctx *model.StepContext) error {
}
ev := e.getEvaluator()
if ev == nil {
return model.NewBadReqErr(fmt.Errorf("policy evaluator is not initialized"))
}
if e.config.DebugLogging {
log.Debugf(ctx, "OPAPolicyChecker: evaluating policies for action %q (modules=%v)", action, ev.ModuleNames())
@@ -260,12 +286,17 @@ func (e *PolicyEnforcer) CheckPolicy(ctx *model.StepContext) error {
return model.NewBadReqErr(fmt.Errorf("%s", msg))
}
func (e *PolicyEnforcer) Close() {}
func (e *PolicyEnforcer) Close() {
e.closeOnce.Do(func() {
close(e.done)
})
}
func extractAction(urlPath string, body []byte) string {
parts := strings.Split(strings.Trim(urlPath, "/"), "/")
if len(parts) >= 3 {
return parts[len(parts)-1]
// /bpp/caller/confirm/extra as action "extra".
parts := strings.FieldsFunc(strings.Trim(urlPath, "/"), func(r rune) bool { return r == '/' })
if len(parts) == 3 && isBecknDirection(parts[1]) && parts[2] != "" {
return parts[2]
}
var payload struct {
@@ -279,3 +310,12 @@ func extractAction(urlPath string, body []byte) string {
return ""
}
func isBecknDirection(part string) bool {
switch part {
case "caller", "receiver", "reciever":
return true
default:
return false
}
}