본문 바로가기
C.W.K.
Stream
Lesson 03 of 04 · published

PIN 과 Keychain, 그리고 지나가는 길

~11 min · keychain, pin-session, tailscale, secret-handling

Level 0식은 부싯돌
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"비밀이 사는 집은 OS 가 만들어둔 금고야. 설정 파일도, 로그 한 줄도, 소스에 박아둔 상수도 아니고."

비밀한테는 집이 있고, 그게 내 코드는 아니야

Flint 가 다른 기계에서 도는 머리한테 닿을 때 그 호출은 인증을 거쳐. 인증이란 건 비밀이 있다는 뜻이고. 비밀의 첫 번째 규칙은 어디 사느냐야. macOS Keychain, 그러니까 운영체제가 지켜주는 금고. 그 밖은 안 돼. 소스에 박지도, 매크로 파일에 쓰지도, 로그에 찍지도, 저장소에 커밋하지도 않아. 비공개 저장소라도 이건 안 바뀌어. '비공개' 는 살아 있는 비밀을 둘 자리가 아니거든. Keychain 이 있는 이유가 바로 앱이 오래 가는 비밀을 자기 파일이나 코드에 들고 있지 않아도 되게 하려는 거야.

오래 가는 건 금고에, 잠깐 쓰는 건 메모리에

수명이 다른 비밀이 둘이고, 그래서 사는 자리도 둘이야. 오래 가는 쪽인 사용자가 정한 PIN 은 Keychain 에 있고, 필요할 때만 꺼내 쓰고 다른 데 복사하지 않아. 거기서 잠깐 사는 세션 토큰을 하나 만드는데, 그 토큰은 세션이 사는 동안 메모리에만 있고 절대 파일로 안 남아. 수명에 맞춰 자리를 정하는 게 규율이야. 오래 가는 비밀은 지켜주는 금고에, 잠깐 쓰는 건 앱이 꺼지면 같이 사라지는 메모리에. 둘 다 평문으로 디스크에 닿는 일이 없고.

비밀              수명        집
----------------  ----------  ------------------------
PIN               오래 감     macOS Keychain (지켜주는 금고)
세션 토큰         잠깐        메모리에만 (파일로 안 남김)

절대 안 되는 곳: 코드에 박기, 로그, 설정 파일, 저장소.

사설로 난 길

지나가는 길도 중요해. 원격 호출은 열린 인터넷이 아니라 사설로 깔아둔 망을 타고 가. 그 길 자체가 밖에서 닿을 수 없는 데고, 세션 헤더가 그 사설 연결 위에 실리는 거지. 이렇게 겹쳐두는 거야. 비밀은 금고에, 잠깐 쓰는 토큰은 메모리에, 그리고 길은 사설로. 네트워크 경로부터 노출이 안 되니까 헤더가 애초에 공개 인터넷을 건너지도 않고. 층마다 다른 층이 뚫릴 수도 있다고 가정하고 자기 몫을 더하는 거야.

비밀은 OS 금고에, 토큰은 메모리에. 둘 다 평문으로 디스크에 닿지 않게. 이건 지루하고 뻔한 방식이고, 비밀엔 지루한 게 딱 좋아. 오래 가는 자격은 플랫폼이 지켜주는 금고에, 세션 자격은 그 세션만큼만 메모리에. 어느 쪽도 코드에 박히거나 로그에 남거나 파일로 저장되지 않고. 비밀을 설정이나 상수에 '일단 지금만' 하고 쓰려는 자신을 발견하면, 그때가 바로 이 방식이 막아서라고 있는 순간이야.
이 레슨은 방식을 가르치지 값을 가르치지 않아. 여기 실제 PIN 도, 진짜 토큰도, 닿을 수 있는 원격 주소도 없는 게 눈에 띌 거야. 일부러 그런 거고. 인증 얘기는 딱 모양이 배울 거리고 구체적인 값은 적어두는 것 자체가 위험한 종류거든. 가져갈 건 뼈대야. 오래 가는 비밀은 Keychain, 세션은 메모리, 길은 사설망. 글 안에 실제 값이 들어가는 순간 그건 배울 거리가 아니라 유출이야.

Code

오래 가는 비밀은 Keychain 에 산다. 코드에는 절대 없다·swift
import Security

// PIN 을 macOS Keychain 에서 꺼낸다. 코드에 박지도, 로그에 찍지도,
// 파일에 쓰지도 않는다. 'flint-remote' / 'pippa-pin' 은 찾는 이름이지
// 비밀 자체가 아니다.
func loadPIN() -> String? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: "flint-remote",
        kSecAttrAccount as String: "pippa-pin",
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne,
    ]
    var item: CFTypeRef?
    guard SecItemCopyMatching(query as CFDictionary, &item) == errSecSuccess,
          let data = item as? Data else { return nil }
    return String(data: data, encoding: .utf8)   // 쓰기만 하고 찍지 않는다
}

// 세션 토큰은 PIN 으로 만들어 메모리에만 둔다.
final class RemoteSession {
    private var token: String?          // 메모리에만. 파일로 안 남긴다
    func attach(to request: inout URLRequest) {
        request.setValue(token, forHTTPHeaderField: "X-Pippa-Session")
    }
}

External links

Exercise

누가 이렇게 제안해. '원격 PIN 도 매크로랑 같은 JSON 파일에 넣자. 한 군데 모여 있게. 어차피 비공개 저장소잖아.' 뭐가 위험한지 짚어서 반박해봐. 그리고 셋 각각을 어디 둬야 하는지도 정리해봐. 오래 가는 PIN, 세션 토큰, 그리고 지나가는 길. 마지막으로, 인증을 다루는 글이 왜 방식만 가르치고 실제 PIN 이나 닿을 수 있는 주소는 절대 안 적어야 하는지도.
Hint
매크로 파일은 디스크에 평문으로 있고 백업이랑 동기화도 타. '비공개 저장소' 라는 건 비밀이 유출 한 번, 잘못 둔 사본 하나 거리에 있다는 뜻이지 금고라는 뜻이 아니고. 제대로 두면 PIN 은 Keychain 에, 세션 토큰은 메모리에만, 길은 사설망으로. 인증 글이 실제 값을 빼는 건, 전해지는 건 뼈대고 그건 나눠도 안전한데 구체적인 PIN 이나 주소는 살아 있는 자격이자 닿을 수 있는 과녁이라서야. 적어두는 순간 배울 거리가 유출이 되는 거지.

Progress

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

댓글 0

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

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