Skip to content

13.6 Environment configuration

บทนี้สำรวจวิธีที่แต่ละ service อ่าน configuration, ตัวแปร environment ที่ใช้, และจุดที่มีค่า default อันตราย

กลไกการอ่าน configuration

Serviceกลไกไฟล์ที่เกี่ยวข้อง
vtrc-apirequire('dotenv').config() + process.envapi/src/config.js:1
cu-central-apirequire('dotenv').config() + process.envmain-api/src/config.js
centralize-api@nestjs/config ConfigServicesrc/app.module.ts และ src/config/
vtrc-webbuild-time — เลือก endpoint ที่ runtime ผ่าน window.location.hrefsrc/config/apollo.js
vtrc-rc-backofficebuild-time — เช่นเดียวกันsrc/config/apollo.js

รูปแบบทั่วไปของ backend คืออ่าน env ตอน start แล้ว export เป็น object ที่ใช้ทั่วระบบ — vtrc-api/api/src/config.js เป็นตัวอย่างชัดเจน

รายการ environment variable หลัก

ตารางรวบรวม env var จาก .env.example ของทั้ง 3 backend services:

vtrc-api (จาก vtrc-api/api/.env.example)

หมวดVariableค่า defaultหมายเหตุ
RuntimeNODE_ENVdevelopmentใช้ตรวจสอบ IS_PRODUCTION
RuntimeAPP_NO0ระบุ replica
RuntimeAPP_PORT8081port ที่ service ฟัง
RuntimeTIME_ZONEAsia/Bangkok
DatabaseDB_SCHEMA'' (ว่าง)mariadb หรือ mysql
DatabaseDB_HOST'' (ว่าง)
DatabaseDB_NAME'' (ว่าง)vtrc
DatabaseDB2_NAMEvtrc-centralize
SecretsDATA_ENCRYPT_KEYคีย์เข้ารหัสข้อมูล
SecretsJWT_SECRET'secret' ⚠️ค่า default อันตราย — ถ้า env ไม่ตั้ง ทุก JWT จะเซ็นด้วย 'secret'
ExternalEND_POINT_CENTRALIZEชี้ไป cu-central-api
ExternalEND_POINT_CENTRALIZE_AUTHชี้ไป cu-central-api auth
ExternalEND_POINT_CONICLEConicle LMS
ExternalAPI_CENTRALIZE_KEYAPI key สำหรับเรียก cu-central-api
ExternalCLIENT_ID_CENTRALIZEclient identifier
File storageFILE_STORAGE_PATH/root_path/storage
File storageLOG_PATH/root_path/logs
TransferTRANSFER_PROTOTAL (typo)scp
TransferTRANSFER_HOSThost สำหรับ scp
TransferTRANSFER_USERNAME
TransferTRANSFER_PASSWORD

cu-central-api (จาก cu-central-api/main-api/.env.example)

หมวดVariableค่า defaultหมายเหตุ
RuntimeAPP_PORT8080
RuntimeTIME_ZONE+07:00
SecretsHASH_KEYคีย์สำหรับ hash
SecretsJWT_SECRET'JWT_SECRET_KEY' ⚠️ค่า default ที่ไม่ใช่ค่าจริง — ต้อง override
DatabaseDB_SCHEMA, DB_HOST, DB_NAME, ...'' (ว่าง)
RedisREDIS_HOSTlocalhost
RedisREDIS_PASSWORD'REDIS_PASSWORD'placeholder
LDAPAD_URLldap://localhostLDAP ขององค์กร
LDAPAD_BASE_DNdc=redcross,dc=or,dc=th
LDAPAD_USERNAMEbind DN
LDAPAD_PASSWORDbind password
SMTPSMTP_HOST, SMTP_PORT, ...ส่ง email
ExternalHRMI_END_POINTHRMI service
ExternalHRMI_USERNAME, HRMI_PASSWORD

centralize-api (จาก src/config/)

centralize-api ใช้ NestJS ConfigService ที่อ่านจาก .env โดยอัตโนมัติ — variable ที่ใช้มีโครงสร้างคล้าย cu-central-api (DB, MSSQL, JWT_ISSUER, JWT_SECRET)

จุดสังเกตจาก centralize-api/src/main.ts:18-19:

typescript
const config = app.get<ConfigService>(ConfigService);
const port = config.get('port') || 3000;

ค่า default ที่อันตราย

vtrc-api — JWT_SECRET

vtrc-api/api/src/config.js:12:

javascript
JWT_SECRET = 'secret',

ถ้าตัวแปร environment JWT_SECRET ไม่ถูกตั้งในขณะที่ service start, JWT ทุกตัวจะถูกเซ็นด้วย string 'secret' ซึ่งเป็นความลับที่ไม่เป็นความลับ — ผู้โจมตีที่รู้สามารถปลอมแปลง JWT ของผู้ใช้คนใดก็ได้

ปัญหานี้ถูกรายงานใน docs/audit/05-tech-debt-register.md (SEC-4) ว่าเป็น "Critical"

vtrc-api — DB credentials ใน .env.example

vtrc-api/api/.env.example:13-14:

DB_USERNAME="root"
DB_PASSWORD="1q2w3e4r00"

แม้เป็น .env.example ค่า 1q2w3e4r00 อาจเป็น password จริงที่ทีมเคยใช้และลืมเปลี่ยน — ควรเป็น <your-password-here> แทน

vtrc-api — DATA_ENCRYPT_KEY ใน .env.example

vtrc-api/api/.env.example:3:

