비밀번호 SSH는 이제 그만 — 키 인증으로 전환하기

비밀번호 SSH는 이제 그만 — 키 인증으로 전환하기

여러 서버를 관리하다 보면 매번 비밀번호를 입력하는 게 번거로워진다. SSH 키 인증으로 전환하면 편의성과 보안을 동시에 챙길 수 있다. 이 글에서는 키 생성부터 GitHub를 활용한 다중 서버 배포, 그리고 키 노출 사고 대응까지 직접 겪은 과정을 정리한다.

왜 키 인증인가

비밀번호 방식은 입력이 번거롭고, 무차별 대입 공격에 노출되기 쉽다. SSH 키 인증은 한 번 설정해두면 입력이 필요 없고, 키 자체가 패스프레이즈로 추가 보호될 수 있어 보안 측면에서도 우월하다.

서버를 여러 대 운영하고 여러 기기에서 접속한다면 효과가 더 크다. 핵심 원리는 단순하다.

  • 개인키는 내 컴퓨터에만 보관하며 절대 외부로 유출되어서는 안 된다.
  • 공개키만 서버의 authorized_keys 파일에 등록한다.
  • 서버는 등록된 공개키와 짝이 맞는 개인키를 가진 클라이언트만 인증한다.

키 생성하기

ed25519 타입을 권장한다. RSA보다 키 길이가 짧으면서도 더 안전하고 빠르다.

ssh-keygen -t ed25519 -C "사용자명@기기이름"

옵션을 정리하면 다음과 같다.

옵션 의미
-t 키 타입 (ed25519 권장)
-C 코멘트(라벨), 기기 식별용
-f 저장 경로 지정
-a 패스프레이즈 해싱 강도 (높이면 보안 강화)

-C 옵션으로 라벨을 붙여두는 습관이 중요하다. 기기가 여러 대면 authorized_keys에 줄이 여러 개 쌓이는데, 라벨이 없으면 나중에 어느 키가 어떤 기기 것인지 구분할 수 없다.

생성 중 패스프레이즈를 입력하라는 메시지가 나온다. 입력해두면 키 파일이 유출되더라도 한 겹 더 보호되므로 권장한다.

패스프레이즈와 자동화의 충돌, 그리고 해법

패스프레이즈를 걸면 매번 접속할 때마다 암호를 입력해야 하는 것 아니냐는 의문이 생길 수 있다. 특히 자동화 도구가 SSH로 여러 작업을 수행하는 환경이라면 이 부분이 걸림돌처럼 보인다.

답은 ssh-agent다. 패스프레이즈는 접속할 때마다 입력하는 게 아니라, agent에 키를 등록할 때 한 번만 입력한다. 이후로는 세션이 유지되는 동안 agent가 인증을 대신 처리해 패스프레이즈를 묻지 않는다.

Windows 환경에서 PuTTY를 써봤다면 Pageant와 같은 개념이라고 이해하면 된다. Pageant가 .ppk 키를 메모리에 들고 있는 것처럼, ssh-agent는 OpenSSH 형식 키를 메모리에 들고 있다가 인증을 대신한다.

Windows에서 설정하는 방법:

# 관리자 권한 PowerShell에서 실행
Get-Service ssh-agent | Set-Service -StartupType Automatic
Start-Service ssh-agent

# 키 등록 (패스프레이즈 한 번 입력)
ssh-add $env:USERPROFILE\.ssh\id_ed25519

# 등록 확인
ssh-add -l

한 번 등록해두면 그 이후의 SSH 접속은 패스프레이즈 없는 것과 동일하게 매끄럽게 동작한다.

서버에 공개키를 등록하는 방법

비밀번호 접속이 가능한 경우

가장 간단하다. Linux/Mac에서는 ssh-copy-id로 한 줄이면 끝난다.

ssh-copy-id user@서버주소

Windows에는 ssh-copy-id가 없으므로 파이프로 대체한다.

type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh user@서버주소 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"

여기서 >>는 파일에 한 줄을 추가하는 연산자다. >로 덮어쓰면 기존에 등록된 다른 키들이 전부 사라지므로 절대 혼동하지 말아야 한다.

비밀번호 접속이 막혀 있는 경우

서버 정책상 비밀번호 인증이 비활성화되어 있다면, 이미 키로 접속 가능한 경로를 찾아야 한다.

  • 키로 접속되는 다른 기기가 있다면 그 경로로 들어가 직접 authorized_keys에 추가한다.
  • 모든 경로가 막혀 있다면 콘솔 접근(Proxmox 웹콘솔, IPMI, 물리 키보드/모니터 등)으로 들어가야 한다.

권한 설정도 함께 확인한다.

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

서버가 수십, 수백 대라면

서버마다 손으로 키를 등록하는 방식은 규모가 커지면 한계에 부딫힌다. 두 가지 방향이 있다.

GitHub를 공개키 배포 창구로 활용

GitHub는 모든 사용자의 등록된 공개키를 공개 엔드포인트로 제공한다.

https://github.com/사용자명.keys

여기서 명확히 구분해야 할 점은, GitHub에 올라가는 것은 공개키뿐이라는 사실이다. 개인키는 어떤 경우에도 GitHub나 클라우드에 올려서는 안 된다.

서버에서는 다음 명령으로 GitHub에 등록된 공개키 전체를 가져와 추가할 수 있다.

sudo apt install ssh-import-id   # 설치돼 있지 않다면
ssh-import-id gh:사용자명

