956 lines
25 KiB
Markdown
956 lines
25 KiB
Markdown
# SECURITY HARDENING - ERP Generic
|
|
|
|
**Última actualización:** 2025-11-24
|
|
**Responsable:** Security Team / DevOps Team
|
|
**Estado:** ✅ Production-Ready
|
|
|
|
---
|
|
|
|
## TABLE OF CONTENTS
|
|
|
|
1. [Overview](#1-overview)
|
|
2. [OWASP Top 10 Mitigations](#2-owasp-top-10-mitigations)
|
|
3. [Authentication & Authorization](#3-authentication--authorization)
|
|
4. [Input Validation & Sanitization](#4-input-validation--sanitization)
|
|
5. [SQL Injection Prevention](#5-sql-injection-prevention)
|
|
6. [XSS & CSRF Protection](#6-xss--csrf-protection)
|
|
7. [Security Headers](#7-security-headers)
|
|
8. [Rate Limiting & DDoS Protection](#8-rate-limiting--ddos-protection)
|
|
9. [Secrets Management](#9-secrets-management)
|
|
10. [SSL/TLS Configuration](#10-ssltls-configuration)
|
|
11. [Security Scanning & Auditing](#11-security-scanning--auditing)
|
|
12. [Incident Response](#12-incident-response)
|
|
|
|
---
|
|
|
|
## 1. OVERVIEW
|
|
|
|
### 1.1 Security Posture
|
|
|
|
**Defense in Depth Strategy:**
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Layer 7: Application Security │
|
|
│ - Input validation, Output encoding │
|
|
│ - OWASP Top 10 mitigations, Code review │
|
|
└─────────────────┬───────────────────────────────────────┘
|
|
│
|
|
┌─────────────────▼───────────────────────────────────────┐
|
|
│ Layer 6: Authentication & Authorization │
|
|
│ - JWT with rotation, RBAC, MFA │
|
|
└─────────────────┬───────────────────────────────────────┘
|
|
│
|
|
┌─────────────────▼───────────────────────────────────────┐
|
|
│ Layer 5: API Security │
|
|
│ - Rate limiting, CORS, API Gateway │
|
|
└─────────────────┬───────────────────────────────────────┘
|
|
│
|
|
┌─────────────────▼───────────────────────────────────────┐
|
|
│ Layer 4: Network Security │
|
|
│ - Firewall, Security groups, WAF │
|
|
└─────────────────┬───────────────────────────────────────┘
|
|
│
|
|
┌─────────────────▼───────────────────────────────────────┐
|
|
│ Layer 3: Data Security │
|
|
│ - Encryption at rest/transit, RLS, Backup encryption │
|
|
└─────────────────┬───────────────────────────────────────┘
|
|
│
|
|
┌─────────────────▼───────────────────────────────────────┐
|
|
│ Layer 2: Infrastructure Security │
|
|
│ - OS hardening, Container security, Secrets mgmt │
|
|
└─────────────────┬───────────────────────────────────────┘
|
|
│
|
|
┌─────────────────▼───────────────────────────────────────┐
|
|
│ Layer 1: Physical Security │
|
|
│ - Data center security, Access controls │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 1.2 Security Objectives
|
|
|
|
- **Confidentiality:** Protect sensitive data (PII, financial data)
|
|
- **Integrity:** Prevent unauthorized data modification
|
|
- **Availability:** Ensure system uptime (DDoS protection)
|
|
- **Accountability:** Audit trail for all sensitive operations
|
|
- **Non-Repudiation:** Digital signatures for critical transactions
|
|
|
|
---
|
|
|
|
## 2. OWASP TOP 10 MITIGATIONS
|
|
|
|
### 2.1 A01:2021 - Broken Access Control
|
|
|
|
**Threat:** Users accessing resources without proper authorization.
|
|
|
|
**Mitigation:**
|
|
|
|
```typescript
|
|
// backend/src/common/guards/rbac.guard.ts
|
|
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
|
|
import { Reflector } from '@nestjs/core';
|
|
import { Permission } from '../decorators/permission.decorator';
|
|
|
|
@Injectable()
|
|
export class RBACGuard implements CanActivate {
|
|
constructor(private reflector: Reflector) {}
|
|
|
|
canActivate(context: ExecutionContext): boolean {
|
|
const requiredPermissions = this.reflector.get<string[]>('permissions', context.getHandler());
|
|
if (!requiredPermissions) {
|
|
return true; // No permissions required
|
|
}
|
|
|
|
const request = context.switchToHttp().getRequest();
|
|
const user = request.user;
|
|
|
|
// Check if user has required permissions
|
|
const hasPermission = requiredPermissions.every((permission) =>
|
|
user.permissions?.includes(permission)
|
|
);
|
|
|
|
if (!hasPermission) {
|
|
throw new ForbiddenException('Insufficient permissions');
|
|
}
|
|
|
|
// Check tenant isolation
|
|
if (request.body?.tenant_id && request.body.tenant_id !== user.tenant_id) {
|
|
throw new ForbiddenException('Access denied: Cross-tenant access prohibited');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Usage in controller
|
|
@Controller('sales/orders')
|
|
@UseGuards(JwtAuthGuard, RBACGuard)
|
|
export class SalesOrdersController {
|
|
@Post()
|
|
@Permissions('sales:orders:create') // Decorator defines required permission
|
|
createOrder(@Body() dto: CreateOrderDto, @CurrentUser() user: User) {
|
|
// Enforce tenant_id from authenticated user (prevent tampering)
|
|
dto.tenant_id = user.tenant_id;
|
|
return this.ordersService.create(dto);
|
|
}
|
|
}
|
|
```
|
|
|
|
**RLS (Row-Level Security) in PostgreSQL:**
|
|
|
|
```sql
|
|
-- Enable RLS on all tenant-specific tables
|
|
ALTER TABLE sales.orders ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Policy: Users can only see orders from their tenant
|
|
CREATE POLICY tenant_isolation_policy ON sales.orders
|
|
FOR ALL
|
|
TO public
|
|
USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- Service role can bypass RLS (for admin operations)
|
|
CREATE ROLE service_role BYPASSRLS;
|
|
```
|
|
|
|
---
|
|
|
|
### 2.2 A02:2021 - Cryptographic Failures
|
|
|
|
**Threat:** Sensitive data exposed due to weak or missing encryption.
|
|
|
|
**Mitigation:**
|
|
|
|
```typescript
|
|
// Encryption at rest (sensitive fields in database)
|
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto';
|
|
|
|
export class EncryptionService {
|
|
private algorithm = 'aes-256-gcm';
|
|
private key: Buffer;
|
|
|
|
constructor() {
|
|
// Derive key from secret (stored in environment variable)
|
|
this.key = scryptSync(process.env.ENCRYPTION_SECRET, 'salt', 32);
|
|
}
|
|
|
|
encrypt(plaintext: string): string {
|
|
const iv = randomBytes(16);
|
|
const cipher = createCipheriv(this.algorithm, this.key, iv);
|
|
|
|
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
|
|
encrypted += cipher.final('hex');
|
|
|
|
const authTag = cipher.getAuthTag();
|
|
|
|
// Return: iv:authTag:encrypted
|
|
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
|
|
}
|
|
|
|
decrypt(ciphertext: string): string {
|
|
const [ivHex, authTagHex, encrypted] = ciphertext.split(':');
|
|
|
|
const iv = Buffer.from(ivHex, 'hex');
|
|
const authTag = Buffer.from(authTagHex, 'hex');
|
|
const decipher = createDecipheriv(this.algorithm, this.key, iv);
|
|
decipher.setAuthTag(authTag);
|
|
|
|
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
|
|
return decrypted;
|
|
}
|
|
}
|
|
|
|
// Usage: Encrypt SSN, credit card numbers, etc.
|
|
@Column({ type: 'text' })
|
|
get ssn(): string {
|
|
return this._ssn;
|
|
}
|
|
|
|
set ssn(value: string) {
|
|
this._ssn = this.encryptionService.encrypt(value);
|
|
}
|
|
```
|
|
|
|
**SSL/TLS Configuration:**
|
|
- TLS 1.2+ only (disable TLS 1.0, 1.1)
|
|
- Strong cipher suites only
|
|
- HSTS header enabled
|
|
- Certificate pinning (mobile apps)
|
|
|
|
---
|
|
|
|
### 2.3 A03:2021 - Injection
|
|
|
|
**Threat:** SQL injection, NoSQL injection, Command injection.
|
|
|
|
**Mitigation:**
|
|
|
|
```typescript
|
|
// Prisma ORM (parameterized queries - safe by default)
|
|
const users = await prisma.user.findMany({
|
|
where: {
|
|
email: userInput, // Automatically parameterized
|
|
tenant_id: tenantId,
|
|
},
|
|
});
|
|
|
|
// NEVER do this (vulnerable to SQL injection):
|
|
// const query = `SELECT * FROM users WHERE email = '${userInput}'`;
|
|
|
|
// Command injection prevention
|
|
import { exec } from 'child_process';
|
|
import { promisify } from 'util';
|
|
const execAsync = promisify(exec);
|
|
|
|
// BAD: Direct command execution with user input
|
|
// await execAsync(`convert ${userInput} output.png`); // VULNERABLE!
|
|
|
|
// GOOD: Whitelist allowed values
|
|
const allowedFormats = ['png', 'jpg', 'gif'];
|
|
if (!allowedFormats.includes(format)) {
|
|
throw new BadRequestException('Invalid format');
|
|
}
|
|
await execAsync(`convert input.${format} output.png`);
|
|
```
|
|
|
|
---
|
|
|
|
### 2.4 A04:2021 - Insecure Design
|
|
|
|
**Mitigation:**
|
|
- Security requirements in design phase
|
|
- Threat modeling (STRIDE methodology)
|
|
- Secure by default configurations
|
|
- Principle of least privilege
|
|
|
|
---
|
|
|
|
### 2.5 A05:2021 - Security Misconfiguration
|
|
|
|
**Mitigation:**
|
|
|
|
```typescript
|
|
// helmet.config.ts - Security headers
|
|
import helmet from 'helmet';
|
|
|
|
export const helmetConfig = helmet({
|
|
contentSecurityPolicy: {
|
|
directives: {
|
|
defaultSrc: ["'self'"],
|
|
styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
|
|
scriptSrc: ["'self'"],
|
|
imgSrc: ["'self'", 'data:', 'https:'],
|
|
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
|
|
connectSrc: ["'self'", 'https://api.erp-generic.com'],
|
|
frameSrc: ["'none'"],
|
|
objectSrc: ["'none'"],
|
|
},
|
|
},
|
|
hsts: {
|
|
maxAge: 31536000, // 1 year
|
|
includeSubDomains: true,
|
|
preload: true,
|
|
},
|
|
frameguard: { action: 'deny' }, // Prevent clickjacking
|
|
noSniff: true, // X-Content-Type-Options
|
|
xssFilter: true, // X-XSS-Protection
|
|
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
|
|
});
|
|
|
|
// main.ts
|
|
app.use(helmetConfig);
|
|
```
|
|
|
|
**Disable Debug Info in Production:**
|
|
|
|
```typescript
|
|
// main.ts
|
|
if (process.env.NODE_ENV === 'production') {
|
|
app.enableCors({
|
|
origin: process.env.ALLOWED_ORIGINS.split(','),
|
|
credentials: true,
|
|
});
|
|
|
|
// Disable detailed error messages
|
|
app.useGlobalFilters(new ProductionExceptionFilter());
|
|
} else {
|
|
app.enableCors({ origin: '*' }); // Dev only
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2.6 A06:2021 - Vulnerable and Outdated Components
|
|
|
|
**Mitigation:**
|
|
|
|
```bash
|
|
# Automated dependency scanning
|
|
npm audit --audit-level=high
|
|
|
|
# Snyk vulnerability scanning
|
|
snyk test
|
|
|
|
# Update dependencies regularly
|
|
npm outdated
|
|
npm update
|
|
|
|
# Renovate Bot (automated PRs for dependency updates)
|
|
# .github/renovate.json
|
|
{
|
|
"extends": ["config:base"],
|
|
"schedule": ["every weekend"],
|
|
"labels": ["dependencies"],
|
|
"automerge": false
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2.7 A07:2021 - Identification and Authentication Failures
|
|
|
|
**Mitigation:** See Section 3 (JWT with rotation, MFA, password policies)
|
|
|
|
---
|
|
|
|
### 2.8 A08:2021 - Software and Data Integrity Failures
|
|
|
|
**Mitigation:**
|
|
|
|
```typescript
|
|
// Verify integrity of uploads
|
|
import * as crypto from 'crypto';
|
|
|
|
function verifyFileIntegrity(file: Buffer, expectedHash: string): boolean {
|
|
const hash = crypto.createHash('sha256').update(file).digest('hex');
|
|
return hash === expectedHash;
|
|
}
|
|
|
|
// Code signing for deployments
|
|
// Sign Docker images
|
|
docker trust sign erp-generic-backend:v1.0.0
|
|
|
|
// Verify signature before deployment
|
|
docker trust inspect erp-generic-backend:v1.0.0
|
|
```
|
|
|
|
---
|
|
|
|
### 2.9 A09:2021 - Security Logging and Monitoring Failures
|
|
|
|
**Mitigation:**
|
|
|
|
```typescript
|
|
// Audit logging for sensitive operations
|
|
logger.audit('USER_LOGIN', user.id, tenantId, {
|
|
ip: request.ip,
|
|
userAgent: request.headers['user-agent'],
|
|
success: true,
|
|
timestamp: new Date(),
|
|
});
|
|
|
|
logger.audit('DATA_EXPORT', user.id, tenantId, {
|
|
table: 'sales.orders',
|
|
rowCount: 1500,
|
|
format: 'CSV',
|
|
});
|
|
|
|
logger.audit('PERMISSION_CHANGE', adminUser.id, tenantId, {
|
|
targetUser: userId,
|
|
oldRole: 'user',
|
|
newRole: 'admin',
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### 2.10 A10:2021 - Server-Side Request Forgery (SSRF)
|
|
|
|
**Mitigation:**
|
|
|
|
```typescript
|
|
// Validate URLs before making requests
|
|
import { URL } from 'url';
|
|
|
|
function isUrlSafe(urlString: string): boolean {
|
|
try {
|
|
const url = new URL(urlString);
|
|
|
|
// Whitelist allowed protocols
|
|
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
return false;
|
|
}
|
|
|
|
// Blacklist private IP ranges
|
|
const hostname = url.hostname;
|
|
const privateRanges = [
|
|
/^localhost$/,
|
|
/^127\.\d+\.\d+\.\d+$/,
|
|
/^10\.\d+\.\d+\.\d+$/,
|
|
/^192\.168\.\d+\.\d+$/,
|
|
/^172\.(1[6-9]|2\d|3[01])\.\d+\.\d+$/,
|
|
];
|
|
|
|
for (const range of privateRanges) {
|
|
if (range.test(hostname)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
if (!isUrlSafe(userProvidedUrl)) {
|
|
throw new BadRequestException('Invalid URL');
|
|
}
|
|
|
|
const response = await axios.get(userProvidedUrl);
|
|
```
|
|
|
|
---
|
|
|
|
## 3. AUTHENTICATION & AUTHORIZATION
|
|
|
|
### 3.1 JWT Configuration
|
|
|
|
```typescript
|
|
// jwt.config.ts
|
|
export const jwtConfig = {
|
|
secret: process.env.JWT_SECRET,
|
|
signOptions: {
|
|
expiresIn: '15m', // Short-lived access tokens
|
|
algorithm: 'HS256',
|
|
issuer: 'erp-generic',
|
|
audience: 'erp-generic-api',
|
|
},
|
|
refreshSecret: process.env.JWT_REFRESH_SECRET,
|
|
refreshSignOptions: {
|
|
expiresIn: '7d', // Long-lived refresh tokens
|
|
},
|
|
};
|
|
|
|
// auth.service.ts
|
|
async login(email: string, password: string) {
|
|
const user = await this.validateUser(email, password);
|
|
|
|
const payload = {
|
|
sub: user.id,
|
|
email: user.email,
|
|
tenant_id: user.tenant_id,
|
|
roles: user.roles,
|
|
permissions: user.permissions,
|
|
};
|
|
|
|
return {
|
|
access_token: this.jwtService.sign(payload),
|
|
refresh_token: this.jwtService.sign(payload, {
|
|
secret: jwtConfig.refreshSecret,
|
|
expiresIn: jwtConfig.refreshSignOptions.expiresIn,
|
|
}),
|
|
expires_in: 900, // 15 minutes
|
|
};
|
|
}
|
|
```
|
|
|
|
### 3.2 Password Policy
|
|
|
|
```typescript
|
|
// Strong password requirements
|
|
const passwordPolicy = {
|
|
minLength: 12,
|
|
requireUppercase: true,
|
|
requireLowercase: true,
|
|
requireNumbers: true,
|
|
requireSpecialChars: true,
|
|
preventCommonPasswords: true,
|
|
preventPasswordReuse: 5, // Last 5 passwords
|
|
maxAge: 90, // days (force reset after 90 days)
|
|
};
|
|
|
|
// Password hashing with bcrypt
|
|
import * as bcrypt from 'bcrypt';
|
|
|
|
const SALT_ROUNDS = 12;
|
|
|
|
async hashPassword(password: string): Promise<string> {
|
|
return bcrypt.hash(password, SALT_ROUNDS);
|
|
}
|
|
|
|
async comparePassword(password: string, hash: string): Promise<boolean> {
|
|
return bcrypt.compare(password, hash);
|
|
}
|
|
```
|
|
|
|
### 3.3 Multi-Factor Authentication (MFA)
|
|
|
|
```typescript
|
|
// MFA with TOTP (Time-based One-Time Password)
|
|
import * as speakeasy from 'speakeasy';
|
|
import * as qrcode from 'qrcode';
|
|
|
|
async enableMFA(userId: string) {
|
|
const secret = speakeasy.generateSecret({
|
|
name: `ERP Generic (${user.email})`,
|
|
issuer: 'ERP Generic',
|
|
});
|
|
|
|
// Store secret in database (encrypted)
|
|
await this.prisma.user.update({
|
|
where: { id: userId },
|
|
data: {
|
|
mfa_secret: this.encryptionService.encrypt(secret.base32),
|
|
mfa_enabled: false, // Enable after verification
|
|
},
|
|
});
|
|
|
|
// Generate QR code for user to scan
|
|
const qrCodeUrl = await qrcode.toDataURL(secret.otpauth_url);
|
|
|
|
return { qrCodeUrl, secret: secret.base32 };
|
|
}
|
|
|
|
async verifyMFA(userId: string, token: string): Promise<boolean> {
|
|
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
|
const secret = this.encryptionService.decrypt(user.mfa_secret);
|
|
|
|
return speakeasy.totp.verify({
|
|
secret,
|
|
encoding: 'base32',
|
|
token,
|
|
window: 1, // Allow 1 time step before/after
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. INPUT VALIDATION & SANITIZATION
|
|
|
|
```typescript
|
|
// Using class-validator and class-transformer
|
|
import { IsEmail, IsString, MinLength, MaxLength, Matches, IsUUID } from 'class-validator';
|
|
import { Transform } from 'class-transformer';
|
|
import * as sanitizeHtml from 'sanitize-html';
|
|
|
|
export class CreateUserDto {
|
|
@IsEmail()
|
|
@Transform(({ value }) => value.toLowerCase().trim())
|
|
email: string;
|
|
|
|
@IsString()
|
|
@MinLength(12)
|
|
@MaxLength(128)
|
|
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/)
|
|
password: string;
|
|
|
|
@IsString()
|
|
@MinLength(2)
|
|
@MaxLength(100)
|
|
@Transform(({ value }) => sanitizeHtml(value, { allowedTags: [], allowedAttributes: {} }))
|
|
firstName: string;
|
|
|
|
@IsUUID()
|
|
tenant_id: string;
|
|
}
|
|
|
|
// Global validation pipe
|
|
app.useGlobalPipes(
|
|
new ValidationPipe({
|
|
whitelist: true, // Strip unknown properties
|
|
forbidNonWhitelisted: true, // Throw error if unknown properties present
|
|
transform: true, // Auto-transform payloads to DTO instances
|
|
})
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## 5. SQL INJECTION PREVENTION
|
|
|
|
**Prisma ORM (Safe by Default):**
|
|
```typescript
|
|
// ✅ SAFE: Parameterized query
|
|
const user = await prisma.user.findUnique({
|
|
where: { email: userInput },
|
|
});
|
|
|
|
// ✅ SAFE: Raw query with parameters
|
|
const result = await prisma.$queryRaw`
|
|
SELECT * FROM users WHERE email = ${userInput}
|
|
`;
|
|
|
|
// ❌ NEVER DO THIS:
|
|
// const result = await prisma.$queryRawUnsafe(
|
|
// `SELECT * FROM users WHERE email = '${userInput}'`
|
|
// );
|
|
```
|
|
|
|
---
|
|
|
|
## 6. XSS & CSRF PROTECTION
|
|
|
|
### 6.1 XSS Prevention
|
|
|
|
```typescript
|
|
// Frontend: Sanitize user input before rendering
|
|
import DOMPurify from 'dompurify';
|
|
|
|
const sanitizedHtml = DOMPurify.sanitize(userInput, {
|
|
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
|
|
ALLOWED_ATTR: ['href'],
|
|
});
|
|
|
|
// React automatically escapes JSX
|
|
<div>{userInput}</div> // Safe
|
|
|
|
// Dangerous: Bypass escaping
|
|
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} /> // Use with caution
|
|
```
|
|
|
|
### 6.2 CSRF Protection
|
|
|
|
```typescript
|
|
// CSRF token in NestJS
|
|
import * as csurf from 'csurf';
|
|
|
|
app.use(csurf({ cookie: true }));
|
|
|
|
// Endpoint to get CSRF token
|
|
@Get('csrf-token')
|
|
getCsrfToken(@Req() req) {
|
|
return { csrfToken: req.csrfToken() };
|
|
}
|
|
|
|
// Frontend: Include CSRF token in requests
|
|
axios.post('/api/orders', orderData, {
|
|
headers: {
|
|
'X-CSRF-Token': csrfToken,
|
|
},
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 7. SECURITY HEADERS
|
|
|
|
```typescript
|
|
// Comprehensive security headers configuration
|
|
app.use(
|
|
helmet({
|
|
contentSecurityPolicy: {
|
|
directives: {
|
|
defaultSrc: ["'self'"],
|
|
scriptSrc: ["'self'", "'unsafe-inline'"], // Remove unsafe-inline in production
|
|
styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
|
|
imgSrc: ["'self'", 'data:', 'https:'],
|
|
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
|
|
connectSrc: ["'self'", 'https://api.erp-generic.com'],
|
|
frameSrc: ["'none'"],
|
|
objectSrc: ["'none'"],
|
|
upgradeInsecureRequests: [],
|
|
},
|
|
},
|
|
hsts: {
|
|
maxAge: 31536000,
|
|
includeSubDomains: true,
|
|
preload: true,
|
|
},
|
|
noSniff: true,
|
|
frameguard: { action: 'deny' },
|
|
xssFilter: true,
|
|
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
|
|
permissionsPolicy: {
|
|
features: {
|
|
geolocation: ["'none'"],
|
|
microphone: ["'none'"],
|
|
camera: ["'none'"],
|
|
payment: ["'self'"],
|
|
},
|
|
},
|
|
})
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## 8. RATE LIMITING & DDOS PROTECTION
|
|
|
|
```typescript
|
|
// Rate limiting with @nestjs/throttler
|
|
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
|
|
|
|
@Module({
|
|
imports: [
|
|
ThrottlerModule.forRoot({
|
|
ttl: 60, // Time window in seconds
|
|
limit: 100, // Max requests per ttl
|
|
}),
|
|
],
|
|
})
|
|
export class AppModule {}
|
|
|
|
// Apply globally
|
|
app.useGlobalGuards(new ThrottlerGuard());
|
|
|
|
// Custom rate limits per endpoint
|
|
@Throttle(10, 60) // 10 requests per minute
|
|
@Post('login')
|
|
async login(@Body() dto: LoginDto) {
|
|
return this.authService.login(dto);
|
|
}
|
|
|
|
// IP-based rate limiting for brute-force protection
|
|
@Throttle(5, 300) // 5 failed login attempts per 5 minutes
|
|
@Post('login')
|
|
async login(@Body() dto: LoginDto, @Ip() ip: string) {
|
|
// Log failed attempts
|
|
const attempts = await this.redis.incr(`login_attempts:${ip}`);
|
|
await this.redis.expire(`login_attempts:${ip}`, 300);
|
|
|
|
if (attempts > 5) {
|
|
throw new TooManyRequestsException('Too many login attempts. Try again in 5 minutes.');
|
|
}
|
|
|
|
return this.authService.login(dto);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. SECRETS MANAGEMENT
|
|
|
|
### 9.1 HashiCorp Vault Integration
|
|
|
|
```typescript
|
|
// vault.service.ts
|
|
import * as vault from 'node-vault';
|
|
|
|
export class VaultService {
|
|
private client: vault.client;
|
|
|
|
constructor() {
|
|
this.client = vault({
|
|
apiVersion: 'v1',
|
|
endpoint: process.env.VAULT_ADDR,
|
|
token: process.env.VAULT_TOKEN,
|
|
});
|
|
}
|
|
|
|
async getSecret(path: string): Promise<any> {
|
|
const result = await this.client.read(path);
|
|
return result.data;
|
|
}
|
|
|
|
async setSecret(path: string, data: any): Promise<void> {
|
|
await this.client.write(path, { data });
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
const dbPassword = await vaultService.getSecret('secret/erp-generic/prod/database');
|
|
```
|
|
|
|
### 9.2 Environment Variables Security
|
|
|
|
```bash
|
|
# .env file (NEVER commit to Git!)
|
|
# Use .env.example as template
|
|
|
|
DATABASE_URL=postgresql://user:STRONG_PASSWORD@localhost:5432/db
|
|
JWT_SECRET=RANDOM_64_CHAR_STRING
|
|
ENCRYPTION_SECRET=RANDOM_64_CHAR_STRING
|
|
|
|
# AWS Secrets Manager (production)
|
|
aws secretsmanager create-secret --name erp-generic/prod/jwt-secret --secret-string "RANDOM_64_CHAR_STRING"
|
|
```
|
|
|
|
---
|
|
|
|
## 10. SSL/TLS CONFIGURATION
|
|
|
|
### 10.1 Nginx SSL Configuration
|
|
|
|
```nginx
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name erp-generic.com;
|
|
|
|
# SSL certificates
|
|
ssl_certificate /etc/nginx/ssl/erp-generic.com.crt;
|
|
ssl_certificate_key /etc/nginx/ssl/erp-generic.com.key;
|
|
|
|
# SSL protocols and ciphers
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
|
|
ssl_prefer_server_ciphers on;
|
|
|
|
# HSTS
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
|
|
|
# OCSP Stapling
|
|
ssl_stapling on;
|
|
ssl_stapling_verify on;
|
|
ssl_trusted_certificate /etc/nginx/ssl/chain.pem;
|
|
|
|
# SSL session cache
|
|
ssl_session_cache shared:SSL:10m;
|
|
ssl_session_timeout 10m;
|
|
|
|
location / {
|
|
proxy_pass http://backend:3000;
|
|
proxy_set_header X-Forwarded-Proto https;
|
|
}
|
|
}
|
|
|
|
# Redirect HTTP to HTTPS
|
|
server {
|
|
listen 80;
|
|
server_name erp-generic.com;
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
```
|
|
|
|
### 10.2 Let's Encrypt Automation
|
|
|
|
```bash
|
|
# Install Certbot
|
|
apt-get install certbot python3-certbot-nginx
|
|
|
|
# Obtain certificate
|
|
certbot --nginx -d erp-generic.com -d www.erp-generic.com
|
|
|
|
# Auto-renewal (cron)
|
|
0 0 1 * * certbot renew --quiet
|
|
```
|
|
|
|
---
|
|
|
|
## 11. SECURITY SCANNING & AUDITING
|
|
|
|
### 11.1 Automated Security Scans
|
|
|
|
```yaml
|
|
# .github/workflows/security-scan.yml
|
|
name: Security Scan
|
|
|
|
on:
|
|
push:
|
|
branches: [main, develop]
|
|
schedule:
|
|
- cron: '0 0 * * *' # Daily
|
|
|
|
jobs:
|
|
snyk:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Run Snyk to check for vulnerabilities
|
|
uses: snyk/actions/node@master
|
|
env:
|
|
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
|
with:
|
|
args: --severity-threshold=high
|
|
|
|
owasp-zap:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: OWASP ZAP Scan
|
|
uses: zaproxy/action-baseline@v0.7.0
|
|
with:
|
|
target: 'https://staging.erp-generic.com'
|
|
rules_file_name: '.zap/rules.tsv'
|
|
cmd_options: '-a'
|
|
```
|
|
|
|
### 11.2 Security Audit Checklist
|
|
|
|
**Monthly Security Audit:**
|
|
- [ ] Review access logs for anomalies
|
|
- [ ] Check failed login attempts
|
|
- [ ] Verify MFA enrollment rate (target: 90%)
|
|
- [ ] Review user permissions (principle of least privilege)
|
|
- [ ] Scan for outdated dependencies (npm audit)
|
|
- [ ] Review firewall rules
|
|
- [ ] Check SSL certificate expiration
|
|
- [ ] Verify backup encryption
|
|
- [ ] Test disaster recovery plan
|
|
- [ ] Review security alerts from monitoring
|
|
|
|
---
|
|
|
|
## 12. INCIDENT RESPONSE
|
|
|
|
### 12.1 Security Incident Response Plan
|
|
|
|
**Phases:**
|
|
1. **Detection:** Alerts via monitoring, reports from users
|
|
2. **Containment:** Isolate affected systems, block attacker
|
|
3. **Eradication:** Remove malware, patch vulnerabilities
|
|
4. **Recovery:** Restore services, verify integrity
|
|
5. **Post-Mortem:** Document lessons learned, improve defenses
|
|
|
|
**Contact:**
|
|
- **Security Team:** security@erp-generic.com
|
|
- **On-Call:** security-oncall@erp-generic.com (PagerDuty)
|
|
- **Escalation:** CISO, CTO, CEO
|
|
|
|
---
|
|
|
|
## REFERENCES
|
|
|
|
- [OWASP Top 10 2021](https://owasp.org/www-project-top-ten/)
|
|
- [CWE Top 25](https://cwe.mitre.org/top25/archive/2021/2021_cwe_top25.html)
|
|
- [NIST Cybersecurity Framework](https://www.nist.gov/cyberframework)
|
|
- [Deployment Guide](./DEPLOYMENT-GUIDE.md)
|
|
|
|
---
|
|
|
|
**Documento:** SECURITY-HARDENING.md
|
|
**Versión:** 1.0
|
|
**Total Páginas:** ~15
|
|
**Última Actualización:** 2025-11-24
|