C.W.K.
Stream
Lesson 03 of 04 · published

잠긴 문

~12 min · auth, delegation, cookies, gating

Level 0Open Gate
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Keep 은 자기 열쇠를 안 쥐어. 뇌한테 너를 체크인시켜 달라 하고, 이 머신에서만, 일주일 동안 작동하는 손목띠를 건네."

Keep 은 PIN 을 안 저장해

정문은 PIN 인데, 반전이 여기 있어: Keep 은 자기 PIN 을 안 저장해. loopback 으로 cwkPippa 에 PIN 검증을 위임해. PIN 을 입력하면 Keep 이 로컬 머신에서 cwkPippa 한테 '이거 맞아?' 를 묻고, cwkPippa 가 답해. Keep 은 비밀을 절대 안 쥐어. 그게 의도적 경계야 — 정체성을 소유하는 시스템(cwkPippa)이 credential 체크도 소유하고, 금융 표면인 Keep 은 비밀-저장 사업에서 완전히 빠져. PIN 이 사는 곳 하나가 줄면 샐 수 있는 곳 하나가 줄어.

손목띠: IP-bound, 시간-bound 쿠키

성공한 체크는 여러 의도적 속성을 가진 세션 쿠키를 mint 해. HMAC-서명(변조 감지 — 위조나 편집 못 해), IP-bound(발급된 주소에서만 작동, 그래서 훔친 쿠키가 다른 데선 쓸모없음), 7일(만료돼, 접근이 영구 아님), HttpOnly(JavaScript 가 못 읽어, script injection 절도를 무디게), SameSite=Strict(cross-site 요청에 안 실려, CSRF 한 부류 차단). 각 속성이 특정 공격을 닫고; 함께면 손목띠를 훔치기 어렵고 훔쳐도 쓸모없게 만들어.

비밀-체크를 비밀을 소유한 시스템에 위임하고; 좁게 scope 된 token 을 돌려받아. 게이트가 필요한 표면이 credential 도 저장하면 안 돼 — 정체성 소유자가 검증하게 하고, 요청 표면엔 시간·binding·닿는 범위가 bound 된 token 을 발급해. 비밀 복사가 적고 타이트하게 scope 된 세션이 필요한 모든 앱에 저장된 credential 을 이겨.

대부분 놓치는 HTTP/HTTPS 분리

Keep 은 두 방식으로 닿아: LAN/tailnet 의 평문 HTTP, 그리고 Tailscale Serve 의 HTTPS. 설계가 다루는 미묘한 함정이 여기 있어: 브라우저 쿠키는 hostname 으로 scope 되지 port 로 안 돼. 그래서 HTTP port 용 쿠키와 HTTPS port 용 쿠키가 순진하면 이름을 공유하고 조용히 서로 덮어, 덜 안전한 세션이 더 안전한 걸 대체하게 둬. Keep 은 HTTP 와 HTTPS 에 별개 쿠키 이름을 써 정확히 두 세션이 서로 못 뭉개게. 그리고 HTTPS 세션은 Secure flag 를 받고(신뢰된 loopback proxy 헤더로) HTTP 는 못 받아 — 그래서 더 안전한 경로가 우연히 downgrade 되지 않고 진짜로 더 안전해.

shell 은 public; 데이터는 gated. static PWA shell 은 lock 화면을 렌더할 수 있게 닿는 채로 남아 — 로그인하려면 앱을 로드해야 하니까. 근데 모든 금융 API, 호환 alias, WebSocket 은 세션 뒤에 gate 돼. health, 세션 상태, PIN 검증, 데이터 없는 static 자산만 public. 벽이 문까지 걸어와 잠긴 걸 보게는 하되; 안을 보게는 안 해.

Code

체크를 위임; 타이트하게 scope 된 쿠키 발급 (예시)·python
def login(pin, request):
    # Keep 은 pin 을 안 저장 — loopback 의 cwkPippa 한테 검증 요청.
    if not cwkpippa_verify_pin_loopback(pin):
        raise HTTPException(401)

    # 세션 손목띠 mint, 중요한 모든 방식으로 scope.
    token = hmac_sign({"ip": client_ip(request), "exp": now() + days(7)})
    is_https = request_is_https(request)   # 신뢰된 loopback proxy 헤더로
    set_cookie(
        # 별개 이름: 쿠키는 port 아니라 HOST scope, 그래서
        # HTTP 와 HTTPS 세션이 이름 공유하고 뭉개면 안 돼.
        name="keep_sess_https" if is_https else "keep_sess_http",
        value=token,
        http_only=True, same_site="Strict",
        secure=is_https,                   # HTTPS 경로만 Secure
    )

External links

Exercise

아는 앱에 대해 세션 쿠키를 스케치하고 각 속성(HttpOnly, SameSite, Secure, 만료, binding)을 그게 막는 특정 공격과 함께 주석 달아. 그다음 미묘한 경우를 다뤄: 앱이 같은 host 의 두 port 에서 닿으면, 왜 쿠키가 충돌할 수 있고, 한 세션이 다른 걸 조용히 대체하는 걸 어떻게 막아? 마지막으로 뭐가 public 으로 남고(로그인 화면 로드용) 뭐가 gate 돼야 하는지 정해.
Hint
각 쿠키 속성이 위협에 매핑돼: HttpOnly vs script 절도, SameSite vs CSRF, Secure vs 평문 가로채기, 만료 vs 영구 접근, IP-binding vs 다른 데서 쿠키 재생. port 함정: 쿠키는 port 아니라 hostname 으로 key, 그래서 같은-host 다른-port 세션은 별개 이름이 필요하거나 덮어. 그리고 shell 은 '로그인해줘' 를 렌더할 만큼 public 하되 모든 데이터 경로는 잠긴 채로.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.