SSH 비밀번호 로그인을 껐는데 여전히 켜져 있던 이유: sshd_config.d는 먼저 읽힌 값이 이긴다
홈서버 보안을 손보면서 제일 먼저 한 일은 SSH 비밀번호 로그인을 끄는 것이었습니다. 키 인증만 허용하고 root 직접 로그인도 막는, 누구나 하는 기본 설정입니다. 설정 파일을 만들고 SSH를 재시작한 뒤 “끝났다”고 생각했습니다. 실제로는 아무것도 바뀌지 않은 상태였고, 이걸 알게 된 건 며칠 뒤였습니다.
무엇을 했는가
우분투 24.04의 /etc/ssh/sshd_config 맨 위에는 이런 줄이 있습니다.
Include /etc/ssh/sshd_config.d/*.conf
원본 파일을 직접 고치는 대신 이 폴더에 파일을 추가하는 게 요즘 권장되는 방식입니다. 패키지가 업데이트돼도 내 설정이 덮어써지지 않으니까요. 저는 “내 설정이 맨 마지막에 적용되게” 하려고 파일 이름을 99-hardening.conf로 지었습니다.
PasswordAuthentication no
PermitRootLogin no
LogLevel VERBOSE
nginx나 systemd 드롭인 설정처럼 나중에 읽힌 값이 앞의 값을 덮어쓴다고 당연하게 생각했던 겁니다.
실제로 일어난 일
sshd는 반대로 동작합니다. 같은 키워드가 여러 번 나오면 처음 나온 값을 쓰고 나머지는 무시합니다. sshd_config 매뉴얼에도 적혀 있는 동작입니다. “For each keyword, the first obtained value will be used.”
그 폴더에는 이미 클라우드 초기화 도구가 만든 50-cloud-init.conf가 있었고, 거기에 PasswordAuthentication yes가 들어 있었습니다. 파일은 알파벳 순서로 읽히니 순서는 이렇게 됩니다.
| 읽는 순서 | 파일 | PasswordAuthentication | 결과 |
|---|---|---|---|
| 1 | 50-cloud-init.conf | yes | 적용됨 |
| 2 | 99-hardening.conf | no | 무시됨 |
제가 만든 설정은 정확히 반대 이유로 무시당하고 있었습니다. 다행히 키 인증도 같이 켜져 있어서 평소 접속에는 아무 이상이 없었고, 그래서 더 눈치채기 어려웠습니다.
어떻게 알았나
서버 상태를 주기적으로 점검하는 작은 감시 스크립트를 만들어뒀는데, 첫 실행에서 이걸 잡아냈습니다. 스크립트는 설정 파일 내용을 보지 않고, sshd가 실제로 적용하고 있는 값을 확인합니다.
sudo sshd -T | grep -Ei 'passwordauthentication|permitrootlogin'
sshd -T는 모든 설정 파일을 합친 최종 결과를 출력합니다. 여기에 passwordauthentication yes가 찍혀 있었습니다. 설정 파일을 눈으로 읽는 것과 프로그램이 실제로 해석한 결과는 다를 수 있다는 걸 이때 제대로 배웠습니다.
고친 방법
파일 이름을 00-hardening.conf로 바꿔서 cloud-init 파일보다 먼저 읽히게 했습니다.
sudo mv /etc/ssh/sshd_config.d/99-hardening.conf /etc/ssh/sshd_config.d/00-hardening.conf
sudo sshd -t # 문법 검사
sudo systemctl restart ssh
sudo sshd -T | grep -i passwordauthentication # no 확인
재시작 전에 기존 SSH 세션을 하나 열어둔 채로 새 터미널에서 접속이 되는지 먼저 확인하세요. 설정을 잘못하면 서버에 못 들어가는 상황이 생길 수 있는데, 열어둔 세션이 있으면 되돌릴 수 있습니다.
같이 걸린 함정: fail2ban이 아무것도 안 막고 있었다
같은 날 fail2ban도 설치했습니다. SSH 로그인에 여러 번 실패한 IP를 자동으로 차단해주는 도구입니다. 설치하고 sshd jail을 켰는데 차단 기록이 하나도 쌓이지 않았습니다.
원인은 서비스 이름이었습니다. 우분투 24.04에서 SSH 서비스 유닛 이름은 sshd.service가 아니라 ssh.service입니다. systemd 저널에서 로그를 읽는 방식으로 설정하면 fail2ban이 엉뚱한 유닛 이름으로 로그를 찾고 있었던 겁니다. /etc/fail2ban/jail.local에 이렇게 명시해서 해결했습니다.
[sshd]
enabled = true
backend = systemd
journalmatch = _SYSTEMD_UNIT=ssh.service
확인은 sudo fail2ban-client status sshd로 합니다. “Currently failed” 숫자가 올라가기 시작하면 제대로 로그를 읽고 있는 겁니다.
이 일로 바꾼 습관
- 보안 설정은 설정 파일이 아니라 실제 적용값으로 확인합니다. SSH는
sshd -T, 방화벽은ufw status verbose, 열린 포트는ss -tlnp로 봅니다. - 설정 폴더에 파일을 추가할 때는 그 프로그램이 “첫 값 우선”인지 “마지막 값 우선”인지 먼저 찾아봅니다. 프로그램마다 다릅니다.
- 한 번 설정하고 끝내지 않고, 주기적으로 같은 명령을 돌려 결과가 그대로인지 확인합니다. 패키지 업데이트나 다른 도구가 설정을 슬쩍 되돌리는 일이 실제로 있습니다.
설정 파일이 문법 오류 없이 잘 읽혔다는 것과 내가 원하는 대로 동작한다는 것은 다른 이야기였습니다. 참고로 sshd 설정 규칙은 OpenBSD의 sshd_config 매뉴얼에 가장 정확하게 나와 있습니다.