TECH 으로 돌아가기
TECH HACKER NEWS 오늘 9분 읽기 25 READS

이메일처럼 흩어져 대화한다: 평범한 IRC로 말하는 연합 채팅 'Parley'

채팅 서비스의 무게중심은 대개 한 회사의 서버에 있다. 슬랙이든 디스코드든, 대화의 흐름과 신원 확인은 결국 그 회사가 운영하는 중앙 인프라를 거친다. Parley는 이 전제를 뒤집는다. 중심이 없는 채팅 네트워크를 표방하며, 개인이나 팀이 각자 자신의 도메인을 위한 작은 인스턴스를 직접 운영한다. 인스턴스들은 DNS와 잘 알려진(well-known) 신원 문서를 통해 서로를 발견하고, 서명된 메시지를 HTTPS로 주고받는다. 그러면서도 사용자에게는 irssi, WeeChat, Textual 같은 평범한 IRC 클라이언트로, 별도 플러그인 없이 이 연합 네트워크 전체가 그대로 노출된다.

신원 체계가 이메일을 빼닮은 점이 핵심이다. alice@foo.com은 foo.com에서 돌아가고 bob@bar.com은 bar.com에서 돌아간다. bob이 자신의 클라이언트에서 /msg alice@foo.com hi라고 입력하면, 두 인스턴스가 서로를 한 번도 접한 적 없더라도 그 자리에서 연합이 성립한다. 이후 alice에게 bob은 bar.com의 bob으로, bob에게 alice는 foo.com의 alice로 보인다. 여러 인스턴스에 복제되는 #lobby 같은 전역 채널도 함께 제공된다. 현재 상태는 설계를 처음부터 끝까지 실증하고 실제 인스턴스도 구동하지만 아직 보안 강화(hardening)는 되지 않은 '동작하는 개념 증명(proof of concept)'이라고 프로젝트 스스로 못 박고 있다.

운영은 결국 프록시와 DNS의 문제

실무자가 눈여겨볼 부분은 아키텍처보다 운영의 세부다. parleyd 자체는 -tls-cert와 -tls-key를 주지 않는 한 평문 HTTP를 제공하므로, example.com용 인스턴스를 chat.example.com에서 서비스하려면 8443 포트 앞에서 TLS를 종단하는 리버스 프록시가 필요하다. IRC 리스너 역시 평문이라 그 앞에서도 TLS를 종단해야 하며, 다른 인스턴스가 자신을 찾도록 _parley._tcp SRV 레코드를 둔다. SRV가 없으면 피어는 https://example.com/.well-known/parley/로 폴백한다. 데이터 디렉터리의 /data는 인스턴스 키, 피어 캐시, 채널 로그, 계정을 담으므로 반드시 영속 스토리지에 둬야 한다.

설정을 두 곳으로 명확히 갈라놓은 설계도 인상적이다. 프로세스가 DB를 열기 전에 알아야 하는 것, 기계와 네트워크를 서술하는 것, 그리고 누가 어떤 신원을 주장해도 되는지에 관한 비밀과 신뢰 결정은 플래그(각각 대응하는 환경변수 포함)로 지정한다. 반면 운영 중 관리자가 바꿀 만한 값은 DB 안의 '설정(settings)'에 두어, 저장 즉시 재시작 없이 반영된다. 기본값과 다른 값만 저장하므로, 업그레이드로 기본값이 바뀌면 그 키를 한 번도 건드리지 않은 모든 인스턴스에 새 기본값이 적용된다. 별도의 설정 파일은 없으며, 기존 config.json으로 실행하면 각 키가 어떤 플래그·변수·설정으로 바뀌었는지 안내하고 종료한다.

프록시 뒤에서 무너지는 주소 기반 방어

