Files
lcbp3/specs/04-Infrastructure-OPS/04-06-security-operations.md
admin 5eff8861e1
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
refactor(specs): merge 08-infrastructure into canonical 04-06 dirs
- Append live QNAP configs to 04-01-docker-compose.md (Appendix A)
  - MariaDB+PMA, Redis+Elasticsearch, NPM, Gitea, n8n, App stack
- Append SSH setup + Secrets management to 04-06-security-operations.md
  - Appendix A: SSH key setup, config, hardening, port forwarding
  - Appendix B: .env structure, secret generation, rotation, GPG backup
- Append QNAP/Gitea CI/CD docs to 04-04-deployment-guide.md
  - Appendix A: Container Station deployment steps
  - Appendix B: Gitea Actions CI/CD pipeline setup
  - Appendix C: act_runner (ASUSTOR) installation
- Move Git_command.md -> 05-Engineering-Guidelines/05-05-git-cheatsheet.md
- Move docker-compose-app.yml, lcbp3-monitoring.yml, lcbp3-registry.yml,
  grafana/ -> 04-Infrastructure-OPS/
- Archive lcbp3-db.md -> 99-archives/
- Remove all legacy 08-infrastructure/* files from git
- Remove Google OAuth client_secret JSON from git index (security)
- Add .gitignore rules: *client_secret*.json, *service_account*.json,
  specs/08-infrastructure/
- Update 04-Infrastructure-OPS/README.md with new file index
2026-02-23 15:03:35 +07:00

17 KiB

Security Operations

Project: LCBP3-DMS Version: 1.8.0 Last Updated: 2025-12-02


📋 Overview

This document outlines security monitoring, access control management, vulnerability management, and security incident response for LCBP3-DMS.


🔒 Access Control Management

User Access Review

Monthly Tasks:

#!/bin/bash
# File: /scripts/audit-user-access.sh

# Export active users
docker exec lcbp3-mariadb mysql -u root -p -e "
  SELECT user_id, username, email, primary_organization_id, is_active, last_login_at
  FROM lcbp3_dms.users
  WHERE is_active = 1
  ORDER BY last_login_at DESC;
" > /reports/active-users-$(date +%Y%m%d).csv

# Find dormant accounts (no login > 90 days)
docker exec lcbp3-mariadb mysql -u root -p -e "
  SELECT user_id, username, email, last_login_at,
         DATEDIFF(NOW(), last_login_at) AS days_inactive
  FROM lcbp3_dms.users
  WHERE is_active = 1
  AND (last_login_at IS NULL OR last_login_at < DATE_SUB(NOW(), INTERVAL 90 DAY));
"

echo "User access audit completed: $(date)"

Role & Permission Audit

-- Review users with elevated permissions
SELECT u.username, u.email, r.role_name, r.scope
FROM users u
JOIN user_assignments ua ON u.user_id = ua.user_id
JOIN roles r ON ua.role_id = r.role_id
WHERE r.role_name IN ('Superadmin', 'Document Controller', 'Project Manager')
ORDER BY r.role_name, u.username;

-- Review Global scope roles (highest privilege)
SELECT u.username, r.role_name
FROM users u
JOIN user_assignments ua ON u.user_id = ua.user_id
JOIN roles r ON ua.role_id = r.role_id
WHERE r.scope = 'Global';

🛡️ Security Monitoring

Log Monitoring for Security Events

#!/bin/bash
# File: /scripts/monitor-security-events.sh

# Check for failed login attempts
docker logs lcbp3-backend | grep "Failed login" | tail -20

# Check for unauthorized access attempts (403)
docker logs lcbp3-backend | grep "403" | tail -20

# Check for unusual activity patterns
docker logs lcbp3-backend | grep -E "DELETE|DROP|TRUNCATE" | tail -20

# Check for SQL injection attempts
docker logs lcbp3-backend | grep -i "SELECT.*FROM.*WHERE" | grep -v "legitimate" | tail -20

Failed Login Monitoring

-- Find accounts with multiple failed login attempts
SELECT username, failed_attempts, locked_until
FROM users
WHERE failed_attempts >= 3
ORDER BY failed_attempts DESC;

-- Unlock user account after verification
UPDATE users
SET failed_attempts = 0, locked_until = NULL
WHERE user_id = ?;

🔐 Secrets & Credentials Management

Password Rotation Schedule

Credential Rotation Frequency Owner
Database Root Password Every 90 days DBA
Database App Password Every 90 days DevOps
JWT Secret Every 180 days Backend Team
Redis Password Every 90 days DevOps
SMTP Password When provider requires Operations
SSL Private Key With certificate renewal Operations

Password Rotation Procedure

#!/bin/bash
# File: /scripts/rotate-db-password.sh

# Generate new password
NEW_PASSWORD=$(openssl rand -base64 32)

# Update database user password
docker exec lcbp3-mariadb mysql -u root -p -e "
  ALTER USER 'lcbp3_user'@'%' IDENTIFIED BY '$NEW_PASSWORD';
  FLUSH PRIVILEGES;
"

# Update application .env file
sed -i "s/^DB_PASS=.*/DB_PASS=$NEW_PASSWORD/" /app/backend/.env

