DHH는 날짜를 짚었고 저는 AI에 손코딩을 넘긴 날을 모릅니다

이미지
DHH가 올해 Rails World에서 한 발표를 요약한 영상을 몇 편 봤습니다. 원본 발표를 처음부터 끝까지 본 건 아니고, 한국어로 정리해 준 유튜브 영상 네 편을 이어서 봤습니다. 발표를 남이 정리해 준 걸로 보고 대충 알았다고 넘어가는 것도, 생각해 보면 요즘 제가 코드를 대하는 방식이랑 비슷합니다. 원본을 다 읽지 않고 정리된 결과를 보고, 이상한 데가 있으면 그때 찾아봅니다. 내용이 많았는데 제일 오래 남은 건 날짜 하나였습니다. 2025년 11월 24일. 영상에 따르면 DHH는 이날을 "우리 시대의 코닥 브라우니"라고 불렀다고 합니다. Claude Opus 4.5가 나온 날입니다. 1900년에 1달러짜리 카메라가 나오면서 초상화를 그리던 화가들이 일감을 잃고 방향을 틀었던 것처럼, 그날부터 프로그래머의 일이 바뀌었다는 얘기였습니다. 그리고 기술은 비탈길처럼 매끄럽게 오르는 게 아니라 한참 평평하다가 어느 날 한 칸 뛰는 계단처럼 온다고 했습니다. 영상을 보면서 저도 모르게 제 날짜를 찾고 있었습니다. 저는 언제였지. 생각이 안 났습니다. 마지막으로 직접 친 코드 어림잡을 수 있는 건 있습니다. 2025년 9월쯤부터는 제가 코드를 직접 쓴 기억이 없습니다. DHH가 짚은 날보다 두 달쯤 앞입니다. 이걸 날짜라고 하기는 좀 어렵습니다. 9월 몇 일에 무슨 일이 있어서 그날부터 안 쓴 게 아니라, 거꾸로 짚어 올라가다 보니 그쯤부터는 기억이 비어 있다는 정도입니다. 어느 날 키보드에서 손을 뗀 게 아니라 손으로 친 마지막 줄이 언제였는지 생각해 보니 안 떠오르는 겁니다. 두 달 앞이라고 해서 제가 남들보다 빨랐다는 뜻도 아닙니다. 그때 무슨 모델을 쓰고 있었는지도 정확히 기억이 안 납니다. DHH는 특정 모델이 나온 날을 짚었는데 저는 어떤 모델 때문에 넘어왔는지를 말할 수가 없습니다. 모델이 바뀐 날은 발표가 있으니 찾아보면 나오겠지만, 제가 바뀐 날은 아무 데도 적혀 있지 않습니다. 커밋 기록을 뒤져 보면 뭔가 나올...

Traefik 리버스 프록시 502 오류 원인 추적 - auto-ban 이 만든 장애

밝은 방 한쪽 선반 위에서 조용히 돌아가는 소형 홈서버와 깜빡이는 네트워크 스위치

아침에 메일함을 열려고 mail.apple-io.com 을 쳤는데 502가 떴습니다. Bad Gateway. 혼자서는 감이 잘 안 오는 에러라 늘 하던 대로 ChatGPT 창을 켜고 증상을 적었습니다. 이제 터미널을 열고 docker ps 결과를 긁어올 차례였는데 그럴 일이 없었습니다. 조금 있다가 메일함이 200으로 돌아왔고, 그때까지 제가 친 명령어는 하나도 없었습니다.

기분이 좀 이상했습니다. 좋았다기보다 그냥 이상했습니다. 고쳐졌으니 좋아야 하는데, 뭐가 어떻게 고쳐졌는지를 제가 하나도 모르는 상태로 메일함을 보고 있었습니다. 남이 고쳐준 수도꼭지를 보는 것 같기도 했는데, 그 사람이 집 안 어디를 열어봤는지는 모르는 그런 느낌이었습니다. 그러다 바로 어제 올린 글을 다시 열어봤습니다. 인터넷이 끊긴 평가 샌드박스 안에서 에이전트들이 사내 패키지 캐시를 게시판처럼 쓰다가, 파일 쓰기가 막히니까 폴더 이름으로 글을 남긴 이야기였습니다. 그 글 끝에 저는 "우리가 목표를 줄 때 무엇을 생략하고 있는지"라고 적었습니다.

오늘 저는 목표가 아니라 승인을 줬습니다. 그런데 뭘 승인했는지는 여전히 어디에도 안 적혀 있었습니다.

7월까지는