가장 실전적인 함정은 TLS를 IRC 앞에서 종단하는 순간 모든 클라이언트가 프록시 주소 하나로 도착한다는 점이다. 이렇게 되면 로그는 엉뚱한 호스트를 가리키고, 주소별 제한은 아무 일도 못 하거나 모두를 한꺼번에 잠가버린다. 해법은 프록시를 irc_proxies에 등록하고, 그 프록시가 모든 연결에 PROXY 프로토콜 헤더(v1 또는 v2)를 앞에 붙이도록 하는 것이다. 문서는 이 둘이 반드시 '함께' 바뀌어야 한다고 경고한다. 헤더를 보내지 않는 프록시를 등록하면 그로부터의 모든 연결이 끊기고, 등록되지 않은 주소에서 온 헤더는 IRC 파서에 쓰레기로 먹힌다. 안전한 순서란 없으니 둘을 동시에 바꾸고 되돌릴 준비를 하라는 것이다.

등록할 주소는 추측하지 않는다. 한 번 접속한 뒤 로그를 읽으면 처음 본 클라이언트 주소가 보고되는데, 이는 언제나 소켓상의 실제 주소이지 헤더가 실어 나른 주소가 아니다. 헤더 주소를 보고하면 목록과 결코 맞지 않을 값을 손에 쥐여주는 셈이기 때문이다. 프록시가 등록되고 헤더를 보내기 시작하면 같은 줄이 trusted_proxy=true로 다시 나타나 두 절반이 합의했음을 확인해준다. 웹 리스너도 같은 맹점을 가져 http_proxies라는 별도 설정으로 X-Forwarded-For를 신뢰하게 하는데, 이를 auth.trusted_headers.proxies와 혼동하면 안 된다. 후자에 오른 프록시는 사용자가 누구인지 주장할 수 있어, /login에 Remote-User를 실으면 암호 없이 로그인된다. 단지 레이트 리밋을 고치겠다고 무암호 로그인을 켤 이유는 없다.

키 하나의 무게와 사용성의 실수 방지

Parley는 복구 불가능한 실패 지점을 솔직하게 노출한다. /identity.key는 도메인의 신원 그 자체인 키쌍이며, 잃으면 옛 공개키를 캐시한 모든 피어가 인스턴스를 거부한다. 다만 키 유출 시 교체는 재난이 아니라 지원되는 작업으로 설계됐다. 검증 실패한 서명을 만난 피어는 인스턴스 문서를 한 번 다시 읽어 새 공개키를 얻으므로 비용은 요청 한 번의 실패뿐이고, 옛 키는 identity.key..bak로 보존된다. 이 명령들이 API가 아니라 데이터 디렉터리를 직접 읽고 쓰는 이유도 분명하다. 관리자 토큰으로 개인키를 네트워크 너머에서 가져올 수 있어서는 안 되기 때문이다.

작은 결정 하나가 프로토콜 호환성을 어떻게 좌우하는지 보여주는 사례도 있다. 원격 사용자 표기를 alice/foo.com에서 alice:foo.com으로 바꾼 변경이 그것이다. 슬래시 구분자는 draft/relaymsg에서 빌려온 것인데, 엄격한 클라이언트는 서버명과 닉을 혼동하지 않으려 '점과 콜론이 둘 다 있거나 둘 다 없을 것'을 요구한다. 도메인이 붙은 이름은 점만 있고 콜론이 없어, 그런 클라이언트는 접두사 전체를 서버명으로 읽고 원격 사용자의 JOIN·PART·QUIT·NICK·AWAY를 전부 버렸다. PRIVMSG는 조용히 열화돼, 대화는 멀쩡해 보이는데 명단만 갱신되지 않는 증상이 나타났다. 이제 ISUPPORT의 PARLEYSEP가 어떤 문자가 쓰이는지 알려주므로, 봇은 구분자를 하드코딩하지 말고 여기서 읽어야 한다. 외부에서 인스턴스를 점검하는 parleyctl check까지 갖춰, SRV 레코드 누락 같은 흔한 실패를 피어에게도 지적할 수 있게 해둔 점은 이 프로젝트가 '연합'을 말뿐 아니라 운영 도구 수준에서 진지하게 다루고 있음을 보여준다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://git.mills.io/prologic/parley
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...