# Restart backend to apply new password
docker restart lcbp3-backend

# Verify connection
sleep 10
curl -f http://localhost:3000/health || {
  echo "FAILED: Backend cannot connect with new password"
  # Rollback procedure...
  exit 1
}

echo "Database password rotated successfully: $(date)"
# Store password securely (e.g., password manager)

🚨 Vulnerability Management

Dependency Vulnerability Scanning

#!/bin/bash
# File: /scripts/scan-vulnerabilities.sh

# Backend dependencies
cd /app/backend
npm audit --production

# Critical/High vulnerabilities
VULNERABILITIES=$(npm audit --production --json | jq '.metadata.vulnerabilities.high + .metadata.vulnerabilities.critical')

if [ "$VULNERABILITIES" -gt 0 ]; then
  echo "WARNING: $VULNERABILITIES critical/high vulnerabilities found!"
  npm audit --production > /reports/security-audit-$(date +%Y%m%d).txt
  # Send alert
  /scripts/send-alert-email.sh "Security Vulnerabilities Detected" "Found $VULNERABILITIES critical/high vulnerabilities"
fi

# Frontend dependencies
cd /app/frontend
npm audit --production

Container Image Scanning

#!/bin/bash
# File: /scripts/scan-images.sh

# Install Trivy (if not installed)
# wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | apt-key add -
# echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | tee -a /etc/apt/sources.list.d/trivy.list
# apt-get update && apt-get install trivy

# Scan Docker images
trivy image --severity HIGH,CRITICAL lcbp3-backend:latest
trivy image --severity HIGH,CRITICAL lcbp3-frontend:latest
trivy image --severity HIGH,CRITICAL mariadb:11.8
trivy image --severity HIGH,CRITICAL redis:7.2-alpine

🔍 Security Hardening

Server Hardening Checklist

  • Disable root SSH login
  • Use SSH key authentication only
  • Configure firewall (allow only necessary ports)
  • Enable automatic security updates
  • Remove unnecessary services
  • Configure fail2ban for brute-force protection
  • Enable SELinux/AppArmor
  • Regular security patch updates

Docker Security

# docker-compose.yml - Security best practices

services:
  backend:
    # Run as non-root user
    user: 'node:node'

    # Read-only root filesystem
    read_only: true

    # No new privileges
    security_opt:
      - no-new-privileges:true

    # Limit capabilities
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE

    # Resource limits
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G
        reservations:
          memory: 512M

Database Security

-- Remove anonymous users
DELETE FROM mysql.user WHERE User='';

-- Remove test database
DROP DATABASE IF EXISTS test;

-- Remove remote root login
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1');

-- Create dedicated backup user with minimal privileges
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'STRONG_PASSWORD';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON lcbp3_dms.* TO 'backup_user'@'localhost';

-- Enable SSL for database connections
-- GRANT USAGE ON *.* TO 'lcbp3_user'@'%' REQUIRE SSL;

FLUSH PRIVILEGES;

🚨 Security Incident Response

Incident Classification

Type Examples Response Time
Data Breach Unauthorized data access Immediate (< 1h)
Account Compromise Stolen credentials Immediate (< 1h)
DDoS Attack Service unavailable Immediate (< 1h)
Malware/Ransomware Infected systems Immediate (< 1h)
Unauthorized Access Failed authentication spikes High (< 4h)
Suspicious Activity Unusual patterns Medium (< 24h)

Data Breach Response