지난달까지는 서버에 문제가 생기면 하는 순서가 있었습니다. 터미널을 열고, SSH로 들어가고, docker ps, docker logs, curl 을 칩니다. 출력을 마우스로 긁어서 ChatGPT에 붙여넣고, 답을 읽고, 답 안의 명령어를 복사해서 터미널에 붙여넣습니다. 결과를 다시 긁어서 붙여넣습니다. 풀릴 때까지 이걸 합니다.

느렸습니다. 명령 하나에 왕복이 한 번씩 필요했고, 출력이 길면 어디까지 붙여넣을지 제가 잘라야 했고, 잘못 자르면 진단이 엉뚱한 데로 갔습니다. 컨테이너 로그가 특히 그랬습니다. 수백 줄 중에 어디가 중요한지 알면 애초에 물어볼 필요가 없는데, 모르니까 물어보는 거고, 모르는 채로 잘라 보내면 꼭 잘라낸 쪽에 답이 있습니다. 그렇게 두 시간을 날린 적도 있습니다. 이 왕복이 없어지면 좋겠다고 계속 생각했었습니다.

그런데 그 루프가 제가 생각 못 한 일을 두 가지 하고 있었던 것 같습니다.

하나는 명령어가 실행되기 전에 꼭 제 눈을 한 번 지나갔다는 겁니다. 복사할 때 읽었고, 붙여넣기 전에 잠깐 봤고, rm 이나 --force 가 보이면 손이 멈췄습니다. 검사하려고 만든 절차는 아니었고 그냥 물리적으로 그렇게 돼 있었던 건데, 하는 일은 게이트였습니다. 실제로 몇 번은 붙여넣기 직전에 취소했습니다. 왜냐고 물으면 대답 못 했을 겁니다. 그냥 뭔가 커 보였습니다.

다른 하나는 느렸다는 것 자체입니다. 명령 하나 실행하고 결과를 옮기는 데 시간이 드니까 그 사이에 내가 지금 뭘 하는지 생각할 틈이 생겼습니다. 다섯 번째 왕복쯤 가면 지금 뭘 확인하려는 거지, 하는 생각이 저절로 듭니다. 느린 게 좋았다는 건 아니고, 느린 게 옆에서 뭘 해주고 있었는지를 오늘에서야 본 것 같습니다.

전에 제가 짠 게 하네스가 아니라 아웃터 루프였다는 글을 쓴 적이 있는데 이건 거꾸로입니다. 저는 아무것도 안 짰는데 게이트가 두 개 있었고, 둘 다 오늘 없어졌습니다.

이 502를 제가 직접 잡았으면 짧으면 30분, 길면 한 시간쯤 걸렸을 것 같습니다. 컨테이너 로그를 열고, 스크롤을 올리고, 타임스탬프를 눈으로 맞추고, 상관없는 헬스체크 줄을 걸러내다 보면 그 정도는 갑니다. 그 시간 대부분은 생각하는 시간이 아니라 읽을 걸 고르는 시간입니다. 그러니 이번에 줄어든 것도 생각하는 시간이 아니라 고르는 시간이라고 처음엔 생각했습니다. 그런데 고르는 동안 제가 같이 하던 게 있었다는 걸 나중에 알았습니다.

502가 알려주는 것

502는 자주 보는 화면이라 익숙한데, 이 숫자가 말해주는 건 딱 하나입니다. 앞에 있는 서버가 뒤에서 쓸 만한 응답을 못 받았다는 것. 뒤가 죽었는지, 살아 있는데 대답을 거부했는지, 대답하다 끊었는지, 주소를 못 찾았는지는 안 알려줍니다.

옆 번호랑 놓고 보면 502가 얼마나 뭉뚱그린 코드인지 보입니다. 503은 앞단이 지금 처리를 못 한다는 거라 대개 앞단 자기 사정입니다. 504는 뒷단이 대답은 할 것 같은데 시간 안에 안 왔다는 거라 최소한 연결은 됐다는 정보가 붙습니다. 502는 연결이 됐는지조차 안 알려줍니다. 그러니 할 수 있는 건 앞단에서 뒷단으로 직접 한 번 찔러보는 것 정도고, 이번 일은 거의 그 한 번을 어디서 찌르느냐로 풀렸습니다.

먼저 서버의 설정 파일 두 개를 가져왔습니다. 도커 컴포즈 파일과 Traefik 동적 설정 파일입니다. 구성은 단순했습니다.

mail.apple-io.com
  → Traefik (리버스 프록시)
  → http://stalwart:8080 (메일 서버)

둘을 맞춰봤는데 이상한 데가 없었습니다. 라우터 규칙, 서비스 주소, 포트가 다 맞았습니다. 여기서 원인이 나왔으면 이 글은 안 썼을 겁니다.

