1
+ package audit
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "fmt"
7
+ "os"
8
+ "path/filepath"
9
+ "strings"
10
+ "sync"
11
+ "time"
12
+
13
+ "github.com/hyperledger/firefly-common/pkg/fftypes"
14
+ "github.com/hyperledger/firefly-common/pkg/log"
15
+ )
16
+
17
+ const (
18
+ maxLogSize = 100 * 1024 * 1024 // 100MB
19
+ maxLogAge = 30 * 24 * time .Hour // 30 days
20
+ maxLogBackups = 10
21
+ )
22
+
23
+ // AuditEvent represents a security audit event
24
+ type AuditEvent struct {
25
+ Timestamp time.Time `json:"timestamp"`
26
+ EventType string `json:"eventType"`
27
+ UserID string `json:"userId"`
28
+ Action string `json:"action"`
29
+ Resource string `json:"resource"`
30
+ IPAddress string `json:"ipAddress"`
31
+ UserAgent string `json:"userAgent"`
32
+ Details map [string ]interface {} `json:"details"`
33
+ Status string `json:"status"`
34
+ Error string `json:"error,omitempty"`
35
+ }
36
+
37
+ // AuditLogger handles security audit logging
38
+ type AuditLogger struct {
39
+ mu sync.RWMutex
40
+ logFile * os.File
41
+ basePath string
42
+ size int64
43
+ }
44
+
45
+ // NewAuditLogger creates a new audit logger
46
+ func NewAuditLogger (basePath string ) (* AuditLogger , error ) {
47
+ if err := os .MkdirAll (basePath , 0700 ); err != nil {
48
+ return nil , fmt .Errorf ("failed to create audit log directory: %v" , err )
49
+ }
50
+
51
+ // Create a new log file with timestamp
52
+ timestamp := time .Now ().Format ("2006-01-02" )
53
+ logPath := filepath .Join (basePath , fmt .Sprintf ("audit-%s.log" , timestamp ))
54
+ logFile , err := os .OpenFile (logPath , os .O_APPEND | os .O_CREATE | os .O_WRONLY , 0600 )
55
+ if err != nil {
56
+ return nil , fmt .Errorf ("failed to create audit log file: %v" , err )
57
+ }
58
+
59
+ // Get initial file size
60
+ info , err := logFile .Stat ()
61
+ if err != nil {
62
+ logFile .Close ()
63
+ return nil , fmt .Errorf ("failed to get log file info: %v" , err )
64
+ }
65
+
66
+ return & AuditLogger {
67
+ logFile : logFile ,
68
+ basePath : basePath ,
69
+ size : info .Size (),
70
+ }, nil
71
+ }
72
+
73
+ // LogEvent logs a security audit event
74
+ func (al * AuditLogger ) LogEvent (ctx context.Context , event * AuditEvent ) error {
75
+ al .mu .Lock ()
76
+ defer al .mu .Unlock ()
77
+
78
+ // Ensure timestamp is set
79
+ if event .Timestamp .IsZero () {
80
+ event .Timestamp = time .Now ()
81
+ }
82
+
83
+ // Marshal event to JSON
84
+ data , err := json .Marshal (event )
85
+ if err != nil {
86
+ return fmt .Errorf ("failed to marshal audit event: %v" , err )
87
+ }
88
+
89
+ // Add newline for log file
90
+ data = append (data , '\n' )
91
+
92
+ // Check if we need to rotate the log
93
+ if al .size + int64 (len (data )) > maxLogSize {
94
+ if err := al .rotateLog (); err != nil {
95
+ return fmt .Errorf ("failed to rotate log: %v" , err )
96
+ }
97
+ }
98
+
99
+ // Write to log file
100
+ n , err := al .logFile .Write (data )
101
+ if err != nil {
102
+ return fmt .Errorf ("failed to write audit event: %v" , err )
103
+ }
104
+
105
+ // Update size
106
+ al .size += int64 (n )
107
+
108
+ // Also log to standard logger for monitoring
109
+ log .L (ctx ).Infof ("AUDIT: %s - %s - %s - %s - %s" ,
110
+ event .EventType ,
111
+ event .UserID ,
112
+ event .Action ,
113
+ event .Resource ,
114
+ event .Status )
115
+
116
+ return nil
117
+ }
118
+
119
+ // Close closes the audit logger
120
+ func (al * AuditLogger ) Close () error {
121
+ al .mu .Lock ()
122
+ defer al .mu .Unlock ()
123
+
124
+ if al .logFile != nil {
125
+ return al .logFile .Close ()
126
+ }
127
+ return nil
128
+ }
129
+
130
+ // RotateLog rotates the audit log file
131
+ func (al * AuditLogger ) rotateLog () error {
132
+ // Close current file
133
+ if al .logFile != nil {
134
+ al .logFile .Close ()
135
+ }
136
+
137
+ // Create new file with current timestamp
138
+ timestamp := time .Now ().Format ("2006-01-02" )
139
+ logPath := filepath .Join (al .basePath , fmt .Sprintf ("audit-%s.log" , timestamp ))
140
+ logFile , err := os .OpenFile (logPath , os .O_APPEND | os .O_CREATE | os .O_WRONLY , 0600 )
141
+ if err != nil {
142
+ return fmt .Errorf ("failed to create new audit log file: %v" , err )
143
+ }
144
+
145
+ al .logFile = logFile
146
+ al .size = 0
147
+
148
+ // Clean up old log files
149
+ if err := al .cleanupOldLogs (); err != nil {
150
+ return fmt .Errorf ("failed to cleanup old logs: %v" , err )
151
+ }
152
+
153
+ return nil
154
+ }
155
+
156
+ // cleanupOldLogs removes old log files
157
+ func (al * AuditLogger ) cleanupOldLogs () error {
158
+ entries , err := os .ReadDir (al .basePath )
159
+ if err != nil {
160
+ return err
161
+ }
162
+
163
+ now := time .Now ()
164
+ for _ , entry := range entries {
165
+ if entry .IsDir () {
166
+ continue
167
+ }
168
+
169
+ // Check if file is an audit log
170
+ if ! strings .HasPrefix (entry .Name (), "audit-" ) || ! strings .HasSuffix (entry .Name (), ".log" ) {
171
+ continue
172
+ }
173
+
174
+ // Parse timestamp from filename
175
+ timestamp , err := time .Parse ("2006-01-02" , strings .TrimSuffix (strings .TrimPrefix (entry .Name (), "audit-" ), ".log" ))
176
+ if err != nil {
177
+ continue
178
+ }
179
+
180
+ // Check if file is too old
181
+ if now .Sub (timestamp ) > maxLogAge {
182
+ path := filepath .Join (al .basePath , entry .Name ())
183
+ if err := os .Remove (path ); err != nil {
184
+ return fmt .Errorf ("failed to remove old log file %s: %v" , path , err )
185
+ }
186
+ }
187
+ }
188
+
189
+ return nil
190
+ }
0 commit comments