운영 서명 키를 실제로 넣는 작업이다. #101이 문제를 기록했고, #100이 머지되며 전달 수단이 생겼다 — 그래서 #101 본문의 절차(인프라가 수동으로 SealedSecret을 만든다)는 지금 기준으로 낡았다. 이 이슈는 새 수단으로 실행하는 부분만 다룬다.
지금 상태 — 확인한 것
.github/workflows/seal-runtime-secrets.yml(#100, BI-26)이 pinlog-secrets-prod Environment의 시크릿을 읽어 Infra SealedSecret Draft PR을 만든다. 그 Environment에 9개 중 7개가 이미 등록되어 있다.
| 시크릿 |
등록 |
담당 |
GOOGLE_CLIENT_ID · GOOGLE_CLIENT_SECRET |
✅ |
— |
KAKAO_CLIENT_ID · KAKAO_CLIENT_SECRET |
✅ |
— |
NAVER_CLIENT_ID · NAVER_CLIENT_SECRET |
✅ |
— |
PINLOG_AI_INTERNAL_SECRET |
✅ |
값이 AI 쪽과 같아야 한다(#102) |
JWT_PRIVATE_KEY |
❌ |
이 이슈 |
PINLOG_INFRA_SECRET_PR_TOKEN |
❌ |
인프라 — 직접 발급·등록 예정(#102 코멘트) |
즉 workflow는 이 두 개가 채워지기 전까지 실행할 수 없다. 하나는 이 이슈, 하나는 인프라 소관이다.
왜 지금 해야 하는가
키가 없으면 JwtKeyProvider가 기동할 때 임시 키쌍을 만든다. 운영에서는 배포·재시작마다 전체 사용자 로그아웃이고, replicaCount가 2 이상이면 간헐적 401이 상시 발생한다(#101의 표). 재현이 안 되는 로그인 실패로 나타나서 원인을 찾기 어려운 종류의 고장이다.
이 작업은 prod 프로파일 활성화를 기다리지 않는다. private-key: ${JWT_PRIVATE_KEY:}가 application.yml의 base 설정이라 프로파일이 꺼진 지금도 주입한 값이 그대로 읽힌다. 프로파일 활성화(=fail-fast 복원)는 #101·#102의 순서 문제로 남고, 실제 사용자 피해는 이 이슈만으로 사라진다.
할 일
주의 두 가지
PEM 개행. JwtKeyProvider.fromPem이 파싱 전에 replaceAll("\s", "")로 공백·개행을 모두 지운다. 그래서 실제 개행이 들어간 PEM 원문을 그대로 넣으면 된다. 반대로 \n 두 글자 문자열로 치환해 넣으면 역슬래시가 남아 base64 디코딩이 깨진다 — 로컬 .env에 \n으로 적는 방식(properties 파서가 실제 개행으로 되돌린다)과 다르므로 혼동하지 않는다.
키 회전은 구현되어 있지 않다. kid 헤더만 심어 뒀고 여러 키를 동시에 검증하지 않는다. 한번 정한 키를 바꾸면 그 시점에 전체 로그아웃이 되고, 키를 잃어버리는 경우도 같다. 등록한 값을 안전하게 보관해야 한다(BD-31).
완료 조건
참고
운영 서명 키를 실제로 넣는 작업이다. #101이 문제를 기록했고, #100이 머지되며 전달 수단이 생겼다 — 그래서 #101 본문의 절차(인프라가 수동으로 SealedSecret을 만든다)는 지금 기준으로 낡았다. 이 이슈는 새 수단으로 실행하는 부분만 다룬다.
지금 상태 — 확인한 것
.github/workflows/seal-runtime-secrets.yml(#100, BI-26)이pinlog-secrets-prodEnvironment의 시크릿을 읽어 Infra SealedSecret Draft PR을 만든다. 그 Environment에 9개 중 7개가 이미 등록되어 있다.GOOGLE_CLIENT_ID·GOOGLE_CLIENT_SECRETKAKAO_CLIENT_ID·KAKAO_CLIENT_SECRETNAVER_CLIENT_ID·NAVER_CLIENT_SECRETPINLOG_AI_INTERNAL_SECRETJWT_PRIVATE_KEYPINLOG_INFRA_SECRET_PR_TOKEN즉 workflow는 이 두 개가 채워지기 전까지 실행할 수 없다. 하나는 이 이슈, 하나는 인프라 소관이다.
왜 지금 해야 하는가
키가 없으면
JwtKeyProvider가 기동할 때 임시 키쌍을 만든다. 운영에서는 배포·재시작마다 전체 사용자 로그아웃이고,replicaCount가 2 이상이면 간헐적 401이 상시 발생한다(#101의 표). 재현이 안 되는 로그인 실패로 나타나서 원인을 찾기 어려운 종류의 고장이다.이 작업은
prod프로파일 활성화를 기다리지 않는다.private-key: ${JWT_PRIVATE_KEY:}가application.yml의 base 설정이라 프로파일이 꺼진 지금도 주입한 값이 그대로 읽힌다. 프로파일 활성화(=fail-fast 복원)는 #101·#102의 순서 문제로 남고, 실제 사용자 피해는 이 이슈만으로 사라진다.할 일
RSA 2048 PKCS#8 PEM 키를 만든다. 저장소에 커밋하지 않는다(public 저장소다)
Team-PinLog/back→ Settings → Environments →pinlog-secrets-prod에JWT_PRIVATE_KEY로 등록한다. PEM 전문을 개행 그대로 붙여 넣는다인프라가
PINLOG_INFRA_SECRET_PR_TOKEN을 등록했는지 확인한다 (없으면 workflow가 PR을 만들 수 없다)Seal backend runtime secretsworkflow를 수동 실행(workflow_dispatch)한다생성된 Infra Draft PR을 인프라와 함께 검토·병합한다
로컬 개발용으로도 필요하면
.env에 넣는 방법을configuration.md·BT-05 기준으로 안내한다(선택)주의 두 가지
PEM 개행.
JwtKeyProvider.fromPem이 파싱 전에replaceAll("\s", "")로 공백·개행을 모두 지운다. 그래서 실제 개행이 들어간 PEM 원문을 그대로 넣으면 된다. 반대로\n두 글자 문자열로 치환해 넣으면 역슬래시가 남아 base64 디코딩이 깨진다 — 로컬.env에\n으로 적는 방식(properties 파서가 실제 개행으로 되돌린다)과 다르므로 혼동하지 않는다.키 회전은 구현되어 있지 않다.
kid헤더만 심어 뒀고 여러 키를 동시에 검증하지 않는다. 한번 정한 키를 바꾸면 그 시점에 전체 로그아웃이 되고, 키를 잃어버리는 경우도 같다. 등록한 값을 안전하게 보관해야 한다(BD-31).완료 조건
pinlog-secrets-prod에JWT_PRIVATE_KEY가 등록된다Seal backend runtime secretsworkflow가 성공하고 Infra SealedSecret PR이 생성된다JWT_PRIVATE_KEY가 주입된다참고
SPRING_PROFILES_ACTIVE=prod는 여기 범위가 아니다PINLOG_AI_INTERNAL_SECRET·PINLOG_AI_BASE_URL. 프로파일 활성화의 선행 조건이며 시연 범위 결정을 기다리는 중이다docs/development/configuration.md—JWT_PRIVATE_KEY는 운영 필수