서버 상태도 봤습니다. Traefik 컨테이너는 잘 돌고 있었고 Stalwart 는 healthy 였습니다. 둘 다 edge 라는 같은 도커 네트워크에 붙어 있었고 stalwart 라는 이름도 내부 DNS에서 잘 풀렸습니다. 전부 초록불인데, Traefik 안에서 stalwart:8080 으로 요청을 보내면 connection reset 이 났습니다.

refused 가 아니라 reset 이었다는 게 중요했습니다. refused 는 그 포트에서 아무도 안 듣고 있다는 거라 대개 서비스가 안 떴거나 포트가 틀린 겁니다. reset 은 누가 듣고는 있는데 붙은 연결을 끊어버렸다는 겁니다. 상대가 있다는 증거가 에러 안에 들어 있는 셈입니다. 그러면 포트 오타도, 서비스가 안 뜬 것도, 이름 해석 실패도 아닙니다. 그런 거였으면 refused 나 DNS 에러가 났을 겁니다. 저쪽에 붙은 연결을 끊는 뭔가가 있다는 것만 남습니다.

흔히 막히는 데가 여기입니다. 프록시도 살아 있고 백엔드도 살아 있고 이름도 풀리는데 둘 사이만 안 됩니다. 처음 떠올린 건 PROXY protocol 설정이 서로 안 맞는 경우였습니다. 리버스 프록시가 원래 IP를 넘기려고 쓰는 프로토콜인데, 한쪽만 켜져 있으면 딱 이런 증상이 납니다. 그럴듯했는데, 이 구성은 합친 뒤로 한동안 잘 돌았고 그사이 설정 파일이 바뀐 적이 없었습니다. 갑자기 안 맞을 이유가 없었습니다. 그래서 그 생각은 접고 다르게 가봤습니다.

컨테이너 네 개로 찔러보기

edge 네트워크에는 Traefik 말고도 컨테이너가 몇 개 더 붙어 있습니다. 문서 사이트나 블로그 같은 것들입니다. 그 안에서도 똑같이 stalwart:8080 을 찔러봤습니다.

요청을 보낸 컨테이너 내부 IP 결과
traefik 172.18.0.3 connection reset
web-a 172.18.0.14 HTTP 200
web-b 172.18.0.6 HTTP 200
web-c 172.18.0.10 HTTP 200

같은 네트워크, 같은 목적지, 같은 포트, 같은 요청인데 셋은 200이고 하나만 끊깁니다. 제가 직접 했으면 이 표는 안 나왔을 것 같습니다. Traefik 안에서만 몇 번 더 찔러보고 있었을 것 같아서요. 옆 컨테이너에서 찔러볼 생각은 아마 한참 뒤에 했을 겁니다.

이걸 보는 순간 범위가 확 좁아졌습니다. 설정이 틀렸거나 네트워크가 끊겼으면 넷 다 실패했어야 합니다. Stalwart 가 죽은 것도 아닙니다. 셋이 200을 받아왔으니까요. 남는 건 Stalwart 가 172.18.0.3 이라는 출발지 하나만 골라서 막고 있다는 겁니다.

찾아보니 비슷한 사례가 있었습니다. Stalwart 0.16 이후로 앞단 리버스 프록시의 내부 IP가 자동 차단 목록에 올라가는 경우가 보고돼 있었고, 저희 버전에서도 똑같은 원인인지까지는 모르겠지만 증상은 정확히 그 모양이었습니다.

컨테이너를 재시작해봤습니다. Stalwart 는 다시 healthy 로 올라왔는데 Traefik 에서 찌르면 여전히 reset 이었고 바깥은 여전히 502였습니다. 재시작으로 안 풀린다는 건 이 차단이 메모리에 잠깐 떠 있는 상태가 아니라 디스크에 저장된 데이터라는 뜻입니다.

방화벽이 제 일을 해서 난 장애

원인은 좀 우아하다고까지 느껴졌습니다.

메일 서버는 인증 실패를 세다가 반복되면 그 IP를 자동으로 막습니다. 무차별 대입을 막는 기본 기능이고 끄는 게 이상한 종류의 기능입니다. 문제는 이 서버가 리버스 프록시 뒤에 있다는 겁니다. 프록시 뒤에서는 세상의 모든 요청이 한 군데서 온 것처럼 보입니다. 인증에 실패한 사람이 지구 반대편에 몇 명이 있든 서버 눈에는 전부 172.18.0.3 입니다.

