Files
pos-system/docs/vi/skills/security.md
Ho Ngoc Hai 2640b351c3 Enhance documentation with detailed diagrams and structured flows
- Added request/response flow diagrams to api-design and api-gateway-advanced skills for better visualization of processes.
- Introduced configuration loading flow in configuration-management skill to clarify the configuration process.
- Included error propagation flow in error-handling-patterns skill to illustrate error handling across layers.
- Enhanced various skills with additional diagrams to improve understanding of complex concepts.

These updates aim to provide clearer guidance and improve the overall documentation experience for developers.
2026-01-01 23:22:54 +07:00

25 KiB

Bảo Mật

Thực hành và mẫu bảo mật tốt nhất cho nền tảng microservices GoodGo. Sử dụng khi triển khai xác thực, phân quyền, bảo vệ dữ liệu, xác thực đầu vào, giới hạn tốc độ, quản lý bí mật hoặc kiểm tra bảo mật trên tất cả các dịch vụ.

Tổng Quan

Skill Security cung cấp các mẫu bảo mật toàn diện, thực hành tốt nhất và ví dụ triển khai để bảo vệ các microservices GoodGo. Nó bao gồm xác thực, phân quyền, bảo vệ dữ liệu, xác thực đầu vào, giới hạn tốc độ, quản lý bí mật và kiểm tra bảo mật.

Khi Nào Sử Dụng

Sử dụng skill này khi:

  • Triển khai xác thực và phân quyền trong bất kỳ service nào
  • Bảo vệ dữ liệu nhạy cảm (PII, thông tin đăng nhập, token)
  • Xác thực đầu vào người dùng và tải lên tệp
  • Triển khai giới hạn tốc độ và bảo vệ DDoS
  • Thiết lập ghi nhật ký kiểm toán và giám sát bảo mật
  • Mã hóa dữ liệu khi nghỉ và khi truyền
  • Quản lý bí mật và thông tin đăng nhập
  • Triển khai kiểm tra bảo mật
  • Xử lý sự cố bảo mật
  • Thiết kế các endpoint API an toàn

Khái Niệm Chính

Nguyên Tắc Bảo Mật Cốt Lõi

  1. Defense in Depth: Multiple layers of security controls
  2. Least Privilege: Grant minimum required permissions
  3. Fail Secure: Default to deny access
  4. Separation of Duties: Critical operations require multiple approvals
  5. Audit Everything: Log all security-relevant events
  6. Encrypt Sensitive Data: PII, tokens, credentials must be encrypted
  7. Validate All Inputs: Never trust user input
  8. Principle of Least Exposure: Minimize attack surface
  9. Secure by Default: Security built-in, not bolted on
  10. Assume Breach: Design for detection and response

VI:

  1. Defense in Depth: Nhiều lớp kiểm soát bảo mật
  2. Least Privilege: Cấp quyền tối thiểu cần thiết
  3. Fail Secure: Mặc định từ chối truy cập
  4. Separation of Duties: Các thao tác quan trọng yêu cầu nhiều phê duyệt
  5. Audit Everything: Ghi log tất cả sự kiện liên quan đến bảo mật
  6. Encrypt Sensitive Data: PII, token, thông tin đăng nhập phải được mã hóa
  7. Validate All Inputs: Không bao giờ tin tưởng đầu vào người dùng
  8. Principle of Least Exposure: Giảm thiểu bề mặt tấn công
  9. Secure by Default: Bảo mật được tích hợp sẵn, không phải thêm vào sau
  10. Assume Breach: Thiết kế để phát hiện và phản ứng

Các Pattern Thường Dùng

Xác Thực & Phân Quyền

Xác Thực Token JWT

Sơ đồ sau minh họa luồng xác thực khi client gửi request với JWT token:

sequenceDiagram
    participant Client
    participant Middleware as Auth Middleware
    participant JWTService as JWT Service
    participant Request as Express Request

    Client->>Middleware: HTTP Request with Token
    Middleware->>Middleware: Extract token from<br/>Authorization header or cookie
    
    alt Token not found
        Middleware->>Client: 401 Unauthorized<br/>(AUTH_REQUIRED)
    else Token found
        Middleware->>JWTService: verifyAccessToken(token)
        
        alt Token invalid or expired
            JWTService->>Middleware: Verification failed
            Middleware->>Client: 401 Unauthorized<br/>(INVALID_TOKEN)
        else Token valid
            JWTService->>Middleware: Payload (sub, email, roles, permissions)
            Middleware->>Request: Attach user to req.user
            Middleware->>Client: Continue to next middleware
        end
    end

