110 lines
2.6 KiB
Go
110 lines
2.6 KiB
Go
package audit
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/sha256"
|
||
|
|
"fmt"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// ImmutableLogEntry represents a tamper-proof audit entry
|
||
|
|
type ImmutableLogEntry struct {
|
||
|
|
Sequence int64 `json:"sequence"`
|
||
|
|
PrevHash string `json:"prev_hash"`
|
||
|
|
Content string `json:"content"`
|
||
|
|
Hash string `json:"hash"`
|
||
|
|
Timestamp time.Time `json:"timestamp"`
|
||
|
|
Signature string `json:"signature,omitempty"`
|
||
|
|
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// ImmutableLog maintains a tamper-proof audit trail
|
||
|
|
type ImmutableLog struct {
|
||
|
|
mu sync.RWMutex
|
||
|
|
entries []*ImmutableLogEntry
|
||
|
|
logPath string
|
||
|
|
sequence int64
|
||
|
|
prevHash string
|
||
|
|
workflowKey string
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewImmutableLog creates a new immutable log
|
||
|
|
func NewImmutableLog(logPath string, workflowKey string) *ImmutableLog {
|
||
|
|
return &ImmutableLog{
|
||
|
|
entries: make([]*ImmutableLogEntry, 0),
|
||
|
|
logPath: logPath,
|
||
|
|
sequence: 0,
|
||
|
|
prevHash: "genesis",
|
||
|
|
workflowKey: workflowKey,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Append adds an entry to the immutable log
|
||
|
|
func (il *ImmutableLog) Append(content string, metadata map[string]interface{}) (*ImmutableLogEntry, error) {
|
||
|
|
il.mu.Lock()
|
||
|
|
defer il.mu.Unlock()
|
||
|
|
|
||
|
|
il.sequence++
|
||
|
|
hash := il.computeHash(il.sequence, il.prevHash, content)
|
||
|
|
|
||
|
|
entry := &ImmutableLogEntry{
|
||
|
|
Sequence: il.sequence,
|
||
|
|
PrevHash: il.prevHash,
|
||
|
|
Content: content,
|
||
|
|
Hash: hash,
|
||
|
|
Timestamp: time.Now(),
|
||
|
|
Metadata: metadata,
|
||
|
|
}
|
||
|
|
|
||
|
|
il.entries = append(il.entries, entry)
|
||
|
|
il.prevHash = hash
|
||
|
|
|
||
|
|
return entry, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify verifies the integrity of the log
|
||
|
|
func (il *ImmutableLog) Verify() (bool, error) {
|
||
|
|
il.mu.RLock()
|
||
|
|
defer il.mu.RUnlock()
|
||
|
|
|
||
|
|
prevHash := "genesis"
|
||
|
|
|
||
|
|
for _, entry := range il.entries {
|
||
|
|
expectedHash := il.computeHash(entry.Sequence, entry.PrevHash, entry.Content)
|
||
|
|
|
||
|
|
if entry.Hash != expectedHash || entry.PrevHash != prevHash {
|
||
|
|
return false, fmt.Errorf("integrity check failed at sequence %d", entry.Sequence)
|
||
|
|
}
|
||
|
|
|
||
|
|
prevHash = entry.Hash
|
||
|
|
}
|
||
|
|
|
||
|
|
return true, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetEntries returns all entries
|
||
|
|
func (il *ImmutableLog) GetEntries() []*ImmutableLogEntry {
|
||
|
|
il.mu.RLock()
|
||
|
|
defer il.mu.RUnlock()
|
||
|
|
|
||
|
|
result := make([]*ImmutableLogEntry, len(il.entries))
|
||
|
|
copy(result, il.entries)
|
||
|
|
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetLastHash returns the last hash
|
||
|
|
func (il *ImmutableLog) GetLastHash() string {
|
||
|
|
il.mu.RLock()
|
||
|
|
defer il.mu.RUnlock()
|
||
|
|
|
||
|
|
return il.prevHash
|
||
|
|
}
|
||
|
|
|
||
|
|
// computeHash computes SHA256 hash
|
||
|
|
func (il *ImmutableLog) computeHash(seq int64, prevHash, content string) string {
|
||
|
|
data := fmt.Sprintf("%d:%s:%s:%s", seq, prevHash, content, il.workflowKey)
|
||
|
|
hash := sha256.Sum256([]byte(data))
|
||
|
|
return fmt.Sprintf("%x", hash)
|
||
|
|
}
|