777 lines
23 KiB
Go
777 lines
23 KiB
Go
package native
|
|
|
|
import (
|
|
"encoding/binary"
|
|
|
|
"github.com/tutus-one/tutus-chain/pkg/core/dao"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/storage"
|
|
"github.com/tutus-one/tutus-chain/pkg/util"
|
|
)
|
|
|
|
// ARCH-005: Comprehensive Audit Logging
|
|
// Provides structured audit logging for all sensitive operations,
|
|
// supporting compliance requirements, incident investigation, and
|
|
// security monitoring.
|
|
|
|
// AuditCategory categorizes audit events.
|
|
type AuditCategory uint8
|
|
|
|
const (
|
|
AuditCategoryAuth AuditCategory = iota
|
|
AuditCategoryAccess
|
|
AuditCategoryData
|
|
AuditCategoryGovernance
|
|
AuditCategoryFinancial
|
|
AuditCategorySecurity
|
|
AuditCategorySystem
|
|
AuditCategoryCompliance
|
|
)
|
|
|
|
// AuditSeverity indicates the importance of an audit event.
|
|
type AuditSeverity uint8
|
|
|
|
const (
|
|
AuditSeverityInfo AuditSeverity = iota
|
|
AuditSeverityNotice
|
|
AuditSeverityWarning
|
|
AuditSeverityAlert
|
|
AuditSeverityCritical
|
|
)
|
|
|
|
// AuditOutcome indicates the result of an audited operation.
|
|
type AuditOutcome uint8
|
|
|
|
const (
|
|
AuditOutcomeSuccess AuditOutcome = iota
|
|
AuditOutcomeFailure
|
|
AuditOutcomeDenied
|
|
AuditOutcomeError
|
|
AuditOutcomePartial
|
|
)
|
|
|
|
// AuditEntry represents a single audit log entry.
|
|
type AuditEntry struct {
|
|
// EntryID is unique identifier for this entry
|
|
EntryID uint64
|
|
// Timestamp is the block height when event occurred
|
|
Timestamp uint32
|
|
// Category classifies the event
|
|
Category AuditCategory
|
|
// Severity indicates importance
|
|
Severity AuditSeverity
|
|
// Outcome is the result of the operation
|
|
Outcome AuditOutcome
|
|
// ContractID identifies the originating contract
|
|
ContractID int32
|
|
// Actor is who performed the action
|
|
Actor util.Uint160
|
|
// Target is the subject of the action (if applicable)
|
|
Target util.Uint160
|
|
// Action is the operation performed
|
|
Action string
|
|
// ResourceID identifies the affected resource
|
|
ResourceID []byte
|
|
// Details contains additional context
|
|
Details string
|
|
// IPHash is hash of source IP (for off-chain correlation)
|
|
IPHash util.Uint256
|
|
// PreviousState is hash of state before change
|
|
PreviousState util.Uint256
|
|
// NewState is hash of state after change
|
|
NewState util.Uint256
|
|
}
|
|
|
|
// AuditQuery defines parameters for searching audit logs.
|
|
type AuditQuery struct {
|
|
// StartBlock is the earliest block to search
|
|
StartBlock uint32
|
|
// EndBlock is the latest block to search
|
|
EndBlock uint32
|
|
// Category filters by event category (nil = all)
|
|
Category *AuditCategory
|
|
// Severity filters by minimum severity
|
|
MinSeverity *AuditSeverity
|
|
// Actor filters by actor address
|
|
Actor *util.Uint160
|
|
// Target filters by target address
|
|
Target *util.Uint160
|
|
// ContractID filters by contract
|
|
ContractID *int32
|
|
// Limit is maximum results to return
|
|
Limit int
|
|
// Offset is the starting position
|
|
Offset int
|
|
}
|
|
|
|
// Storage prefixes for audit logging.
|
|
const (
|
|
auditPrefixEntry byte = 0xA0 // entryID -> AuditEntry
|
|
auditPrefixByBlock byte = 0xA1 // block + entryID -> exists
|
|
auditPrefixByActor byte = 0xA2 // actor + block + entryID -> exists
|
|
auditPrefixByTarget byte = 0xA3 // target + block + entryID -> exists
|
|
auditPrefixByCategory byte = 0xA4 // category + block + entryID -> exists
|
|
auditPrefixBySeverity byte = 0xA5 // severity + block + entryID -> exists
|
|
auditPrefixByContract byte = 0xA6 // contractID + block + entryID -> exists
|
|
auditPrefixCounter byte = 0xAF // -> next entryID
|
|
auditPrefixRetention byte = 0xAE // -> retention config
|
|
)
|
|
|
|
// AuditLogger provides comprehensive audit logging.
|
|
type AuditLogger struct {
|
|
contractID int32
|
|
}
|
|
|
|
// NewAuditLogger creates a new audit logger.
|
|
func NewAuditLogger(contractID int32) *AuditLogger {
|
|
return &AuditLogger{contractID: contractID}
|
|
}
|
|
|
|
// Log records an audit entry.
|
|
func (al *AuditLogger) Log(d *dao.Simple, entry *AuditEntry) uint64 {
|
|
// Get and increment entry counter
|
|
entry.EntryID = al.getNextEntryID(d)
|
|
|
|
// Store main entry
|
|
al.putEntry(d, entry)
|
|
|
|
// Create indices for efficient querying
|
|
al.indexEntry(d, entry)
|
|
|
|
return entry.EntryID
|
|
}
|
|
|
|
// LogAuth logs an authentication event.
|
|
func (al *AuditLogger) LogAuth(d *dao.Simple, actor util.Uint160, action string, outcome AuditOutcome,
|
|
blockHeight uint32, details string) uint64 {
|
|
return al.Log(d, &AuditEntry{
|
|
Timestamp: blockHeight,
|
|
Category: AuditCategoryAuth,
|
|
Severity: al.severityForOutcome(outcome, AuditCategoryAuth),
|
|
Outcome: outcome,
|
|
Actor: actor,
|
|
Action: action,
|
|
Details: details,
|
|
})
|
|
}
|
|
|
|
// LogAccess logs a data access event.
|
|
func (al *AuditLogger) LogAccess(d *dao.Simple, actor, target util.Uint160, action string,
|
|
resourceID []byte, outcome AuditOutcome, blockHeight uint32) uint64 {
|
|
return al.Log(d, &AuditEntry{
|
|
Timestamp: blockHeight,
|
|
Category: AuditCategoryAccess,
|
|
Severity: al.severityForOutcome(outcome, AuditCategoryAccess),
|
|
Outcome: outcome,
|
|
Actor: actor,
|
|
Target: target,
|
|
Action: action,
|
|
ResourceID: resourceID,
|
|
})
|
|
}
|
|
|
|
// LogDataChange logs a data modification event.
|
|
func (al *AuditLogger) LogDataChange(d *dao.Simple, actor util.Uint160, contractID int32,
|
|
action string, resourceID []byte, prevState, newState util.Uint256, blockHeight uint32) uint64 {
|
|
return al.Log(d, &AuditEntry{
|
|
Timestamp: blockHeight,
|
|
Category: AuditCategoryData,
|
|
Severity: AuditSeverityNotice,
|
|
Outcome: AuditOutcomeSuccess,
|
|
ContractID: contractID,
|
|
Actor: actor,
|
|
Action: action,
|
|
ResourceID: resourceID,
|
|
PreviousState: prevState,
|
|
NewState: newState,
|
|
})
|
|
}
|
|
|
|
// LogGovernance logs a governance action.
|
|
func (al *AuditLogger) LogGovernance(d *dao.Simple, actor util.Uint160, action string,
|
|
details string, outcome AuditOutcome, blockHeight uint32) uint64 {
|
|
severity := AuditSeverityNotice
|
|
if outcome == AuditOutcomeSuccess {
|
|
severity = AuditSeverityWarning // Governance changes warrant attention
|
|
}
|
|
|
|
return al.Log(d, &AuditEntry{
|
|
Timestamp: blockHeight,
|
|
Category: AuditCategoryGovernance,
|
|
Severity: severity,
|
|
Outcome: outcome,
|
|
Actor: actor,
|
|
Action: action,
|
|
Details: details,
|
|
})
|
|
}
|
|
|
|
// LogFinancial logs a financial transaction.
|
|
func (al *AuditLogger) LogFinancial(d *dao.Simple, actor, target util.Uint160,
|
|
action string, resourceID []byte, outcome AuditOutcome, blockHeight uint32) uint64 {
|
|
return al.Log(d, &AuditEntry{
|
|
Timestamp: blockHeight,
|
|
Category: AuditCategoryFinancial,
|
|
Severity: AuditSeverityNotice,
|
|
Outcome: outcome,
|
|
Actor: actor,
|
|
Target: target,
|
|
Action: action,
|
|
ResourceID: resourceID,
|
|
})
|
|
}
|
|
|
|
// LogSecurity logs a security-related event.
|
|
func (al *AuditLogger) LogSecurity(d *dao.Simple, actor util.Uint160, action string,
|
|
severity AuditSeverity, outcome AuditOutcome, details string, blockHeight uint32) uint64 {
|
|
return al.Log(d, &AuditEntry{
|
|
Timestamp: blockHeight,
|
|
Category: AuditCategorySecurity,
|
|
Severity: severity,
|
|
Outcome: outcome,
|
|
Actor: actor,
|
|
Action: action,
|
|
Details: details,
|
|
})
|
|
}
|
|
|
|
// LogCompliance logs a compliance-related event.
|
|
func (al *AuditLogger) LogCompliance(d *dao.Simple, actor, target util.Uint160,
|
|
action string, details string, blockHeight uint32) uint64 {
|
|
return al.Log(d, &AuditEntry{
|
|
Timestamp: blockHeight,
|
|
Category: AuditCategoryCompliance,
|
|
Severity: AuditSeverityNotice,
|
|
Outcome: AuditOutcomeSuccess,
|
|
Actor: actor,
|
|
Target: target,
|
|
Action: action,
|
|
Details: details,
|
|
})
|
|
}
|
|
|
|
// LogRightsAccess logs access to rights-protected resources.
|
|
func (al *AuditLogger) LogRightsAccess(d *dao.Simple, actor, subject util.Uint160,
|
|
rightID uint8, action string, outcome AuditOutcome, blockHeight uint32) uint64 {
|
|
return al.Log(d, &AuditEntry{
|
|
Timestamp: blockHeight,
|
|
Category: AuditCategoryCompliance,
|
|
Severity: AuditSeverityWarning,
|
|
Outcome: outcome,
|
|
Actor: actor,
|
|
Target: subject,
|
|
Action: action,
|
|
ResourceID: []byte{rightID},
|
|
})
|
|
}
|
|
|
|
// GetEntry retrieves an audit entry by ID.
|
|
func (al *AuditLogger) GetEntry(d *dao.Simple, entryID uint64) *AuditEntry {
|
|
key := al.makeEntryKey(entryID)
|
|
si := d.GetStorageItem(al.contractID, key)
|
|
if si == nil {
|
|
return nil
|
|
}
|
|
return al.deserializeEntry(si)
|
|
}
|
|
|
|
// Query searches audit logs based on query parameters.
|
|
func (al *AuditLogger) Query(d *dao.Simple, query *AuditQuery) []*AuditEntry {
|
|
var entries []*AuditEntry
|
|
var entryIDs []uint64
|
|
|
|
// Choose the most selective index
|
|
if query.Actor != nil {
|
|
entryIDs = al.queryByActor(d, *query.Actor, query.StartBlock, query.EndBlock, query.Limit+query.Offset)
|
|
} else if query.Target != nil {
|
|
entryIDs = al.queryByTarget(d, *query.Target, query.StartBlock, query.EndBlock, query.Limit+query.Offset)
|
|
} else if query.Category != nil {
|
|
entryIDs = al.queryByCategory(d, *query.Category, query.StartBlock, query.EndBlock, query.Limit+query.Offset)
|
|
} else if query.MinSeverity != nil {
|
|
entryIDs = al.queryBySeverity(d, *query.MinSeverity, query.StartBlock, query.EndBlock, query.Limit+query.Offset)
|
|
} else {
|
|
entryIDs = al.queryByBlock(d, query.StartBlock, query.EndBlock, query.Limit+query.Offset)
|
|
}
|
|
|
|
// Apply offset and limit
|
|
start := query.Offset
|
|
if start > len(entryIDs) {
|
|
return entries
|
|
}
|
|
end := start + query.Limit
|
|
if end > len(entryIDs) {
|
|
end = len(entryIDs)
|
|
}
|
|
|
|
// Fetch entries
|
|
for _, id := range entryIDs[start:end] {
|
|
if entry := al.GetEntry(d, id); entry != nil {
|
|
// Apply additional filters
|
|
if al.matchesQuery(entry, query) {
|
|
entries = append(entries, entry)
|
|
}
|
|
}
|
|
}
|
|
|
|
return entries
|
|
}
|
|
|
|
// GetEntriesForActor retrieves audit entries for a specific actor.
|
|
func (al *AuditLogger) GetEntriesForActor(d *dao.Simple, actor util.Uint160, limit int) []*AuditEntry {
|
|
return al.Query(d, &AuditQuery{
|
|
Actor: &actor,
|
|
Limit: limit,
|
|
})
|
|
}
|
|
|
|
// GetEntriesForTarget retrieves audit entries for a specific target.
|
|
func (al *AuditLogger) GetEntriesForTarget(d *dao.Simple, target util.Uint160, limit int) []*AuditEntry {
|
|
return al.Query(d, &AuditQuery{
|
|
Target: &target,
|
|
Limit: limit,
|
|
})
|
|
}
|
|
|
|
// GetSecurityAlerts retrieves high-severity security events.
|
|
func (al *AuditLogger) GetSecurityAlerts(d *dao.Simple, startBlock, endBlock uint32, limit int) []*AuditEntry {
|
|
severity := AuditSeverityAlert
|
|
category := AuditCategorySecurity
|
|
return al.Query(d, &AuditQuery{
|
|
StartBlock: startBlock,
|
|
EndBlock: endBlock,
|
|
Category: &category,
|
|
MinSeverity: &severity,
|
|
Limit: limit,
|
|
})
|
|
}
|
|
|
|
// GetComplianceLog retrieves compliance-related entries for reporting.
|
|
func (al *AuditLogger) GetComplianceLog(d *dao.Simple, startBlock, endBlock uint32, limit int) []*AuditEntry {
|
|
category := AuditCategoryCompliance
|
|
return al.Query(d, &AuditQuery{
|
|
StartBlock: startBlock,
|
|
EndBlock: endBlock,
|
|
Category: &category,
|
|
Limit: limit,
|
|
})
|
|
}
|
|
|
|
// severityForOutcome determines severity based on outcome and category.
|
|
func (al *AuditLogger) severityForOutcome(outcome AuditOutcome, category AuditCategory) AuditSeverity {
|
|
switch outcome {
|
|
case AuditOutcomeSuccess:
|
|
return AuditSeverityInfo
|
|
case AuditOutcomeFailure:
|
|
return AuditSeverityNotice
|
|
case AuditOutcomeDenied:
|
|
if category == AuditCategorySecurity || category == AuditCategoryAuth {
|
|
return AuditSeverityAlert
|
|
}
|
|
return AuditSeverityWarning
|
|
case AuditOutcomeError:
|
|
return AuditSeverityWarning
|
|
default:
|
|
return AuditSeverityInfo
|
|
}
|
|
}
|
|
|
|
// matchesQuery checks if an entry matches additional query filters.
|
|
func (al *AuditLogger) matchesQuery(entry *AuditEntry, query *AuditQuery) bool {
|
|
if query.Category != nil && entry.Category != *query.Category {
|
|
return false
|
|
}
|
|
if query.MinSeverity != nil && entry.Severity < *query.MinSeverity {
|
|
return false
|
|
}
|
|
if query.ContractID != nil && entry.ContractID != *query.ContractID {
|
|
return false
|
|
}
|
|
if query.Actor != nil && entry.Actor != *query.Actor {
|
|
return false
|
|
}
|
|
if query.Target != nil && entry.Target != *query.Target {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Index query methods.
|
|
func (al *AuditLogger) queryByBlock(d *dao.Simple, startBlock, endBlock uint32, limit int) []uint64 {
|
|
var ids []uint64
|
|
prefix := []byte{auditPrefixByBlock}
|
|
|
|
d.Seek(al.contractID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
|
|
if len(k) < 12 {
|
|
return true
|
|
}
|
|
block := binary.BigEndian.Uint32(k[0:4])
|
|
if block < startBlock || (endBlock > 0 && block > endBlock) {
|
|
return true
|
|
}
|
|
entryID := binary.BigEndian.Uint64(k[4:12])
|
|
ids = append(ids, entryID)
|
|
return len(ids) < limit
|
|
})
|
|
|
|
return ids
|
|
}
|
|
|
|
func (al *AuditLogger) queryByActor(d *dao.Simple, actor util.Uint160, startBlock, endBlock uint32, limit int) []uint64 {
|
|
var ids []uint64
|
|
prefix := make([]byte, 21)
|
|
prefix[0] = auditPrefixByActor
|
|
copy(prefix[1:], actor.BytesBE())
|
|
|
|
d.Seek(al.contractID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
|
|
if len(k) < 12 {
|
|
return true
|
|
}
|
|
block := binary.BigEndian.Uint32(k[0:4])
|
|
if block < startBlock || (endBlock > 0 && block > endBlock) {
|
|
return true
|
|
}
|
|
entryID := binary.BigEndian.Uint64(k[4:12])
|
|
ids = append(ids, entryID)
|
|
return len(ids) < limit
|
|
})
|
|
|
|
return ids
|
|
}
|
|
|
|
func (al *AuditLogger) queryByTarget(d *dao.Simple, target util.Uint160, startBlock, endBlock uint32, limit int) []uint64 {
|
|
var ids []uint64
|
|
prefix := make([]byte, 21)
|
|
prefix[0] = auditPrefixByTarget
|
|
copy(prefix[1:], target.BytesBE())
|
|
|
|
d.Seek(al.contractID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
|
|
if len(k) < 12 {
|
|
return true
|
|
}
|
|
block := binary.BigEndian.Uint32(k[0:4])
|
|
if block < startBlock || (endBlock > 0 && block > endBlock) {
|
|
return true
|
|
}
|
|
entryID := binary.BigEndian.Uint64(k[4:12])
|
|
ids = append(ids, entryID)
|
|
return len(ids) < limit
|
|
})
|
|
|
|
return ids
|
|
}
|
|
|
|
func (al *AuditLogger) queryByCategory(d *dao.Simple, category AuditCategory, startBlock, endBlock uint32, limit int) []uint64 {
|
|
var ids []uint64
|
|
prefix := []byte{auditPrefixByCategory, byte(category)}
|
|
|
|
d.Seek(al.contractID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
|
|
if len(k) < 12 {
|
|
return true
|
|
}
|
|
block := binary.BigEndian.Uint32(k[0:4])
|
|
if block < startBlock || (endBlock > 0 && block > endBlock) {
|
|
return true
|
|
}
|
|
entryID := binary.BigEndian.Uint64(k[4:12])
|
|
ids = append(ids, entryID)
|
|
return len(ids) < limit
|
|
})
|
|
|
|
return ids
|
|
}
|
|
|
|
func (al *AuditLogger) queryBySeverity(d *dao.Simple, minSeverity AuditSeverity, startBlock, endBlock uint32, limit int) []uint64 {
|
|
var ids []uint64
|
|
|
|
for sev := minSeverity; sev <= AuditSeverityCritical; sev++ {
|
|
prefix := []byte{auditPrefixBySeverity, byte(sev)}
|
|
|
|
d.Seek(al.contractID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
|
|
if len(k) < 12 {
|
|
return true
|
|
}
|
|
block := binary.BigEndian.Uint32(k[0:4])
|
|
if block < startBlock || (endBlock > 0 && block > endBlock) {
|
|
return true
|
|
}
|
|
entryID := binary.BigEndian.Uint64(k[4:12])
|
|
ids = append(ids, entryID)
|
|
return len(ids) < limit
|
|
})
|
|
|
|
if len(ids) >= limit {
|
|
break
|
|
}
|
|
}
|
|
|
|
return ids
|
|
}
|
|
|
|
// Storage key helpers.
|
|
func (al *AuditLogger) makeEntryKey(entryID uint64) []byte {
|
|
key := make([]byte, 9)
|
|
key[0] = auditPrefixEntry
|
|
binary.BigEndian.PutUint64(key[1:], entryID)
|
|
return key
|
|
}
|
|
|
|
func (al *AuditLogger) getNextEntryID(d *dao.Simple) uint64 {
|
|
key := []byte{auditPrefixCounter}
|
|
si := d.GetStorageItem(al.contractID, key)
|
|
|
|
var nextID uint64 = 1
|
|
if si != nil && len(si) >= 8 {
|
|
nextID = binary.BigEndian.Uint64(si) + 1
|
|
}
|
|
|
|
data := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(data, nextID)
|
|
d.PutStorageItem(al.contractID, key, data)
|
|
|
|
return nextID
|
|
}
|
|
|
|
func (al *AuditLogger) putEntry(d *dao.Simple, entry *AuditEntry) {
|
|
key := al.makeEntryKey(entry.EntryID)
|
|
data := al.serializeEntry(entry)
|
|
d.PutStorageItem(al.contractID, key, data)
|
|
}
|
|
|
|
func (al *AuditLogger) indexEntry(d *dao.Simple, entry *AuditEntry) {
|
|
// Index by block
|
|
blockKey := make([]byte, 13)
|
|
blockKey[0] = auditPrefixByBlock
|
|
binary.BigEndian.PutUint32(blockKey[1:5], entry.Timestamp)
|
|
binary.BigEndian.PutUint64(blockKey[5:13], entry.EntryID)
|
|
d.PutStorageItem(al.contractID, blockKey, []byte{1})
|
|
|
|
// Index by actor
|
|
if entry.Actor != (util.Uint160{}) {
|
|
actorKey := make([]byte, 33)
|
|
actorKey[0] = auditPrefixByActor
|
|
copy(actorKey[1:21], entry.Actor.BytesBE())
|
|
binary.BigEndian.PutUint32(actorKey[21:25], entry.Timestamp)
|
|
binary.BigEndian.PutUint64(actorKey[25:33], entry.EntryID)
|
|
d.PutStorageItem(al.contractID, actorKey, []byte{1})
|
|
}
|
|
|
|
// Index by target
|
|
if entry.Target != (util.Uint160{}) {
|
|
targetKey := make([]byte, 33)
|
|
targetKey[0] = auditPrefixByTarget
|
|
copy(targetKey[1:21], entry.Target.BytesBE())
|
|
binary.BigEndian.PutUint32(targetKey[21:25], entry.Timestamp)
|
|
binary.BigEndian.PutUint64(targetKey[25:33], entry.EntryID)
|
|
d.PutStorageItem(al.contractID, targetKey, []byte{1})
|
|
}
|
|
|
|
// Index by category
|
|
catKey := make([]byte, 14)
|
|
catKey[0] = auditPrefixByCategory
|
|
catKey[1] = byte(entry.Category)
|
|
binary.BigEndian.PutUint32(catKey[2:6], entry.Timestamp)
|
|
binary.BigEndian.PutUint64(catKey[6:14], entry.EntryID)
|
|
d.PutStorageItem(al.contractID, catKey, []byte{1})
|
|
|
|
// Index by severity
|
|
sevKey := make([]byte, 14)
|
|
sevKey[0] = auditPrefixBySeverity
|
|
sevKey[1] = byte(entry.Severity)
|
|
binary.BigEndian.PutUint32(sevKey[2:6], entry.Timestamp)
|
|
binary.BigEndian.PutUint64(sevKey[6:14], entry.EntryID)
|
|
d.PutStorageItem(al.contractID, sevKey, []byte{1})
|
|
|
|
// Index by contract
|
|
if entry.ContractID != 0 {
|
|
contractKey := make([]byte, 17)
|
|
contractKey[0] = auditPrefixByContract
|
|
binary.BigEndian.PutUint32(contractKey[1:5], uint32(entry.ContractID))
|
|
binary.BigEndian.PutUint32(contractKey[5:9], entry.Timestamp)
|
|
binary.BigEndian.PutUint64(contractKey[9:17], entry.EntryID)
|
|
d.PutStorageItem(al.contractID, contractKey, []byte{1})
|
|
}
|
|
}
|
|
|
|
// Serialization helpers.
|
|
func (al *AuditLogger) serializeEntry(e *AuditEntry) []byte {
|
|
actionBytes := []byte(e.Action)
|
|
detailsBytes := []byte(e.Details)
|
|
|
|
size := 8 + 4 + 1 + 1 + 1 + 4 + 20 + 20 +
|
|
4 + len(actionBytes) +
|
|
4 + len(e.ResourceID) +
|
|
4 + len(detailsBytes) +
|
|
32 + 32 + 32
|
|
|
|
data := make([]byte, size)
|
|
offset := 0
|
|
|
|
binary.BigEndian.PutUint64(data[offset:], e.EntryID)
|
|
offset += 8
|
|
binary.BigEndian.PutUint32(data[offset:], e.Timestamp)
|
|
offset += 4
|
|
data[offset] = byte(e.Category)
|
|
offset++
|
|
data[offset] = byte(e.Severity)
|
|
offset++
|
|
data[offset] = byte(e.Outcome)
|
|
offset++
|
|
binary.BigEndian.PutUint32(data[offset:], uint32(e.ContractID))
|
|
offset += 4
|
|
copy(data[offset:], e.Actor.BytesBE())
|
|
offset += 20
|
|
copy(data[offset:], e.Target.BytesBE())
|
|
offset += 20
|
|
|
|
binary.BigEndian.PutUint32(data[offset:], uint32(len(actionBytes)))
|
|
offset += 4
|
|
copy(data[offset:], actionBytes)
|
|
offset += len(actionBytes)
|
|
|
|
binary.BigEndian.PutUint32(data[offset:], uint32(len(e.ResourceID)))
|
|
offset += 4
|
|
copy(data[offset:], e.ResourceID)
|
|
offset += len(e.ResourceID)
|
|
|
|
binary.BigEndian.PutUint32(data[offset:], uint32(len(detailsBytes)))
|
|
offset += 4
|
|
copy(data[offset:], detailsBytes)
|
|
offset += len(detailsBytes)
|
|
|
|
copy(data[offset:], e.IPHash[:])
|
|
offset += 32
|
|
copy(data[offset:], e.PreviousState[:])
|
|
offset += 32
|
|
copy(data[offset:], e.NewState[:])
|
|
|
|
return data
|
|
}
|
|
|
|
func (al *AuditLogger) deserializeEntry(data []byte) *AuditEntry {
|
|
if len(data) < 60 {
|
|
return nil
|
|
}
|
|
|
|
e := &AuditEntry{}
|
|
offset := 0
|
|
|
|
e.EntryID = binary.BigEndian.Uint64(data[offset:])
|
|
offset += 8
|
|
e.Timestamp = binary.BigEndian.Uint32(data[offset:])
|
|
offset += 4
|
|
e.Category = AuditCategory(data[offset])
|
|
offset++
|
|
e.Severity = AuditSeverity(data[offset])
|
|
offset++
|
|
e.Outcome = AuditOutcome(data[offset])
|
|
offset++
|
|
e.ContractID = int32(binary.BigEndian.Uint32(data[offset:]))
|
|
offset += 4
|
|
e.Actor, _ = util.Uint160DecodeBytesBE(data[offset : offset+20])
|
|
offset += 20
|
|
e.Target, _ = util.Uint160DecodeBytesBE(data[offset : offset+20])
|
|
offset += 20
|
|
|
|
if offset+4 > len(data) {
|
|
return nil
|
|
}
|
|
actionLen := binary.BigEndian.Uint32(data[offset:])
|
|
offset += 4
|
|
if offset+int(actionLen) > len(data) {
|
|
return nil
|
|
}
|
|
e.Action = string(data[offset : offset+int(actionLen)])
|
|
offset += int(actionLen)
|
|
|
|
if offset+4 > len(data) {
|
|
return nil
|
|
}
|
|
resourceLen := binary.BigEndian.Uint32(data[offset:])
|
|
offset += 4
|
|
if offset+int(resourceLen) > len(data) {
|
|
return nil
|
|
}
|
|
e.ResourceID = make([]byte, resourceLen)
|
|
copy(e.ResourceID, data[offset:offset+int(resourceLen)])
|
|
offset += int(resourceLen)
|
|
|
|
if offset+4 > len(data) {
|
|
return nil
|
|
}
|
|
detailsLen := binary.BigEndian.Uint32(data[offset:])
|
|
offset += 4
|
|
if offset+int(detailsLen) > len(data) {
|
|
return nil
|
|
}
|
|
e.Details = string(data[offset : offset+int(detailsLen)])
|
|
offset += int(detailsLen)
|
|
|
|
if offset+96 > len(data) {
|
|
return e // Return partial if hashes missing
|
|
}
|
|
copy(e.IPHash[:], data[offset:offset+32])
|
|
offset += 32
|
|
copy(e.PreviousState[:], data[offset:offset+32])
|
|
offset += 32
|
|
copy(e.NewState[:], data[offset:offset+32])
|
|
|
|
return e
|
|
}
|
|
|
|
// StandardAuditActions defines common audit action names.
|
|
var StandardAuditActions = struct {
|
|
// Auth actions
|
|
AuthLogin string
|
|
AuthLogout string
|
|
AuthFailedLogin string
|
|
AuthRoleAssigned string
|
|
AuthRoleRevoked string
|
|
|
|
// Access actions
|
|
AccessRead string
|
|
AccessWrite string
|
|
AccessDelete string
|
|
AccessDenied string
|
|
|
|
// Financial actions
|
|
FinTransfer string
|
|
FinMint string
|
|
FinBurn string
|
|
FinInvest string
|
|
FinWithdraw string
|
|
|
|
// Governance actions
|
|
GovProposalCreate string
|
|
GovVote string
|
|
GovProposalPass string
|
|
GovProposalReject string
|
|
|
|
// Security actions
|
|
SecCircuitTrip string
|
|
SecRollback string
|
|
SecInvariantFail string
|
|
SecRightsRestrict string
|
|
}{
|
|
AuthLogin: "auth.login",
|
|
AuthLogout: "auth.logout",
|
|
AuthFailedLogin: "auth.failed_login",
|
|
AuthRoleAssigned: "auth.role_assigned",
|
|
AuthRoleRevoked: "auth.role_revoked",
|
|
AccessRead: "access.read",
|
|
AccessWrite: "access.write",
|
|
AccessDelete: "access.delete",
|
|
AccessDenied: "access.denied",
|
|
FinTransfer: "financial.transfer",
|
|
FinMint: "financial.mint",
|
|
FinBurn: "financial.burn",
|
|
FinInvest: "financial.invest",
|
|
FinWithdraw: "financial.withdraw",
|
|
GovProposalCreate: "governance.proposal_create",
|
|
GovVote: "governance.vote",
|
|
GovProposalPass: "governance.proposal_pass",
|
|
GovProposalReject: "governance.proposal_reject",
|
|
SecCircuitTrip: "security.circuit_trip",
|
|
SecRollback: "security.rollback",
|
|
SecInvariantFail: "security.invariant_fail",
|
|
SecRightsRestrict: "security.rights_restrict",
|
|
}
|