358 lines
8.6 KiB
Markdown
358 lines
8.6 KiB
Markdown
# Milestone 3: Audit Logging & Rate Limiting
|
|
|
|
**Status**: Complete ✓
|
|
**Goal**: Track security events and prevent API abuse.
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This milestone adds security event logging and rate limiting to prevent abuse. The audit log tracks permission checks, sandbox violations, and resource usage. The rate limiter uses a token bucket algorithm to limit API call frequency.
|
|
|
|
### Key Deliverables
|
|
|
|
1. **AuditLog class** - Security event logging with ring buffer
|
|
2. **RateLimiter class** - Token bucket rate limiting
|
|
3. **Thread safety** - Concurrent access support
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
```
|
|
src/main/cpp/sandbox/
|
|
├── lua_sandbox.h # (existing)
|
|
├── lua_sandbox.cpp # (existing)
|
|
├── permission_gate.h # (existing)
|
|
├── permission_gate.cpp # (existing)
|
|
├── audit_log.h # NEW - Audit logging
|
|
├── audit_log.cpp # NEW - Implementation
|
|
├── rate_limiter.h # NEW - Rate limiting
|
|
└── rate_limiter.cpp # NEW - Implementation
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Details
|
|
|
|
### 1. AuditEvent Enum
|
|
|
|
```cpp
|
|
// audit_log.h
|
|
enum class AuditEvent {
|
|
// Lifecycle
|
|
AppStart,
|
|
AppStop,
|
|
|
|
// Permissions
|
|
PermissionCheck,
|
|
PermissionGranted,
|
|
PermissionDenied,
|
|
|
|
// Network
|
|
NetworkRequest,
|
|
NetworkBlocked,
|
|
|
|
// Storage
|
|
FileAccess,
|
|
FileBlocked,
|
|
DatabaseAccess,
|
|
|
|
// Hardware
|
|
CameraAccess,
|
|
MicrophoneAccess,
|
|
LocationAccess,
|
|
|
|
// Security
|
|
SandboxViolation,
|
|
ResourceLimitHit,
|
|
RateLimitHit,
|
|
|
|
// Other
|
|
Custom
|
|
};
|
|
```
|
|
|
|
### 2. AuditLog Class
|
|
|
|
```cpp
|
|
// audit_log.h
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <mutex>
|
|
#include <chrono>
|
|
|
|
namespace mosis {
|
|
|
|
enum class AuditEvent { /* ... */ };
|
|
|
|
struct AuditEntry {
|
|
std::chrono::system_clock::time_point timestamp;
|
|
AuditEvent event;
|
|
std::string app_id;
|
|
std::string details;
|
|
bool success;
|
|
};
|
|
|
|
class AuditLog {
|
|
public:
|
|
explicit AuditLog(size_t max_entries = 10000);
|
|
|
|
// Log an event
|
|
void Log(AuditEvent event, const std::string& app_id,
|
|
const std::string& details = "", bool success = true);
|
|
|
|
// Query entries
|
|
std::vector<AuditEntry> GetEntries(size_t count = 100) const;
|
|
std::vector<AuditEntry> GetEntriesForApp(const std::string& app_id,
|
|
size_t count = 100) const;
|
|
std::vector<AuditEntry> GetEntriesByEvent(AuditEvent event,
|
|
size_t count = 100) const;
|
|
|
|
// Statistics
|
|
size_t GetTotalEntries() const;
|
|
size_t CountEvents(AuditEvent event, const std::string& app_id = "") const;
|
|
|
|
// Clear
|
|
void Clear();
|
|
|
|
private:
|
|
mutable std::mutex m_mutex;
|
|
std::vector<AuditEntry> m_entries;
|
|
size_t m_max_entries;
|
|
size_t m_write_index = 0;
|
|
size_t m_total_logged = 0;
|
|
};
|
|
|
|
// Global audit log (singleton pattern)
|
|
AuditLog& GetAuditLog();
|
|
|
|
} // namespace mosis
|
|
```
|
|
|
|
### 3. RateLimiter Class (Token Bucket)
|
|
|
|
```cpp
|
|
// rate_limiter.h
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <mutex>
|
|
#include <chrono>
|
|
|
|
namespace mosis {
|
|
|
|
struct RateLimitConfig {
|
|
double tokens_per_second; // Refill rate
|
|
double max_tokens; // Bucket capacity
|
|
};
|
|
|
|
class RateLimiter {
|
|
public:
|
|
// Default limits for common operations
|
|
RateLimiter();
|
|
|
|
// Check if operation is allowed (consumes token if yes)
|
|
bool Check(const std::string& app_id, const std::string& operation);
|
|
|
|
// Check without consuming
|
|
bool CanProceed(const std::string& app_id, const std::string& operation) const;
|
|
|
|
// Configure limits for an operation
|
|
void SetLimit(const std::string& operation, const RateLimitConfig& config);
|
|
|
|
// Get current token count
|
|
double GetTokens(const std::string& app_id, const std::string& operation) const;
|
|
|
|
// Reset an app's tokens (e.g., on app restart)
|
|
void ResetApp(const std::string& app_id);
|
|
|
|
private:
|
|
struct Bucket {
|
|
double tokens;
|
|
std::chrono::steady_clock::time_point last_refill;
|
|
};
|
|
|
|
void Refill(Bucket& bucket, const RateLimitConfig& config) const;
|
|
Bucket& GetBucket(const std::string& app_id, const std::string& operation);
|
|
|
|
mutable std::mutex m_mutex;
|
|
std::unordered_map<std::string, RateLimitConfig> m_configs;
|
|
std::unordered_map<std::string, Bucket> m_buckets; // Key: app_id:operation
|
|
};
|
|
|
|
// Global rate limiter
|
|
RateLimiter& GetRateLimiter();
|
|
|
|
} // namespace mosis
|
|
```
|
|
|
|
### 4. Default Rate Limits
|
|
|
|
```cpp
|
|
// rate_limiter.cpp
|
|
RateLimiter::RateLimiter() {
|
|
// Network
|
|
SetLimit("network.request", {10.0, 100.0}); // 10/sec, burst 100
|
|
SetLimit("network.websocket", {2.0, 10.0}); // 2/sec, burst 10
|
|
|
|
// Storage
|
|
SetLimit("storage.read", {100.0, 500.0}); // 100/sec, burst 500
|
|
SetLimit("storage.write", {20.0, 100.0}); // 20/sec, burst 100
|
|
SetLimit("database.query", {50.0, 200.0}); // 50/sec, burst 200
|
|
|
|
// Hardware
|
|
SetLimit("camera.capture", {30.0, 30.0}); // 30 fps max
|
|
SetLimit("microphone.record", {1.0, 1.0}); // 1 session at a time
|
|
SetLimit("location.request", {1.0, 5.0}); // 1/sec, burst 5
|
|
|
|
// Timers
|
|
SetLimit("timer.create", {10.0, 100.0}); // 10/sec, burst 100
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Test Cases
|
|
|
|
### Test 1: Audit Log Basic
|
|
|
|
```cpp
|
|
bool Test_AuditLogBasic(std::string& error_msg) {
|
|
mosis::AuditLog log(1000);
|
|
|
|
log.Log(mosis::AuditEvent::AppStart, "test.app", "App started");
|
|
log.Log(mosis::AuditEvent::PermissionCheck, "test.app", "camera", true);
|
|
log.Log(mosis::AuditEvent::PermissionDenied, "test.app", "microphone", false);
|
|
|
|
auto entries = log.GetEntries(10);
|
|
EXPECT_TRUE(entries.size() == 3);
|
|
|
|
auto app_entries = log.GetEntriesForApp("test.app", 10);
|
|
EXPECT_TRUE(app_entries.size() == 3);
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Test 2: Audit Log Ring Buffer
|
|
|
|
```cpp
|
|
bool Test_AuditLogRingBuffer(std::string& error_msg) {
|
|
mosis::AuditLog log(100); // Small buffer
|
|
|
|
// Log more than capacity
|
|
for (int i = 0; i < 200; i++) {
|
|
log.Log(mosis::AuditEvent::Custom, "test.app", std::to_string(i));
|
|
}
|
|
|
|
// Should only have latest 100
|
|
auto entries = log.GetEntries(200);
|
|
EXPECT_TRUE(entries.size() == 100);
|
|
|
|
// Total logged should be 200
|
|
EXPECT_TRUE(log.GetTotalEntries() == 200);
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Test 3: Rate Limiter Basic
|
|
|
|
```cpp
|
|
bool Test_RateLimiterBasic(std::string& error_msg) {
|
|
mosis::RateLimiter limiter;
|
|
|
|
// Should succeed initially (has tokens)
|
|
EXPECT_TRUE(limiter.Check("test.app", "network.request"));
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Test 4: Rate Limiter Exhaustion
|
|
|
|
```cpp
|
|
bool Test_RateLimiterExhaustion(std::string& error_msg) {
|
|
mosis::RateLimiter limiter;
|
|
limiter.SetLimit("test.op", {0.0, 5.0}); // 5 tokens, no refill
|
|
|
|
// Use all tokens
|
|
for (int i = 0; i < 5; i++) {
|
|
EXPECT_TRUE(limiter.Check("test.app", "test.op"));
|
|
}
|
|
|
|
// Should be denied now
|
|
EXPECT_FALSE(limiter.Check("test.app", "test.op"));
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Test 5: Rate Limiter Refill
|
|
|
|
```cpp
|
|
bool Test_RateLimiterRefill(std::string& error_msg) {
|
|
mosis::RateLimiter limiter;
|
|
limiter.SetLimit("test.op", {1000.0, 1.0}); // 1000/sec, max 1
|
|
|
|
// Use the token
|
|
EXPECT_TRUE(limiter.Check("test.app", "test.op"));
|
|
EXPECT_FALSE(limiter.Check("test.app", "test.op"));
|
|
|
|
// Wait a bit for refill (1ms = 1 token at 1000/sec)
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
|
|
|
// Should have token again
|
|
EXPECT_TRUE(limiter.Check("test.app", "test.op"));
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Test 6: App Isolation
|
|
|
|
```cpp
|
|
bool Test_RateLimiterAppIsolation(std::string& error_msg) {
|
|
mosis::RateLimiter limiter;
|
|
limiter.SetLimit("test.op", {0.0, 1.0}); // 1 token, no refill
|
|
|
|
// App 1 uses its token
|
|
EXPECT_TRUE(limiter.Check("app1", "test.op"));
|
|
EXPECT_FALSE(limiter.Check("app1", "test.op"));
|
|
|
|
// App 2 should still have its token
|
|
EXPECT_TRUE(limiter.Check("app2", "test.op"));
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Acceptance Criteria
|
|
|
|
All tests must pass:
|
|
|
|
- [x] `Test_AuditLogBasic` - Logging and retrieval work
|
|
- [x] `Test_AuditLogRingBuffer` - Ring buffer wraps correctly
|
|
- [x] `Test_AuditLogThreadSafe` - Thread safety verified
|
|
- [x] `Test_RateLimiterBasic` - Basic rate limiting works
|
|
- [x] `Test_RateLimiterExhaustion` - Tokens exhaust correctly
|
|
- [x] `Test_RateLimiterRefill` - Tokens refill over time
|
|
- [x] `Test_RateLimiterAppIsolation` - Apps have separate buckets
|
|
- [x] `Test_RateLimiterReset` - App reset clears buckets
|
|
- [x] `Test_RateLimiterNoConfig` - Unconfigured ops allowed
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
After Milestone 3 passes:
|
|
1. Integrate AuditLog into LuaSandbox and PermissionGate
|
|
2. Integrate RateLimiter into API implementations
|
|
3. Milestone 4: Safe Path & Require
|