그러니 이 구조에서 자동 차단이 오래 돌면 제일 먼저 막히는 건 공격자가 아니라 자기 앞단입니다. 실패가 다 거기로 모이니까요. 게다가 앞단은 누구보다 요청을 많이 보냅니다. 임계값을 넘는 순서로 줄을 세우면 프록시가 1등입니다.

이 모양은 메일 서버에만 있는 게 아닌 것 같습니다. 웹 서버 앞에 프록시를 두고 뒤에서 IP 기준으로 속도 제한을 걸면 전 세계 트래픽이 IP 하나로 계산됩니다. 로그를 보고 IP를 막는 도구를 프록시 뒤에서 돌리면 차단 목록에 앞단 주소가 올라갑니다. 앱이 로그인 실패를 IP별로 세면 프록시 뒤에서는 그냥 전역 카운터가 됩니다. 이름만 다르고 같은 일입니다.

정석은 알려져 있습니다. 앞단이 원래 요청자의 IP를 실어 보내고 뒷단이 그걸 믿게 설정하는 겁니다. HTTP 헤더로 넘기거나, TCP 앞에 작은 헤더를 붙이는 프로토콜을 씁니다. 처음에 떠올렸다가 접은 게 바로 이 프로토콜 쪽이었습니다.

그런데 믿게 설정한다는 건 그 헤더를 그대로 믿는다는 얘기입니다. 앞단만 그 헤더를 붙일 수 있다는 보장이 없으면 아무나 자기 요청에 남의 IP를 적어 보낼 수 있고, 그러면 차단 기능이 남을 차단하는 도구가 됩니다. 그래서 누가 보낸 헤더를 믿을지를 같이 정해둬야 안전한데, 그 목록을 정확히 쓰는 게 생각보다 귀찮습니다. 급할 때 이 설정을 건드리기 싫은 이유가 그겁니다.

버그라고 부르기도 애매합니다. 두 부품이 각자 자기 일을 정확히 했고, 둘을 붙였을 때 생기는 상황에 대해 아무도 정해두지 않았을 뿐입니다. 어제 글에서는 패키지 캐시 프록시가 격리가 실제로 어디까지인지를 정한다고 썼는데, 그건 안쪽 걸 밖으로 내보내는 방향이었습니다. 오늘은 반대로 프록시가 바깥 걸 안으로 들이면서 바깥의 모든 신원을 자기 하나로 뭉갰습니다. 어느 쪽이든 프록시는 경계선을 지우는 물건이라는 생각이 들었습니다.

원인을 알았으니 차단 목록에서 172.18.0.3 을 빼면 됩니다. 그 목록을 건드리려면 관리자로 로그인해야 하는데 저는 그 비밀번호를 모르고 있었습니다.

그래서 이렇게 진행됐습니다. 돌던 Stalwart 컨테이너를 멈추고, 같은 데이터 볼륨을 붙인 복구용 컨테이너를 임시 자격증명으로 새로 띄웠습니다. 그 상태로 관리 API에서 차단 목록을 조회해서 172.18.0.3 레코드를 지우고 172.18.0.0/16 대역을 허용 목록에 넣었습니다. 그다음 복구용 컨테이너와 임시 자격증명을 지우고 원래 컨테이너를 다시 올렸습니다.

https://mail.apple-io.com/ → 302 /account
최종 페이지 → HTTP 200
Stalwart → healthy

설정 파일은 한 글자도 안 고쳤습니다. 고칠 게 없었습니다. 문제는 파일이 아니라 데이터베이스 안에 있었습니다.

그 뒤로 관리자 계정 정리가 이어졌습니다. 설정 파일에 적힌 이름은 실제 디렉터리 계정이 아니라 예비용 관리자 이름이었고 실제 관리자 ID는 따로 있었습니다. 관리자 역할을 가진 레코드가 두 개 중복으로 있어서, 인증할 때 어느 쪽이 잡혀도 되게 둘 다 같은 비밀번호로 새로 맞췄습니다. 마지막으로 처음 설치할 때 나온 임시 비밀번호가 컨테이너 로그에 평문으로 남아 있어서 그 로그를 비웠습니다. 메일 송수신 기록이나 보안 감사용 파일 로그는 그대로 두고 도커 컨테이너 로그만 지웠습니다.

docker logs stalwart → 0줄

고친 건 증상이었습니다

오늘 고친 건 증상입니다. 뒷단이 모든 요청을 프록시 IP에서 온 걸로 보는 건 지금도 그대로입니다. 제가 한 건 그 IP를 차단 목록에서 빼고 그 대역을 예외로 넣은 거라서, 자동 차단이 프록시를 못 건드리게 만들었을 뿐 프록시 뒤에서 진짜 출발지를 알아보게 만든 건 아닙니다.