Immediate Actions:

  1. Contain the breach

    # Block suspicious IPs at firewall level
    iptables -A INPUT -s <SUSPICIOUS_IP> -j DROP
    
    # Disable compromised user accounts
    docker exec lcbp3-mariadb mysql -u root -p -e "
      UPDATE lcbp3_dms.users
      SET is_active = 0
      WHERE user_id = <COMPROMISED_USER_ID>;
    "
    
  2. Assess impact

    -- Check audit logs for unauthorized access
    SELECT * FROM audit_logs
    WHERE user_id = <COMPROMISED_USER_ID>
    AND created_at >= '<SUSPECTED_START_TIME>'
    ORDER BY created_at DESC;
    
    -- Check what documents were accessed
    SELECT DISTINCT entity_id, entity_type, action
    FROM audit_logs
    WHERE user_id = <COMPROMISED_USER_ID>;
    
  3. Notify stakeholders

    • Security officer
    • Management
    • Affected users (if applicable)
    • Legal team (if required by law)
  4. Document everything

    • Timeline of events
    • Data accessed/compromised
    • Actions taken
    • Lessons learned

Account Compromise Response

#!/bin/bash
# File: /scripts/respond-account-compromise.sh

USER_ID=$1

# 1. Immediately disable account
docker exec lcbp3-mariadb mysql -u root -p -e "
  UPDATE lcbp3_dms.users
  SET is_active = 0,
      locked_until = DATE_ADD(NOW(), INTERVAL 24 HOUR)
  WHERE user_id = $USER_ID;
"

# 2. Invalidate all sessions
docker exec lcbp3-redis redis-cli DEL "session:user:$USER_ID:*"

# 3. Generate audit report
docker exec lcbp3-mariadb mysql -u root -p -e "
  SELECT * FROM lcbp3_dms.audit_logs
  WHERE user_id = $USER_ID
  AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
  ORDER BY created_at DESC;
" > /reports/compromise-audit-user-$USER_ID-$(date +%Y%m%d).txt

# 4. Notify security team
/scripts/send-alert-email.sh "Account Compromise" "User ID $USER_ID has been compromised and disabled"

echo "Account compromise response completed for User ID: $USER_ID"

📊 Security Metrics & KPIs

Monthly Security Report

Metric Target Actual
Failed Login Attempts < 100/day Track
Locked Accounts < 5/month Track
Critical Vulnerabilities 0 Track
High Vulnerabilities < 5 Track
Unpatched Systems 0 Track
Security Incidents 0 Track
Mean Time To Detect (MTTD) < 1 hour Track
Mean Time To Respond (MTTR) < 4 hours Track

🔐 Compliance & Audit

Audit Log Retention

  • Access Logs: 1 year
  • Security Events: 2 years
  • Admin Actions: 3 years
  • Data Changes: 7 years (as required)

Compliance Checklist

  • Regular security audits (quarterly)
  • Penetration testing (annually)
  • Access control reviews (monthly)
  • Encryption at rest and in transit
  • Secure password policies enforced
  • Multi-factor authentication (if required)
  • Data backup and recovery tested
  • Incident response plan documented and tested

Security Operations Checklist

Daily

  • Review security alerts and logs
  • Monitor failed login attempts
  • Check for unusual access patterns
  • Verify backup completion

Weekly

  • Review user access logs
  • Scan for vulnerabilities
  • Update virus definitions
  • Review firewall logs

Monthly

  • User access audit
  • Role and permission review
  • Security patch application
  • Compliance review

Quarterly

  • Full security audit
  • Penetration testing
  • Disaster recovery drill
  • Update security policies


Version: 1.8.0 Last Review: 2025-12-01 Next Review: 2026-03-01


Appendix A — SSH Setup (QNAP & ASUSTOR)

คู่มือการตั้งค่าและใช้งาน SSH สำหรับ NAS ทั้ง 2 เครื่อง

A.1 Connection Info

Item QNAP (TS-473A) ASUSTOR (AS5403T)
Role Application Server Infrastructure / Backup
IP 192.168.10.8 192.168.10.9
SSH Port 22 22
SSH Alias qnap asustor

A.2 Enable SSH on NAS

QNAP: QTS Web UI → Control Panel → Network & File Services → Telnet/SSH → Enable SSH (port 22)

ASUSTOR: ADM Web UI → Settings → Terminal & SNMP → Enable SSH service (port 22)

A.3 SSH Key Setup (Client → NAS)

