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>
148 lines
13 KiB
Plaintext
148 lines
13 KiB
Plaintext
Sub-scope 05 (httpclient) security/** + auth/** — manifest and probes
|
|
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
|
|
generatedAt=2026-08-30T05:33:58+00:00
|
|
|
|
=== OWNED FILES ===
|
|
45b19ed9277da7a019f1b33b33280e96ecda3bf4 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/AccessToken.java 33
|
|
8f2f07b8dc8d1646a8e50de31346855f85ce84a1 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/ApiKeyHeaderCredentialProvider.java 59
|
|
e43ca6b1e0e3b5470f6756de32107d8aed466983 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/BasicCredentialProvider.java 55
|
|
7d272f2742d78e61641c67541af0719723fd1442 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/CredentialProviderRegistry.java 47
|
|
4e464421d85d81c4056585691d61e3143f6793b8 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/CredentialRequest.java 66
|
|
1f9cd1536e0612f51f69cd5fdf3def0e931ec178 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/CredentialType.java 35
|
|
49b66bcdc6f21b50300fe2642841462f70ab6620 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/NoAuthCredentialProvider.java 17
|
|
d87646466ae9d824dc51abae5dc18b3008d82ea0 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/OAuth2CredentialProvider.java 128
|
|
028d1432fc9987ded1036002391a7c577d55bd30 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/OAuth2TokenCacheKey.java 31
|
|
7c1681d8eff1d75e4d3b44b8e9743d0c6c29f192 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/ProxyCredentialProvider.java 36
|
|
9de59317b5bfe55d22c4bb72b12da3ebf1da8eed main/java/dev/caskeleton/adapter/outbound/httpclient/auth/ReactiveCredentialProviderRegistry.java 79
|
|
2f94c38826b84fef42e4847424af4f84c0e05960 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/ReactiveRequestCredentialProvider.java 40
|
|
fcae06235a0fd1392f306d5a9f1d9d31a757745f main/java/dev/caskeleton/adapter/outbound/httpclient/auth/RequestCredentialProvider.java 12
|
|
3ca957ac2cd00700a7258ca8451f929d2f7c344e main/java/dev/caskeleton/adapter/outbound/httpclient/auth/RequestCredentials.java 47
|
|
7dfdc6e201938f153a718113a524f29dbfa8d5a5 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/SingleFlightTokenLoader.java 148
|
|
5ea287bb9ace53e4bef53c071cffe700c11cab39 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/StaticBearerCredentialProvider.java 40
|
|
7294534b20e4bf409c3d95a546cc0d136c2d93af main/java/dev/caskeleton/adapter/outbound/httpclient/auth/UnauthorizedRetryContext.java 21
|
|
005a87161019ff41b2d01e2bebf6a62f27050f85 main/java/dev/caskeleton/adapter/outbound/httpclient/auth/UnauthorizedRetryPolicy.java 32
|
|
ac4fcf808241934a918a6a6f09a90e2fd9018e3f main/java/dev/caskeleton/adapter/outbound/httpclient/security/BodyLimitPolicy.java 56
|
|
066670590995474046a81db14eddcdb1fd9ebdc0 main/java/dev/caskeleton/adapter/outbound/httpclient/security/ClientCertificateIdentity.java 19
|
|
6be107ea25d7c44268fb02da336c26c6582e1ee3 main/java/dev/caskeleton/adapter/outbound/httpclient/security/HeaderPolicy.java 92
|
|
f5542419a3fe4d6e812245d480aeb007ea24ae80 main/java/dev/caskeleton/adapter/outbound/httpclient/security/KeyMaterialRef.java 15
|
|
171dc3e1697aae380cad944b2a76c8ae6af1c942 main/java/dev/caskeleton/adapter/outbound/httpclient/security/PreparedOperation.java 50
|
|
43785c77995e416fbe4f6b35d18f3aa8ff09c7ef main/java/dev/caskeleton/adapter/outbound/httpclient/security/PreparedTarget.java 32
|
|
c77b25e17d6c1301fecde51055a4d5e36cffeaa8 main/java/dev/caskeleton/adapter/outbound/httpclient/security/RedirectContext.java 38
|
|
1285d1aaf28ae3f367f2d79bb9988ca2604ffae3 main/java/dev/caskeleton/adapter/outbound/httpclient/security/RedirectDecision.java 30
|
|
f0b786787fbc050cb8f0e8d9abeba6b7315a2f7f main/java/dev/caskeleton/adapter/outbound/httpclient/security/RedirectEvaluator.java 28
|
|
6aed71021b55d5d1dfe08de7018e5faeac255fe1 main/java/dev/caskeleton/adapter/outbound/httpclient/security/RedirectPolicy.java 40
|
|
94df5c655ea8692dbf0e255dcaf1d5263da9de08 main/java/dev/caskeleton/adapter/outbound/httpclient/security/SensitiveHeaderStripper.java 68
|
|
dfacd90bd51cbe577ae4e494fc8ba6ec409c6ce9 main/java/dev/caskeleton/adapter/outbound/httpclient/security/SslContextMaterial.java 40
|
|
b06fab3702a39730b4ef29b8c8d7adb9fd41f6e3 main/java/dev/caskeleton/adapter/outbound/httpclient/security/TlsMaterialProvider.java 176
|
|
2bb1808f232b47eb3adfa624b572877e602603fe main/java/dev/caskeleton/adapter/outbound/httpclient/security/TlsPolicyValidator.java 60
|
|
f46c98cc02a47c3fa6f893583d3a244d22bb1b98 main/java/dev/caskeleton/adapter/outbound/httpclient/security/TlsProfile.java 53
|
|
827602fd445f1dafbaa497e503085f99dc175215 main/java/dev/caskeleton/adapter/outbound/httpclient/security/TlsProfileId.java 16
|
|
1d2305691c08de607f7184196d3e7565b74c1fce main/java/dev/caskeleton/adapter/outbound/httpclient/security/TlsRuntimeRotationCoordinator.java 78
|
|
2ff2ae0f15bf65227056807964b7259bcf66b1d1 main/java/dev/caskeleton/adapter/outbound/httpclient/security/TlsViolation.java 18
|
|
c7fbe0c84b7f6a9869b578c2c2a1bc1f0b773abd main/java/dev/caskeleton/adapter/outbound/httpclient/security/TrustMaterialRef.java 30
|
|
893051f246483ecf711513b7c21e7db1199e31db main/java/dev/caskeleton/adapter/outbound/httpclient/security/TrustedTargetPolicy.java 176
|
|
7e9120e1e75e9c71c648cccf76519d7f324df067 main/java/dev/caskeleton/adapter/outbound/httpclient/security/UriTemplateExpander.java 31
|
|
19a410d8b58e3ea2c5d96a41a1a72bd2975951f2 test/java/dev/caskeleton/adapter/outbound/httpclient/auth/SingleFlightTokenLoaderTest.java 73
|
|
78dafebd6089c54e0570ef8f7eb9de4db52a2cc6 test/java/dev/caskeleton/adapter/outbound/httpclient/auth/UnauthorizedRetryPolicyTest.java 61
|
|
db79b2c0d0c099886a72efb6f23dc2ade70ef1b9 test/java/dev/caskeleton/adapter/outbound/httpclient/security/MutualTlsHandshakeContractTest.java 172
|
|
259568f2f1d3f9f2cea74341edf9a4f107fc81c0 test/java/dev/caskeleton/adapter/outbound/httpclient/security/RedirectEvaluatorTest.java 101
|
|
f1c7e41165464d192cbd552bed1269261ace3a6e test/java/dev/caskeleton/adapter/outbound/httpclient/security/TlsPolicyValidatorTest.java 58
|
|
e77b752a85b380b537c83e604c8288254f80a756 test/java/dev/caskeleton/adapter/outbound/httpclient/security/TlsRuntimeRotationCoordinatorTest.java 114
|
|
104df751c46e88bbb38bf9b5f0262d3022fbd316 test/java/dev/caskeleton/adapter/outbound/httpclient/security/TrustedRequestPolicyTest.java 161
|
|
|
|
main: 39 test: 7
|
|
=== 8.1 자격증명이 toString으로 새는 타입이 있는가 ===
|
|
$ git grep -Ln 'public String toString' -- 'adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth'
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/ApiKeyHeaderCredentialProvider.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/BasicCredentialProvider.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/CredentialProviderRegistry.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/CredentialType.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/NoAuthCredentialProvider.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/OAuth2CredentialProvider.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/OAuth2TokenCacheKey.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/ProxyCredentialProvider.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/ReactiveCredentialProviderRegistry.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/ReactiveRequestCredentialProvider.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/RequestCredentialProvider.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/SingleFlightTokenLoader.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/StaticBearerCredentialProvider.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/UnauthorizedRetryContext.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/UnauthorizedRetryPolicy.java
|
|
exit=0
|
|
|
|
$ grep -rn 'REDACTED' adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security | head
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/RequestCredentials.java:45: + ", values=REDACTED]";
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/AccessToken.java:31: return "AccessToken[REDACTED, expiresAt=" + expiresAt + "]";
|
|
exit=0
|
|
|
|
=== 8.2 UNBOUND 가짜 메타데이터의 production 사용 ===
|
|
$ grep -n 'UNBOUND' adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/HeaderPolicy.java
|
|
36: private static final HttpFailureMetadata UNBOUND =
|
|
59: return validate(input, UNBOUND);
|
|
exit=0
|
|
|
|
$ git grep -rn '\.validate(' -- 'adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient' | grep -i header | head
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/DefaultDynamicTargetGateway.java:156: .validate(operation.headers(), metadata);
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/TrustedTargetPolicy.java:80: .validate(operation.headers(), metadata);
|
|
exit=0
|
|
|
|
=== 8.3 SSRF 방어 지점 전수 ===
|
|
$ git grep -n 'requireAllowedTarget\|requireAllowedOrigin' -- 'adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient'
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingAttemptExecutor.java:145: runtime.targetPolicy()::requireAllowedTarget);
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/TrustedTargetPolicy.java:76: requireAllowedOrigin(target, metadata);
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/TrustedTargetPolicy.java:162: public void requireAllowedTarget(java.net.URI target, HttpFailureMetadata metadata) {
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/TrustedTargetPolicy.java:163: requireAllowedOrigin(PreparedTarget.of(target, target.getPath()), metadata);
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/TrustedTargetPolicy.java:166: private void requireAllowedOrigin(PreparedTarget target, HttpFailureMetadata metadata) {
|
|
exit=0
|
|
|
|
$ git grep -ln 'IpAddressClassifier' -- 'adapter/outbound/httpclient/src/main'
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/IpAddressClassifier.java
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/ValidatedDnsResolver.java
|
|
exit=0
|
|
|
|
=== 8.4 test ===
|
|
$ git ls-files 'adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/security' 'adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/auth' | xargs -n1 basename
|
|
SingleFlightTokenLoaderTest.java
|
|
UnauthorizedRetryPolicyTest.java
|
|
MutualTlsHandshakeContractTest.java
|
|
RedirectEvaluatorTest.java
|
|
TlsPolicyValidatorTest.java
|
|
TlsRuntimeRotationCoordinatorTest.java
|
|
TrustedRequestPolicyTest.java
|
|
exit=0
|
|
|
|
$ git grep -h ' void [a-z]' -- 'adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/security' 'adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/auth' | sed 's/^ *//'
|
|
void concurrentRequestsShareOneTokenRefresh() throws Exception {
|
|
void tokenValueIsNeverRenderedInTextForm() {
|
|
void denies401ReplayForOneShotPost() {
|
|
void allowsExactlyOneReplayForASafeReplayableOperation() {
|
|
void allowsAKeyedWriteOnlyWhenAuthFailedBeforeAnySideEffect() {
|
|
void aClientCertificateSatisfiesAServerThatRequiresOne() throws Exception {
|
|
void aMissingClientCertificateCannotReachTheUpstream() throws Exception {
|
|
void aHostnameMismatchIsAPermanentTlsFailure() throws Exception {
|
|
void anExpiredCertificateIsAPermanentTlsFailure() throws Exception {
|
|
void anUntrustedAuthorityIsAPermanentTlsFailure() throws Exception {
|
|
void rejects307WhenBodyIsOneShot() {
|
|
void rejectsWhenRedirectsAreDisabledOrHopsAreExhausted() {
|
|
void rejectsCrossOriginUnlessExplicitlyAllowed() {
|
|
void followsAReplayableSameOriginHop() {
|
|
void stripsCredentialsOnCrossOriginRedirect() {
|
|
void rejectsTrustAllAndHostnameVerificationDisablement() {
|
|
void rejectsLegacyProtocolVersions() {
|
|
void acceptsAStandardProfile() {
|
|
void tlsProfileHasNoRepresentationForTrustAll() {
|
|
void swapsRuntimeWhenCertificateIdentityChanges() {
|
|
void rotationTouchesOnlyTheProfilesThatUseTheRotatedCertificate() {
|
|
void rejectsAbsoluteUriInTrustedGenericGateway() {
|
|
void rejectsHeaderInjection() {
|
|
void rejectsPlatformOwnedHeaderOverride() {
|
|
void allowsIdempotencyKeyOnlyWhenTheOperationDeclaresIt() {
|
|
void rejectsKnownBodyLargerThanProfileLimit() {
|
|
void encodesTemplateVariablesPerComponent() {
|
|
void carriesProfileBudgetsForwardToTheExecutor() {
|
|
void aRegisteredIdempotencyKeyIsRenderedAsAHeader() {
|
|
void aCallerSuppliedIdempotencyHeaderIsRefused() {
|
|
void aKeyWhoseRequirementWasNotRegisteredIsRefused() {
|
|
exit=0
|
|
|