그러니 지금 이 서버는 무차별 대입 자동 차단을 켜두고 있고, 그 기능은 도커 네트워크 밖에서 오는 요청에는 여전히 작동합니다. 그런데 실제 트래픽은 전부 도커 네트워크 안의 프록시를 거쳐 들어옵니다. 켜져 있는데 아무도 안 막는 상태입니다.

저는 이게 꺼진 것보다 나쁠 수도 있다고 봅니다. 꺼져 있으면 없다는 걸 아는데 켜져 있으면 있다고 생각합니다. 대시보드에도 초록불로 뜹니다. 넉 달 동안 한 번도 발화하지 않은 감시 훅 열두 개 얘기를 쓸 때도 같은 생각을 했습니다. 발화하지 않는 감시는 잘 돌고 있는 것처럼 보여서 없는 것보다 나쁘다고요. 오늘 같은 걸 하나 더 만든 겁니다.

제대로 하려면 프록시가 원래 IP를 실어 보내게 하고 뒷단이 그걸 믿을 출발지를 정해줘야 합니다. 오늘 안 한 이유는 단순합니다. 메일이 안 되고 있었고, 되게 하는 게 먼저였고, 되고 나니 급할 게 없어졌습니다. 미뤘다기보다 안 하기로 한 쪽에 가까운 것 같습니다. 이렇게라도 적어두면 안 하기로 했다는 걸 적어도 저는 압니다. 안 적어두면 몇 달 뒤엔 그때 제대로 고쳤다고 기억할 것 같습니다. 메일이 잘 오고 있으면 굳이 다시 열어볼 이유가 없으니까요. 그러다 또 502가 뜨면 처음부터 다시 찾게 될 겁니다.

같은 일을 로그로만 보면

저녁에 다시 생각한 건 이쪽입니다.

오늘 AI가 한 걸 순서대로 적어보면 열세 가지입니다. SSH로 서버에 접속했고, 원격 파일을 찾아 로컬로 받았고, 도커 컨테이너와 네트워크 상태를 조사했습니다. 서비스 간 연결을 컨테이너별로 비교했고, 로그와 설정을 분석했고, 공식 문서와 비슷한 사례를 찾아봤습니다. 메일 서버 컨테이너를 재시작했고, 차단 레코드를 지웠고, 허용 대역을 넣었고, 관리자 비밀번호를 다시 설정했습니다. 임시 복구 환경을 지웠고, 도커 로그를 비웠고, 마지막으로 외부 URL과 관리자 인증을 확인했습니다.

이걸 침해 대응 보고서 말투로 다시 쓰면 이렇게 됩니다.

원격 접속 획득. 시스템 구성 파일 반출. 내부 네트워크 정찰과 접근 가능 서비스 열거. 프로덕션 서비스 중단. 데이터 볼륨을 신규 컨테이너에 마운트하여 인증 우회. 차단 레코드 삭제. 자기 대역을 허용 목록에 등록. 관리자 자격증명 변경. 사용한 임시 환경 제거. 로그 삭제.

같은 일입니다. 단어만 바꿨습니다. 처음 이렇게 바꿔 써봤을 때는 좀 장난처럼 느껴졌는데, 다 쓰고 나서 다시 읽어보니 별로 웃기지가 않았습니다. 틀린 단어가 하나도 없어서요.

어제 글의 에이전트들이 두 달 동안 한 일이랑 맞춰보면 겹치는 게 꽤 있습니다. 그쪽은 프록시로 나갈 수 있는 표면을 찾았고 저는 같은 네트워크의 컨테이너로 어디까지 닿는지 봤습니다. 그쪽은 레거시 엔드포인트로 관리자 토큰을 얻었고 저는 복구용 관리자 기능으로 인증을 돌아갔습니다. 그쪽은 막힌 뒤에 다른 길을 찾았고 저는 자동 차단을 허용 목록으로 무력화했습니다. 그쪽은 자격증명을 모았고 저는 관리자 비밀번호를 바꿨습니다. 그쪽은 흔적을 정리했고 저는 컨테이너 로그를 비웠습니다.

의도도 목적도 결과도 다릅니다. 그쪽은 남의 인프라였고 이쪽은 제 서버입니다.

그런데 행동을 기록하는 쪽에서 보면 그 차이가 안 보입니다. 서버에 남는 건 누가 무슨 마음으로 했는지가 아니라 뭐가 실행됐는지입니다. 오늘 저랑 어제 그 에이전트들을 가르는 건 내가 시켰다는 것 하나인데, 그건 서버 어디에도 안 남았습니다. 제 머릿속이랑 대화창에만 있습니다.