DATA_ENCRYPT_KEY="79E1289D21753BA7C9FEA0BD0F6CB14F5AD65D6B9B6C7501E9B291F9D1819E4F"

ค่านี้เป็น 64 หลัก hex ที่ดูเหมือน production key — ถ้าใช้จริง การรั่วไหลของ .env.example (ซึ่งอยู่ใน repository) เท่ากับเปิดเผยคีย์เข้ารหัส

cu-central-api — NODE_ENV=development ใน docker-compose

cu-central-api/docker-compose.yml:45,64,81:

yaml
environment:
  - NODE_ENV=development

compose นี้ใช้ใน production แต่ตั้ง NODE_ENV=development ทำให้ code ที่ตรวจ IS_PRODUCTION ทำงานผิดพลาด — เช่น GraphQL playground จะเปิดทำงาน (vtrc-api/api/src/index.js:68-69)

การจัดการ secrets ในปัจจุบัน

จากการตรวจสอบ repository พบว่า:

  • ไม่มี secret manager — ไม่มี HashiCorp Vault, GCP Secret Manager, หรือ AWS Secrets Manager
  • ไม่มี KMS encryption — ไม่มีการเข้ารหัส secret ที่เก็บใน env
  • .env commit ใน centralize-api Dockerfilecentralize-api/Dockerfile:19 คัดลอก .env จาก build stage เข้า image production หมายความว่า secret ทุกตัวอยู่ใน image
  • GCP service account key commit ใน repovtrc-api/vtrc-gcp-sa-key.json อยู่ใน version control (รายละเอียด Volume 9 บทที่ 5)

vtrc-api config.js — ที่เก็บข้อมูลที่ละเอียดอ่อน

vtrc-api/api/src/config.js:41-148 มีข้อมูลที่เก็บแบบ hard-coded:

javascript
API_DEVICE_KEY = {
  prod: [
    {
      id: '93361f06-6f55-11eb-9439-0242ac130002',
      key: 'fd77b56f-1489-4fb1-a094-a614f0d16198',
      APP_TYPE: 'WEB',
      APP_NAME: 'VTRC-WEB'
    },
    // ... 7 prod keys + 10 UAT keys
  ],
  uat: [ ... ]
}

Device key เหล่านี้ใช้เป็น apiKey header ในทุก GraphQL request — ค่า key ทั้งหมดอยู่ใน code ที่ commit ลง repository (รายละเอียด Volume 9 บทที่ 5 — SEC-3)

config ที่ควรอยู่ใน env แต่อยู่ใน code

นอกจาก API_DEVICE_KEY ยังมี config อื่นที่ฝังใน config.js แทน env:

  • JWT_EXP = '15m' — TTL ของ JWT
  • JWT_ISSUER = 'redcross.or.th:vtrc' — issuer claim
  • DB_MORE_DATABASE = ['vtrc-centralize'] — schema list
  • สถานะทั้งหมดของ workflow (statusLeave, statusHrmiName, ...) — แม้จะไม่ใช่ secret แต่การฝังใน code ทำให้เปลี่ยนไม่ได้โดยไม่ deploy ใหม่

การตรวจสอบ IS_PRODUCTION

vtrc-api/api/src/config.js กำหนดค่าคงที่:

javascript
export const IS_PRODUCTION = (NODE_ENV === 'production')

IS_PRODUCTION ถูกใช้ใน code เพื่อ:

  • เปิด/ปิด GraphQL playground (แต่ index.js:68-69 ไม่ได้ gate — เปิดเสมอ)
  • ตัดสินใจเรื่อง logging level
  • ตัดสินใจเรื่อง schema sync

ปัญหาคือ NODE_ENV=development ใน docker-compose ของ cu-central-api ทำให้ตรรกะเหล่านี้ทำงานผิด

ปัญหาเชิงปฏิบัติการ

ปัญหาที่มาผลกระทบ
JWT_SECRET = 'secret' defaultconfig.js:12ถ้า env ว่าง, JWT ทุกตัวเซ็นด้วย 'secret'
.env.example มีค่าที่ดูเป็น production.env.example:3,14รั่วไหลถ้า push ไป public repo
NODE_ENV=development ใน compose ของ cu-central-apidocker-compose.yml:45,64,81IS_PRODUCTION = false ในทุกที่
.env คัดลอกใน centralize-api Docker buildDockerfile:19secret อยู่ใน image ที่ push ไป GCR
Device keys อยู่ใน config.jsconfig.js:42-148ผู้ที่อ่าน repo ได้ = ปลอมตัวเป็น app
ไม่มี rotation mechanismทั้งระบบkey ใช้มานาน, ไม่มี log การใช้งาน
ชื่อ env var TRANSFER_PROTOTAL (typo ของ PROTOCOL).env.example:26ใช้มานานแล้ว เปลี่ยนทีหลังทำให้เสีย compatibility

ข้อแนะนำเพื่อปรับปรุง

  1. ยกเลิกค่า default ที่อันตราย — service ต้อง crash ตอน start ถ้า JWT_SECRET หรือ DB_PASSWORD ว่าง
  2. ย้าย secret ไป secret manager — GCP Secret Manager สำหรับ GCP-hosted services
  3. เปลี่ยน .env.example ให้เป็น placeholder<your-jwt-secret-here> แทนค่าจริง
  4. แยก .env.production จาก .env.development — และไม่ commit .env.production
  5. อย่าคัดลอก .env ใน Dockerfile — inject ผ่าน docker run --env-file หรือ secret mount
  6. เพิ่ม pre-commit hook ที่ block การ commit ค่าที่ดูเป็น secret (regex ของ UUID, ความยาว ≥ 32)