guest@websec ~ % man 15-environmental-security
15 · ความปลอดภัยของสภาพแวดล้อม
การเสริมความแข็งแรงเซิร์ฟเวอร์เป็นงานเฉพาะ แต่นักพัฒนาทุกคนรับผิดชอบส่วนหนึ่ง: secret ที่แอปใช้ ต้องไม่ไปอยู่ในที่ที่ผู้โจมตีอ่านได้
อะไรนับเป็น secret
- URL และรหัสผ่านของฐานข้อมูล
- คีย์ลงนามของแอป (
secret_key_base) ใครได้ไปก็ปลอม session ได้ทุกคน - API key ของบุคคลที่สาม: ผู้ให้บริการชำระเงิน บริการอีเมล ข้อมูลรับรองคลาวด์
- OAuth client secret, secret สำหรับลงนาม webhook, คีย์ส่วนตัวของ TLS/SSH
รั่วจากที่ไหน: Git commit, Docker image, log, แชต, bundle ฝั่งหน้าเว็บ
สามวิธีในการจัดการ secret
secret มาจาก environment variable ที่แพลตฟอร์มตั้งให้ ในเครื่องใช้ไฟล์ .env ที่อยู่ใน gitignore
ง่าย แต่ไม่มี audit trail
แบบ Rails: secret อยู่ในไฟล์เข้ารหัสที่ commit ได้ (credentials.yml.enc) ส่วนคีย์ (master.key) ห้ามเข้า Git
master.key อยู่ใน gitignore ส่วนเซิร์ฟเวอร์ใช้ RAILS_MASTER_KEYบริการอย่าง Vault หรือ AWS Secrets Manager ส่ง secret ให้แอป บันทึกทุกการอ่าน และหมุนเวียนให้
ลองเข้ารหัส secret ดูว่าอะไร commit ได้ แล้วลองใช้คีย์ผิด
credentials.yml.enc (commit ลง git)
เซิร์ฟเวอร์บูต
การอ่าน secret ในโค้ด
โหลด secret ตอนเริ่มทำงาน และ ให้แอปพังถ้าขาด ค่าสำรองเงียบๆ อย่าง "changeme" คือเหตุที่แอปไปลงนาม session ด้วยคีย์ที่คนรู้
# bin/rails credentials:edit --environment production # → config/credentials/production.yml.enc + config/credentials/production.key Rails.application.credentials.stripe_secret_key! # raises if blank: fail fast Rails.application.credentials.fetch(:database) # KeyError if missing Rails.application.credentials.dig(:aws, :secret_access_key) ENV.fetch("DATABASE_URL") # fetch, don't default ENV["SECRET_KEY_BASE"] || "changeme"
// config.js: validate env at boot with zod (or envalid) import { z } from "zod"; const env = z.object({ DATABASE_URL: z.string().url(), SESSION_SECRET: z.string().min(32), STRIPE_SECRET_KEY: z.string().startsWith("sk_"), }).parse(process.env); // throws at startup if anything is missing or malformed export default env; // local dev only: load .env (git-ignored) if (process.env.NODE_ENV !== "production") await import("dotenv/config"); // never: process.env.SESSION_SECRET || "changeme"
# settings.py: pydantic-settings validates and fails fast from pydantic import Field, PostgresDsn from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env") # .env is git-ignored database_url: PostgresDsn secret_key: str = Field(min_length=50) stripe_secret_key: str = Field(pattern=r"^sk_") settings = Settings() # raises ValidationError at startup if missing # never: SECRET_KEY = os.environ.get("SECRET_KEY", "changeme")
credentials.some_key คืน nil แบบเงียบๆ ให้ใช้ credentials.some_key! เพื่อให้แอปหยุดบูตถ้าขาด secretcommit ได้อย่างปลอดภัยไหม?
ถ้า secret รั่ว
ลบ commit ไม่ช่วย บอทเก็บคีย์ไปภายในไม่กี่นาที หมุนเวียนคีย์ก่อน แล้วเพิ่มตัวสแกนใน pre-commit และ CI