Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/a-weaker-private-copy-on-the-wired-path.txt
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
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>
2026-09-04 22:51:59 +09:00

103 lines
5.5 KiB
Plaintext

# 값 타입의 컴팩트 생성자가 하는 검사 전부
22: public WebPushSubscriptionValue {
23: Objects.requireNonNull(endpoint, "endpoint");
24: Objects.requireNonNull(p256dh, "p256dh");
25: Objects.requireNonNull(authSecret, "authSecret");
26: Objects.requireNonNull(vapidKeyId, "vapidKeyId");
27: if (!isSecureOrLoopback(endpoint)) {
28: throw new IllegalArgumentException("endpoint must be https outside the loopback interface");
29: }
30: if (p256dh.length != P256DH_LENGTH) {
31: throw new IllegalArgumentException("p256dh must be an uncompressed P-256 point");
32: }
33: if (authSecret.length != AUTH_SECRET_LENGTH) {
34: throw new IllegalArgumentException("authSecret must be 16 bytes");
35: }
36: if (vapidKeyId.isBlank()) {
37: throw new IllegalArgumentException("vapidKeyId");
38: }
# 다섯째 검사의 구현 — 같은 파일 안의 private 메서드
50: private static boolean isSecureOrLoopback(java.net.URI endpoint) {
51: String scheme = endpoint.getScheme() == null ? "" : endpoint.getScheme();
52: if ("https".equalsIgnoreCase(scheme)) {
53: return true;
54: }
55: String host =
56: endpoint.getHost() == null ? "" : endpoint.getHost().toLowerCase(java.util.Locale.ROOT);
57: return "http".equalsIgnoreCase(scheme)
58: && ("127.0.0.1".equals(host) || "::1".equals(host) || "localhost".equals(host));
59: }
# 그 private 사본이 베낀 원본. 같은 모듈의 공개 함수다.
24: public static URI requireSecureOrLoopback(URI endpoint, String name) {
25: Objects.requireNonNull(endpoint, name);
26: String scheme =
27: endpoint.getScheme() == null ? "" : endpoint.getScheme().toLowerCase(Locale.ROOT);
28: if ("https".equals(scheme)) {
29: return endpoint;
30: }
31: String host = endpoint.getHost() == null ? "" : endpoint.getHost().toLowerCase(Locale.ROOT);
32: if ("http".equals(scheme) && LOOPBACK_HOSTS.contains(host)) {
33: return endpoint;
34: }
35: throw new IllegalArgumentException(name + " must use https outside the loopback interface");
# 같은 파일의 다른 공개 함수가 자기가 누구를 위한 것인지 적는다
47: * <p>{@link #requireSecureOrLoopback} checks the scheme and nothing else, so any HTTPS URL was
48: * accepted — including {@code https://169.254.169.254/}, the cloud metadata service, and any RFC
49: * 1918 address. Web Push endpoints and webhook targets are supplied by clients, which makes this
50: * a server-side request forgery primitive: the platform will happily fetch an internal address
51: * and, for a webhook, deliver the message body there.
# 그 함수가 실제로 하는 검사
65: public static URI requireExternallyRoutable(URI endpoint, String name, boolean allowLoopback) {
66: Objects.requireNonNull(endpoint, name);
67: requireSecureOrLoopback(endpoint, name);
68: if (endpoint.getUserInfo() != null) {
71: throw new IllegalArgumentException(name + " must not carry userinfo");
72: }
73: String host = endpoint.getHost();
74: if (host == null || host.isBlank()) {
75: throw new IllegalArgumentException(name + " has no host");
76: }
77: if (allowLoopback && isLoopback(endpoint)) {
78: return endpoint;
79: }
81: java.net.InetAddress[] resolved;
82: try {
83: resolved = java.net.InetAddress.getAllByName(host);
84: } catch (java.net.UnknownHostException unresolvable) {
85: throw new IllegalArgumentException(name + " does not resolve", unresolvable);
86: }
87: if (resolved.length == 0) {
88: throw new IllegalArgumentException(name + " does not resolve");
89: }
90: for (java.net.InetAddress address : resolved) {
94: if (isInternal(address)) {
95: throw new IllegalArgumentException(
96: name + " resolves to an address inside the deployment's own network");
97: }
98: }
99: return endpoint;
# 강한 쪽의 프로덕션 호출처
SesProviderProperties.java:27: NotificationEndpoints.requireExternallyRoutable(endpoint, "SES endpoint", true);
WebhookSubscription.java:49: NotificationEndpoints.requireExternallyRoutable(target, "webhook target", trusted);
# 그중 웹푸시: 0
# 값 타입이 두 공개 함수 중 하나라도 부르는 줄: 0
# 호출처 도달을 고정하려고 만든 테스트가, 무엇을 목록으로 들고 있는가
17: * <p>{@code EndpointRoutabilityTest} already proves {@code requireExternallyRoutable} rejects the
18: * metadata service, RFC 1918, link-local and the rest. It proved that for months while the function
19: * had no caller: both sites it was written for — a webhook target and an SES endpoint — kept
20: * calling {@code requireSecureOrLoopback}, which reads the scheme and nothing else. A green test on
21: * a control nothing invokes is the shape this repository keeps finding, and testing the helper
22: * again would not have caught it.
37: @DisplayName("a webhook target on the cloud metadata service is refused")
45: @DisplayName("a webhook target inside the deployment's own network is refused")
54: @DisplayName("a webhook target carrying userinfo is refused")
68: @DisplayName("an SES endpoint inside the deployment's own network is refused")
82: @DisplayName("a client-supplied webhook target on the loopback interface is refused")
95: @DisplayName("a client-supplied webhook target on 127.0.0.1 is refused")
106: @DisplayName("loopback stays available, because local and contract profiles address it")
# 그 파일에서 WebPush 를 언급하는 줄: 0