--- kind: CASE slug: a06-f020-tls-stable title: 프로파일이 선언한 여섯 값이 드라이버에 하나도 도달하지 않는다 topic: security-and-trust-boundaries project: clean-architecture-backend-template status: 게시 전 sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 rootTreeNode: case:a06-f020-tls-stable evidenceCapturedOn: 2026-09-02 body: case-a06-f020-tls-stable.body.md assets: - key: a06-f020-tls-stable file: ../../../final/evidence/rendered/a06-f020-tls-stable.svg - key: a06-f020-tls-stable-probe file: ../../../final/evidence/rendered/a06-f020-tls-stable-probe.svg evidence: - ../../../final/evidence/raw/a06-f020-tls-stable.txt - ../../../final/evidence/raw/a06-f020-tls-stable-probe.txt source: - 원본 분석 절은 analysis/06-adapter-outbound-persistence-mongo.md#L1161 이다. 등급은 P1 이다. 팩토리의 javadoc, 호출자가 없다는 계수, 자격증명 해석기의 미도달, 실제 클라이언트를 Boot 가 만든다는 사실, 탐침이 잰 값들, 이미 고쳐진 형제, 그리고 기존 시험 둘이 이 경계를 보지 못한다는 판정이 그 절에 있다. - 팩토리가 만들었을 설정의 여섯 값, 커스터마이저를 적용한 뒤에도 실제값이 그대로라는 것, ssl 속성이 두 번째 경로라는 것, README 의 속성 키가 이 Boot 버전에서 바인딩되지 않는다는 것, 그리고 프로파일을 선언하려면 토폴로지 프로브가 함께 있어야 한다는 것은 이 기록에서 확인했다. --- # 프로파일이 선언한 여섯 값이 드라이버에 하나도 도달하지 않는다 프로파일의 값을 드라이버 설정으로 옮기는 클래스가 있고, 저장소 어디에도 그것을 부르는 코드가 없다. Boot 가 클라이언트를 만들 때 쓰는 설정은 TLS 가 꺼져 있고 타임아웃과 풀과 서버 API 와 UUID 표현이 전부 드라이버 기본값이다. ## 관계 - **검증기가 운영에 TLS를 요구하고, 실제로 조립되는 생산자에는 그 설정이 없다** 다른 리프에서 같은 형태가 난 사례다. - **@Bean이 있다는 것은 조립 증거가 아니다** 수리 코드가 배선되지 않은 형태다. - **조립 결함을 판정하려면 조립하는 쪽을 먼저 읽어야 한다** 이 사례를 찾는 방법이다. ## 문제 값을 옮기는 클래스는 존재하고, 자기 javadoc 에 왜 만들어졌는지 적어 두었다. 프로파일과 자격증명 해석기와 TLS 와 Stable API 플래그와 풀과 타임아웃 정책이 전부 있었고 전부 단위 시험돼 있었는데 그중 어느 것도 설정에 도달하지 않았다는 것이다. 같은 형태가 한 단계 뒤에서 반복된다. 그 클래스는 작성됐고, 그것을 부르는 배선은 없다. ## 결론 참조가 자기 파일과 자기 시험 밖으로 나가지 않는다. 저장소 전체에서 남는 언급 넷은 안내 문서, 옛 리뷰, 생성된 API 표면 기준선이며 호출은 하나도 없다. 유일하게 그 클래스만 쓰는 자격증명 해석기도 같다. Boot 가 클라이언트를 만들 때 쓰는 설정을 재었다. 기본 빈에 등록된 커스터마이저 둘을 적용한 결과가 그 설정이다. 프로덕션 프로파일은 TLS 요구, 엄격한 서버 API 고정, 연결 2초, 서버 선택 3초, 풀 상한 40, 고정 UUID 표현을 말한다. 실제 설정은 TLS 꺼짐, 연결 10초, 서버 선택 30초, 풀 상한 100, 서버 API 없음, UUID 표현 미지정이다. 여섯이 전부 다르다. 같은 프로파일로 팩토리를 부르면 여섯이 전부 선언대로 나온다. 첫 값만 성질이 다르다. 타임아웃과 풀 상한이 성능 문제라면 TLS 는 노출 문제다. TLS 를 켜는 경로는 둘이다. 배포가 URI 에 그 옵션을 넣거나, spring.mongodb.ssl 속성을 켜는 것이다. 어느 쪽이든 프로파일이 선언한 값과는 무관하게 배포가 한 번 더 말해야 한다. 이 선언이 실제로 검증되는 배포는 아직 없다. 이 모듈 아래 출하 설정이 여는 키는 둘 — 활성화 여부와 활성 프로파일 이름이다. 프로파일 본체를 채워 주는 자리는 없다. 넣으려면 포크가 토폴로지 프로브 빈까지 함께 공급해야 하고, 넣지 않으면 기동이 그 사실을 말하며 거부한다. 지금 도는 배포에서 터지는 결함이 아니다. 프로파일이 선언되는 순간부터 성립한다. 이 경계를 보는 시험은 없다. 팩토리 시험은 팩토리를 직접 만들어 그 출력이 프로파일대로인지 확인한다. 그 시험의 javadoc 은 타이핑된 프로파일이 드라이버가 실제로 만들어지는 설정에 도달한다고 적는데, 확인하는 것은 팩토리의 출력이고 그 팩토리는 아무도 부르지 않는다. TLS 레인 시험은 ssl.enabled(true) 를 손수 붙인 클라이언트로 서버가 TLS 를 강제하는지 확인한다. 수리 수단은 이 리프에 이미 있다. 관측 쪽에도 설정 빌더 적용 지점에 호출자가 없던 시기가 있었다. 관측 쪽은 자동 구성이 Boot 커스터마이저를 빈으로 올려 해결했다. 프로덕션에서 그 수단이 등록되는 자리는 하나다. ## 검증 환경 OpenJDK : 21.0.12 Spring Boot : 4.0.8 확인 방식 : 참조 전수 계수, 실제 자동 구성을 올린 컨텍스트에서 커스터마이저 적용 후 설정 값 비교 소스 수정 : x ## 재현 조건 1. 클라이언트 설정 팩토리의 javadoc 을 읽는다. 2. 그 타입과 자격증명 해석기를 언급하는 곳을 저장소 전체에서 센다. 3. 프로파일 검증이 production 에 TLS 를 요구하는 자리와, 그 검증이 켜지려면 무엇이 있어야 하는지를 읽는다. 4. 출하 설정이 이 모듈 아래 노출하는 키와 프로파일을 넣는 곳의 수를 센다. 5. 이 리프의 자동 구성과 Boot 의 Mongo 자동 구성을 올린 컨텍스트를 띄운다. 6. 기본 설정 빈에 등록된 커스터마이저를 전부 적용하고 값을 읽는다. 7. URI 를 건드리지 않고 spring.mongodb.ssl.enabled 만 켜서 같은 값을 읽는다. 8. 같은 프로파일로 팩토리를 불러 만들어졌을 설정의 값을 읽는다. 9. 이 경계를 보는 시험이 있는지 확인한다. ## 본문 값을 옮기는 클래스는 있다. 자기 javadoc 에 왜 만들어졌는지도 적혀 있다. ## 설정을 만드는 팩토리 :::evidence key="a06-f020-tls-stable" alt="클라이언트 설정 팩토리의 javadoc, 저장소 전체에서 그 타입과 자격증명 해석기를 언급하는 곳 전수, 프로파일 검증이 production 에 TLS 를 요구하는 자리, 그 검증이 켜지려면 토폴로지 프로브가 필요하다는 기동 가드, 출하 설정이 이 모듈 아래 노출하는 키와 프로파일을 넣는 곳의 수, 이 리프가 관측 쪽에 쓴 Boot 빌더 커스터마이저 등록과 그 수단의 프로덕션 등록 수, 그리고 팩토리 시험의 javadoc 과 TLS 레인 시험이 쓰는 손수 만든 클라이언트를 출력한 터미널 기록." caption="팩토리와 해석기를 언급하는 곳은 문서 넷뿐이고 호출은 없음 · 검증은 프로파일이 선언됐을 때만 켜지고 토폴로지 프로브가 없으면 기동이 거부 · 출하 키는 enabled 와 active-profile 둘, 프로파일을 넣는 곳 0 · 빌더 커스터마이저의 프로덕션 등록은 1 · 두 시험 다 팩토리 출력이나 손수 만든 설정을 본다 — 69줄 · exit 0" zoom="true" ::: ```java *
The profile, the credential resolver, the TLS and Stable-API flags and the pool and timeout * policy all existed and were all unit-tested. None of them reached a {@link MongoClientSettings}: * the values were checked as intermediate objects and whatever the driver ended up configured with * was decided elsewhere, by defaults nobody had chosen. A policy that nothing applies reads exactly * like a policy that is applied — the tests pass, the record is populated, and the client connects * with a three-second timeout it inherited from the driver rather than the two the profile states. ``` 이 클래스를 참조하는 곳은 자기 파일과 자기 시험뿐이다. 저장소 전체를 훑으면 안내 문서와 옛 리뷰 문서와 생성된 API 표면 기준선의 언급 넷이 남고, 어느 것도 호출이 아니다. ## 선언값과 실제값 :::evidence key="a06-f020-tls-stable-probe" alt="같은 URI 를 두 속성 키로 각각 넣어 얻은 접속 호스트, 프로덕션 프로파일이 선언하는 TLS 요구와 서버 API 고정과 두 타임아웃과 풀 상한과 UUID 표현, 그 선언을 설정 검증이 통과시키는 결과, 이 리프와 Boot 의 자동 구성을 올린 컨텍스트에서 커스터마이저를 전부 적용한 뒤 읽은 같은 값들, URI 를 건드리지 않고 ssl 속성만 켰을 때의 결과, 그리고 같은 프로파일로 팩토리를 불러 만들어졌을 설정의 같은 값들을 출력한 터미널 기록." caption="README 가 보여 주는 키는 호스트를 바꾸지 못하고 다른 키만 바꾼다 · 선언은 TLS 요구·엄격 서버 API·2초·3초·40·STANDARD · 커스터마이저 둘을 적용한 실제 설정은 TLS 꺼짐·10초·30초·100·없음·UNSPECIFIED · ssl 속성만 켜도 TLS 는 켜짐 · 팩토리가 만들었을 설정은 여섯이 전부 선언대로 — 21줄 · exit 0" zoom="true" ::: 이 리프의 자동 구성과 Boot 의 Mongo 자동 구성을 올리고, 기본 설정 빈에 등록된 커스터마이저를 Boot 가 하는 대로 전부 적용한 뒤 값을 읽었다. ```text [선언] production 프로파일이 말하는 값 tlsRequired=true stableApiStrict=true connectTimeout=2000ms serverSelection=3000ms poolMax=40 uuid=STANDARD 이 선언을 설정 검증이 통과시킨다 [실제] Boot 가 클라이언트를 만들 때 쓰는 설정 (커스터마이저 적용 후) 커스터마이저 2개 적용 sslEnabled=false connectTimeoutMs=10000 serverSelectionTimeoutMs=30000 poolMaxSize=100 serverApi=null uuidRepresentation=UNSPECIFIED [미도달] 팩토리가 만들었을 설정 sslEnabled=true connectTimeoutMs=2000 serverSelectionTimeoutMs=3000 poolMaxSize=40 serverApi=ServerApi{version=V1, deprecationErrors=true, strict=true} uuidRepresentation=STANDARD ``` 여섯이 전부 다르다. 관측 커스터마이저가 등록된 상태에서도 같다 — 그 커스터마이저는 리스너만 붙이고 나머지에 손대지 않는다. `serverApi=null` 은 엄격한 서버 API 고정이 없다는 뜻이고, `uuidRepresentation=UNSPECIFIED` 는 팩토리가 저장된 문서 아래서 값이 움직이는 것을 아무도 쓰지 않은 마이그레이션이라 부르며 고정하려던 값이다. 첫 값은 나머지와 성질이 다르다. 타임아웃과 풀 상한은 성능이고, TLS 는 노출이다. ## TLS 를 켜는 경로는 프로파일이 아니다 ```text spring.mongodb.ssl.enabled=true -> sslEnabled=true ``` URI 를 건드리지 않고 속성만 켜도 TLS 가 켜진다. URI 에 옵션을 직접 넣는 경로도 있다. 어느 쪽이든 배포가 프로파일과 별개로 한 번 더 말해야 한다. 덧붙여, 모듈 README 가 보여 주는 속성 키는 이 Boot 버전에서 바인딩되지 않는다. 같은 URI 를 두 키로 넣어 접속 호스트를 읽으면 갈린다. ```text spring.mongodb.uri -> [canary-host:31337] spring.data.mongodb.uri -> [localhost:27017] ``` README 의 키를 그대로 넣은 배포는 기본 호스트로 뜬다. ## 이 선언이 검증되는 배포는 아직 없다 프로파일 검증은 프로덕션 프로파일이 TLS 를 요구하지 않으면 거부하고, 그 메시지가 같은 결함의 이전 판을 적고 있다. ```java "a production MongoDB profile requires TLS; the flag existed and nothing checked it, so a" + " deployment could carry tls-required=false and still be called production" ``` 다만 그 검증은 프로파일이 선언됐을 때만 켜진다. 출하 설정이 이 모듈 아래 노출하는 키는 활성화 여부와 활성 프로파일 이름 둘뿐이고, 프로파일 자체를 넣는 곳은 저장소에 없다. 넣으려면 토폴로지 프로브 빈이 함께 있어야 한다. ```java if (probe.getIfAvailable() == null) { throw new IllegalStateException( "the Mongo platform has configured profiles but no MongoTopologyProbe bean, so the " + "startup validator has nothing to ask about the server: … ``` 그래서 이것은 지금 도는 배포의 결함이 아니라 조립하는 쪽이 프로파일을 선언하는 순간 성립하는 결함이다. ## 이 경계를 보는 시험이 없다 ```java /** * The typed profile reaches the settings the driver is actually built from (MNG-INT-002). ``` 팩토리 시험의 javadoc 이다. 확인하는 것은 팩토리의 출력이고, 그 팩토리는 아무도 부르지 않는다. TLS 레인 시험은 반대쪽에서 같은 자리를 비껴간다. ```java MongoClientSettings.builder() .applyConnectionString(new ConnectionString(connectionString)) .applyToSslSettings(ssl -> ssl.enabled(true).context(trustOnly(authorityPem))) ``` 손수 붙인 설정으로 서버가 TLS 를 강제하는지 확인한다. 어느 쪽도 프로파일의 선언이 실제 연결을 TLS 로 만드는지 묻지 않는다. ## 이 리프가 이미 쓰는 배선 수단 ```java /** The customizer Boot applies when it builds the client. */ @Bean @ConditionalOnBean(MeterRegistry.class) @ConditionalOnMissingBean(name = "mongoDriverObservabilityCustomizer") public MongoClientSettingsBuilderCustomizer mongoDriverObservabilityCustomizer( ``` 관측 쪽도 설정 빌더에 적용하는 자리에 호출자가 없는 같은 형태였고, 자동 구성이 Boot 의 빌더 커스터마이저를 등록해 고쳤다. 프로덕션에서 그 수단을 등록하는 곳은 이 한 자리뿐이다. 설정 쪽에는 쓰이지 않았다. ## 확인하지 못한 것 실제 서버에 평문으로 연결되는 것을 관측하지 않았다. 확인한 것은 Boot 가 클라이언트를 만들 때 넘기는 설정 객체의 값이다.