Example from services/iam-service/src/middlewares/auth.middleware.ts:

import { jwtService } from '../modules/token/jwt.service';
import { logger } from '@goodgo/logger';

export const authenticate = () => {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      // Extract token from Authorization header or cookie
      let token: string | null = null;
      
      const authHeader = req.headers.authorization;
      if (authHeader?.startsWith('Bearer ')) {
        token = authHeader.substring(7);
      } else if (req.cookies?.access_token) {
        token = req.cookies.access_token;
      }

      if (!token) {
        return res.status(401).json({
          success: false,
          error: { code: 'AUTH_001', message: 'Authentication required' }
        });
      }

      // Verify token
      const payload = await jwtService.verifyAccessToken(token);
      
      // Attach user to request
      req.user = {
        id: payload.sub,
        userId: payload.sub,
        email: payload.email,
        roles: payload.roles || [],
        permissions: payload.permissions || []
      };

      next();
    } catch (error) {
      logger.warn('Authentication failed', { error: error.message });
      return res.status(401).json({
        success: false,
        error: { code: 'AUTH_002', message: 'Invalid or expired token' }
      });
    }
  };
};

Ví dụ từ services/iam-service/src/middlewares/auth.middleware.ts:

import { jwtService } from '../modules/token/jwt.service';
import { logger } from '@goodgo/logger';

export const authenticate = () => {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      // Trích xuất token từ header Authorization hoặc cookie
      let token: string | null = null;
      
      const authHeader = req.headers.authorization;
      if (authHeader?.startsWith('Bearer ')) {
        token = authHeader.substring(7);
      } else if (req.cookies?.access_token) {
        token = req.cookies.access_token;
      }

      if (!token) {
        return res.status(401).json({
          success: false,
          error: { code: 'AUTH_001', message: 'Yêu cầu xác thực' }
        });
      }

      // Xác minh token
      const payload = await jwtService.verifyAccessToken(token);
      
      // Gắn user vào request
      req.user = {
        id: payload.sub,
        userId: payload.sub,
        email: payload.email,
        roles: payload.roles || [],
        permissions: payload.permissions || []
      };

      next();
    } catch (error) {
      logger.warn('Xác thực thất bại', { error: error.message });
      return res.status(401).json({
        success: false,
        error: { code: 'AUTH_002', message: 'Token không hợp lệ hoặc hết hạn' }
      });
    }
  };
};

Phân Quyền Dựa Trên Quyền

Luồng quyết định phân quyền xác định xem người dùng có quyền truy cập tài nguyên hay không:

flowchart TD
    Start([Request Received]) --> CheckAuth{User<br/>Authenticated?}
    
    CheckAuth -->|No| Return401[Return 401<br/>AUTH_REQUIRED]
    CheckAuth -->|Yes| CheckType{Authorization<br/>Type?}
    
    CheckType -->|Role-Based| CheckRole{User has<br/>Required Role?}
    CheckType -->|Permission-Based| CheckPermission{User has<br/>Resource:Action<br/>Permission?}
    CheckType -->|Ownership| CheckOwnership{Resource ID<br/>matches User ID?}
    
    CheckRole -->|No| LogDenial[Log Permission Denied<br/>with user roles]
    CheckPermission -->|No| LogDenial
    CheckOwnership -->|No| LogDenial
    
    LogDenial --> Return403[Return 403<br/>FORBIDDEN]
    
    CheckRole -->|Yes| Allow[Allow Request<br/>Continue to Handler]
    CheckPermission -->|Yes| Allow
    CheckOwnership -->|Yes| Allow
    
    Return401 --> End([End])
    Return403 --> End
    Allow --> End
    
    style CheckAuth fill:#e1f5ff
    style CheckType fill:#e1f5ff
    style Return401 fill:#ffebee
    style Return403 fill:#ffebee
    style Allow fill:#e8f5e9

