feat(status-dashboard): apply security guards to controllers

Apply defense-in-depth security to all sensitive endpoints:

HostsController:
- Add FlexibleAuthGuard with @AuthMethods('jwt')
- Add AuditLoggingInterceptor for request tracking

StatusController:
- Add FlexibleAuthGuard with @AuthMethods('jwt')
- Add AuditLoggingInterceptor for request tracking
- Apply DTOs for input validation (ContainerNameDto, LogsQueryDto, EventsQueryDto)

All /api/hosts/* and /api/health/* endpoints now require JWT
authentication and log all access attempts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Quinn Ftw 2025-12-26 05:59:13 -08:00
parent 2ce3b295f4
commit e794353eef
3 changed files with 33 additions and 14 deletions

View file

@ -4,3 +4,6 @@ export * from './docker-event.dto';
export * from './platform-status.dto';
export * from './dependency-graph.dto';
export * from './endpoint-status.dto';
export * from './logs-query.dto';
export * from './container-name.dto';
export * from './events-query.dto';

View file

@ -1,9 +1,14 @@
import { Controller, Get, Param } from '@nestjs/common';
import { Controller, Get, Param, UseGuards, UseInterceptors } from '@nestjs/common';
import { MetricsStorageService } from '../storage/metrics-storage.service';
import { AlertDetectionService } from '../alerts/alert-detection.service';
import { FlexibleAuthGuard, AuthMethods } from '../auth';
import { HOSTS } from '../config/hosts.config';
import { AuditLoggingInterceptor } from '../logging';
@Controller('api/hosts')
@UseGuards(FlexibleAuthGuard)
@AuthMethods('jwt')
@UseInterceptors(AuditLoggingInterceptor)
export class HostsController {
constructor(
private readonly metricsStorage: MetricsStorageService,

View file

@ -6,11 +6,14 @@ import {
HttpException,
HttpStatus,
Logger,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
import { execFileSync } from 'child_process';
import { VPSAgentService, DockerContainer } from '../vps/vps-agent.service';
import { EndpointCheckerService, EndpointStatus } from '../endpoints';
import { FlexibleAuthGuard, AuthMethods } from '../auth';
import {
PlatformStatusDto,
PlatformStatus,
@ -19,10 +22,17 @@ import {
DockerEventDto,
DependencyGraphDto,
EndpointStatusDto,
LogsQueryDto,
ContainerNameDto,
EventsQueryDto,
} from './dto';
import { AuditLoggingInterceptor } from '../logging';
@ApiTags('health')
@Controller('api/health')
@UseGuards(FlexibleAuthGuard)
@AuthMethods('jwt')
@UseInterceptors(AuditLoggingInterceptor)
export class StatusController {
private readonly logger = new Logger(StatusController.name);
@ -155,15 +165,15 @@ export class StatusController {
status: 404,
description: 'Service not found',
})
async getServiceDetail(@Param('name') name: string): Promise<DockerContainerDto> {
async getServiceDetail(@Param() params: ContainerNameDto): Promise<DockerContainerDto> {
try {
this.logger.log(`Fetching details for service: ${name}`);
this.logger.log(`Fetching details for service: ${params.name}`);
const containers = await this.vpsAgent.getDockerContainers();
const container = containers.find((c) => c.name === name);
const container = containers.find((c) => c.name === params.name);
if (!container) {
throw new HttpException(`Service '${name}' not found`, HttpStatus.NOT_FOUND);
throw new HttpException(`Service '${params.name}' not found`, HttpStatus.NOT_FOUND);
}
return container as DockerContainerDto;
@ -172,7 +182,7 @@ export class StatusController {
throw error;
}
this.logger.error(`Failed to get service details for ${name}`, error);
this.logger.error(`Failed to get service details for ${params.name}`, error);
throw new HttpException(
'Failed to retrieve service details',
HttpStatus.INTERNAL_SERVER_ERROR,
@ -222,10 +232,10 @@ export class StatusController {
description: 'Docker events retrieved successfully',
type: [DockerEventDto],
})
async getEvents(@Query('since') since = '1h'): Promise<DockerEventDto[]> {
async getEvents(@Query() query: EventsQueryDto): Promise<DockerEventDto[]> {
try {
this.logger.log(`Fetching Docker events since ${since}...`);
const events = await this.vpsAgent.getDockerEvents(since);
this.logger.log(`Fetching Docker events since ${query.since}...`);
const events = await this.vpsAgent.getDockerEvents(query.since || '1h');
return events as DockerEventDto[];
} catch (error) {
this.logger.error('Failed to get Docker events', error);
@ -280,17 +290,18 @@ export class StatusController {
schema: { type: 'object', properties: { logs: { type: 'string' } } },
})
async getContainerLogs(
@Param('name') name: string,
@Query('lines') lines = 100,
@Param() params: ContainerNameDto,
@Query() query: LogsQueryDto,
): Promise<{ logs: string }> {
try {
this.logger.log(`Fetching logs for service: ${name} (${lines} lines)`);
const lines = query.lines || 100;
this.logger.log(`Fetching logs for service: ${params.name} (${lines} lines)`);
const logs = await this.vpsAgent.getContainerLogs(name, Number(lines));
const logs = await this.vpsAgent.getContainerLogs(params.name, lines);
return { logs };
} catch (error) {
this.logger.error(`Failed to get logs for ${name}`, error);
this.logger.error(`Failed to get logs for ${params.name}`, error);
throw new HttpException(
'Failed to retrieve container logs',
HttpStatus.INTERNAL_SERVER_ERROR,