시크릿 관리¶
이 페이지는 시크릿이 어디에 있고, 어떻게 회전하는가를 정리한다. 실제 시크릿 값(password, API key)은 본 페이지에 적지 않으며 — 위치 포인터만 제공한다. 값을 확인하려면 해당 yml 파일 또는 외부 dashboard를 직접 열어볼 것.
⚠️ plaintext 커밋 경고: application-prod.yml / application-local.yml의 여러 시크릿이 현재 평문 커밋되어 있다. Git history에 남아 있어 회전이 필수이지만, user가 이미 enumerated 항목들은 accept한 상태(feedback_accepted_repo_secrets.md 참조). 신규 시크릿 추가 시에는 절대 평문 커밋 금지.
1. 시크릿 카테고리 한눈에¶
flowchart TB
subgraph CODE["코드 레포 (커밋)"]
YML_P["application-prod.yml
(평문 — 회전 필요)"]
YML_L["application-local.yml
(dev/local 공용)"]
YML_O["application-oauth.yml
(naver/kakao/google)"]
end
subgraph EXT["외부 dashboard / vault"]
TOSS["Toss Payments
console"]
OAI["OpenAI
dashboard"]
LF["Langfuse
cloud.langfuse.com"]
NCP["Naver Cloud
console"]
TOSS_PROD["Toss live keys
(미수령)"]
end
subgraph LIGHTSAIL["Lightsail box (런타임 only)"]
ENV_BE["/etc/semugpt/backend.env
(prod, mode 0600)"]
ENV_FE["/etc/semugpt/frontend.env
(prod, mode 0600)"]
SLACK["/etc/semugpt/slack-webhook.url
(mode 0600)"]
SSH_KEY["~/.ssh/aws-semugpt
(개발자 Mac)"]
end
YML_P -.회전.-> ENV_BE
TOSS_PROD -.set in.-> ENV_BE
OAI -.regen-> ENV_BE
LF -.regen-> ENV_BE
2. 시크릿 카테고리별 정리¶
2.1 JWT 서명 키¶
| 항목 | 값 |
|---|---|
| 위치 | application-prod.yml:52 (jwt.secret), application-local.yml:50 |
| 환경변수 override | JWT_SECRET (Spring @Value binding) |
| 현재 상태 | local과 prod 동일 값 평문 커밋 — 회전 필요 |
| 회전 절차 | 새 secret 생성 → /etc/semugpt/backend.env의 JWT_SECRET 갱신 → sudo systemctl restart semugpt-backend. 모든 기존 세션이 로그아웃됨 (refresh token도 무효) |
⚠️ Hard cutover 후 회전 권장. 현재 legacy backend도 동일 secret을 쓰는 상태라 두 곳을 함께 갱신해야 함.
2.2 Toss Payments¶
| 환경 | 위치 | 키 |
|---|---|---|
| Local (test keys) | application-local.yml:78-83 |
tosspayments.client-key / secret-key / billing.client-key / billing.secret-key. test_gck_... / test_gsk_... / test_ck_... / test_sk_... 4종 |
| Production (live keys) | application-prod.yml:61-66 |
${TOSS_CLIENT_KEY} / ${TOSS_SECRET_KEY} / ${TOSS_BILLING_CLIENT_KEY} / ${TOSS_BILLING_SECRET_KEY} — 환경변수만 정의, live 값은 미주입 |
| Production live keys 자체 | ⚠️ 미수령 | 클라이언트(세림세무법인 김창진/김진우)에게 제공 요청 중 |
회전:
1. Toss Payments console에서 새 키 발급
2. /etc/semugpt/backend.env(backend secret) + /etc/semugpt/frontend.env(client key — NEXT_PUBLIC_TOSS_CLIENT_KEY)에 반영
3. sudo systemctl restart semugpt-backend semugpt-frontend
4. Toss console에서 webhook URL을 https://api-new.semugpt.co.kr/...로 등록 (현재 pending — Phase 11 작업)
2.3 OpenAI¶
| 항목 | 값 |
|---|---|
| 위치 | application-prod.yml:47-48 (openai.token), application-local.yml:45-46 |
| 환경변수 override | OPENAI_TOKEN |
| 현재 상태 | local과 prod 동일 평문 키 커밋 — 회전 필요 |
| 사용처 | RAG(임베딩) + 답변 생성 + HyDE + intent router 등 7개 LLM call site |
| 회전 | OpenAI dashboard에서 새 token 발급 → backend env 갱신 → restart |
⚠️ Token이 유출된 채로 history에 남아 있으므로 hard cutover 시 반드시 회전. 회전 시 데이터 파이프라인 (packages/data-pipeline)도 함께 갱신해야 함 (CLI 실행 시 사용).
2.4 Langfuse¶
| 환경 | 위치 | 값 |
|---|---|---|
| Local | application-local.yml:53-57 |
pk-lf-15fc00c9-... / sk-lf-087b4f9f-... 평문 커밋 |
| Production | application-prod.yml:70-77 |
${LANGFUSE_PUBLIC_KEY} / ${LANGFUSE_SECRET_KEY} — env vars만 정의 |
Langfuse는 7개 LLM 프롬프트의 model/temperature/maxTokens config를 관리한다 (rag-final-answer, hyde-generator, intent-router, query-rewrite, keyword-extraction 등 — CLAUDE.md "Langfuse 프롬프트 모델 설정" 참조). langfuse.enabled=false이거나 키 미주입 시 코드 fallback(하드코딩 prompt + default model)으로 silently 동작.
회전: Langfuse cloud → Settings → API Keys → regenerate → /etc/semugpt/backend.env 갱신 → restart.
2.5 Naver Cloud SENS (SMS)¶
| 항목 | 값 |
|---|---|
| 위치 | application-prod.yml:38-44, application-local.yml:36-42 (local/prod 동일 값) |
| 키 이름 | ncp.access-key, ncp.secret-key |
| Service ID | ncp.sens.sms.service-id = ncp:sms:kr:315481964654:semu-gpt |
| Calling number | 028302220 |
| 구현체 | SensMessageSender (@Component 활성). SnsMessageSender는 //@Component 주석으로 비활성 |
| 회전 | NCP console → IAM → access key regenerate → backend env 갱신 → restart |
⚠️ AWS SNS 아님 주의. 과거 위키에서 SMS 구현체를 AWS SNS로 잘못 인용한 사례가 있어 (CLAUDE.md Prior incidents 참조), application.yml의 aws.sns.enabled=true는 별개 설정.
2.6 AWS credentials¶
| 사용 패턴 | 자격증명 | 비고 |
|---|---|---|
| 개발자 CLI | aws --profile ob ... (SSO) |
semugpt 인프라 작업의 표준 진입점. 토큰 만료 1-12h → aws sso login --profile ob |
| Backend 런타임 (IAM access key, legacy) | application-{local,prod}.yml의 spring.cloud.aws.credentials.access-key / secret-key (AKIAQLD6ZXDVRTY2FHER / +kCL8z...) |
평문 커밋 — 회전 필요. SDK가 직접 호출(SNS 등). 신규 plan에서 미사용이지만 deactivate 전에 다른 consumer 점검 필요 |
| Frontend 배포 한정 IAM | aws --profile semugpt-frontend |
S3 + CloudFront 권한만. 같은 계정(023888247019)의 별도 IAM 세션 |
023888247019_AdministratorAccess |
SSO 수동 세션 토큰 | ob fallback 용 |
| Bootalk prod | aws --profile bootalk-prod |
⚠️ 다른 회사 계정 (296797354236), semugpt와 무관. 혼동 주의 |
회전 (IAM key):
1. aws --profile ob iam create-access-key --user-name <user>
2. application-prod.yml 평문 라인을 env var ref로 바꾸고 /etc/semugpt/backend.env에 새 키 주입
3. 기존 key AKIAQLD6ZXDVRTY2FHER deactivate → 72시간 모니터링 후 delete
4. Bootalk 다른 프로젝트(예: ../semugpt-backend의 SNS) 영향 확인
2.7 RDS MySQL¶
| 항목 | 값 |
|---|---|
| 엔드포인트 | tax-gpt.cl2zydns6yrm.ap-northeast-2.rds.amazonaws.com:3306 |
| 사용자 | admin |
| 비밀번호 | selim3400!! (legacy app yml + 새 application-prod.yml:14 평문 커밋) |
| 접근 | ⚠️ PubliclyAccessible=true + SG sg-09b20a06663fcfa01이 0.0.0.0/0:3306 개방 |
| 회전 | aws --profile ob rds modify-db-instance --db-instance-identifier tax-gpt --master-user-password <new> |
⚠️ 회전 시점에 legacy backend와 new backend의 env를 동시에 갱신해야 함. 그렇지 않으면 한쪽이 connection 실패. Hard cutover 후 회전 권장.
⚠️ RDS SG는 hard cutover 후 잠글 예정 (0.0.0.0/0 제거 + 사무실 IP + Lightsail VPC + default VPC만 허용). 현재는 보안 우려 상태.
Local MySQL은 별개 — docker-compose.dev.yml의 root/selim3400이며 dev box / 로컬 모두 동일.
2.8 Elasticsearch¶
| 환경 | 위치 | 값 |
|---|---|---|
| Local | application-local.yml:29-33 |
localhost:9200, elastic / uiti0701! |
| Production | application-prod.yml:31-35 |
3.39.210.101:9200, elastic / uiti0701! (현재 동일 password) |
uiti0701!의 ! 문자가 shell parsing을 깨므로 개발 환경 페이지의 "ES password 주의" 섹션 참조 (printf + base64 패턴 또는 %21 URL-encode).
2.9 Lightsail SSH¶
| 항목 | 값 |
|---|---|
| 키 파일 (개발자 Mac) | ~/.ssh/aws-semugpt |
| SSH alias | ssh semugpt-aws (dev) / ssh semugpt-prod (prod, Phase 3 후) |
| Lightsail key pair name | semugpt-aws (dev/prod 공용 — --key-pair-name semugpt-aws로 prod도 같은 키 사용) |
| 허용 SSH IP (dev) | 49.175.194.2/32 (사무실 only) |
| 허용 SSH IP (prod, 예정) | 동일 (49.175.194.2/32) |
⚠️ SSH key 자체는 git에 커밋되지 않음. 인수 시 별도 안전 채널로 전달 필요 — 접근 권한 목록 참조.
2.10 Slack incoming webhook (Lightsail)¶
| 항목 | 값 |
|---|---|
| 위치 | /etc/semugpt/slack-webhook.url on Lightsail dev (and prod, 예정) |
| 권한 | mode 0600, root-only |
| Git 커밋 | ❌ 절대 안 됨 — Lightsail box에만 존재 |
| Mention prefix | /etc/semugpt/slack-mention.txt (현재 <!here> @주우철) |
| 사용처 | /usr/local/bin/disk-alarm.sh + /usr/local/bin/health-monitor.sh |
| 회전 | Slack workspace → Apps → Incoming Webhooks → revoke + regenerate → echo "..." | sudo tee /etc/semugpt/slack-webhook.url |
2.11 OAuth (Naver / Kakao / Google)¶
| 항목 | 위치 |
|---|---|
| Naver | application-oauth.yml:9-10 (client-secret: FCEfIxQ4m1, client-id: AAIEDPGvImaOXuCCxGRx) |
| Kakao | application-oauth.yml:16-17 (client-id: 5a146993d53d52171a002a1d87bf40a2) |
application-oauth.yml:22-24 (client-secret: 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, client-id: 708060257306-...) |
⚠️ 현재 미사용 — auth 코드에 OAuth2 도메인은 있으나 활성 라우트가 없다. (auth 페이지 "관련 문서"의 "알려진 이슈" 참조) 회전 시 각 provider console에서 secret regenerate. 활성화 전 어차피 redirect URI를 prod URL로 갱신해야 하므로 hard cutover 시점에 함께.
2.12 Cloudflare Tunnel (dev only)¶
| 항목 | 값 |
|---|---|
| Tunnel ID | 078d8083-eba8-4df2-8026-ebafcb53666b (semugpt-backend) |
| Cert 위치 (Lightsail) | /etc/cloudflared/cert.pem |
| Config | /etc/cloudflared/config.yml (ingress 정의) |
| 회전 | Cloudflare dashboard → Zero Trust → Tunnels → 새 tunnel 생성 + cert 교체 + systemctl reload cloudflared |
⚠️ prod에는 cloudflared tunnel 사용 불가 (Route 53이라 zone-on-Cloudflare 요건 미충족). prod는 ALB로 직접 받음.
3. 회전 우선순위¶
Hard cutover 시점에 반드시 회전해야 할 시크릿 (Git history에 평문 노출됨):
| Priority | 시크릿 | 영향 |
|---|---|---|
| P0 | RDS master password | 전 사용자 데이터 |
| P0 | OpenAI token | 토큰 도용 시 비용 발생 + RAG 중단 |
| P0 | JWT secret | 도용 시 임의 사용자 위장 |
| P1 | NCP SENS keys | 도용 시 임의 SMS 발송 |
| P1 | AWS IAM access key AKIAQLD6ZXDVRTY2FHER |
도용 시 AWS 리소스 접근 (semugpt-backend SNS 등) |
| P1 | Langfuse keys | 도용 시 prompt 변조 가능 |
| P2 | ES password | 외부 노출은 없지만 dev/prod 동일 |
| P2 | OAuth secrets | 활성 라우트 없으므로 즉시 회전 의무 약함 |
4. 환경변수 override 메커니즘¶
application-prod.yml의 평문 값은 Spring @Value binding 덕에 환경변수로 override 가능하다 (verified, spec Section 10). 즉:
# /etc/semugpt/backend.env
SPRING_DATASOURCE_PASSWORD=<new password>
OPENAI_TOKEN=<new token>
JWT_SECRET=<new secret>
LANGFUSE_PUBLIC_KEY=<...>
LANGFUSE_SECRET_KEY=<...>
TOSS_CLIENT_KEY=<live>
TOSS_SECRET_KEY=<live>
TOSS_BILLING_CLIENT_KEY=<live>
TOSS_BILLING_SECRET_KEY=<live>
CORS_ALLOWED_ORIGINS=https://semugpt.co.kr,https://www.semugpt.co.kr,https://new.semugpt.co.kr
systemd unit이 EnvironmentFile=/etc/semugpt/backend.env 지시문으로 로드 → JVM 시작 시 주입 → Spring이 yml hardcoded 값을 override.
⚠️ Frontend는 빌드 타임에 NEXT_PUBLIC_* env가 번들에 박힌다 — .env.local 변경 시 빌드 재실행 필요.
5. 시크릿 추가 시 안전 패턴¶
새 시크릿(예: 새 외부 SaaS) 추가 시:
application-prod.yml에는 env var ref만 쓴다:myservice.api-key: "${MYSERVICE_API_KEY}"application-local.yml에는 dev/test 키 또는 placeholder. 절대 prod 값을 commit 하지 말 것/etc/semugpt/backend.env에 실제 값 주입 (Lightsail prod) — 권한 0600, root 소유- 신규 시크릿은 접근 권한 목록에 등록 — 인수 시점에 누락 방지
알려진 이슈 / 개선 예정¶
- Git history에 평문 시크릿 다수 — 회전 외에는 해결 불가 (히스토리 rewrite는 협업 흐름 깨짐). user-accepted (
feedback_accepted_repo_secrets.md) - secret manager 부재 — AWS Secrets Manager / SSM Parameter Store 미사용. 작은 팀에 과한 인프라라 도입 보류
application-prod.yml의 IAM access key 평문 — 회전 + env var ref 전환 필요 (TODO)- Toss live keys 미수령 — 클라이언트(김창진/김진우) 측 manual 작업
application-oauth.yml의 OAuth secret 평문 — 활성 라우트 없어 즉시 위험은 낮음. 활성화 시 회전 필수- Backend Sentry DSN 부재 — 시크릿이라기보다 모니터링 갭. 모니터링 페이지 참조
- 인증서 만료 모니터링 부재 — ACM cert는 자동 갱신이지만 만료(2026-08-13) 임박 시 알림 부재. CloudWatch alarm 또는 외부 cron 도입 검토 필요