Example from services/iam-service/src/middlewares/rbac.middleware.ts:

export const requirePermission = (
  resource: string,
  action: string,
  scope?: string
) => {
  return async (req: Request, res: Response, next: NextFunction) => {
    const userId = req.user?.id || req.user?.sub;
    
    if (!userId) {
      return res.status(401).json({
        success: false,
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' }
      });
    }

    const hasPermission = await rbacService.hasPermission(
      userId,
      resource,
      action,
      scope
    );

    if (!hasPermission) {
      return res.status(403).json({
        success: false,
        error: {
          code: 'INSUFFICIENT_PERMISSIONS',
          message: `Requires ${action} permission on ${resource}`
        }
      });
    }

    next();
  };
};

Ví dụ từ services/iam-service/src/middlewares/rbac.middleware.ts:

export const requirePermission = (
  resource: string,
  action: string,
  scope?: string
) => {
  return async (req: Request, res: Response, next: NextFunction) => {
    const userId = req.user?.id || req.user?.sub;
    
    if (!userId) {
      return res.status(401).json({
        success: false,
        error: { code: 'UNAUTHORIZED', message: 'Yêu cầu xác thực' }
      });
    }

    const hasPermission = await rbacService.hasPermission(
      userId,
      resource,
      action,
      scope
    );

    if (!hasPermission) {
      return res.status(403).json({
        success: false,
        error: {
          code: 'INSUFFICIENT_PERMISSIONS',
          message: `Yêu cầu quyền ${action} trên ${resource}`
        }
      });
    }

    next();
  };
};

Xác Thực Đầu Vào

Example from services/iam-service/src/middlewares/validation.middleware.ts:

import { AnyZodObject, ZodError } from 'zod';

export const validateDto = (
  schema: AnyZodObject,
  property: 'body' | 'query' | 'params' = 'body'
) => {
  return (req: Request, res: Response, next: NextFunction) => {
    try {
      // Sanitize input by trimming strings
      const sanitizedData = sanitizeInput(req[property]);
      
      // Validate the sanitized data
      const validatedData = schema.parse(sanitizedData);
      
      // Replace original data with validated data
      (req as any)[property] = validatedData;
      
      next();
    } catch (error) {
      if (error instanceof ZodError) {
        return res.status(400).json({
          success: false,
          error: {
            code: 'VALIDATION_ERROR',
            message: 'Invalid request data',
            details: error.errors.map(err => ({
              field: err.path.join('.'),
              message: err.message,
              code: err.code
            }))
          }
        });
      }
      throw error;
    }
  };
};

function sanitizeInput(data: any): any {
  if (typeof data === 'string') {
    return data.trim();
  }
  if (Array.isArray(data)) {
    return data.map(sanitizeInput);
  }
  if (data !== null && typeof data === 'object') {
    const sanitized: any = {};
    for (const [key, value] of Object.entries(data)) {
      sanitized[key] = sanitizeInput(value);
    }
    return sanitized;
  }
  return data;
}

Ví dụ từ services/iam-service/src/middlewares/validation.middleware.ts:

import { AnyZodObject, ZodError } from 'zod';

export const validateDto = (
  schema: AnyZodObject,
  property: 'body' | 'query' | 'params' = 'body'
) => {
  return (req: Request, res: Response, next: NextFunction) => {
    try {
      // Làm sạch đầu vào bằng cách cắt chuỗi
      const sanitizedData = sanitizeInput(req[property]);
      
      // Xác thực dữ liệu đã được làm sạch
      const validatedData = schema.parse(sanitizedData);
      
      // Thay thế dữ liệu gốc bằng dữ liệu đã xác thực
      (req as any)[property] = validatedData;
      
      next();
    } catch (error) {
      if (error instanceof ZodError) {
        return res.status(400).json({
          success: false,
          error: {
            code: 'VALIDATION_ERROR',
            message: 'Dữ liệu request không hợp lệ',
            details: error.errors.map(err => ({
              field: err.path.join('.'),
              message: err.message,
              code: err.code
            }))
          }
        });
      }
      throw error;
    }
  };
};