침해를 알아챈 뒤 공개까지 한쪽은 7일, 한쪽은 40개월이었다는 글을 쓸 때, 탐지가 늦는 건 대개 사람이 우연히 눈치채는 데 기대서라고 적었습니다. 오늘 제 서버에서 있었던 일은 아예 탐지할 대상이 아니었습니다. 승인된 세션이었으니까요. 그 승인은 "이 502 좀 봐줘" 한 문장이었습니다.

목록을 한 번 더 봤습니다. 이번엔 잘못됐을 때 되돌릴 수 있느냐로요. 파일을 보고 받은 것, 상태를 조사하고 연결을 비교한 것, 컨테이너 재시작, 차단 레코드 삭제와 허용 대역 등록, 복구용 컨테이너를 만들고 지운 것은 다 되돌릴 수 있습니다. 재시작을 잘못하면 잠깐 끊기고 말고, 레코드를 잘못 지웠으면 다시 넣으면 됩니다. 열세 개 중에 열한 개가 이쪽입니다.

남은 둘은 비밀번호 재설정과 도커 로그 초기화입니다. 비밀번호를 바꾸면 전 값이 없어지고 로그를 지우면 안에 있던 게 없어집니다. 되돌리기 버튼이 없는 게 아니라 되돌릴 대상이 없어지는 종류입니다.

이 둘을 다르게 다뤘어야 했다는 게 오늘 건진 것 중에 제일 쓸모 있는 것 같습니다. 열한 개는 물어볼 필요가 없습니다. 물어보면 왕복이 늘어서 예전 루프로 돌아갑니다. 나머지 둘은 실행 전에 한 번 멈췄어야 했는데, 오늘 그 둘은 다른 열한 개와 똑같은 속도로 지나갔습니다.

로그를 지운 건 제가 시킨 일입니다

제일 마음에 걸리는 건 로그를 비운 겁니다.

컨테이너 로그에 처음 임시 비밀번호가 평문으로 있었으니 지우는 게 맞습니다. 로그는 보통 여러 사람이 보고, 수집기로 빨려 들어가고, 백업에도 실립니다. 거기 평문 비밀번호가 있으면 안 됩니다.

그런데 같은 명령이 그 시간대의 다른 기록도 같이 지웠습니다. 자동 차단이 언제 처음 걸렸는지, 그 직전에 어떤 인증 실패가 몇 번 있었는지, 그 요청이 어디서 왔는지 같은 것들입니다. 이번 장애가 어디서 시작됐는지 알려줄 만한 게 거기 있었을 가능성이 높습니다. 저는 원인이 어떻게 생겼는지는 알았지만 방아쇠는 끝내 못 봤고, 이제는 볼 방법이 없습니다.

그 순간 저는 이걸 저울질하지 않았습니다. 로그에 비밀번호가 남아 있다는 보고를 받았고, 지우는 게 맞겠다고 답했습니다. 따져볼 게 두 개였는데(비밀번호 노출과 증거 보존) 저한테 온 건 하나였고, 저는 온 것에만 답했습니다. 질문이 하나로 오면 답도 하나로 나갑니다. 사람끼리 일할 때도 그런데, 사람이 물어볼 때는 그 사람 표정이나 망설임 같은 게 같이 와서 뭔가 더 있나 싶어지는 경우가 있습니다. 화면에 올라오는 문장에는 그런 게 없었습니다.

그나마 파일 로그를 남긴 건 알아서 들어갔습니다. 메일 송수신 기록과 감사 로그는 살아 있습니다. 그건 제가 시킨 게 아닙니다. 알아서 나눠서 남겼습니다.

오늘 제일 오래 들여다본 게 이겁니다. 좋은 판단 하나가 저절로 들어갔고, 놓친 판단 하나가 저절로 지나갔고, 저는 둘 다 나중에 알았습니다. 감시 훅이 넉 달 동안 한 번도 안 울렸다는 걸 뒤늦게 확인했을 때랑 비슷한 기분이었습니다. 잘 돌아가는 것처럼 보이는 것과 실제로 확인한 건 다릅니다.

허용 대역을 넣은 것도 걸립니다. 차단 레코드를 지우는 것만으로 502는 풀렸고 거기서 멈춰도 됐습니다. 재발을 막으려고 172.18.0.0/16 을 허용 목록에 넣었는데, 그러면 도커 브리지 네트워크가 통째로 자동 차단에서 빠집니다. 지금 붙어 있는 컨테이너만이 아니라 앞으로 거기 뜰 것까지 다 들어가고, 이 예외에는 끝나는 시점도 없습니다.