# Create key (once on dev machine)
ssh-keygen -t ed25519 -C "nattanin@np-dms"

# Copy public key to NAS
ssh-copy-id -i ~/.ssh/id_ed25519.pub nattanin@192.168.10.8   # QNAP
ssh-copy-id -i ~/.ssh/id_ed25519.pub nattanin@192.168.10.9   # ASUSTOR

A.4 SSH Config (~/.ssh/config)

Host gitea
    HostName git.np-dms.work
    User git
    Port 2222
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

Host qnap
    HostName 192.168.10.8
    User nattanin
    Port 22
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

Host asustor
    HostName 192.168.10.9
    User nattanin
    Port 22
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

A.5 SSH Hardening (/etc/ssh/sshd_config on NAS)

PasswordAuthentication no   # Key-only login
PermitRootLogin no
AllowUsers nattanin

# Restart SSH
/etc/init.d/login_server.sh restart   # QNAP
/etc/init.d/sshd restart              # ASUSTOR

⚠️ ตรวจสอบว่า SSH Key ใช้งานได้ก่อนปิด PasswordAuthentication

A.6 SSH Port Forwarding (Useful Tunnels)

# MariaDB local tunnel
ssh -L 3306:localhost:3306 qnap

# Elasticsearch tunnel
ssh -L 9200:localhost:9200 qnap

# Grafana tunnel (from ASUSTOR)
ssh -L 3000:localhost:3000 asustor

A.7 SSH Troubleshooting

Problem Cause Fix
Connection refused SSH not enabled on NAS Enable SSH via Web UI
Permission denied (publickey) Wrong key or permissions chmod 700 ~/.ssh, chmod 600 ~/.ssh/authorized_keys
Host key verification failed IP changed, stale key ssh-keygen -R 192.168.10.8
Connection timed out Firewall / wrong IP Check ACL and ping

Appendix B — Secrets Management (QNAP Deployment)

⚠️ Security Level: CONFIDENTIAL — ห้าม commit secrets ลง Git

B.1 Secret Categories

Category Examples Storage
Database Credentials MYSQL_ROOT_PASSWORD .env (gitignored)
API Keys JWT_SECRET, REDIS_PASSWORD .env (gitignored)
SSL Certificates Let's Encrypt NPM volume
SSH Keys Backup access keys ASUSTOR secure storage

B.2 Environment File (QNAP)

# File: /share/np-dms/.env
# ⚠️ MUST be in .gitignore

# === Database ===
MYSQL_ROOT_PASSWORD=<strong-password>
MYSQL_DATABASE=lcbp3
MYSQL_USER=center
MYSQL_PASSWORD=<strong-password>

# === Redis ===
REDIS_PASSWORD=<strong-password>

# === Application ===
JWT_SECRET=<random-64-hex>       # openssl rand -hex 64
JWT_REFRESH_SECRET=<random-64-hex>

# === Monitoring ===
GRAFANA_PASSWORD=<admin-password>

# === External Services ===
LINE_CHANNEL_SECRET=<line-secret>
LINE_CHANNEL_ACCESS_TOKEN=<line-token>

B.3 Generate Strong Secrets

openssl rand -base64 32    # Strong password (24+ chars)
openssl rand -hex 64       # JWT Secret (64 hex chars)

B.4 Rotation Schedule

Secret Period Impact
JWT Secret 90 days Users re-login required
Database Password 180 days Service restart needed
Redis Password 180 days Service restart needed
SSL Certificates Auto (Let's Encrypt) None

B.5 Encrypted Secret Backup

# Encrypt and back up to ASUSTOR
gpg --symmetric --cipher-algo AES256 /share/np-dms/.env -o /tmp/env.gpg
scp /tmp/env.gpg admin@192.168.10.9:/volume1/backup/secrets/
rm /tmp/env.gpg

# Restore
scp admin@192.168.10.9:/volume1/backup/secrets/env.gpg /tmp/
gpg --decrypt /tmp/env.gpg > /share/np-dms/.env
rm /tmp/env.gpg

B.6 Gitea Actions Secrets (CI/CD)

Configure at: Gitea → Repository → Settings → Actions → Secrets

Secret Name Description
HOST QNAP IP (192.168.10.8)
PORT SSH Port (22)
USERNAME SSH user with Docker access
PASSWORD SSH password (prefer SSH Key)