The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
15 lines
7.3 KiB
JSON
15 lines
7.3 KiB
JSON
{
|
|
"assetKey": "a06-f020-tls-stable-probe",
|
|
"kind": "terminal",
|
|
"command": "set -e\nD=$(mktemp -d)\ntrap 'rm -rf \"$D\"' EXIT\n\nprintf %s '<configuration><root level=\"OFF\"/></configuration>' > \"$D/logback-off.xml\"\n\ncat > \"$D/TlsReachProbe.java\" <<'JAVA'\nimport com.mongodb.MongoClientSettings;\nimport dev.caskeleton.adapter.outbound.mongo.autoconfigure.MongoPlatformSettings;\nimport dev.caskeleton.adapter.outbound.mongo.autoconfigure.MongoProfileProperties;\nimport dev.caskeleton.adapter.outbound.mongo.client.MongoClientSettingsFactory;\nimport dev.caskeleton.adapter.outbound.mongo.security.MongoCredentialReference;\nimport dev.caskeleton.adapter.outbound.mongo.security.MongoCredentialResolver;\nimport io.micrometer.core.instrument.MeterRegistry;\nimport io.micrometer.core.instrument.simple.SimpleMeterRegistry;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration;\nimport org.springframework.boot.mongodb.autoconfigure.MongoClientSettingsBuilderCustomizer;\nimport org.springframework.boot.test.context.assertj.AssertableApplicationContext;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\n\npublic final class TlsReachProbe {\n\n /** The URI the module README shows a deployment using. */\n private static final String README_URI = \"mongodb://canary-host:31337/portfolio\";\n\n private static final ApplicationContextRunner BASE =\n new ApplicationContextRunner()\n .withConfiguration(AutoConfigurations.of(\n dev.caskeleton.adapter.outbound.mongo.MongoRootAutoConfiguration.class,\n MongoAutoConfiguration.class))\n .withBean(MeterRegistry.class, SimpleMeterRegistry::new)\n .withPropertyValues(\"ca-skeleton.persistence-mongo.enabled=true\");\n\n public static void main(String[] args) {\n System.out.println(\"[속성 키] 같은 URI 를 두 키로 각각 넣어 본다\");\n hosts(\"spring.mongodb.uri \", \"spring.mongodb.uri=\" + README_URI);\n hosts(\"spring.data.mongodb.uri\", \"spring.data.mongodb.uri=\" + README_URI);\n System.out.println();\n\n MongoProfileProperties profile = MongoProfileProperties.production(\"secret://mongodb/uri\");\n MongoPlatformSettings settings =\n new MongoPlatformSettings(Map.of(\"default\", profile), false, false, null);\n System.out.println(\"[선언] production 프로파일이 말하는 값\");\n System.out.println(\" tlsRequired=\" + profile.tlsRequired()\n + \" stableApiStrict=\" + profile.stableApiStrict()\n + \" connectTimeout=\" + profile.connectTimeout().toMillis() + \"ms\"\n + \" serverSelection=\" + profile.serverSelectionTimeout().toMillis() + \"ms\"\n + \" poolMax=\" + profile.poolMaxSize()\n + \" uuid=\" + profile.uuidRepresentation());\n settings.validate();\n System.out.println(\" 이 선언을 설정 검증이 통과시킨다\");\n System.out.println();\n\n System.out.println(\"[실제] Boot 가 클라이언트를 만들 때 쓰는 설정 (커스터마이저 적용 후)\");\n BASE.withPropertyValues(\"spring.mongodb.uri=\" + README_URI)\n .run(context -> {\n System.out.println(\" 커스터마이저 \" + customizers(context).size() + \"개 적용\");\n print(\" \", effective(context));\n });\n System.out.println();\n\n System.out.println(\"[다른 경로] URI 를 건드리지 않고 ssl 속성만 켠다\");\n BASE.withPropertyValues(\"spring.mongodb.uri=\" + README_URI, \"spring.mongodb.ssl.enabled=true\")\n .run(context -> System.out.println(\n \" spring.mongodb.ssl.enabled=true -> sslEnabled=\"\n + effective(context).getSslSettings().isEnabled()));\n System.out.println();\n\n System.out.println(\"[미도달] 팩토리가 만들었을 설정\");\n print(\" \", new MongoClientSettingsFactory(settings, \"default\", resolver()).create());\n System.exit(0);\n }\n\n private static void hosts(String label, String property) {\n BASE.withPropertyValues(property)\n .run(context -> System.out.println(\n \" \" + label + \" -> \" + effective(context).getClusterSettings().getHosts()));\n }\n\n private static List<MongoClientSettingsBuilderCustomizer> customizers(\n AssertableApplicationContext context) {\n return context.getBeanProvider(MongoClientSettingsBuilderCustomizer.class)\n .orderedStream().toList();\n }\n\n /** What Boot hands the driver: the base bean with every customizer applied, as Boot does. */\n private static MongoClientSettings effective(AssertableApplicationContext context) {\n MongoClientSettings.Builder builder =\n MongoClientSettings.builder(context.getBean(MongoClientSettings.class));\n customizers(context).forEach(customizer -> customizer.customize(builder));\n return builder.build();\n }\n\n private static void print(String indent, MongoClientSettings built) {\n System.out.println(indent + \"sslEnabled=\" + built.getSslSettings().isEnabled());\n System.out.println(indent + \"connectTimeoutMs=\"\n + built.getSocketSettings().getConnectTimeout(TimeUnit.MILLISECONDS)\n + \" serverSelectionTimeoutMs=\"\n + built.getClusterSettings().getServerSelectionTimeout(TimeUnit.MILLISECONDS));\n System.out.println(indent + \"poolMaxSize=\" + built.getConnectionPoolSettings().getMaxSize()\n + \" serverApi=\" + built.getServerApi()\n + \" uuidRepresentation=\" + built.getUuidRepresentation());\n }\n\n private static MongoCredentialResolver resolver() {\n return new MongoCredentialResolver() {\n @Override\n public <T> T withConnectionString(\n MongoCredentialReference reference, Function<String, T> use) {\n return use.apply(README_URI);\n }\n };\n }\n}\nJAVA\n\nCACHE=/root/.gradle/caches/modules-2/files-2.1\nLOCK=src/adapter/outbound/persistence-mongo/gradle.lockfile\nCP=src/adapter/outbound/persistence-mongo/build/classes/java/main\nwhile IFS= read -r line; do\n coord=${line%%=*}; confs=${line#*=}\n case \",$confs,\" in *\",testRuntimeClasspath,\"*) ;; *) continue ;; esac\n g=${coord%%:*}; r=${coord#*:}; n=${r%%:*}; v=${r##*:}\n jar=$(find \"$CACHE/$g/$n/$v\" -name '*.jar' ! -name '*sources*' ! -name '*javadoc*' 2>/dev/null | head -1)\n [ -n \"$jar\" ] && CP=\"$CP:$jar\"\ndone < <(grep -E '^[a-zA-Z0-9._-]+:[^=]+=' \"$LOCK\")\n\njavac -encoding UTF-8 -cp \"$CP\" -d \"$D\" \"$D/TlsReachProbe.java\"\njava -XX:+EnableDynamicAgentLoading -Dlogback.configurationFile=\"$D/logback-off.xml\" \\\n -Dstdout.encoding=UTF-8 -cp \"$D:$CP\" TlsReachProbe 2>/dev/null",
|
|
"cwd": "/shared/codebase/clean-architecture-backend-template",
|
|
"exitCode": 0,
|
|
"executedAt": "2026-09-02T08:41:57+00:00",
|
|
"sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916",
|
|
"raw": "evidence/raw/a06-f020-tls-stable-probe.txt",
|
|
"svg": "evidence/rendered/a06-f020-tls-stable-probe.svg",
|
|
"rawSha256": "e652c8a0dd53f80f6f9085475701a2e8834dc69779f8d75c7f9e2787c66e0971",
|
|
"lines": 21,
|
|
"redaction": "SVG 렌더러가 secret: 로 시작하는 자격증명 참조 문자열을 가렸다 — 탐침 소스의 리터럴이고 실제 비밀값이 아니다"
|
|
}
|