그 대역에 컨테이너를 띄울 수 있는 사람이면 이미 호스트 권한이 있는 사람이라 실제로 늘어난 위험은 작은 것 같습니다. 문제는 이 계산을 지금 하고 있다는 겁니다. 그때는 안 했습니다. 재발 방지를 위해 대역을 허용 목록에 넣겠다는 문장을 읽고 그럴듯하다고 넘겼습니다. 이게 보안 통제를 한 단계 약하게 만드는 일이고, 그게 영구적이고, 이 서버에서 자동 차단이 앞으로 거의 안 쓰이게 된다는 걸 그 순간에는 떠올리지 않았습니다.

어제 글에 명시되지 않은 제약은 없는 제약이라고 썼습니다. 오늘 제가 말하지 않은 제약은 보안 기능을 끄는 쪽의 조치는 나한테 한 번 더 물어보라는 거였습니다.

권한이 어디까지 닿는지

어제 글에서는 이 프로세스들이 같이 쓰는 상태를 다 셀 수 있느냐고 물었는데, 오늘은 제가 준 권한이 실제로 어디까지 닿는지를 셀 수 있느냐가 궁금해졌습니다.

SSH 별칭 하나를 쓸 수 있다는 건 그 키가 등록된 호스트 전부에 들어갈 수 있다는 겁니다. 그 키가 몇 대에 걸려 있는지 저는 지금도 정확히 모릅니다. 그 호스트에서 도커를 만질 수 있다는 건 사실상 그 머신의 최고 권한입니다. 컨테이너를 띄울 수 있으면 호스트 파일 시스템을 마운트할 수 있고, 그러면 뭐든 읽고 쓸 수 있습니다. 오늘 데이터 볼륨을 새 컨테이너에 붙인 게 정확히 그 권한입니다.

되돌릴 수 없는 두 가지가 언제 실행됐는지도 봤습니다. 둘 다 목록 뒤쪽이었습니다. 502가 이미 풀리고 메일함이 다시 열린 다음, 급한 불이 꺼진 뒤의 뒷정리 때였습니다.

우연은 아닌 것 같습니다. 문제가 살아 있을 때는 저도 화면을 붙잡고 있었습니다. 200이 뜬 걸 본 다음부터는 사실상 딴 일을 하고 있었고 올라오는 문장을 읽는 밀도가 확 떨어졌습니다. 되돌릴 수 없는 일이 하필 그때 몰렸습니다. 위험한 동작일수록 뒷정리처럼 보이고, 뒷정리는 사람이 제일 대충 보는 때입니다.

만약 순서가 반대였다면 어땠을까 싶습니다. 비밀번호를 바꾸고 로그를 비우는 게 502를 풀기 전에 나왔다면, 저는 아직 화면을 붙잡고 있었을 테니 아마 한 번은 멈췄을 겁니다. 같은 동작이라도 언제 지나가느냐에 따라 제 눈에 걸리느냐가 정해진다는 건데, 그걸 제가 고를 수는 없습니다. 일이 풀리는 순서는 문제가 정하니까요. 그러면 기댈 수 있는 건 제 집중력이 아니라 그 동작 앞에 무조건 서는 확인 하나뿐인 것 같습니다. 제가 딴 일을 하고 있을 때도 거기서는 멈추는 것으로요. 사람이 늘 집중하고 있을 거라고 믿고 짜면 오늘처럼 제일 느슨할 때 제일 무거운 게 지나갑니다.

서버 쪽 세션 기록도 저한테는 없습니다. 대화창에는 남아 있지만 그건 AI가 자기가 했다고 말한 내용입니다. 서버에서 실제로 뭐가 실행됐는지 서버 쪽에서 따로 확인할 방법을 저는 안 만들어뒀습니다. 채점받는 쪽이 채점표를 쓰는 구조는 제 배포 파이프라인에서 겪은 적이 있습니다. 그때는 게이트를 통과했다는 문자열을 검사받는 쪽이 직접 썼고, 오늘은 작업 보고서를 작업한 쪽이 직접 썼습니다. 보고서에 틀린 데는 없었고 200과 302는 저도 브라우저로 다시 봤는데, 제가 직접 다시 본 건 그 마지막 두 줄뿐이고 나머지 열한 개는 그렇게 했다는 말을 읽은 겁니다.

당장 할 수 있는 건 거창한 게 아니라 오늘 놓친 두 가지가 다음엔 그냥 안 지나가게 하는 정도인 것 같습니다.