function sanitizeInput(data: any): any {
  if (typeof data === 'string') {
    return data.trim();
  }
  if (Array.isArray(data)) {
    return data.map(sanitizeInput);
  }
  if (data !== null && typeof data === 'object') {
    const sanitized: any = {};
    for (const [key, value] of Object.entries(data)) {
      sanitized[key] = sanitizeInput(value);
    }
    return sanitized;
  }
  return data;
}

Giới Hạn Tốc Độ

Example from services/iam-service/src/middlewares/rate-limit.middleware.ts:

import rateLimit from 'express-rate-limit';
import { RateLimiterRedis } from 'rate-limit-redis';
import { getRedisClient } from '../config/redis.config';

export const dynamicRateLimit = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const userId = req.user?.id || req.user?.sub;
  
  // Default limits
  let windowMs = 15 * 60 * 1000; // 15 minutes
  let max = 100; // 100 requests

  if (userId) {
    const roles = await rbacService.getUserRoles(userId);
    
    // Set limits based on role
    if (roles.includes('SUPER_ADMIN')) {
      max = 1000;
    } else if (roles.includes('ADMIN')) {
      max = 500;
    } else if (roles.includes('MODERATOR')) {
      max = 300;
    }
  } else {
    // Unauthenticated users - stricter limits
    max = 50;
  }

  const limiter = rateLimit({
    windowMs,
    max,
    store: new RateLimiterRedis({
      client: getRedisClient(),
      prefix: 'rl:'
    }),
    handler: (req, res) => {
      res.status(429).json({
        success: false,
        error: {
          code: 'RATE_LIMIT_EXCEEDED',
          message: 'Too many requests, please try again later'
        }
      });
    }
  });

  limiter(req, res, next);
};

Ví dụ từ services/iam-service/src/middlewares/rate-limit.middleware.ts:

import rateLimit from 'express-rate-limit';
import { RateLimiterRedis } from 'rate-limit-redis';
import { getRedisClient } from '../config/redis.config';

export const dynamicRateLimit = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const userId = req.user?.id || req.user?.sub;
  
  // Giới hạn mặc định
  let windowMs = 15 * 60 * 1000; // 15 phút
  let max = 100; // 100 requests

  if (userId) {
    const roles = await rbacService.getUserRoles(userId);
    
    // Đặt giới hạn dựa trên vai trò
    if (roles.includes('SUPER_ADMIN')) {
      max = 1000;
    } else if (roles.includes('ADMIN')) {
      max = 500;
    } else if (roles.includes('MODERATOR')) {
      max = 300;
    }
  } else {
    // Người dùng chưa xác thực - giới hạn nghiêm ngặt hơn
    max = 50;
  }

  const limiter = rateLimit({
    windowMs,
    max,
    store: new RateLimiterRedis({
      client: getRedisClient(),
      prefix: 'rl:'
    }),
    handler: (req, res) => {
      res.status(429).json({
        success: false,
        error: {
          code: 'RATE_LIMIT_EXCEEDED',
          message: 'Quá nhiều yêu cầu, vui lòng thử lại sau'
        }
      });
    }
  });

  limiter(req, res, next);
};

Header Bảo Mật

Example from services/iam-service/src/main.ts:

import helmet from 'helmet';
import cors from 'cors';

// Security middleware
app.use(helmet());
app.use(cors({
  origin: appConfig.corsOrigin,
  credentials: true
}));

Ví dụ từ services/iam-service/src/main.ts:

import helmet from 'helmet';
import cors from 'cors';

// Middleware bảo mật
app.use(helmet());
app.use(cors({
  origin: appConfig.corsOrigin,
  credentials: true
}));

Traefik security headers from infra/traefik/dynamic/middlewares.yml:

http:
  middlewares:
    secure-headers:
      headers:
        sslRedirect: true
        stsSeconds: 31536000
        contentTypeNosniff: true
        browserXssFilter: true
        frameDeny: true
        customRequestHeaders:
          X-Forwarded-Proto: "https"

Header bảo mật Traefik từ infra/traefik/dynamic/middlewares.yml:

http:
  middlewares:
    secure-headers:
      headers:
        sslRedirect: true
        stsSeconds: 31536000
        contentTypeNosniff: true
        browserXssFilter: true
        frameDeny: true
        customRequestHeaders:
          X-Forwarded-Proto: "https"

