Skip to content

docs: .env.example 런타임 시크릿 보완과 PEM 형식·기록 정정 - #108

Merged
cherry-go-round merged 2 commits into
devfrom
docs/env-example-runtime-secrets
Jul 30, 2026
Merged

docs: .env.example 런타임 시크릿 보완과 PEM 형식·기록 정정#108
cherry-go-round merged 2 commits into
devfrom
docs/env-example-runtime-secrets

Conversation

@cherry-go-round

Copy link
Copy Markdown
Contributor

요약

.env.exampleJWT_PRIVATE_KEYPINLOG_AI_INTERNAL_SECRET을 추가한다. 그 과정에서 authentication.md가 안내하던 .env 값 형식이 실제로 동작하지 않는 것을 발견해 함께 고쳤다. BI-26의 Secret 개수 오기도 정정한다.

Jira (필수)

  • 없다. 운영 Secret 주입 작업(#105)을 준비하며 발견한 문서·설정 예시 정정이라 티켓을 만들지 않았다. 선례는 BI-19(back#58BI-01(back#12)처럼 이슈만 참조한 기록들이다

관련 GitHub Issue (선택)

변경 사항

  • .env.exampleJWT_PRIVATE_KEY·PINLOG_AI_INTERNAL_SECRET 추가(주석 상태). 없을 때의 동작과 값 형식을 함께 적었다
  • docs/development/authentication.md — 8장의 .env 예시에서 따옴표 제거 + 이유 서술. 운영 Secret은 반대로 실제 개행을 넣는다는 것도 명시
  • docs/backend/implements/BI-26-...md — Secret 개수를 6개9개로 정정하고 "정정" 절 추가

테스트 / 검증

  • ./gradlew clean check --no-daemon394개 통과, 실패 0, 오류 0
  • API 계약 변경 시 관련 문서 갱신 (해당 없음 — 계약 변경 없이 문서만)

소스 변경이 없으므로 RED/GREEN 사이클은 없다. 대신 실측 두 건으로 검증했다.

.env 값 형식 — 따옴표가 깨진다

Properties.loadJwtKeyProvider.fromPem의 정리 과정(fromPem, JwtKeyProvider.java:62-66)을 그대로 적용해 두 형태를 비교했다.

QUOTED: firstChar=0x22  →  decode FAIL: Illegal base64 character 22
BARE:   firstChar=0x2d  →  decode OK, bytes=3

.envspring.config.import: optional:file:.env[.properties]properties 형식으로 파싱되므로 따옴표가 구분자가 아니라 값의 일부가 된다. fromPem이 지우는 것은 공백뿐이라 그 문자가 살아남아 base64 디코딩이 실패한다. 같은 파서가 \n은 실제 개행으로 되돌리므로 개행 표기 자체는 맞았고 따옴표만 문제였다.

② 로그인 3사 E2E — 이메일 필수화 이후에도 정상

#103(이메일 NOT NULL) 머지분 위에서 실제 브라우저로 3사 로그인을 완료했다.

확인 결과
V6 마이그레이션 로컬 DB에 out-of-order 적용 성공, email is_nullable = NO
실패 리다이렉트(OAUTH_FAILED) 없음 — 실패 핸들러 로그 0건
member · social_account 3명 / Kakao·Naver·Google 각 1건, 전부 활성
이메일 저장 3건 모두 저장됨(@kakao.com·@naver.com·@gmail.com)
Redis Refresh 회원 3명 각각 키 + 인덱스 존재

scope도 함께 확인했다 — Google openid email · Kakao account_email · Naver email. 이메일 필수화의 전제(공급자가 실제로 이메일을 준다)가 실측으로 성립한다.

배경

application.yml이 읽는 환경변수 13개 중 .env.example에는 6개만 있었다. 없으면 운영이 조용히 망가지는 JWT_PRIVATE_KEY가 목록에 없는 것이 가장 큰 구멍이었다.

넣은 것과 넣지 않은 것을 갈랐다.

변수 판단
JWT_PRIVATE_KEY 넣음 — 운영 필수, 로컬 선택. 없으면 재시작마다 재로그인
PINLOG_AI_INTERNAL_SECRET 넣음 — 없으면 401을 삼켜 임베딩이 조용히 안 생긴다
PINLOG_AI_BASE_URL · AUTH_CLIENT_REDIRECT_URI 넣지 않음 — 로컬 기본값이 정답
PINLOG_AI_EMBEDDING_PROFILE 일부러 넣지 않음 — 비밀이 아니라 교체가 PR·리뷰를 거치게 파일에 리터럴로 둔 값이다(BD-39). 예시에 올리면 그 결정이 흐려진다
DB_PASSWORD · BUILD_SHA 넣지 않음 — 운영·CI 전용

이 판단의 근거가 기동 로그에서 그대로 확인됐다. 로컬 기동 시 뜨는 경고 두 개가 추가한 두 키와 정확히 일치한다.

WARN JWT 서명 키가 주입되지 않아 임시 키쌍을 생성한다. 재시작하면 기존 토큰이 모두 무효가 된다.
WARN pinlog.ai.internal-secret이 비어 있다. AI 호출은 401로 거절되며 실패는 삼켜진다 …

리뷰 포인트

  1. 두 값이 소셜 로그인 자격증명과 반대라는 것을 예시에 적었다. KAKAO_CLIENT_ID=는 빈 값이면 기동이 실패하지만(BT-05), 이 둘은 기본값이 빈 문자열이라 "정의했지만 비었다"와 "정의하지 않았다"가 같은 결과로 수렴한다. 기존 경고문이 "빈 값으로 두면 안 된다"로 뭉뚱그려져 있어 반대 사례를 명시하지 않으면 오해를 낳는다.

  2. BI-26은 보존 구역이라 숫자만 바꾸지 않았다. "정정" 절을 추가해 무엇이 틀렸고 근거가 무엇인지 남겼다(implements/README"잘못 작성된 문서도 정정으로 처리"). 세 숫자가 각각 다른 것을 세므로 함께 적었다 — 안 적으면 다음 사람이 79를 또 모순으로 읽는다.

    세는 대상
    9 워크플로가 action에 넘기는 env 전체
    8 그중 런타임 키(bridge token 제외)
    7 봉인 정책 infra/policy/sealedsecrets/back-prod.yaml이 요구하는 키
  3. BI-26은 내 기록이 아니다(chore(S15P11A705-154): add runtime secret handoff workflow #100, @tpals0409). 정정 내용에 이견이 있으면 알려 주면 좋겠다 — 코드는 문제가 없고 서술만 틀린 건이다.

미결 / 후속

  • E2E 중 동시 가입 재시도 경로가 실제로 발동했다. Google 로그인에서 ux_social_account_provider_user 중복이 잡히고 social account created concurrently, retrying once 후 정상 가입했다(26ms 차). 부분 유니크가 설계대로 막은 것이고 결과에 중복 행은 없다. 브라우저가 콜백을 두 번 보냈을 가능성이 유력하지만 원인을 특정하지 못했다 — 이 PR 범위 밖이고, 재현된 것이 처음이라 기록만 남긴다
  • .env.example에 검증 테스트가 없다. application.yml이 읽는 변수와 예시 파일이 어긋나는 것을 사람이 대조해야 발견한다 — 이번이 그 사례다. 계약 테스트로 고정할 만하지만 별건이다

cherry-go-round and others added 2 commits July 30, 2026 16:25
"런타임 5개 + bridge 1개 = 6개"로 적혀 있었으나 실제는 "런타임 8개 + bridge 1개
= 9개"다. 서술만 틀렸고 워크플로와 계약 테스트는 처음부터 9개를 고정하고 있었다 —
RuntimeSecretWorkflowContractTests의 containsExactlyElementsOf가 9개를 그대로
열거하므로, 기록과 통과하는 테스트가 서로 어긋난 상태였다.

보존 구역이라 숫자만 바꾸지 않고 "정정" 절을 추가해 무엇이 틀렸는지 남겼다
(implements/README "잘못 작성된 문서도 정정으로 처리").

세 숫자가 각각 다른 것을 세므로 함께 적었다. 안 적으면 다음 사람이 7과 9를 또
모순으로 읽는다.

- 9 — 워크플로가 action에 넘기는 env 전체
- 8 — 그중 런타임 키(bridge token 제외)
- 7 — 봉인 정책 infra/policy/sealedsecrets/back-prod.yaml이 요구하는 키
      (PINLOG_AI_INTERNAL_SECRET과 bridge token 제외, back#105)

RuntimeSecretWorkflowContractTests 통과 확인

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
application.yml이 읽는 환경변수 13개 중 .env.example에는 6개만 있었다. 없으면 운영이
조용히 망가지는 JWT_PRIVATE_KEY가 목록에 없는 것이 가장 큰 구멍이었다.

넣은 것과 넣지 않은 것을 갈랐다.

- JWT_PRIVATE_KEY — 운영 필수, 로컬 선택. 없으면 재시작마다 재로그인이다
- PINLOG_AI_INTERNAL_SECRET — 없으면 401을 삼켜 임베딩이 조용히 안 생긴다
- PINLOG_AI_BASE_URL·AUTH_CLIENT_REDIRECT_URI — 로컬 기본값이 정답이라 넣지 않았다
- PINLOG_AI_EMBEDDING_PROFILE — 비밀이 아니라 일부러 파일에 리터럴로 둔 값이다(BD-39).
  예시에 올리면 "교체가 PR·리뷰를 거치게 한다"는 결정이 흐려진다
- DB_PASSWORD·BUILD_SHA — 운영·CI 전용

authentication.md 8장이 권하던 형식이 실제로 깨진다. 검증하다 발견했다.

	JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...-----END PRIVATE KEY-----"

.env는 spring.config.import로 properties 형식으로 파싱되므로 따옴표가 구분자가 아니라
값의 일부가 된다. fromPem이 지우는 것은 공백뿐이라 그 문자가 살아남아
Illegal base64 character 22(0x22 = ")로 디코딩이 실패한다. 같은 파서가 \n은 실제 개행으로
되돌리므로 개행 표기 자체는 맞았다 — 따옴표만 문제다.

실측으로 확인했다. 같은 값을 따옴표 있는 형태와 없는 형태로 .env에 넣고 Properties.load
후 fromPem의 정리 과정을 그대로 적용했다.

	QUOTED: firstChar=0x22 → decode FAIL: Illegal base64 character 22
	BARE:   firstChar=0x2d → decode OK

두 곳(.env.example, authentication.md)을 바로잡고 운영 Secret은 반대로 실제 개행을
넣는다는 것도 함께 적었다 — 두 경로가 다르다는 것이 혼동의 원인이다.

코드는 문제가 없다. JwtKeyProviderTest.readsInjectedPemAndDerivesPublicKey가 실제 개행이
든 PEM을 이미 고정하고 있어 받아들이는 형식은 검증돼 있었고, 틀린 것은 적어 둔 예시뿐이다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cherry-go-round
cherry-go-round merged commit 5bcbf37 into dev Jul 30, 2026
2 checks passed
@cherry-go-round
cherry-go-round deleted the docs/env-example-runtime-secrets branch July 30, 2026 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant