한 줄로
EC2 인스턴스 화면에 뜨는 KeyName은 인스턴스를 만들 때 어떤 키페어를 지정했는지 적어둔 생성 시점의 기록이다. 지금 그 서버가 실제로 인정하는 열쇠가 무엇인지와는 별개다. 키 회전 티켓의 전제는 실측에서 뒤집혔다.
KeyName이 가리키는 것과 가리키지 않는 것
이번 키 대조에서 확인한 것은 서버의 ~/.ssh/authorized_keys였다. KeyName은 인스턴스 생성 시점의 기록이며, 현재 서버가 인정하는 키와는 별개다.
콘솔 기록과 현재 서버 설정은 구분해야 한다. 누가 authorized_keys를 직접 편집해 키를 더 넣거나 빼도 콘솔의 KeyName은 그대로다. 그래서 콘솔 값은 과거에 무엇을 넣었는지를 말할 뿐 지금 무엇이 문을 여는지를 말하지 않는다.
실제로 겪은 것 — 티켓의 전제가 틀렸다
사내 SSH 키 회전 티켓은 구 인스턴스 키라는 키페어가 서버 7대를 연다고 적고 있었다. 티켓의 전제와 실제 접속 결과를 대조했다. 회전 절차 4단계는 “기존 키 제거”였다.
계획을 밀기 전에 실제로 붙어봤다. 아래는 키와 서버 이름을 익명화한 접속 결과다.
구 인스턴스 키로 운영 서버 접속 → Permission denied
실제 접속 키로 같은 서버 접속 → 로그인 성공
드라이브에 보관돼 있던 구 인스턴스 키 개인키 사본은 어느 서버도 열지 못했다. 실제로 서버를 여는 건 이름이 다른 실제 접속 키였다.
전제가 틀린 채로 4단계를 실행했다면, 티켓 범위 밖이면서 다른 두 서버도 함께 쓰는 실제 접속 키를 뽑아버릴 뻔했다. 콘솔 화면 한 줄을 사실로 받아들인 대가가 서버 접속 수단 상실이었다는 뜻이다.
주석은 사람이 적는 자유 문자열이다
스테이징 서버의 authorized_keys에는 주석이 구 인스턴스 키로 적힌 제3의 키(공개키 A)가 들어 있었다. 이름만 보면 문제의 그 키다. 하지만 공개키 본문 자체가 드라이브 사본(공개키 B)과 달랐다.
authorized_keys 한 줄은 <타입> <공개키 본문> <주석> 형태이고, 마지막 주석은 아무 글자나 적을 수 있는 자리다. 비교해야 하는 건 본문이지 주석이 아니다.
최종 판정
| 키 | 판정 |
|---|---|
ec2-bastion |
회전 완료 — 티켓 전제가 맞았음 |
| 구 인스턴스 키 | 회전 불필요 — 드라이브 사본이 어느 서버도 못 엶 |
티켓 두 줄 중 한 줄만 맞았다. 맞은 쪽은 회전했고, 틀린 쪽은 회전할 대상 자체가 없었다.
회전하면서 또 걸린 것들
이름을 되찾으려면 지우고 다시 넣어야 한다
ec2-bastion을 회전할 때 신규 키페어를 ec2-bastion-2026으로 만들었다가, 최종적으로 이름을 ec2-bastion으로 되돌리기로 했다. AWS는 같은 이름의 키페어를 두 개 둘 수 없으니 구 키페어를 지우고 그 이름으로 다시 import 해야 한다.
부작용을 10개 항목으로 사전 점검했는데, 결정적 근거는 같은 계정 안의 선례였다. 다른 인스턴스 인스턴스가 이미 삭제된 키페어를 참조한 채 running 상태로 정상 동작 중이었다. 키페어 삭제가 실행 중인 인스턴스를 건드리지 않는다는 걸 문서가 아니라 눈앞의 사례로 확인한 셈이다. 이것도 결국 같은 이야기다 — 콘솔의 KeyName은 서버와 연결돼 있지 않다.
절차는 이렇게 잡았다.
- 구 키페어 공개키 백업 (복구 수단 확보)
- 구 키페어 삭제
- SSM으로 서버 무사 확인 — authorized_keys 무변동
- 신규 공개키를 ec2-bastion 이름으로 import-key-pair
- 임시 키(-2026) 정리
- 접속 재확인
3단계가 핵심이다. 키페어를 지운 직후 서버의 authorized_keys가 변하지 않았음을 실제로 확인한다. 실제 확인 결과는 무변동이었다.
같은 키인데 지문이 달라 보인다
지문 표기도 주의할 항목이었다. import한 키는 MD5(d5:a9:49:0a:...)로, AWS가 생성한 키는 SHA1로 콘솔 지문을 계산한다. 해시 알고리즘이 다르니 두 방식으로 계산한 지문은 서로 달라 직접 비교할 수 없다. 지문을 대조할 때는 무엇으로 계산한 값인지부터 맞춰야 한다.
비상 통로가 하필 그 서버에만 없었다
준비 중에 bastion 서버에 SSM(Systems Manager, SSH 없이 AWS를 통해 서버 안으로 들어가는 경로)이 아예 붙어 있지 않다는 걸 발견했다. SSH가 막힐 때 쓸 SSM 통로가 없는 상태였다. “SSM이 안전망”이라는 전제가 하필 그 서버에서만 안 지켜지고 있었던 것이다.
IAM 프로파일을 붙였고, 이후 구 키페어 삭제 검증에 이 SSM 경로를 썼다.
로컬 키 정리도 같은 기준으로
작업을 마치고 다운로드 폴더의 개인키 6개를 지우고 ~/.ssh 4개를 남겼다. 판단 기준은 파일명·날짜가 아니라 지문 대조로 했다. 실제 접속 키 파일은 2024년 5월 파일이라 이름과 날짜만 보면 “옛날 것”인데, 지금 운영 서버를 여는 그 키였다. 이름만 보고 지웠으면 접속 수단을 잃을 뻔했다.
남긴 파일들의 권한이 644라 ssh-keygen이 읽기를 거부한다. 개인키는 600이어야 한다.
vault 이관에서 나온 것
신규 키는 팀 vault로 옮겼다. 여기서도 두 가지가 걸렸다.
개인키 필드가 줄바꿈을 못 담는다. 템플릿의 hidden 필드가 input[type=password]로 렌더링돼 개행이 공백으로 바뀐다. 27줄 1678자가 1줄 1652자가 됐고, 그대로 쓰면 invalid format이 난다. 복원 방법을 항목 메모에 적어뒀다. vault에 이미 있던 구 키도 1줄이었던 걸 보면 예전부터 그랬다.
이걸 발견한 건 저장만 하지 않고 되받아 검증했기 때문이다. 꺼내기 → 복원 → 지문 대조 → 실제 SSH까지 돌렸다. 저장 후 화면에 값이 보이는 것으로 끝냈다면 다음 사람이 꺼낼 때 터졌을 문제다.
vault에 이미 해당 서버의 SSH 항목 항목이 있었고 그 안에 구 키가 들어 있었다. 신규 생성이 아니라 갱신이 맞는 상황이었다.
계획에 “팀 공유” 단계가 없었다
회전 4단계 완료 시점부터 vault 이관 완료 시점까지, 그 서버에 접속 가능한 사람이 한 명뿐인 틈이 생겼다. 계획서에 공유 단계가 아예 없어서 생긴 공백이다. 다음 회전 때는 “구 키 제거 → 즉시 vault 이관”을 한 묶음으로 잡아야 한다.
팀에는 Slack으로 안내만 보냈다. 개인키 파일은 보내지 않았다 — 메신저로 보내면 서버·각자 기기·검색 기록에 평문으로 남아, 애초에 드라이브에 키를 뒀던 문제가 그대로 반복된다. “vault에 올렸다”는 사실만 전달했다.
드라이브 정리
드라이브의 구 키 파일 4개는 휴지통으로 보냈다. 영구 삭제는 공유 드라이브 관리자 전용이고, 콘텐츠 관리자 권한으로는 휴지통 이동까지만 된다(30일 후 자동 영구 삭제). 4개 모두 죽은 키임을 세 서버 접속 시도로 검증했으므로 위험은 없다.
여기서도 이름이 거짓말을 했다. 신규(20240423) 폴더 쪽이 오히려 오늘 무효화한 구 키였다.
정리
이번 작업에서 반복해서 나온 건 하나다. 이름·라벨·메타데이터는 사람이 적은 기록이고, 실제 동작은 별도로 확인해야 한다.
- 콘솔
KeyName— 생성 시점 기록. 지금 서버가 인정하는 키가 아니다 -
authorized_keys주석 — 자유 문자열. 공개키 본문을 봐야 한다 - 파일명·날짜 —
신규(20240423)이 구 키였고, 2024년 5월 파일이 현역이었다 - 콘솔 지문 — import 키는 MD5, 생성 키는 SHA1. 계산 방식을 맞춰야 한다
검증 수단은 두 개뿐이었다. 실제로 붙어보기, 그리고 지문 대조. 티켓의 전제가 틀렸다는 것도, 남길 로컬 키를 고른 것도, vault에 올린 키가 멀쩡한지 확인한 것도 전부 이 둘로 판정했다.