docs: harden ops guidance and CI smoke test
TaxBaik CI/CD / build-and-deploy (push) Successful in 50s

This commit is contained in:
2026-06-27 01:42:48 +09:00
parent 1d7dd71011
commit 0df5d2d31c
4 changed files with 60 additions and 5 deletions
+54 -2
View File
@@ -35,6 +35,12 @@ TaxBaik.Web ASP.NET Core 앱 (포트 5001)
- 관리자: `/taxbaik/admin` (Blazor Server)
- 로그인: `/taxbaik/admin/login`
**운영 원칙:**
- 단일 앱, 단일 서비스, 단일 배포 경로를 유지한다.
- 운영 변경은 코드 또는 CI에서만 반영한다.
- 서버에 임시 수동 수정이나 파일 드리프트가 생기지 않도록 한다.
- 공개 사이트와 관리자 UI는 같은 앱에서 처리하되, 보안 경계는 인증과 권한으로 분리한다.
### 2.2 계층 책임
- **Domain**: 비즈니스 규칙, 엔티티 정의
- **Infrastructure**: DB 접근, Dapper 구현체, 마이그레이션 실행
@@ -65,6 +71,11 @@ TaxBaik.Web ASP.NET Core 앱 (포트 5001)
- 복잡한 조인, 페이징, 성능 제어 용이
- EF Core 대비 SQL 완전 제어 가능
**왜 이 운영 모델인가?**
- 운영 복잡도를 낮춰 장애 포인트를 줄인다.
- 배포를 CI로 고정하면 서버 간 상태 드리프트를 줄인다.
- 민감 정보는 코드/문서/로그에 남기지 않고 환경 변수와 서버 비밀 저장소에만 둔다.
---
## 3. 로컬 개발 환경 설정
@@ -139,6 +150,11 @@ dotnet run
**중요**: 로컬 appsettings.json은 버전 관리에서 제외 또는 .local suffix 사용
**보안 규칙**:
- `appsettings.Production.json`에는 비밀값을 두지 않는다.
- JWT Secret, DB 비밀번호, 외부 API 키는 환경 변수 또는 서버 전용 비밀 경로에서만 읽는다.
- 값이 비어 있으면 조용히 넘어가지 말고 시작 시 즉시 실패시킨다.
```bash
# 로컬 오버라이드
appsettings.Development.json # gitignore에 추가
@@ -168,6 +184,10 @@ CREATE TABLE IF NOT EXISTS new_table (
- 테스트 블로그 포스트 5개
- 테스트 카테고리 5개
**운영 보안 주의**:
- 시드 계정은 운영 초기화용이다. 배포 후에는 반드시 별도 강한 비밀번호로 교체한다.
- 테스트 계정이 운영에 남아 있으면, 배포 후 즉시 비밀번호 재설정 또는 계정 비활성화를 수행한다.
수동 추가:
```sql
-- Admin 추가
@@ -209,6 +229,11 @@ git push "http://kjh2064:${token}@localhost:3000/kjh2064/taxbaik.git" master
- ✅ 안전 (token은 로컬 루프백)
- ✅ 신뢰성 높음
**보안 규칙**:
- 토큰은 채팅/문서/스크린샷에 붙이지 않는다.
- push URL에 토큰이 남아 있으면 즉시 제거한다.
- 가능하면 SSH key 기반 인증을 우선 사용한다.
#### 방법 B: SSH로 직접 Push (SSH key 필요)
```bash
@@ -261,10 +286,13 @@ ssh kjh2064@178.104.200.7
- 로컬 또는 서버에서 수동 `dotnet publish`로 운영 배포하지 않는다
- `rsync`로 직접 아티팩트를 올리지 않는다
- 배포 실패 시 CI 로그를 먼저 본다
- 배포된 아티팩트는 CI가 만든 것만 신뢰한다
- 배포 후 검증은 홈, 관리자 로그인 페이지, 로그인 API를 모두 포함한다
**롤백**:
- 이전 정상 커밋을 `master`에 revert 또는 hotfix로 되돌린다
- 서버 파일을 수동으로 복구하지 않는다
- 롤백은 커밋 단위로 추적 가능해야 한다
### 3.4 서비스 파일 위치
```
@@ -298,6 +326,11 @@ location /taxbaik {
**참고**: 단일 `/taxbaik` 블록이 공개 사이트와 관리자(Blazor WebSocket)를 모두 처리합니다. 운영은 `5001` 통합 앱 기준이며, 설정 반영은 CI 배포로만 수행한다.
**Nginx 보안**:
- `Upgrade` 헤더는 Blazor WebSocket 경로에만 허용하고, 필요 없는 location에는 넣지 않는다.
- `Host``X-Forwarded-Proto`는 유지해 원본 URL과 스킴을 보존한다.
- `/taxbaik/admin`는 robots.txt에서 차단한다.
---
## 6. 데이터베이스
@@ -311,6 +344,12 @@ Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Usernam
**절대 appsettings.Production.json에 비밀값을 하드코딩하지 말 것.**
**운영 보안 규칙**:
- DB 계정은 애플리케이션 전용 최소 권한으로 둔다.
- 관리자 비밀번호는 bcrypt로 해시하고, 평문 저장/전송을 금지한다.
- `PasswordHash`는 null이 되면 안 되며, null이면 인증 실패로 즉시 처리한다.
- 로그인 실패 로그는 사용자 이름만 남기고 비밀번호/해시를 절대 남기지 않는다.
### 3.2 Dapper 사용 패턴
**DbConnectionFactory.cs**:
@@ -342,6 +381,7 @@ public async Task<BlogPost?> GetBySlugAsync(string slug, CancellationToken ct)
- 항상 `@ParameterName` 파라미터 사용 (SQL injection 방지)
- 절대 문자열 연결 금지
- PostgreSQL `snake_case` 컬럼은 Dapper underscore 매핑을 전제로 함
- 조회 쿼리는 필요한 컬럼만 명시한다. `SELECT *`는 스키마 변경 시 매핑 사고를 만든다.
### 3.3 마이그레이션
@@ -495,6 +535,11 @@ builder.Services.AddAuthorizationCore();
토큰은 localStorage에 저장되며, `CustomAuthenticationStateProvider`가 자동으로 복원:
**보안 규칙**:
- JWT 만료 시간을 짧고 명확하게 유지한다.
- localStorage 토큰은 XSS가 없다는 전제 없이 다뤄야 한다.
- 관리자 기능은 `[Authorize]`로 감싸고, 클라이언트 렌더링만으로 권한을 믿지 않는다.
```csharp
// CustomAuthenticationStateProvider.cs
public async Task LoginAsync(string token)
@@ -578,6 +623,9 @@ Admin 로그인 페이지만 [AllowAnonymous]:
- [x] 카테고리 목록 캐시 (IMemoryCache, 10분 유효)
- [x] 비밀값은 환경 변수에서 읽기
- [x] `[ValidateAntiForgeryToken]` POST 메서드에 추가
- [x] 운영 배포는 CI-only
- [x] 관리자 로그인은 서버에서 직접 bypass하지 않기
- [x] DB/인증 문제는 로그와 쿼리로 먼저 확인
### DON'T ❌
- [ ] 비밀값을 appsettings.Production.json에 하드코딩
@@ -589,6 +637,9 @@ Admin 로그인 페이지만 [AllowAnonymous]:
- [ ] robots.txt에서 `/taxbaik/admin` allow 금지 (disallow 필수)
- [ ] 폼 제출 후 redirect (fire-and-forget 또는 same-page 응답)
- [ ] 절대 `Thread.Sleep` 또는 `Task.Delay` in request handler
- [ ] 운영 서버에서 수동 publish/rsync/파일 교체
- [ ] 비밀번호/토큰을 로그에 출력
- [ ] `SELECT *`로 인증/권한 테이블 조회
---
@@ -651,7 +702,7 @@ dotnet build TaxBaik.sln
ssh kjh2064@178.104.200.7
# DB 확인
psql -U kjh2064 -d taxbaikdb -c "\dt"
psql -U taxbaik -d taxbaikdb -c "\dt"
# 서비스 상태 (통합 Web 앱만)
systemctl status taxbaik
@@ -672,7 +723,7 @@ curl -X POST http://178.104.200.7/taxbaik/contact \
# 관리자 DB에서 확인
ssh kjh2064@178.104.200.7
psql -U kjh2064 -d taxbaikdb
psql -U taxbaik -d taxbaikdb
SELECT * FROM inquiries ORDER BY created_at DESC LIMIT 1;
```
@@ -687,6 +738,7 @@ SELECT * FROM inquiries ORDER BY created_at DESC LIMIT 1;
| 404 /taxbaik | Nginx 설정 재로드: `sudo nginx -t && sudo systemctl reload nginx` |
| Blazor WebSocket 안 됨 | `/taxbaik/admin` 경로에 Upgrade 헤더 필요 (Nginx 설정 확인) |
| 배포 후 503 | 서비스 시작 대기 (startup 시간 ~5초), `systemctl status taxbaik` 확인 |
| 로그인 실패 | `admin_users.password_hash`와 bcrypt 해시, `AuthService` 로그, `/api/auth/login` 응답 확인 |
---