이 명령은 Ubuntu 설치 마법사에도 통합되어 있어, autoinstall/cloud-init 설정에 한 줄만 넣으면 신규 설치되는 서버 수십 대가 동일한 공개키 묶음을 자동으로 받아간다.

ssh:
  install-server: true
  authorized-keys-from:
    - gh:사용자명

여러 기기에서 키를 만들었다면 GitHub 계정 하나에 공개키를 계속 추가 등록하면 된다. 키를 만들 때마다 GitHub에 올릴 필요는 없다 — 키 생성은 기기를 추가할 때만 일어나는 일이고, 서버는 설치/운영 중에 그 묶음을 가져다 쓰는 구조다.

키 생성 GitHub 등록 서버에서 가져오기
빈도 기기 추가 시 키 만들 때 한 번 서버 설치/동기화 시마다

다만 ssh-import-id새 키를 추가만 하고 기존 키를 자동으로 제거하지는 않는다. GitHub에서 키를 뺐다고 서버의 authorized_keys에서 자동으로 사라지지 않으므로, 진정한 의미의 동기화가 필요하다면 별도 조치가 필요하다.

Ansible로 선언적 동기화

키 추가뿐 아니라 제거까지 자동으로 반영하려면 Ansible 같은 설정 관리 도구가 적합하다.

- hosts: all
  tasks:
    - name: authorized_keys를 GitHub 키 목록과 정확히 일치시킴
      authorized_key:
        user: 사용자명
        state: present
        exclusive: true
        key: "{{ lookup('url', 'https://github.com/사용자명.keys', split_lines=false) }}"

exclusive: true가 핵심이다. 목록에 없는 키는 제거하고, 목록에 있는 키는 추가하여 서버 상태와 GitHub 키 목록을 완전히 일치시킨다. 서버 규모가 수십 대를 넘어가면 이 방식이 거의 필수가 된다.

규모별로 정리하면 대략 다음과 같다.

  • 수십 대까지 — ssh-import-id 정도로 충분
  • 수백 대 이상, 또는 잦은 키 회전이 필요한 경우 — Ansible 같은 도구로 선언적 관리
  • 사람 단위 접근 통제와 세션 기록까지 필요한 경우 — SSH 인증서(CA) 방식이나 Teleport 같은 전문 도구

키가 노출되었을 때의 대응

작업 중 실수로 개인키 전체를 메신저나 채팅창에 붙여넣는 사고가 일어날 수 있다. 이런 경우 가장 안전한 대응은 그 키를 신뢰할 수 없는 것으로 간주하고 폐기하는 것이다.

복구 절차에서 가장 중요한 원칙은 새 키로 접속이 확인되기 전에는 기존 키를 절대 제거하지 않는 것이다. 순서가 뒤바뀌면 서버에 접속할 방법이 사라져 잠겨버릴 수 있다.

  1. 새 키 생성
  2. 새 공개키를 서버에 추가 (기존 키는 아직 유지된 상태)
  3. 새 터미널 창에서 새 키로 접속이 되는지 확인
  4. 확인되면 기존(노출된) 키를 authorized_keys에서 제거
  5. 제거 후 다시 한번 새 창에서 접속 테스트로 이중 확인

서버에서 등록된 키를 확인하고 정리하는 명령은 다음과 같다.

# 등록된 키 목록 확인 (줄 번호 포함)
cat -n ~/.ssh/authorized_keys

# 특정 키만 남기고 나머지 제거
grep "유지할_키_라벨" ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp && mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys

# 특정 키만 제거하고 나머지는 유지
grep -v "제거할_키_라벨" ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp && mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys

nano로 직접 편집해도 무방하다. 지울 줄에 커서를 두고 Ctrl+K로 줄 삭제, Ctrl+O로 저장, Ctrl+X로 종료하면 된다.

작업 중 기존 SSH 세션을 닫지 않고 유지한 채로 새 창을 열어 테스트하는 습관을 들이면, 혹시 잘못되더라도 기존 세션으로 복구할 수 있어 안전하다.

GitHub 사용자명 노출에 대한 생각

사용자명.keys 방식을 쓰면 GitHub 사용자명이 곧 모든 서버의 공개키 게이트가 된다. 사용자명 자체는 GitHub 프로필을 통해 이미 공개된 정보이므로 노출 자체보다는 다음 두 가지가 실질적인 위험이다.

  • 키에 붙은 코멘트(user@서버이름 형태)가 운영 중인 인프라 정보를 추측할 단서를 제공할 수 있다.
  • GitHub 계정 자체가 뚫리면 공격자가 키를 추가해 모든 서버에 접근당할 수 있다.

완화 방법으로는 공개키 배포 전용 별도 계정 사용, 코멘트에서 식별 정보를 빼고 의미 없는 라벨로 교체, GitHub 계정에 2FA 필수 적용, 또는 GitHub 의존 없이 cloud-init에 공개키를 직접 명시하는 자체 배포 방식 등을 고려할 수 있다.

정리

키 인증으로 전환하는 일은 단순히 "비밀번호 안 치기"를 넘어, 인프라 접근 권한을 어떻게 설계하고 회수할지의 문제로 이어진다. 기기마다 키를 분리해 만들고, 패스프레이즈와 ssh-agent로 편의성을 잃지 않으면서, 서버 규모가 커지면 GitHub나 Ansible 같은 중앙 관리 체계로 넘어가는 흐름이 합리적이다. 그리고 무엇보다, 키가 노출되는 사고는 언제든 일어날 수 있으므로 "새 키 확인 후 기존 키 제거"라는 순서를 지키는 습관이 가장 중요하다.