guest@websec: ~/websec/15-environmental-security/th — zsh — 100×40 เขียนโดย SaKKo ENTH โค้ด:

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

ไฟล์ credentials เข้ารหัสแล้ว commit ได้ master.key อยู่ใน gitignore ส่วนเซิร์ฟเวอร์ใช้ RAILS_MASTER_KEY

บริการอย่าง Vault หรือ AWS Secrets Manager ส่ง secret ให้แอป บันทึกทุกการอ่าน และหมุนเวียนให้

sim: ไฟล์ credentials ที่เข้ารหัสAES-GCM จริงในเบราว์เซอร์ของคุณ

ลองเข้ารหัส 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! เพื่อให้แอปหยุดบูตถ้าขาด secret

commit ได้อย่างปลอดภัยไหม?

ควิซ: แยกประเภทไฟล์กด commit หรือ ignore สำหรับแต่ละไฟล์

ถ้า secret รั่ว

ลบ commit ไม่ช่วย บอทเก็บคีย์ไปภายในไม่กี่นาที หมุนเวียนคีย์ก่อน แล้วเพิ่มตัวสแกนใน pre-commit และ CI

sim: ตัวสแกน secretวาง diff หรือไฟล์ config