v0.1.16: Security overhaul and systematic deployment preparation

Breaking changes for clean alpha releases:
- JWT authentication with user-provided secrets (no more development defaults)
- Registration token system for secure agent enrollment
- Rate limiting with user-adjustable settings
- Enhanced agent configuration with proxy support
- Interactive server setup wizard (--setup flag)
- Heartbeat architecture separation for better UX
- Package status synchronization fixes
- Accurate timestamp tracking for RMM features

Setup process for new installations:
1. docker-compose up -d postgres
2. ./redflag-server --setup
3. ./redflag-server --migrate
4. ./redflag-server
5. Generate tokens via admin UI
6. Deploy agents with registration tokens
This commit is contained in:
Fimeg
2025-10-29 10:38:18 -04:00
parent b3e1b9e52f
commit 03fee29760
50 changed files with 5807 additions and 466 deletions

View File

@@ -14,7 +14,14 @@ import {
DockerContainerListResponse,
DockerStats,
DockerUpdateRequest,
BulkDockerUpdateRequest
BulkDockerUpdateRequest,
RegistrationToken,
CreateRegistrationTokenRequest,
RegistrationTokenStats,
RateLimitConfig,
RateLimitStats,
RateLimitUsage,
RateLimitSummary
} from '@/types';
// Base URL for API
@@ -75,6 +82,21 @@ export const agentApi = {
await api.post(`/agents/${id}/scan`);
},
// Trigger heartbeat toggle on single agent
toggleHeartbeat: async (id: string, enabled: boolean, durationMinutes: number = 10): Promise<{ message: string; command_id: string; enabled: boolean }> => {
const response = await api.post(`/agents/${id}/heartbeat`, {
enabled: enabled,
duration_minutes: durationMinutes,
});
return response.data;
},
// Get heartbeat status for single agent
getHeartbeatStatus: async (id: string): Promise<{ enabled: boolean; until: string | null; active: boolean; duration_minutes: number }> => {
const response = await api.get(`/agents/${id}/heartbeat`);
return response.data;
},
// Unregister/remove agent
unregisterAgent: async (id: string): Promise<void> => {
await api.delete(`/agents/${id}`);
@@ -147,6 +169,28 @@ export const updateApi = {
});
return response.data;
},
// Clear failed commands with filtering options
clearFailedCommands: async (options?: {
olderThanDays?: number;
onlyRetried?: boolean;
allFailed?: boolean;
}): Promise<{ message: string; count: number; cheeky_warning?: string }> => {
const params = new URLSearchParams();
if (options?.olderThanDays !== undefined) {
params.append('older_than_days', options.olderThanDays.toString());
}
if (options?.onlyRetried) {
params.append('only_retried', 'true');
}
if (options?.allFailed) {
params.append('all_failed', 'true');
}
const response = await api.delete(`/commands/failed${params.toString() ? '?' + params.toString() : ''}`);
return response.data;
},
};
export const statsApi = {
@@ -351,4 +395,144 @@ export const dockerApi = {
},
};
// Admin API endpoints
export const adminApi = {
// Registration Token Management
tokens: {
// Get all registration tokens
getTokens: async (params?: {
page?: number;
page_size?: number;
is_active?: boolean;
label?: string;
}): Promise<{ tokens: RegistrationToken[]; total: number; page: number; page_size: number }> => {
const response = await api.get('/admin/registration-tokens', { params });
return response.data;
},
// Get single registration token
getToken: async (id: string): Promise<RegistrationToken> => {
const response = await api.get(`/admin/registration-tokens/${id}`);
return response.data;
},
// Create new registration token
createToken: async (request: CreateRegistrationTokenRequest): Promise<RegistrationToken> => {
const response = await api.post('/admin/registration-tokens', request);
return response.data;
},
// Revoke registration token
revokeToken: async (id: string): Promise<void> => {
await api.delete(`/admin/registration-tokens/${id}`);
},
// Get registration token statistics
getStats: async (): Promise<RegistrationTokenStats> => {
const response = await api.get('/admin/registration-tokens/stats');
return response.data;
},
// Cleanup expired tokens
cleanup: async (): Promise<{ cleaned: number }> => {
const response = await api.post('/admin/registration-tokens/cleanup');
return response.data;
},
},
// Rate Limiting Management
rateLimits: {
// Get all rate limit configurations
getConfigs: async (): Promise<RateLimitConfig[]> => {
const response = await api.get('/admin/rate-limits');
return response.data;
},
// Update rate limit configuration
updateConfig: async (endpoint: string, config: Partial<RateLimitConfig>): Promise<RateLimitConfig> => {
const response = await api.put(`/admin/rate-limits/${endpoint}`, config);
return response.data;
},
// Update all rate limit configurations
updateAllConfigs: async (configs: RateLimitConfig[]): Promise<RateLimitConfig[]> => {
const response = await api.put('/admin/rate-limits', { configs });
return response.data;
},
// Reset rate limit configurations to defaults
resetConfigs: async (): Promise<RateLimitConfig[]> => {
const response = await api.post('/admin/rate-limits/reset');
return response.data;
},
// Get rate limit statistics
getStats: async (): Promise<RateLimitStats[]> => {
const response = await api.get('/admin/rate-limits/stats');
return response.data;
},
// Get rate limit usage
getUsage: async (): Promise<RateLimitUsage[]> => {
const response = await api.get('/admin/rate-limits/usage');
return response.data;
},
// Get rate limit summary
getSummary: async (): Promise<RateLimitSummary> => {
const response = await api.get('/admin/rate-limits/summary');
return response.data;
},
// Cleanup expired rate limit data
cleanup: async (): Promise<{ cleaned: number }> => {
const response = await api.post('/admin/rate-limits/cleanup');
return response.data;
},
},
// System Administration
system: {
// Get system health and status
getHealth: async (): Promise<{
status: 'healthy' | 'degraded' | 'unhealthy';
uptime: number;
version: string;
database_status: 'connected' | 'disconnected';
active_agents: number;
active_tokens: number;
rate_limits_enabled: boolean;
}> => {
const response = await api.get('/admin/system/health');
return response.data;
},
// Get active agents
getActiveAgents: async (): Promise<{
agents: Array<{
id: string;
hostname: string;
last_seen: string;
status: string;
}>;
count: number;
}> => {
const response = await api.get('/admin/system/active-agents');
return response.data;
},
// Get system configuration
getConfig: async (): Promise<Record<string, any>> => {
const response = await api.get('/admin/system/config');
return response.data;
},
// Update system configuration
updateConfig: async (config: Record<string, any>): Promise<Record<string, any>> => {
const response = await api.put('/admin/system/config', config);
return response.data;
},
},
};
export default api;