🔒
EPISODE 04
TLS · helmet · .env · 시크릿 유출 대응
HTTPS와 환경변수 관리
HTTP vs HTTPS와 TLS 인증서, Let's Encrypt 무료 발급, helmet으로 보안 헤더 일괄 설정, .env로 시크릿 안전 관리, .env.example 패턴, 그리고 시크릿 유출 시 즉시 대응법까지.
HTTPSTLShelmetdotenv
소요 시간
⏱ 60분
난이도
📊 중급~고급
선수 조건
🎯 sec-03
결과물
HTTPS와 보안 헤더로 보호된 운영 가능한 Express 앱
이 강의에서 배우는 것
- 1HTTP/HTTPS와 TLS Handshake의 흐름을 안다
- 2Let's Encrypt + Certbot 으로 무료 인증서를 발급한다
- 3HSTS / X-Frame-Options / X-Content-Type-Options / Referrer-Policy / CSP를 설정한다
- 4helmet 미들웨어로 한 번에 적용한다
- 5.env로 시크릿을 관리하고 .gitignore + .env.example 패턴을 따른다
- 6시크릿이 유출됐을 때 즉시 무효화·히스토리 정리·예방을 한다
1. HTTP vs HTTPS
text
HTTP (평문): 클라이언트 ── 비밀번호:abc123 ─> 서버
↑ 중간자가 읽을 수 있음
HTTPS (암호화): 클라이언트 ── 5h#@x9!K3m... ──> 서버
↑ 암호화 — 해독 불가TLS Handshake: 서버가 인증서(공개키) 전송 → CA 검증 → 대칭키 안전하게 교환 → 이후 통신은 대칭키로 암호화.
Let's Encrypt — 무료 인증서
bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com
# 자동 갱신 (90일 만료)
sudo certbot renew --dry-run2. 보안 HTTP 헤더
javascript
app.use((req, res, next) => {
// HTTPS만 허용 (1년)
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
// 클릭재킹 방지 (iframe 삽입 차단)
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
// MIME 스니핑 방지
res.setHeader('X-Content-Type-Options', 'nosniff');
// 참조 URL 정보 제한
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// 리소스 출처 제한 (XSS 방어 보조)
res.setHeader('Content-Security-Policy', "default-src 'self'");
next();
});3. helmet (권장)
bash
npm install helmetjavascript
const helmet = require('helmet');
// 모든 보안 헤더 한 번에
app.use(helmet());
// 또는 개별 설정
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.example.com"],
},
},
hsts: { maxAge: 31536000, includeSubDomains: true },
}));4. 환경변수로 시크릿 관리
절대 하지 말 것
javascript
// ✗ 하드코딩
const DB_PASSWORD = 'my_secret_password';
const JWT_SECRET = 'jwt-secret-key-123';
const API_KEY = 'sk-abcdefghijk12345';
// ✗ package.json에도 금지
{
"scripts": {
"start": "DB_PASSWORD=secret node app.js"
}
}.env
text
DATABASE_URL=mongodb+srv://user:pass@cluster.mongodb.net/db
JWT_SECRET=매우긴랜덤문자열여기에
API_KEY=sk-abcdefghijk12345
SESSION_SECRET=또다른랜덤문자열
PORT=3000javascript
require('dotenv').config();
mongoose.connect(process.env.DATABASE_URL);
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) {
console.error('JWT_SECRET이 설정되지 않았습니다!');
process.exit(1); // 시크릿 없으면 서버 시작 거부
}5. .gitignore + .env.example
text
# .gitignore
.env
.env.local
.env.production
*.pem # SSL 인증서
node_modules/text
# .env.example (Git에 포함 — 실제 값 없이 키만)
DATABASE_URL=your_mongodb_connection_string
JWT_SECRET=generate_a_random_string_here
API_KEY=your_api_key_here
SESSION_SECRET=another_random_string
PORT=30006. 시크릿 유출 대응
즉시 해야 할 일
- 즉시 키 무효화 — API 키, 비밀번호 즉시 변경
- Git 히스토리 정리 — 과거 커밋에서 시크릿 제거
- 피해 평가 — 유출된 키로 무엇을 할 수 있었는지
bash
# 히스토리에서 시크릿 파일 제거
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch .env' \
--prune-empty --tag-name-filter cat -- --all
# 또는 BFG Repo Cleaner (더 안전)
bfg --delete-files .env예방
bash
# pre-commit hook으로 커밋 전 검사
npm install -g git-secrets
git secrets --install
git secrets --register-aws