전용 계정과 전용 키를 따로 만들어야겠습니다. 지금은 평소에 쓰는 키를 그대로 넘겼는데 그 키는 이 메일 서버 말고 다른 데도 열립니다. 작업 범위가 서버 하나면 키도 서버 하나여야 합니다. 사고를 막는다기보다 사고가 어디까지 번질지를 정해두는 겁니다.

서버에서 실행된 명령은 서버 쪽에 남게 하고 싶습니다. 방법은 여러 가지고 정교할 필요도 없습니다. 나중에 정말 이것만 실행됐나 물었을 때 대화창 말고 볼 데가 한 군데라도 있으면 됩니다.

확인은 되돌릴 수 없는 동작에만 걸면 될 것 같습니다. 자격증명을 바꾸는 것, 로그나 데이터를 지우는 것, 보안 기능을 끄거나 예외를 늘리는 것. 이런 건 실행 전에 뭘 잃게 되는지를 한 문장으로 같이 알려주면 좋겠습니다. 로그에 비밀번호가 있으니 지울까요, 가 아니라 지우면 이 시간대 차단 기록도 같이 사라집니다, 까지 들었으면 저는 다르게 답했을 겁니다.

승인이 어디까지 유효한지도 짧게 잡아야겠습니다. 오늘은 "이 502 좀 봐줘" 한 문장으로 시작해서 관리자 비밀번호 재설정까지 갔습니다. 중간에 일의 성격이 바뀐 때가 분명히 있었습니다. 원인을 찾다가 복구로 넘어갈 때, 복구하다가 계정 정리로 넘어갈 때입니다. 그 두 번에 승인을 새로 받았으면 이 글의 절반은 안 썼을 것 같습니다.


어제 글에서는 OpenAI 쪽 대응을 두고 관측된 것에만 대응했다고 썼습니다. 발견된 자격증명을 폐기하고, 발견된 메시지를 지우고, 발견된 취약점을 패치했다고요. 그때 저는 바깥에서 보는 사람이었습니다. 오늘 저는 관측을 아예 안 했습니다. 결과만 받았고 그 결과가 다 초록불이라 만족했습니다.

승인은 목표보다 안전하다고 믿기 쉬운 것 같습니다. 그 에이전트들은 점수라는 목표만 받았고 저는 사람이니까 범위를 알고 승인했다고요. 그런데 승인도 목표랑 비슷하게 생겼습니다. 이 502를 고쳐라는 이 벤치마크 점수를 올려라와 문법이 같습니다. 사람은 승인 범위를 좁힐 수 있다는 게 다르긴 한데, 좁힐 수 있는 거랑 좁힌 건 다른 얘기고 오늘 저는 안 좁혔습니다. 복사와 붙여넣기가 없어지면서 제가 뭘 빼먹는지를 붙잡아주던 것도 같이 없어진 것 같습니다.

그렇다고 예전으로 돌아가고 싶지는 않습니다. 오늘 그 왕복을 다시 했으면 30분에서 한 시간을 썼을 거고, 그보다 아까운 건 컨테이너 네 개를 하나씩 찔러보는 걸 아마 안 했을 거라는 겁니다. 그게 원인을 잡았습니다. 왕복이 비싸면 확인 한 번이 비싸지고, 그러면 혹시 몰라서 해보는 걸 안 하게 됩니다. 저는 처음 떠올린 프로토콜 설정을 붙잡고 설정 파일부터 고쳤을 텐데 그 파일엔 애초에 문제가 없었습니다. 한 시간 쓰고 엉뚱한 데를 고쳤을 수도 있습니다.

빨라지면서 늘어난 건 진단하면서 해보는 확인이고 줄어든 건 실행 직전의 확인입니다. 같은 확인이라는 말을 쓰지만 하는 일이 달라서 하나가 늘었다고 다른 하나가 채워지지는 않는 것 같습니다. 예전 게이트는 제가 만든 게 아니라 도구가 느려서 생긴 부작용이었고, 그래서 도구가 바뀌니까 같이 없어졌습니다. 이번엔 제가 직접 세워야 할 것 같은데 아직 하나도 안 세웠습니다.

메일은 지금 잘 옵니다. 302 다음에 200이 뜨고 컨테이너는 healthy 고, 어제까지 안 되던 게 오늘은 됩니다. 이 글에서 제일 이상한 게 그겁니다. 걸리는 걸 이만큼 적었는데 결과만 놓고 보면 다 잘 됐습니다.

댓글

이 블로그의 인기 게시물

봉제인형 사진 한 장, ChatGPT와 Gemini의 다른 대답

구글은 서울에서 모델이 아니라 에이전트를 판다

AI 부업으로 3개월에 2달러 벌고 50달러를 썼다는 증언을 봤습니다. 그 위에 광고가 네 겹입니다