Ghi Log Kiểm Toán

Example from services/iam-service/src/core/events/audit.service.ts:

export class AuditService {
  async logAuthEvent(
    eventType: string,
    data: {
      userId?: string;
      success: boolean;
      errorMessage?: string;
      ipAddress?: string;
      userAgent?: string;
      metadata?: any;
    }
  ): Promise<void> {
    await this.prisma.authEvent.create({
      data: {
        userId: data.userId || null,
        eventType,
        eventData: data.metadata || {},
        ipAddress: data.ipAddress,
        userAgent: data.userAgent,
        success: data.success,
        errorMessage: data.errorMessage
      }
    });
  }
}

Ví dụ từ services/iam-service/src/core/events/audit.service.ts:

export class AuditService {
  async logAuthEvent(
    eventType: string,
    data: {
      userId?: string;
      success: boolean;
      errorMessage?: string;
      ipAddress?: string;
      userAgent?: string;
      metadata?: any;
    }
  ): Promise<void> {
    await this.prisma.authEvent.create({
      data: {
        userId: data.userId || null,
        eventType,
        eventData: data.metadata || {},
        ipAddress: data.ipAddress,
        userAgent: data.userAgent,
        success: data.success,
        errorMessage: data.errorMessage
      }
    });
  }
}

Thực Hành Tốt Nhất

Bảo Mật Mật Khẩu

Luồng hash và xác minh mật khẩu:

sequenceDiagram
    participant User
    participant Service
    participant PasswordService
    participant Bcrypt
    participant Database

    Note over User,Database: Registration/Password Change
    User->>Service: Submit password
    Service->>PasswordService: hash(password)
    PasswordService->>Bcrypt: bcrypt.hash(password, 12)
    Bcrypt->>Bcrypt: Generate salt
    Bcrypt->>Bcrypt: Hash with cost factor 12
    Bcrypt->>PasswordService: Hashed password
    PasswordService->>Service: Return hash
    Service->>Database: Store passwordHash
    Service->>Service: Sanitize password<br/>before logging

    Note over User,Database: Login Verification
    User->>Service: Submit credentials
    Service->>Database: Fetch user by email
    Database->>Service: User with passwordHash
    Service->>PasswordService: verify(password, hash)
    PasswordService->>Bcrypt: bcrypt.compare(password, hash)
    Bcrypt->>PasswordService: Boolean result
    PasswordService->>Service: Return verification result
    
    alt Password matches
        Service->>User: Authentication success
    else Password mismatch
        Service->>User: Invalid credentials<br/>(generic error)
    end
  • Always use bcrypt with cost factor 12+ in production
  • Never log passwords or password hashes
  • Use strong password requirements (min 8 chars, uppercase, lowercase, number, special char)
  • Implement password reset with secure tokens

VI:

  • Luôn sử dụng bcrypt với hệ số chi phí 12+ trong production
  • Không bao giờ ghi log mật khẩu hoặc hash mật khẩu
  • Sử dụng yêu cầu mật khẩu mạnh (tối thiểu 8 ký tự, chữ hoa, chữ thường, số, ký tự đặc biệt)
  • Triển khai đặt lại mật khẩu với token an toàn

Bảo Mật Token

  • Hash tokens before storing in database
  • Use short-lived access tokens (15 minutes)
  • Use longer-lived refresh tokens (7 days) with rotation
  • Store refresh tokens securely (httpOnly cookies)
  • Implement token revocation

VI:

  • Hash token trước khi lưu vào database
  • Sử dụng access token có thời gian sống ngắn (15 phút)
  • Sử dụng refresh token có thời gian sống dài hơn (7 ngày) với xoay vòng
  • Lưu trữ refresh token an toàn (httpOnly cookies)
  • Triển khai thu hồi token

Mã Hóa Dữ Liệu

Luồng mã hóa và giải mã để bảo vệ dữ liệu nhạy cảm khi lưu trữ:

