security: env-gate trust proxy, 0600 account file, seed+template for account.json untrack
리뷰 지적 추가 반영(서버 전용). - trust proxy 를 항상 켜던 것을 TRUST_PROXY=true 일 때만 켜도록 변경. 직접 노출 시 X-Forwarded-For 조작으로 로그인 rate limit 을 우회하던 문제 차단(프록시 뒤면 TRUST_PROXY=true 설정). - account.local.json 을 0o600(소유자 전용)으로 저장. - 서버 시작 시 account.local.json 이 없으면 account.json 에서 시드(0o600). 이렇게 하면 재배포 직후(로그인 전에도) 로컬 계정 파일이 항상 존재해, 이후 account.json 을 안전하게 추적 해제할 수 있다. - account.example.json 템플릿 추가. account.json 자체의 git 추적 해제는 서버가 한 번 재배포되어 account.local.json 이 생성된 뒤 후속 커밋에서 처리(그 전에 지우면 pull 시 삭제되어 로그인이 막힘). Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
3
account.example.json
Normal file
3
account.example.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[
|
||||||
|
{ "id": "admin", "password": "여기에-비밀번호를-넣으세요" }
|
||||||
|
]
|
||||||
@@ -6,7 +6,8 @@ import fsp from 'node:fs/promises'
|
|||||||
import crypto from 'node:crypto'
|
import crypto from 'node:crypto'
|
||||||
import {
|
import {
|
||||||
manifestRootPath, manifestDirPath, manifestTermsDirPath,
|
manifestRootPath, manifestDirPath, manifestTermsDirPath,
|
||||||
fileDirPath, viewsDirPath, publicDirPath, projectRoot
|
fileDirPath, viewsDirPath, publicDirPath, projectRoot,
|
||||||
|
accountFilePath, accountLocalFilePath
|
||||||
} from '../shared/paths.js'
|
} from '../shared/paths.js'
|
||||||
import {
|
import {
|
||||||
ensurePackTermsDir, isPublicTermsFile, listTermsWithLabels, loadPackDefinition
|
ensurePackTermsDir, isPublicTermsFile, listTermsWithLabels, loadPackDefinition
|
||||||
@@ -27,7 +28,24 @@ const app = express()
|
|||||||
|
|
||||||
app.set('view engine', 'ejs')
|
app.set('view engine', 'ejs')
|
||||||
app.set('views', viewsDirPath)
|
app.set('views', viewsDirPath)
|
||||||
app.set('trust proxy', 1)
|
// 리버스 프록시 뒤일 때만 켠다. 항상 켜두면 직접 노출 시 X-Forwarded-For 조작으로
|
||||||
|
// req.ip 를 위조해 로그인 rate limit 을 우회할 수 있다. 프록시 뒤라면 TRUST_PROXY=true.
|
||||||
|
app.set('trust proxy', process.env.TRUST_PROXY === 'true' ? 1 : false)
|
||||||
|
|
||||||
|
// 추적되는 account.json(과거 평문 노출)을 gitignore 된 account.local.json 으로 시드한다.
|
||||||
|
// 로컬 파일이 이미 있으면 건드리지 않음. 이후 계정 쓰기/자동 해시 업그레이드는 로컬
|
||||||
|
// 파일에만 반영되어, 재배포로 account.json 을 추적 해제해도 로그인이 유지된다.
|
||||||
|
function seedLocalAccounts(): void {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(accountLocalFilePath)) return
|
||||||
|
if (!fs.existsSync(accountFilePath)) return
|
||||||
|
fs.copyFileSync(accountFilePath, accountLocalFilePath)
|
||||||
|
fs.chmodSync(accountLocalFilePath, 0o600)
|
||||||
|
} catch {
|
||||||
|
// 실패해도 readAccounts 가 account.json 으로 폴백하므로 치명적이지 않음.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seedLocalAccounts()
|
||||||
|
|
||||||
app.use(express.urlencoded({ extended: true }))
|
app.use(express.urlencoded({ extended: true }))
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
|
|||||||
@@ -795,6 +795,7 @@ export async function readAccounts(): Promise<AccountEntry[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 운영 계정 저장은 항상 gitignore 된 account.local.json 에만 한다(추적 파일 오염 방지).
|
// 운영 계정 저장은 항상 gitignore 된 account.local.json 에만 한다(추적 파일 오염 방지).
|
||||||
|
// 비밀번호(해시) 파일이므로 소유자만 읽기/쓰기(0o600).
|
||||||
export async function writeAccounts(accounts: AccountEntry[]): Promise<void> {
|
export async function writeAccounts(accounts: AccountEntry[]): Promise<void> {
|
||||||
await fsp.writeFile(accountLocalFilePath, `${JSON.stringify(accounts, null, 2)}\n`, 'utf8')
|
await fsp.writeFile(accountLocalFilePath, `${JSON.stringify(accounts, null, 2)}\n`, { mode: 0o600 })
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user