fix script startup and validate with tests
This commit is contained in:
@@ -30,7 +30,9 @@
|
|||||||
"Bash(CGO_ENABLED=0 go test:*)",
|
"Bash(CGO_ENABLED=0 go test:*)",
|
||||||
"Bash(app/web/dist/index.html)",
|
"Bash(app/web/dist/index.html)",
|
||||||
"Bash(export CGO_ENABLED=0)",
|
"Bash(export CGO_ENABLED=0)",
|
||||||
"Bash(bash:*)"
|
"Bash(bash:*)",
|
||||||
|
"Bash(CGO_ENABLED=0 ORLY_LOG_LEVEL=debug go test:*)",
|
||||||
|
"Bash(/tmp/test-policy-script.sh)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@@ -83,10 +83,12 @@ type PolicyEvent struct {
|
|||||||
// It safely serializes the embedded event and additional context fields.
|
// It safely serializes the embedded event and additional context fields.
|
||||||
func (pe *PolicyEvent) MarshalJSON() ([]byte, error) {
|
func (pe *PolicyEvent) MarshalJSON() ([]byte, error) {
|
||||||
if pe.E == nil {
|
if pe.E == nil {
|
||||||
return json.Marshal(map[string]interface{}{
|
return json.Marshal(
|
||||||
"logged_in_pubkey": pe.LoggedInPubkey,
|
map[string]interface{}{
|
||||||
"ip_address": pe.IPAddress,
|
"logged_in_pubkey": pe.LoggedInPubkey,
|
||||||
})
|
"ip_address": pe.IPAddress,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a safe copy of the event for JSON marshaling
|
// Create a safe copy of the event for JSON marshaling
|
||||||
@@ -227,7 +229,10 @@ func NewWithManager(ctx context.Context, appName string, enabled bool) *P {
|
|||||||
|
|
||||||
if enabled {
|
if enabled {
|
||||||
if err := policy.LoadFromFile(configPath); err != nil {
|
if err := policy.LoadFromFile(configPath); err != nil {
|
||||||
log.W.F("failed to load policy configuration from %s: %v", configPath, err)
|
log.W.F(
|
||||||
|
"failed to load policy configuration from %s: %v", configPath,
|
||||||
|
err,
|
||||||
|
)
|
||||||
log.I.F("using default policy configuration")
|
log.I.F("using default policy configuration")
|
||||||
} else {
|
} else {
|
||||||
log.I.F("loaded policy configuration from %s", configPath)
|
log.I.F("loaded policy configuration from %s", configPath)
|
||||||
@@ -438,7 +443,9 @@ func (sr *ScriptRunner) Start() error {
|
|||||||
// Monitor the process
|
// Monitor the process
|
||||||
go sr.monitorProcess()
|
go sr.monitorProcess()
|
||||||
|
|
||||||
log.I.F("policy script started: %s (pid=%d)", sr.scriptPath, cmd.Process.Pid)
|
log.I.F(
|
||||||
|
"policy script started: %s (pid=%d)", sr.scriptPath, cmd.Process.Pid,
|
||||||
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,7 +480,10 @@ func (sr *ScriptRunner) Stop() error {
|
|||||||
log.I.F("policy script stopped: %s", sr.scriptPath)
|
log.I.F("policy script stopped: %s", sr.scriptPath)
|
||||||
case <-time.After(5 * time.Second):
|
case <-time.After(5 * time.Second):
|
||||||
// Force kill after 5 seconds
|
// Force kill after 5 seconds
|
||||||
log.W.F("policy script did not stop gracefully, sending SIGKILL: %s", sr.scriptPath)
|
log.W.F(
|
||||||
|
"policy script did not stop gracefully, sending SIGKILL: %s",
|
||||||
|
sr.scriptPath,
|
||||||
|
)
|
||||||
if err := sr.currentCmd.Process.Kill(); chk.E(err) {
|
if err := sr.currentCmd.Process.Kill(); chk.E(err) {
|
||||||
log.E.F("failed to kill script process: %v", err)
|
log.E.F("failed to kill script process: %v", err)
|
||||||
}
|
}
|
||||||
@@ -502,7 +512,10 @@ func (sr *ScriptRunner) Stop() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProcessEvent sends an event to the script and waits for a response.
|
// ProcessEvent sends an event to the script and waits for a response.
|
||||||
func (sr *ScriptRunner) ProcessEvent(evt *PolicyEvent) (*PolicyResponse, error) {
|
func (sr *ScriptRunner) ProcessEvent(evt *PolicyEvent) (
|
||||||
|
*PolicyResponse, error,
|
||||||
|
) {
|
||||||
|
log.D.F("processing event: %s", evt.Serialize())
|
||||||
sr.mutex.RLock()
|
sr.mutex.RLock()
|
||||||
if !sr.isRunning || sr.stdin == nil {
|
if !sr.isRunning || sr.stdin == nil {
|
||||||
sr.mutex.RUnlock()
|
sr.mutex.RUnlock()
|
||||||
@@ -525,6 +538,7 @@ func (sr *ScriptRunner) ProcessEvent(evt *PolicyEvent) (*PolicyResponse, error)
|
|||||||
// Wait for response with timeout
|
// Wait for response with timeout
|
||||||
select {
|
select {
|
||||||
case response := <-sr.responseChan:
|
case response := <-sr.responseChan:
|
||||||
|
log.D.S("response", response)
|
||||||
return &response, nil
|
return &response, nil
|
||||||
case <-time.After(5 * time.Second):
|
case <-time.After(5 * time.Second):
|
||||||
return nil, fmt.Errorf("script response timeout")
|
return nil, fmt.Errorf("script response timeout")
|
||||||
@@ -545,10 +559,13 @@ func (sr *ScriptRunner) readResponses() {
|
|||||||
if line == "" {
|
if line == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
log.D.F("policy response: %s", line)
|
||||||
var response PolicyResponse
|
var response PolicyResponse
|
||||||
if err := json.Unmarshal([]byte(line), &response); chk.E(err) {
|
if err := json.Unmarshal([]byte(line), &response); chk.E(err) {
|
||||||
log.E.F("failed to parse policy response from %s: %v", sr.scriptPath, err)
|
log.E.F(
|
||||||
|
"failed to parse policy response from %s: %v", sr.scriptPath,
|
||||||
|
err,
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,12 +573,17 @@ func (sr *ScriptRunner) readResponses() {
|
|||||||
select {
|
select {
|
||||||
case sr.responseChan <- response:
|
case sr.responseChan <- response:
|
||||||
default:
|
default:
|
||||||
log.W.F("policy response channel full for %s, dropping response", sr.scriptPath)
|
log.W.F(
|
||||||
|
"policy response channel full for %s, dropping response",
|
||||||
|
sr.scriptPath,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := scanner.Err(); chk.E(err) {
|
if err := scanner.Err(); chk.E(err) {
|
||||||
log.E.F("error reading policy responses from %s: %v", sr.scriptPath, err)
|
log.E.F(
|
||||||
|
"error reading policy responses from %s: %v", sr.scriptPath, err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,7 +627,10 @@ func (sr *ScriptRunner) monitorProcess() {
|
|||||||
sr.currentCancel = nil
|
sr.currentCancel = nil
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.E.F("policy script exited with error: %s: %v, will retry periodically", sr.scriptPath, err)
|
log.E.F(
|
||||||
|
"policy script exited with error: %s: %v, will retry periodically",
|
||||||
|
sr.scriptPath, err,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
log.I.F("policy script exited normally: %s", sr.scriptPath)
|
log.I.F("policy script exited normally: %s", sr.scriptPath)
|
||||||
}
|
}
|
||||||
@@ -631,9 +656,15 @@ func (sr *ScriptRunner) periodicCheck() {
|
|||||||
// Script exists but not running, try to start
|
// Script exists but not running, try to start
|
||||||
go func() {
|
go func() {
|
||||||
if err := sr.Start(); err != nil {
|
if err := sr.Start(); err != nil {
|
||||||
log.E.F("failed to restart policy script %s: %v, will retry in next cycle", sr.scriptPath, err)
|
log.E.F(
|
||||||
|
"failed to restart policy script %s: %v, will retry in next cycle",
|
||||||
|
sr.scriptPath, err,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
log.I.F("policy script restarted successfully: %s", sr.scriptPath)
|
log.I.F(
|
||||||
|
"policy script restarted successfully: %s",
|
||||||
|
sr.scriptPath,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -646,7 +677,9 @@ func (sr *ScriptRunner) periodicCheck() {
|
|||||||
// Returns an error if the file doesn't exist, can't be read, or contains invalid JSON.
|
// Returns an error if the file doesn't exist, can't be read, or contains invalid JSON.
|
||||||
func (p *P) LoadFromFile(configPath string) error {
|
func (p *P) LoadFromFile(configPath string) error {
|
||||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||||
return fmt.Errorf("policy configuration file does not exist: %s", configPath)
|
return fmt.Errorf(
|
||||||
|
"policy configuration file does not exist: %s", configPath,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
configData, err := os.ReadFile(configPath)
|
configData, err := os.ReadFile(configPath)
|
||||||
@@ -669,7 +702,9 @@ func (p *P) LoadFromFile(configPath string) error {
|
|||||||
// The access parameter should be "write" for accepting events or "read" for filtering events.
|
// The access parameter should be "write" for accepting events or "read" for filtering events.
|
||||||
// Returns true if the event is allowed, false if denied, and an error if validation fails.
|
// Returns true if the event is allowed, false if denied, and an error if validation fails.
|
||||||
// Policy evaluation order: global rules → kind filtering → specific rules → default policy.
|
// Policy evaluation order: global rules → kind filtering → specific rules → default policy.
|
||||||
func (p *P) CheckPolicy(access string, ev *event.E, loggedInPubkey []byte, ipAddress string) (allowed bool, err error) {
|
func (p *P) CheckPolicy(
|
||||||
|
access string, ev *event.E, loggedInPubkey []byte, ipAddress string,
|
||||||
|
) (allowed bool, err error) {
|
||||||
// Handle nil event
|
// Handle nil event
|
||||||
if ev == nil {
|
if ev == nil {
|
||||||
return false, fmt.Errorf("event cannot be nil")
|
return false, fmt.Errorf("event cannot be nil")
|
||||||
@@ -698,22 +733,35 @@ func (p *P) CheckPolicy(access string, ev *event.E, loggedInPubkey []byte, ipAdd
|
|||||||
// Check if script file exists before trying to use it
|
// Check if script file exists before trying to use it
|
||||||
if _, err := os.Stat(rule.Script); err == nil {
|
if _, err := os.Stat(rule.Script); err == nil {
|
||||||
// Script exists, try to use it
|
// Script exists, try to use it
|
||||||
log.D.F("using policy script for kind %d: %s", ev.Kind, rule.Script)
|
log.D.F(
|
||||||
allowed, err := p.checkScriptPolicy(access, ev, rule.Script, loggedInPubkey, ipAddress)
|
"using policy script for kind %d: %s", ev.Kind, rule.Script,
|
||||||
|
)
|
||||||
|
allowed, err := p.checkScriptPolicy(
|
||||||
|
access, ev, rule.Script, loggedInPubkey, ipAddress,
|
||||||
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Script ran successfully, return its decision
|
// Script ran successfully, return its decision
|
||||||
return allowed, nil
|
return allowed, nil
|
||||||
}
|
}
|
||||||
// Script failed, fall through to apply other criteria
|
// Script failed, fall through to apply other criteria
|
||||||
log.W.F("policy script check failed for kind %d: %v, applying other criteria", ev.Kind, err)
|
log.W.F(
|
||||||
|
"policy script check failed for kind %d: %v, applying other criteria",
|
||||||
|
ev.Kind, err,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// Script configured but doesn't exist
|
// Script configured but doesn't exist
|
||||||
log.W.F("policy script configured for kind %d but not found at %s: %v, applying other criteria", ev.Kind, rule.Script, err)
|
log.W.F(
|
||||||
|
"policy script configured for kind %d but not found at %s: %v, applying other criteria",
|
||||||
|
ev.Kind, rule.Script, err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// Script doesn't exist or failed, fall through to apply other criteria
|
// Script doesn't exist or failed, fall through to apply other criteria
|
||||||
} else {
|
} else {
|
||||||
// Policy manager is disabled, fall back to default policy
|
// Policy manager is disabled, fall back to default policy
|
||||||
log.D.F("policy manager is disabled for kind %d, falling back to default policy (%s)", ev.Kind, p.DefaultPolicy)
|
log.D.F(
|
||||||
|
"policy manager is disabled for kind %d, falling back to default policy (%s)",
|
||||||
|
ev.Kind, p.DefaultPolicy,
|
||||||
|
)
|
||||||
return p.getDefaultPolicyAction(), nil
|
return p.getDefaultPolicyAction(), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -747,7 +795,9 @@ func (p *P) checkKindsPolicy(kind uint16) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkGlobalRulePolicy checks if the event passes the global rule filter
|
// checkGlobalRulePolicy checks if the event passes the global rule filter
|
||||||
func (p *P) checkGlobalRulePolicy(access string, ev *event.E, loggedInPubkey []byte) bool {
|
func (p *P) checkGlobalRulePolicy(
|
||||||
|
access string, ev *event.E, loggedInPubkey []byte,
|
||||||
|
) bool {
|
||||||
// Apply global rule filtering
|
// Apply global rule filtering
|
||||||
allowed, err := p.checkRulePolicy(access, ev, p.Global, loggedInPubkey)
|
allowed, err := p.checkRulePolicy(access, ev, p.Global, loggedInPubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -758,7 +808,9 @@ func (p *P) checkGlobalRulePolicy(access string, ev *event.E, loggedInPubkey []b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkRulePolicy applies rule-based filtering (pubkey lists, size limits, etc.)
|
// checkRulePolicy applies rule-based filtering (pubkey lists, size limits, etc.)
|
||||||
func (p *P) checkRulePolicy(access string, ev *event.E, rule Rule, loggedInPubkey []byte) (allowed bool, err error) {
|
func (p *P) checkRulePolicy(
|
||||||
|
access string, ev *event.E, rule Rule, loggedInPubkey []byte,
|
||||||
|
) (allowed bool, err error) {
|
||||||
pubkeyHex := hex.Enc(ev.Pubkey)
|
pubkeyHex := hex.Enc(ev.Pubkey)
|
||||||
|
|
||||||
// Check pubkey-based access control
|
// Check pubkey-based access control
|
||||||
@@ -886,21 +938,29 @@ func (p *P) checkRulePolicy(access string, ev *event.E, rule Rule, loggedInPubke
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkScriptPolicy runs the policy script to determine if event should be allowed
|
// checkScriptPolicy runs the policy script to determine if event should be allowed
|
||||||
func (p *P) checkScriptPolicy(access string, ev *event.E, scriptPath string, loggedInPubkey []byte, ipAddress string) (allowed bool, err error) {
|
func (p *P) checkScriptPolicy(
|
||||||
|
access string, ev *event.E, scriptPath string, loggedInPubkey []byte,
|
||||||
|
ipAddress string,
|
||||||
|
) (allowed bool, err error) {
|
||||||
if p.Manager == nil {
|
if p.Manager == nil {
|
||||||
return false, fmt.Errorf("policy manager is not initialized")
|
return false, fmt.Errorf("policy manager is not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If policy is disabled, fall back to default policy immediately
|
// If policy is disabled, fall back to default policy immediately
|
||||||
if !p.Manager.IsEnabled() {
|
if !p.Manager.IsEnabled() {
|
||||||
log.W.F("policy rule for kind %d is inactive (policy disabled), falling back to default policy (%s)", ev.Kind, p.DefaultPolicy)
|
log.W.F(
|
||||||
|
"policy rule for kind %d is inactive (policy disabled), falling back to default policy (%s)",
|
||||||
|
ev.Kind, p.DefaultPolicy,
|
||||||
|
)
|
||||||
return p.getDefaultPolicyAction(), nil
|
return p.getDefaultPolicyAction(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if script file exists
|
// Check if script file exists
|
||||||
if _, err := os.Stat(scriptPath); os.IsNotExist(err) {
|
if _, err := os.Stat(scriptPath); os.IsNotExist(err) {
|
||||||
// Script doesn't exist, return error so caller can fall back to other criteria
|
// Script doesn't exist, return error so caller can fall back to other criteria
|
||||||
return false, fmt.Errorf("policy script does not exist at %s", scriptPath)
|
return false, fmt.Errorf(
|
||||||
|
"policy script does not exist at %s", scriptPath,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get or create a runner for this specific script path
|
// Get or create a runner for this specific script path
|
||||||
@@ -912,7 +972,9 @@ func (p *P) checkScriptPolicy(access string, ev *event.E, scriptPath string, log
|
|||||||
log.D.F("starting policy script for kind %d: %s", ev.Kind, scriptPath)
|
log.D.F("starting policy script for kind %d: %s", ev.Kind, scriptPath)
|
||||||
if err := runner.ensureRunning(); err != nil {
|
if err := runner.ensureRunning(); err != nil {
|
||||||
// Startup failed, return error so caller can fall back to other criteria
|
// Startup failed, return error so caller can fall back to other criteria
|
||||||
return false, fmt.Errorf("failed to start policy script %s: %v", scriptPath, err)
|
return false, fmt.Errorf(
|
||||||
|
"failed to start policy script %s: %v", scriptPath, err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
log.I.F("policy script started for kind %d: %s", ev.Kind, scriptPath)
|
log.I.F("policy script started for kind %d: %s", ev.Kind, scriptPath)
|
||||||
}
|
}
|
||||||
@@ -927,7 +989,10 @@ func (p *P) checkScriptPolicy(access string, ev *event.E, scriptPath string, log
|
|||||||
// Process event through policy script
|
// Process event through policy script
|
||||||
response, scriptErr := runner.ProcessEvent(policyEvent)
|
response, scriptErr := runner.ProcessEvent(policyEvent)
|
||||||
if chk.E(scriptErr) {
|
if chk.E(scriptErr) {
|
||||||
log.E.F("policy rule for kind %d failed (script processing error: %v), falling back to default policy (%s)", ev.Kind, scriptErr, p.DefaultPolicy)
|
log.E.F(
|
||||||
|
"policy rule for kind %d failed (script processing error: %v), falling back to default policy (%s)",
|
||||||
|
ev.Kind, scriptErr, p.DefaultPolicy,
|
||||||
|
)
|
||||||
// Fall back to default policy on script failure
|
// Fall back to default policy on script failure
|
||||||
return p.getDefaultPolicyAction(), nil
|
return p.getDefaultPolicyAction(), nil
|
||||||
}
|
}
|
||||||
@@ -941,7 +1006,10 @@ func (p *P) checkScriptPolicy(access string, ev *event.E, scriptPath string, log
|
|||||||
case "shadowReject":
|
case "shadowReject":
|
||||||
return false, nil // Treat as reject for policy purposes
|
return false, nil // Treat as reject for policy purposes
|
||||||
default:
|
default:
|
||||||
log.W.F("policy rule for kind %d returned unknown action '%s', falling back to default policy (%s)", ev.Kind, response.Action, p.DefaultPolicy)
|
log.W.F(
|
||||||
|
"policy rule for kind %d returned unknown action '%s', falling back to default policy (%s)",
|
||||||
|
ev.Kind, response.Action, p.DefaultPolicy,
|
||||||
|
)
|
||||||
// Fall back to default policy for unknown actions
|
// Fall back to default policy for unknown actions
|
||||||
return p.getDefaultPolicyAction(), nil
|
return p.getDefaultPolicyAction(), nil
|
||||||
}
|
}
|
||||||
@@ -967,7 +1035,10 @@ func (pm *PolicyManager) startPolicyIfExists() {
|
|||||||
log.I.F("found default policy script at %s, starting...", pm.scriptPath)
|
log.I.F("found default policy script at %s, starting...", pm.scriptPath)
|
||||||
runner := pm.getOrCreateRunner(pm.scriptPath)
|
runner := pm.getOrCreateRunner(pm.scriptPath)
|
||||||
if err := runner.Start(); err != nil {
|
if err := runner.Start(); err != nil {
|
||||||
log.E.F("failed to start default policy script: %v, will retry periodically", err)
|
log.E.F(
|
||||||
|
"failed to start default policy script: %v, will retry periodically",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Silently ignore if default script doesn't exist - it's fine if rules use custom scripts
|
// Silently ignore if default script doesn't exist - it's fine if rules use custom scripts
|
||||||
|
|||||||
@@ -1514,6 +1514,213 @@ func TestDefaultPolicyLogicWithRules(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRuleScriptLoading(t *testing.T) {
|
||||||
|
// This test validates that a policy script loads for a specific Rule
|
||||||
|
// and properly processes events
|
||||||
|
|
||||||
|
// Create temporary directory for test files
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
scriptPath := filepath.Join(tempDir, "test-rule-script.sh")
|
||||||
|
|
||||||
|
// Create a test script that accepts events with "allowed" in content
|
||||||
|
scriptContent := `#!/bin/bash
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if echo "$line" | grep -q 'allowed'; then
|
||||||
|
echo '{"action":"accept","msg":"Content approved"}'
|
||||||
|
else
|
||||||
|
echo '{"action":"reject","msg":"Content not allowed"}'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
`
|
||||||
|
err := os.WriteFile(scriptPath, []byte(scriptContent), 0755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create test script: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create policy manager with script support
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
manager := &PolicyManager{
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
configDir: tempDir,
|
||||||
|
scriptPath: filepath.Join(tempDir, "default-policy.sh"), // Different from rule script
|
||||||
|
enabled: true,
|
||||||
|
runners: make(map[string]*ScriptRunner),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create policy with a rule that uses the script
|
||||||
|
policy := &P{
|
||||||
|
DefaultPolicy: "deny",
|
||||||
|
Manager: manager,
|
||||||
|
Rules: map[int]Rule{
|
||||||
|
4678: {
|
||||||
|
Description: "Test rule with custom script",
|
||||||
|
Script: scriptPath, // Rule-specific script path
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate test keypairs
|
||||||
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
||||||
|
|
||||||
|
// Pre-start the script before running tests
|
||||||
|
runner := manager.getOrCreateRunner(scriptPath)
|
||||||
|
err = runner.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to start script: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for script to be ready
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
if !runner.IsRunning() {
|
||||||
|
t.Fatal("Script should be running after Start()")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test sending a warmup event to ensure script is responsive
|
||||||
|
signer := p8k.MustNew()
|
||||||
|
signer.Generate()
|
||||||
|
warmupEv := event.New()
|
||||||
|
warmupEv.CreatedAt = time.Now().Unix()
|
||||||
|
warmupEv.Kind = 4678
|
||||||
|
warmupEv.Content = []byte("warmup")
|
||||||
|
warmupEv.Tags = tag.NewS()
|
||||||
|
warmupEv.Sign(signer)
|
||||||
|
|
||||||
|
warmupEvent := &PolicyEvent{
|
||||||
|
E: warmupEv,
|
||||||
|
IPAddress: "127.0.0.1",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send warmup event to verify script is responding
|
||||||
|
_, err = runner.ProcessEvent(warmupEvent)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Script not responding to warmup event: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("Script is ready and responding")
|
||||||
|
|
||||||
|
// Test 1: Event with "allowed" content should be accepted
|
||||||
|
t.Run("script_accepts_allowed_content", func(t *testing.T) {
|
||||||
|
testEvent := createTestEvent(t, eventSigner, "this is allowed content", 4678)
|
||||||
|
|
||||||
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Policy check failed: %v", err)
|
||||||
|
// Check if script exists
|
||||||
|
if _, statErr := os.Stat(scriptPath); statErr != nil {
|
||||||
|
t.Errorf("Script file error: %v", statErr)
|
||||||
|
}
|
||||||
|
t.Fatalf("Unexpected error during policy check: %v", err)
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
t.Error("Expected event with 'allowed' content to be accepted by script")
|
||||||
|
t.Logf("Event content: %s", string(testEvent.Content))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the script runner was created and is running
|
||||||
|
manager.mutex.RLock()
|
||||||
|
runner, exists := manager.runners[scriptPath]
|
||||||
|
manager.mutex.RUnlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
t.Fatal("Expected script runner to be created for rule script path")
|
||||||
|
}
|
||||||
|
if !runner.IsRunning() {
|
||||||
|
t.Error("Expected script runner to be running after processing event")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 2: Event without "allowed" content should be rejected
|
||||||
|
t.Run("script_rejects_disallowed_content", func(t *testing.T) {
|
||||||
|
testEvent := createTestEvent(t, eventSigner, "this is not permitted", 4678)
|
||||||
|
|
||||||
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if allowed {
|
||||||
|
t.Error("Expected event without 'allowed' content to be rejected by script")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 3: Verify script path is correct (rule-specific, not default)
|
||||||
|
t.Run("script_path_is_rule_specific", func(t *testing.T) {
|
||||||
|
manager.mutex.RLock()
|
||||||
|
runner, exists := manager.runners[scriptPath]
|
||||||
|
_, defaultExists := manager.runners[manager.scriptPath]
|
||||||
|
manager.mutex.RUnlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
t.Fatal("Expected rule-specific script runner to exist")
|
||||||
|
}
|
||||||
|
if defaultExists {
|
||||||
|
t.Error("Default script runner should not be created when only rule-specific scripts are used")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the runner is using the correct script path
|
||||||
|
if runner.scriptPath != scriptPath {
|
||||||
|
t.Errorf("Expected runner to use script path %s, got %s", scriptPath, runner.scriptPath)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 4: Multiple events should use the same script instance
|
||||||
|
t.Run("script_reused_for_multiple_events", func(t *testing.T) {
|
||||||
|
// Get initial runner
|
||||||
|
manager.mutex.RLock()
|
||||||
|
initialRunner, _ := manager.runners[scriptPath]
|
||||||
|
initialRunnerCount := len(manager.runners)
|
||||||
|
manager.mutex.RUnlock()
|
||||||
|
|
||||||
|
// Process multiple events
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
content := "this is allowed message " + string(rune('0'+i))
|
||||||
|
testEvent := createTestEvent(t, eventSigner, content, 4678)
|
||||||
|
_, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error on event %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify same runner is used
|
||||||
|
manager.mutex.RLock()
|
||||||
|
currentRunner, _ := manager.runners[scriptPath]
|
||||||
|
currentRunnerCount := len(manager.runners)
|
||||||
|
manager.mutex.RUnlock()
|
||||||
|
|
||||||
|
if currentRunner != initialRunner {
|
||||||
|
t.Error("Expected same runner instance to be reused for multiple events")
|
||||||
|
}
|
||||||
|
if currentRunnerCount != initialRunnerCount {
|
||||||
|
t.Errorf("Expected runner count to stay at %d, got %d", initialRunnerCount, currentRunnerCount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 5: Different kind without script should use default policy
|
||||||
|
t.Run("different_kind_uses_default_policy", func(t *testing.T) {
|
||||||
|
testEvent := createTestEvent(t, eventSigner, "any content", 1) // Kind 1 has no rule
|
||||||
|
|
||||||
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// Should be denied by default policy (deny)
|
||||||
|
if allowed {
|
||||||
|
t.Error("Expected event of kind without rule to be denied by default policy")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Cleanup: Stop the script
|
||||||
|
manager.mutex.RLock()
|
||||||
|
runner, exists := manager.runners[scriptPath]
|
||||||
|
manager.mutex.RUnlock()
|
||||||
|
if exists && runner.IsRunning() {
|
||||||
|
runner.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPolicyFilterProcessing(t *testing.T) {
|
func TestPolicyFilterProcessing(t *testing.T) {
|
||||||
// Test policy filter processing using the provided filter JSON specification
|
// Test policy filter processing using the provided filter JSON specification
|
||||||
filterJSON := []byte(`{
|
filterJSON := []byte(`{
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v0.27.2
|
v0.27.3
|
||||||
Reference in New Issue
Block a user