sequenceDiagram
    participant Service
    participant EncryptionService
    participant Crypto as Node.js Crypto
    participant Database

    Note over Service,Database: Encryption Flow
    Service->>EncryptionService: encrypt(plaintext)
    EncryptionService->>Crypto: Generate random IV<br/>(16 bytes)
    EncryptionService->>Crypto: Create cipher<br/>(AES-256-GCM)
    EncryptionService->>Crypto: Encrypt plaintext
    Crypto->>EncryptionService: Encrypted data + Auth Tag
    EncryptionService->>Service: Format: iv:tag:ciphertext
    Service->>Database: Store encrypted data

    Note over Service,Database: Decryption Flow
    Service->>Database: Retrieve encrypted data
    Database->>Service: iv:tag:ciphertext
    Service->>EncryptionService: decrypt(encryptedText)
    EncryptionService->>EncryptionService: Split iv, tag, ciphertext
    EncryptionService->>Crypto: Create decipher<br/>(AES-256-GCM)
    EncryptionService->>Crypto: Set auth tag
    EncryptionService->>Crypto: Decrypt ciphertext
    Crypto->>EncryptionService: Plaintext
    EncryptionService->>Service: Return plaintext

Ngăn Chặn SQL Injection

  • Always use Prisma parameterized queries (automatic)
  • Never use string concatenation for queries
  • Validate and sanitize all inputs

VI:

  • Luôn sử dụng truy vấn tham số hóa của Prisma (tự động)
  • Không bao giờ sử dụng nối chuỗi cho truy vấn
  • Xác thực và làm sạch tất cả đầu vào

Ví Dụ Từ Dự Án

Middleware Xác Thực

See services/iam-service/src/middlewares/auth.middleware.ts for complete authentication implementation.

Xem services/iam-service/src/middlewares/auth.middleware.ts để có implementation xác thực hoàn chỉnh.

Middleware RBAC

See services/iam-service/src/middlewares/rbac.middleware.ts for permission-based authorization.

Xem services/iam-service/src/middlewares/rbac.middleware.ts để có phân quyền dựa trên quyền.

Middleware Xác Thực

See services/iam-service/src/middlewares/validation.middleware.ts for input validation patterns.

Xem services/iam-service/src/middlewares/validation.middleware.ts để có các mẫu xác thực đầu vào.

Giới Hạn Tốc Độ

See services/iam-service/src/middlewares/rate-limit.middleware.ts for dynamic rate limiting.

Xem services/iam-service/src/middlewares/rate-limit.middleware.ts để có giới hạn tốc độ động.

Tham Khảo Nhanh

Danh Sách Kiểm Tra Bảo Mật

Before deploying any service:

  • All endpoints require authentication (except public)
  • Authorization checks implemented (RBAC/ABAC)
  • Input validation with Zod schemas
  • Rate limiting configured
  • Error messages sanitized (no info disclosure)
  • PII encrypted at rest
  • Passwords hashed with bcrypt (cost 12+)
  • Tokens hashed before storing
  • Secrets in environment variables (never hardcoded)
  • HTTPS enforced (TLS 1.2+)
  • CORS configured correctly
  • Security headers set (helmet)
  • Audit logging enabled
  • SQL injection prevented (use Prisma)
  • XSS prevention (input sanitization)
  • File upload validation
  • Security tests passing

Trước khi triển khai bất kỳ service nào:

  • Tất cả endpoint yêu cầu xác thực (trừ public)
  • Kiểm tra phân quyền đã triển khai (RBAC/ABAC)
  • Xác thực đầu vào với Zod schemas
  • Giới hạn tốc độ đã cấu hình
  • Thông báo lỗi đã được làm sạch (không tiết lộ thông tin)
  • PII được mã hóa khi nghỉ
  • Mật khẩu được hash với bcrypt (chi phí 12+)
  • Token được hash trước khi lưu
  • Bí mật trong biến môi trường (không bao giờ hardcode)
  • HTTPS được bắt buộc (TLS 1.2+)
  • CORS được cấu hình đúng
  • Header bảo mật được đặt (helmet)
  • Ghi log kiểm toán được bật
  • SQL injection được ngăn chặn (sử dụng Prisma)
  • Ngăn chặn XSS (làm sạch đầu vào)
  • Xác thực tải lên tệp
  • Kiểm tra bảo mật đã vượt qua

Skills Liên Quan

Tài Nguyên

VI: