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>
171 lines
15 KiB
XML
171 lines
15 KiB
XML
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1822" viewBox="0 0 1200 1822" role="img">
|
|
<title>terminal evidence</title>
|
|
<desc>Terminal-style rendering generated from retained command output. Sensitive-looking values are redacted in the visual asset.</desc>
|
|
<rect x="1" y="1" width="1198" height="1820" rx="14" fill="#0d1117" stroke="#30363d"/>
|
|
<rect x="1" y="1" width="1198" height="44" rx="14" fill="#161b22"/>
|
|
<rect x="1" y="30" width="1198" height="14" fill="#161b22"/>
|
|
<circle cx="24" cy="22" r="6" fill="#ff5f57"/>
|
|
<circle cx="44" cy="22" r="6" fill="#febc2e"/>
|
|
<circle cx="64" cy="22" r="6" fill="#28c840"/>
|
|
<text x="92" y="27" fill="#8b949e" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">terminal evidence</text>
|
|
<text x="24" y="68" fill="#c9d1d9" font-size="15" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">$ set -e
|
|
D=$(mktemp -d)
|
|
trap 'rm -rf "$D"' EXIT
|
|
|
|
printf %s '<configuration><root level="OFF"/></configuration>' > "$D/lo</tspan><tspan x="24" dy="22">gback-off.xml"
|
|
|
|
cat > "$D/TlsReachProbe.java" <<'JAVA'
|
|
import com.mongodb.MongoClientSettings;
|
|
import dev.caskeleton.ada</tspan><tspan x="24" dy="22">pter.outbound.mongo.autoconfigure.MongoPlatformSettings;
|
|
import dev.caskeleton.adapter.outbound.mongo.autoconfigure.Mong</tspan><tspan x="24" dy="22">oProfileProperties;
|
|
import dev.caskeleton.adapter.outbound.mongo.client.MongoClientSettingsFactory;
|
|
import dev.caskeleto</tspan><tspan x="24" dy="22">n.adapter.outbound.mongo.security.MongoCredentialReference;
|
|
import dev.caskeleton.adapter.outbound.mongo.security.MongoC</tspan><tspan x="24" dy="22">redentialResolver;
|
|
import io.micrometer.core.instrument.MeterRegistry;
|
|
import io.micrometer.core.instrument.simple.Simpl</tspan><tspan x="24" dy="22">eMeterRegistry;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.func</tspan><tspan x="24" dy="22">tion.Function;
|
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
|
import org.springframework.boot.mongodb</tspan><tspan x="24" dy="22">.autoconfigure.MongoAutoConfiguration;
|
|
import org.springframework.boot.mongodb.autoconfigure.MongoClientSettingsBuilderC</tspan><tspan x="24" dy="22">ustomizer;
|
|
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
|
import org.springframework</tspan><tspan x="24" dy="22">.boot.test.context.runner.ApplicationContextRunner;
|
|
|
|
public final class TlsReachProbe {
|
|
|
|
/** The URI the module README</tspan><tspan x="24" dy="22"> shows a deployment using. */
|
|
private static final String README_URI = "mongodb://canary-host:31337/portfolio";
|
|
|
|
pri</tspan><tspan x="24" dy="22">vate static final ApplicationContextRunner BASE =
|
|
new ApplicationContextRunner()
|
|
.withConfiguration(Auto</tspan><tspan x="24" dy="22">Configurations.of(
|
|
dev.caskeleton.adapter.outbound.mongo.MongoRootAutoConfiguration.class,
|
|
M</tspan><tspan x="24" dy="22">ongoAutoConfiguration.class))
|
|
.withBean(MeterRegistry.class, SimpleMeterRegistry::new)
|
|
.withProperty</tspan><tspan x="24" dy="22">Values("ca-skeleton.persistence-mongo.enabled=true");
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println</tspan><tspan x="24" dy="22">("[속성 키] 같은 URI 를 두 키로 각각 넣어 본다");
|
|
hosts("spring.mongodb.uri ", "spring.mongodb.uri=" + README_UR</tspan><tspan x="24" dy="22">I);
|
|
hosts("spring.data.mongodb.uri", "spring.data.mongodb.uri=" + README_URI);
|
|
System.out.println();
|
|
|
|
MongoP</tspan><tspan x="24" dy="22">rofileProperties profile = MongoProfileProperties.production("secret:[REDACTED];
|
|
MongoPlatformSettings settings =
|
|
</tspan><tspan x="24" dy="22"> new MongoPlatformSettings(Map.of("default", profile), false, false, null);
|
|
System.out.println("[선언] producti</tspan><tspan x="24" dy="22">on 프로파일이 말하는 값");
|
|
System.out.println(" tlsRequired=" + profile.tlsRequired()
|
|
+ " stableApiStrict=</tspan><tspan x="24" dy="22">" + profile.stableApiStrict()
|
|
+ " connectTimeout=" + profile.connectTimeout().toMillis() + "ms"
|
|
+ " se</tspan><tspan x="24" dy="22">rverSelection=" + profile.serverSelectionTimeout().toMillis() + "ms"
|
|
+ " poolMax=" + profile.poolMaxSize()
|
|
</tspan><tspan x="24" dy="22"> + " uuid=" + profile.uuidRepresentation());
|
|
settings.validate();
|
|
System.out.println(" 이 선언을 설정 검증</tspan><tspan x="24" dy="22">이 통과시킨다");
|
|
System.out.println();
|
|
|
|
System.out.println("[실제] Boot 가 클라이언트를 만들 때 쓰는 설정 (커스</tspan><tspan x="24" dy="22">터마이저 적용 후)");
|
|
BASE.withPropertyValues("spring.mongodb.uri=" + README_URI)
|
|
.run(context -> {
|
|
</tspan><tspan x="24" dy="22"> System.out.println(" 커스터마이저 " + customizers(context).size() + "개 적용");
|
|
print(" ", effective(contex</tspan><tspan x="24" dy="22">t));
|
|
});
|
|
System.out.println();
|
|
|
|
System.out.println("[다른 경로] URI 를 건드리지 않고 ssl 속성만 켠다");
|
|
</tspan><tspan x="24" dy="22"> BASE.withPropertyValues("spring.mongodb.uri=" + README_URI, "spring.mongodb.ssl.enabled=true")
|
|
.run(context </tspan><tspan x="24" dy="22">-> System.out.println(
|
|
" spring.mongodb.ssl.enabled=true -> sslEnabled="
|
|
+ effective(contex</tspan><tspan x="24" dy="22">t).getSslSettings().isEnabled()));
|
|
System.out.println();
|
|
|
|
System.out.println("[미도달] 팩토리가 만들었을 설정");</tspan><tspan x="24" dy="22">
|
|
print(" ", new MongoClientSettingsFactory(settings, "default", resolver()).create());
|
|
System.exit(0);
|
|
}
|
|
|
|
p</tspan><tspan x="24" dy="22">rivate static void hosts(String label, String property) {
|
|
BASE.withPropertyValues(property)
|
|
.run(context -> </tspan><tspan x="24" dy="22">System.out.println(
|
|
" " + label + " -> " + effective(context).getClusterSettings().getHosts()));
|
|
}
|
|
|
|
pri</tspan><tspan x="24" dy="22">vate static List<MongoClientSettingsBuilderCustomizer> customizers(
|
|
AssertableApplicationContext context) {
|
|
re</tspan><tspan x="24" dy="22">turn context.getBeanProvider(MongoClientSettingsBuilderCustomizer.class)
|
|
.orderedStream().toList();
|
|
}
|
|
|
|
/** W</tspan><tspan x="24" dy="22">hat Boot hands the driver: the base bean with every customizer applied, as Boot does. */
|
|
private static MongoClientSet</tspan><tspan x="24" dy="22">tings effective(AssertableApplicationContext context) {
|
|
MongoClientSettings.Builder builder =
|
|
MongoClientSet</tspan><tspan x="24" dy="22">tings.builder(context.getBean(MongoClientSettings.class));
|
|
customizers(context).forEach(customizer -> customizer.cus</tspan><tspan x="24" dy="22">tomize(builder));
|
|
return builder.build();
|
|
}
|
|
|
|
private static void print(String indent, MongoClientSettings built)</tspan><tspan x="24" dy="22"> {
|
|
System.out.println(indent + "sslEnabled=" + built.getSslSettings().isEnabled());
|
|
System.out.println(indent + </tspan><tspan x="24" dy="22">"connectTimeoutMs="
|
|
+ built.getSocketSettings().getConnectTimeout(TimeUnit.MILLISECONDS)
|
|
+ " serverSele</tspan><tspan x="24" dy="22">ctionTimeoutMs="
|
|
+ built.getClusterSettings().getServerSelectionTimeout(TimeUnit.MILLISECONDS));
|
|
System.out.</tspan><tspan x="24" dy="22">println(indent + "poolMaxSize=" + built.getConnectionPoolSettings().getMaxSize()
|
|
+ " serverApi=" + built.getSer</tspan><tspan x="24" dy="22">verApi()
|
|
+ " uuidRepresentation=" + built.getUuidRepresentation());
|
|
}
|
|
|
|
private static MongoCredentialResolv</tspan><tspan x="24" dy="22">er resolver() {
|
|
return new MongoCredentialResolver() {
|
|
@Override
|
|
public <T> T withConnectionString(
|
|
</tspan><tspan x="24" dy="22"> MongoCredentialReference reference, Function<String, T> use) {
|
|
return use.apply(README_URI);
|
|
}
|
|
}</tspan><tspan x="24" dy="22">;
|
|
}
|
|
}
|
|
JAVA
|
|
|
|
CACHE=/root/.gradle/caches/modules-2/files-2.1
|
|
LOCK=src/adapter/outbound/persistence-mongo/gradle.lockfile</tspan><tspan x="24" dy="22">
|
|
CP=src/adapter/outbound/persistence-mongo/build/classes/java/main
|
|
while IFS= read -r line; do
|
|
coord=${line%%=*}; conf</tspan><tspan x="24" dy="22">s=${line#*=}
|
|
case ",$confs," in *",testRuntimeClasspath,"*) ;; *) continue ;; esac
|
|
g=${coord%%:*}; r=${coord#*:}; n=</tspan><tspan x="24" dy="22">${r%%:*}; v=${r##*:}
|
|
jar=$(find "$CACHE/$g/$n/$v" -name '*.jar' ! -name '*sources*' ! -name '*javadoc*' 2>/dev/null | </tspan><tspan x="24" dy="22">head -1)
|
|
[ -n "$jar" ] && CP="$CP:$jar"
|
|
done < <(grep -E '^[a-zA-Z0-9._-]+:[^=]+=' "$LOCK")
|
|
|
|
javac -encoding UTF-8 -cp</tspan><tspan x="24" dy="22"> "$CP" -d "$D" "$D/TlsReachProbe.java"
|
|
java -XX:+EnableDynamicAgentLoading -Dlogback.configurationFile="$D/logback-off.x</tspan><tspan x="24" dy="22">ml" \
|
|
-Dstdout.encoding=UTF-8 -cp "$D:$CP" TlsReachProbe 2>/dev/null</tspan></text>
|
|
<text x="24" y="1256" fill="#8b949e" font-size="13" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">cwd: /shared/codebase/clean-architecture-backend-template</tspan></text>
|
|
<text x="24" y="1278" fill="#8b949e" font-size="13" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">time: 2026-09-02T08:41:57+00:00 · exit 0</tspan></text>
|
|
<line x1="24" y1="1292" x2="1176" y2="1292" stroke="#30363d"/>
|
|
<text x="24" y="1332" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[속성 키] 같은 URI 를 두 키로 각각 넣어 본다</tspan></text>
|
|
<text x="24" y="1354" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> spring.mongodb.uri -> [canary-host:31337]</tspan></text>
|
|
<text x="24" y="1376" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> spring.data.mongodb.uri -> [localhost:27017]</tspan></text>
|
|
<text x="24" y="1398" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
|
|
<text x="24" y="1420" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[선언] production 프로파일이 말하는 값</tspan></text>
|
|
<text x="24" y="1442" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> tlsRequired=true stableApiStrict=true connectTimeout=2000ms serverSelection=3000ms poolMax=40 uuid=STANDARD</tspan></text>
|
|
<text x="24" y="1464" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 이 선언을 설정 검증이 통과시킨다</tspan></text>
|
|
<text x="24" y="1486" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
|
|
<text x="24" y="1508" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[실제] Boot 가 클라이언트를 만들 때 쓰는 설정 (커스터마이저 적용 후)</tspan></text>
|
|
<text x="24" y="1530" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 커스터마이저 2개 적용</tspan></text>
|
|
<text x="24" y="1552" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> sslEnabled=false</tspan></text>
|
|
<text x="24" y="1574" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> connectTimeoutMs=10000 serverSelectionTimeoutMs=30000</tspan></text>
|
|
<text x="24" y="1596" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> poolMaxSize=100 serverApi=null uuidRepresentation=UNSPECIFIED</tspan></text>
|
|
<text x="24" y="1618" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
|
|
<text x="24" y="1640" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[다른 경로] URI 를 건드리지 않고 ssl 속성만 켠다</tspan></text>
|
|
<text x="24" y="1662" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> spring.mongodb.ssl.enabled=true -> sslEnabled=true</tspan></text>
|
|
<text x="24" y="1684" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
|
|
<text x="24" y="1706" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[미도달] 팩토리가 만들었을 설정</tspan></text>
|
|
<text x="24" y="1728" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> sslEnabled=true</tspan></text>
|
|
<text x="24" y="1750" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> connectTimeoutMs=2000 serverSelectionTimeoutMs=3000</tspan></text>
|
|
<text x="24" y="1772" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> poolMaxSize=40 serverApi=ServerApi{version=V1, deprecationErrors=true, strict=true} uuidRepresentation=STANDARD</tspan></text>
|
|
</svg>
|