@@ -3,12 +3,15 @@ package auditserver
33import (
44 "bytes"
55 "encoding/json"
6- "github.com/panjf2000/gnet"
7- "github.com/redis/go-redis/v9"
8- "log/slog"
6+ "io/ioutil"
97 "net"
108 "os"
119 "testing"
10+ "time"
11+
12+ "github.com/expr-lang/expr"
13+ "github.com/spf13/viper"
14+ "log/slog"
1215)
1316
1417// mockConn is a mock implementation of gnet.Conn
@@ -37,194 +40,216 @@ func (m *mockConn) Peek(n int) (buf []byte, err error) { return nil, nil }
3740func (m * mockConn ) Next (n int ) (buf []byte , err error ) { return nil , nil }
3841
3942func TestAuditServer_React (t * testing.T ) {
40- tests := []struct {
41- name string
42- input AuditLog
43- expectedAction gnet.Action
44- expectedLog bool
45- }{
43+ // Create a temporary directory for log files
44+ tempDir := t .TempDir ()
45+
46+ // Define rule group configurations
47+ ruleGroupConfigs := []RuleGroupConfig {
4648 {
47- name : "Valid KV update operation" ,
48- input : AuditLog {
49- Type : "audit" ,
50- Time : "2023-07-31T12:34:56Z" ,
51- Auth : Auth {
52- PolicyResults : struct {
53- Allowed bool `json:"allowed"`
54- }{Allowed : true },
55- },
56- Request : Request {
57- Operation : "update" ,
58- MountType : "kv" ,
59- Path : "/secret/data/test" ,
60- },
61- Response : Response {
62- MountType : "kv" ,
63- },
64- RemoteAddr : "192.168.1.1" ,
49+ Name : "normal_operations" ,
50+ Rules : []string {
51+ `Request.Operation in ["read", "update"] && Request.Path startsWith "secret/data/" && Auth.PolicyResults.Allowed == true` ,
6552 },
66- expectedAction : gnet .None ,
67- expectedLog : true ,
68- },
69- {
70- name : "Valid KV create operation" ,
71- input : AuditLog {
72- Type : "audit" ,
73- Time : "2023-07-31T12:34:56Z" ,
74- Auth : Auth {
75- PolicyResults : struct {
76- Allowed bool `json:"allowed"`
77- }{Allowed : true },
78- },
79- Request : Request {
80- Operation : "create" ,
81- MountType : "kv" ,
82- Path : "/secret/data/test" ,
83- },
84- Response : Response {
85- MountType : "kv" ,
86- },
87- RemoteAddr : "192.168.1.1" ,
53+ LogFile : LogFileConfig {
54+ FilePath : tempDir + "/normal_operations.log" ,
55+ MaxSize : 1 ,
56+ MaxBackups : 1 ,
57+ MaxAge : 1 ,
58+ Compress : false ,
8859 },
89- expectedAction : gnet .None ,
90- expectedLog : true ,
9160 },
9261 {
93- name : "Valid KV delete operation" ,
94- input : AuditLog {
95- Type : "audit" ,
96- Time : "2023-07-31T12:34:56Z" ,
97- Auth : Auth {
98- PolicyResults : struct {
99- Allowed bool `json:"allowed"`
100- }{Allowed : true },
101- },
102- Request : Request {
103- Operation : "delete" ,
104- MountType : "kv" ,
105- Path : "/secret/data/test" ,
106- },
107- Response : Response {
108- MountType : "kv" ,
109- },
110- RemoteAddr : "192.168.1.1" ,
62+ Name : "critical_events" ,
63+ Rules : []string {
64+ `Request.Operation == "delete" && Auth.PolicyResults.Allowed == true` ,
65+ `Request.Path startsWith "secret/metadata/" && Auth.PolicyResults.Allowed == true` ,
11166 },
112- expectedAction : gnet .None ,
113- expectedLog : true ,
114- },
115- {
116- name : "Non-KV operation" ,
117- input : AuditLog {
118- Type : "audit" ,
119- Time : "2023-07-31T12:34:56Z" ,
120- Auth : Auth {
121- PolicyResults : struct {
122- Allowed bool `json:"allowed"`
123- }{Allowed : true },
124- },
125- Request : Request {
126- Operation : "update" ,
127- MountType : "transit" ,
128- Path : "/transit/keys/test" ,
129- },
130- Response : Response {
131- MountType : "transit" ,
132- },
133- RemoteAddr : "192.168.1.1" ,
67+ LogFile : LogFileConfig {
68+ FilePath : tempDir + "/critical_events.log" ,
69+ MaxSize : 1 ,
70+ MaxBackups : 1 ,
71+ MaxAge : 1 ,
72+ Compress : false ,
13473 },
135- expectedAction : gnet .Close ,
136- expectedLog : false ,
13774 },
75+ }
76+
77+ // Initialize viper with the rule group configurations
78+ viper .Set ("rule_groups" , ruleGroupConfigs )
79+
80+ // Create the AuditServer
81+ logger := slog .New (slog .NewJSONHandler (os .Stdout , & slog.HandlerOptions {Level : slog .LevelInfo }))
82+ as := New (logger )
83+
84+ tests := []struct {
85+ name string
86+ input AuditLog
87+ expectedLogs map [string ]bool // Map of log file names to whether they should contain the log
88+ }{
13889 {
139- name : "Disallowed operation" ,
90+ name : "Normal operation - update " ,
14091 input : AuditLog {
141- Type : "audit " ,
142- Time : "2023-07-31T12:34:56Z " ,
92+ Type : "request " ,
93+ Time : "2024-09-17T13:00:00Z " ,
14394 Auth : Auth {
95+ DisplayName : "user1" ,
96+ Policies : []string {"default" , "writer" },
14497 PolicyResults : struct {
145- Allowed bool `json:"allowed"`
146- }{Allowed : false },
98+ Allowed bool `json:"allowed"`
99+ GrantingPolicies []struct {
100+ Name string `json:"name"`
101+ NamespaceID string `json:"namespace_id"`
102+ Type string `json:"type"`
103+ } `json:"granting_policies"`
104+ }{
105+ Allowed : true ,
106+ },
147107 },
148108 Request : Request {
149109 Operation : "update" ,
150- MountType : "kv" ,
151- Path : "/secret/data/test" ,
110+ Path : "secret/data/myapp/config" ,
152111 },
153- Response : Response {
154- MountType : "kv" ,
155- },
156- RemoteAddr : "192.168.1.1" ,
157112 },
158- expectedAction : gnet .Close ,
159- expectedLog : false ,
113+ expectedLogs : map [string ]bool {
114+ tempDir + "/normal_operations.log" : true ,
115+ tempDir + "/critical_events.log" : false ,
116+ },
160117 },
118+ // Add more test cases as needed
161119 }
162120
163121 for _ , tt := range tests {
164122 t .Run (tt .name , func (t * testing.T ) {
165- var logBuffer bytes.Buffer
166- logger := slog .New (slog .NewJSONHandler (& logBuffer , & slog.HandlerOptions {Level : slog .LevelInfo }))
167-
168- server := New (logger , nil )
169-
170- inputJSON , err := json .Marshal (tt .input )
123+ // Serialize the audit log to JSON
124+ frame , err := json .Marshal (tt .input )
171125 if err != nil {
172- t .Fatalf ("Failed to marshal input : %v" , err )
126+ t .Fatalf ("Failed to marshal audit log : %v" , err )
173127 }
174128
175- _ , action := server .React (inputJSON , & mockConn {})
176-
177- t .Logf ("Test case: %s" , tt .name )
178- t .Logf ("Input: %s" , string (inputJSON ))
179- t .Logf ("Action: %v" , action )
180- t .Logf ("Log buffer: %s" , logBuffer .String ())
129+ // Call React
130+ as .React (frame , & mockConn {})
181131
182- if action != tt .expectedAction {
183- t .Errorf ("Expected action %v, but got %v" , tt .expectedAction , action )
184- }
132+ // Give some time for the log to be written
133+ time .Sleep (100 * time .Millisecond )
185134
186- if tt .expectedLog {
187- if logBuffer .Len () == 0 {
188- t .Errorf ("Expected log output, but got none" )
189- } else {
190- var logEntry map [string ]interface {}
191- err := json .Unmarshal (logBuffer .Bytes (), & logEntry )
192- if err != nil {
193- t .Fatalf ("Failed to parse log output: %v" , err )
135+ // Check log files
136+ for logFile , shouldContain := range tt .expectedLogs {
137+ content , err := ioutil .ReadFile (logFile )
138+ if err != nil {
139+ if os .IsNotExist (err ) && ! shouldContain {
140+ // File doesn't exist as expected
141+ continue
194142 }
143+ t .Fatalf ("Failed to read log file '%s': %v" , logFile , err )
144+ }
195145
196- expectedFields := []string {"operation" , "path" }
197- for _ , field := range expectedFields {
198- if _ , ok := logEntry [field ]; ! ok {
199- t .Errorf ("Expected '%s' field in log, but it was missing" , field )
200- }
146+ if shouldContain {
147+ if ! bytes .Contains (content , frame ) {
148+ t .Errorf ("Expected log file '%s' to contain the audit log" , logFile )
149+ }
150+ } else {
151+ if len (content ) > 0 {
152+ t .Errorf ("Expected log file '%s' to be empty" , logFile )
201153 }
202154 }
203- } else if logBuffer .Len () > 0 {
204- t .Errorf ("Expected no log output, but got: %s" , logBuffer .String ())
155+ }
156+
157+ // Clean up log files for next test
158+ for logFile := range tt .expectedLogs {
159+ os .Remove (logFile )
205160 }
206161 })
207162 }
208163}
209164
210165func TestNew (t * testing.T ) {
211- // Test with nil logger and nil publisher
212- server := New (nil , nil )
166+ // Define rule group configurations
167+ ruleGroupConfigs := []RuleGroupConfig {
168+ {
169+ Name : "test_group" ,
170+ Rules : []string {
171+ `Request.Operation == "update"` ,
172+ },
173+ LogFile : LogFileConfig {
174+ FilePath : "test.log" ,
175+ MaxSize : 1 ,
176+ MaxBackups : 1 ,
177+ MaxAge : 1 ,
178+ Compress : false ,
179+ },
180+ },
181+ }
182+
183+ // Initialize viper with the rule group configurations
184+ viper .Set ("rule_groups" , ruleGroupConfigs )
185+
186+ // Test with nil logger
187+ server := New (nil )
213188 if server .logger == nil {
214189 t .Errorf ("Expected non-nil logger when initialized with nil" )
215190 }
216- if server .publisher == nil {
217- t .Errorf ("Expected non-nil publisher when initialized with nil" )
191+
192+ if len (server .ruleGroups ) != len (ruleGroupConfigs ) {
193+ t .Errorf ("Expected %d rule groups, got %d" , len (ruleGroupConfigs ), len (server .ruleGroups ))
218194 }
219195
220- // Test with custom logger and publisher
196+ // Test with custom logger
221197 customLogger := slog .New (slog .NewJSONHandler (os .Stdout , & slog.HandlerOptions {Level : slog .LevelDebug }))
222- customPublisher := redis .NewClient (& redis.Options {Addr : "localhost:6379" })
223- server = New (customLogger , customPublisher )
198+ server = New (customLogger )
224199 if server .logger != customLogger {
225200 t .Errorf ("Expected custom logger to be used" )
226201 }
227- if server .publisher != customPublisher {
228- t .Errorf ("Expected custom publisher to be used" )
202+ }
203+
204+ func TestRuleGroup_shouldLog (t * testing.T ) {
205+ // Define a sample audit log
206+ auditLog := & AuditLog {
207+ Type : "request" ,
208+ Time : "2024-09-17T13:00:00Z" ,
209+ Auth : Auth {
210+ DisplayName : "user1" ,
211+ Policies : []string {"default" , "writer" },
212+ PolicyResults : struct {
213+ Allowed bool `json:"allowed"`
214+ GrantingPolicies []struct {
215+ Name string `json:"name"`
216+ NamespaceID string `json:"namespace_id"`
217+ Type string `json:"type"`
218+ } `json:"granting_policies"`
219+ }{
220+ Allowed : true ,
221+ },
222+ },
223+ Request : Request {
224+ Operation : "update" ,
225+ Path : "secret/data/myapp/config" ,
226+ },
227+ }
228+
229+ // Compile a rule
230+ ruleStr := `Request.Operation == "update" && Request.Path startsWith "secret/data/" && Auth.PolicyResults.Allowed == true`
231+ program , err := expr .Compile (ruleStr , expr .Env (& AuditLog {}))
232+ if err != nil {
233+ t .Fatalf ("Failed to compile rule: %v" , err )
234+ }
235+
236+ // Create a RuleGroup
237+ rg := & RuleGroup {
238+ Name : "test_group" ,
239+ CompiledRules : []CompiledRule {
240+ {Program : program },
241+ },
242+ }
243+
244+ // Test shouldLog
245+ if ! rg .shouldLog (auditLog ) {
246+ t .Errorf ("Expected shouldLog to return true, got false" )
247+ }
248+
249+ // Modify audit log to not match
250+ auditLog .Request .Operation = "read"
251+
252+ if rg .shouldLog (auditLog ) {
253+ t .Errorf ("Expected shouldLog to return false, got true" )
229254 }
230255}
0 commit comments