예약 롤백으로 물리 서버의 nftables 변경 중 SSH 접근 보호

예약 롤백으로 물리 서버의 nftables 변경 중 SSH 접근 보호

예약 롤백으로 물리 서버의 nftables 변경 중 SSH 접근 보호

물리 서버를 원격으로 관리하면서 방화벽을 변경할 때 가장 흔한 오판은 현재 SSH 세션에서 명령이 계속 실행된다는 이유로 규칙 검증이 끝났다고 판단하는 것입니다. 실제로는 연결 추적이 기존 세션을 유지하는 동안 새 연결은 이미 차단되어 있을 수 있습니다. 안전하게 작업하려면 먼저 현재 상태를 저장하고 복구를 예약한 다음 규칙을 적용해야 합니다. 마지막으로 별도의 새 연결을 통해 관리 경로가 정상인지 확인해야 합니다.

이 글에서는 Debian 12, nftables, systemd를 사용해 SSH 접속 출발지만 제한하는 부분적인 규칙 변경을 설명합니다. AMDSSH 서버의 접속 주소와 인증 정보는 결제 후 콘솔에서 확인할 수 있습니다. IPv4 주소가 여러 개라면 SSH 접속에는 첫 번째 주소를 사용합니다. 이 예제는 계정명이나 포트를 미리 가정하지 않으며, 서비스 포트 전체에 대한 방화벽 정책을 구성하지도 않습니다.

1. 먼저 규칙 관리 범위를 확인하기

아래 작업은 계정에 sudo 권한이 있고, 시스템에 nftables가 설치되어 있으며, 모든 규칙을 네이티브 nftables로 일관되게 관리하는 환경을 전제로 합니다. 컨테이너 네트워크, 가상화 브리지 또는 다른 방화벽 도구가 규칙을 동적으로 생성하고 있다면 전체 스냅샷 복원을 그대로 적용해서는 안 됩니다. 롤백 과정에서 변경 작업 중 해당 도구가 추가한 규칙까지 덮어쓸 수 있기 때문입니다. 이런 환경에서는 먼저 규칙을 관리하는 주체를 확인하고, 해당 도구의 변경 절차를 따라야 합니다.

먼저 현재 상태를 확인합니다.

sudo nft list ruleset
sudo systemctl cat nftables.service
command -v nft

기존 규칙, 부팅 시 불러오는 파일, nft의 실제 경로를 확인합니다. 이 글에서는 별도의 inet ops_guard 테이블을 사용합니다. 같은 이름의 테이블이 이미 있다면 반드시 용도를 확인해야 하며, 다른 작업에 쓰이는 규칙을 덮어써서는 안 됩니다.

롤백 타이머는 이번 방화벽 규칙 변경만 되돌립니다. 라우팅, SSH 서비스 또는 관리 접속 출발지가 동시에 바뀌어 발생한 문제까지 복구하지는 못합니다. 한 번의 변경에서는 하나의 변수만 조정하세요.

실제 SSH 연결 정보 읽기

현재 원격 로그인에 사용 중인 Bash에서 실행합니다. 연결 환경 변수가 전달되지 않는 다른 세션으로 먼저 전환해서는 안 됩니다.

test -n "$SSH_CONNECTION" || exit 1
read -r ADMIN_IP CLIENT_PORT SERVER_IP SSH_PORT <<< "$SSH_CONNECTION"
case "$ADMIN_IP" in
  *:*) printf '%s\n' 'IPv4 SSH session required'; exit 1 ;;
esac
printf 'source=%s destination=%s port=%s\n' \
  "$ADMIN_IP" "$SERVER_IP" "$SSH_PORT"

여기서 얻는 주소는 서버에서 관측한 연결 출발지입니다. 작업용 컴퓨터의 내부 네트워크 주소가 아니라 사무실의 외부 접속 주소나 점프 호스트 주소일 수 있습니다. 외부 접속 주소가 바뀔 수 있다면 먼저 안정적인 관리 경로를 마련해야 합니다. 이 예제는 현재 세션이 IPv4를 사용해야 합니다. IPv6 세션에서 규칙을 생성한 뒤 억지로 다음 단계를 진행하지 마세요.

2. 스냅샷을 저장하고 자동 복구 예약하기

스냅샷에는 기존 규칙을 비우는 명령이 포함되어야 합니다. 그렇지 않으면 복원할 때 기존 객체와 중복될 수 있습니다. 규칙에 포함된 주소 구성 정보를 일반 계정이 읽지 못하도록 디렉터리 접근은 관리자에게만 허용합니다.

sudo install -d -m 700 /root/firewall-change
sudo sh -c '
set -e
umask 077
{
  printf "flush ruleset\n"
  nft list ruleset
} > /root/firewall-change/before.nft
'
sudo nft -c -f /root/firewall-change/before.nft

-c는 검사만 수행하며 규칙을 적용하지 않습니다. 구문 오류나 규칙 참조 문제는 찾을 수 있지만, 원격 로그인이 계속 가능한지까지 확인해 주지는 않습니다. 검사에 실패하면 변경을 중단해야 합니다. 오류를 무시하고 계속 진행해서는 안 됩니다.

같은 이름의 이전 작업이 남아 있지 않은지 확인한 뒤 3분 후 복구를 예약합니다.

sudo systemctl status ops-firewall-rollback.timer
sudo systemd-run \
  --unit=ops-firewall-rollback \
  --on-active=3m \
  "$(command -v nft)" \
  -f /root/firewall-change/before.nft
sudo systemctl list-timers ops-firewall-rollback.timer

처음 조회할 때 유닛이 없다는 메시지가 나오는 것은 정상입니다. 기존 작업이 있다면 먼저 생성 경위를 확인해야 합니다. 3분은 이번 작업의 롤백 대기 시간이며, 서비스 제공 시점에 대한 약속이 아닙니다. 타이머 실행이 예약된 것을 반드시 확인한 후 규칙 적용 단계로 넘어가세요. 이 시간 동안 다른 네트워크 규칙을 변경하지 마세요. 전체 규칙을 복원하면 그 변경도 함께 취소됩니다.

3. 별도 테이블로 관리 접속 출발지 제한하기

처음 사용할 때는 빈 테이블을 만듭니다. 같은 이름의 테이블이 없음을 확인한 경우에만 생성 명령을 실행하세요. 이번 작업 전용 테이블이 이미 있다면 이 단계는 건너뜁니다.

sudo nft add table inet ops_guard

이어서 적용할 후보 파일을 생성합니다.

sudo tee /root/firewall-change/candidate.nft > /dev/null <<EOF
delete table inet ops_guard
table inet ops_guard {
  chain input {
    type filter hook input priority 10; policy accept;
    iifname "lo" accept
    ct state established,related accept
    ip saddr $ADMIN_IP tcp dport $SSH_PORT accept
    tcp dport $SSH_PORT counter drop
  }
}
EOF
sudo chmod 600 /root/firewall-change/candidate.nft
sudo nft -c -f /root/firewall-change/candidate.nft
sudo nft -f /root/firewall-change/candidate.nft
sudo nft -a list table inet ops_guard

파일에 있는 삭제와 재생성은 하나의 nftables 트랜잭션으로 함께 반영됩니다. 먼저 비운 뒤 규칙을 하나씩 추가하는 방식이 아닙니다. 이 후보 파일은 적용할 때마다 전용 테이블이 이미 존재해야 하므로, 부팅 시 초기 설정으로 그대로 사용할 수는 없습니다.

이 규칙은 루프백과 이미 성립한 연결을 허용하고, 현재 IPv4 관리 접속 출발지에서 실제 SSH 포트로 들어오는 연결을 허용한 다음, 다른 출발지에서 해당 포트로 들어오는 연결을 차단합니다. 마지막 규칙은 IPv6에도 일치하므로, 이 예제에서는 IPv6를 통한 새로운 SSH 연결이 허용되지 않습니다. IPv6로 관리해야 한다면 명시적인 ip6 saddr 허용 규칙을 미리 추가하고 별도로 테스트해야 합니다.

이 테이블은 다른 서비스 포트를 제한하지 않습니다. 또한 여기의 accept가 다른 기본 체인에 있는 drop을 무효화하지는 않습니다. 새 연결은 기존 규칙에 의해 여전히 차단될 수 있습니다. 이 예제를 완전한 보안 기준 구성으로 받아들이지 마세요. 이를 근거로 기존 서비스 정책을 삭제해서도 안 됩니다.

4. 별도의 새 연결로 변경 확정 여부 판단하기

기존 세션을 종료하지 않은 상태에서 허용된 출발지로부터 다른 연결을 시작합니다. 클라이언트에서 연결 재사용 기능을 사용 중이라면 이를 비활성화해야 합니다. 그렇지 않으면 새 창을 열어도 기존 TCP 연결을 계속 사용할 수 있습니다.

다음 명령은 클라이언트의 Bash에서 실행하며, 콘솔에서 제공된 접속 정보를 대화형으로 입력합니다.

read -r -p 'Host: ' HOST
read -r -p 'User: ' LOGIN
read -r -p 'Port: ' PORT
ssh -o ControlMaster=no -o ControlPath=none \
  -p "$PORT" "$LOGIN@$HOST"

검증에는 최소한 다음 세 항목이 포함되어야 합니다.

비밀번호 또는 키 인증 오류만 발생했다고 해서 출발지 제한이 실패했다고 단정할 수는 없습니다. 연결이 정확히 어느 계층에서 거부되었는지 판단해야 합니다. 테스트를 위해 규칙을 일시적으로 완화하지 마세요.

별도의 새 로그인이 실패하면 현재 상태를 유지하고 롤백을 기다립니다. 허용 규칙을 계속 덧붙이지 마세요. 복구 후에는 타이머에 연결된 서비스의 로그를 확인하고, 출발지 주소, 포트, 다른 체인의 영향을 분석합니다. 검증을 통과한 것이 확인되면 타이머를 중지합니다.

sudo systemctl stop ops-firewall-rollback.timer
sudo systemctl is-active ops-firewall-rollback.timer

복구 서비스가 이미 시작되지는 않았는지도 확인해야 합니다. 타이머가 마침 만료된 시점이라면 현재 적용된 규칙을 다시 읽어야 합니다. 중지 명령이 성공했다는 이유만으로 후보 설정이 그대로 유지된다고 판단해서는 안 됩니다.

5. 영구 저장과 부팅 검증은 따로 진행하기

실행 중인 규칙의 검증을 통과한 뒤에 부팅 설정 파일을 처리합니다. 앞서 확인한 서비스 설정을 기준으로 실제 로드 경로를 확인하세요. Debian 12에서는 일반적으로 /etc/nftables.conf를 사용하지만, 사용자 정의 서비스에서는 경로가 다를 수 있습니다.

규칙이 안정되어 있고 다른 관리 도구가 규칙을 변경하지 않는 상태에서, 현재 적용된 전체 규칙을 flush ruleset이 포함된 파일로 내보냅니다. nft -c -f로 검사하고 기존 부팅 설정 파일을 백업한 뒤 교체합니다. 전용 테이블만 저장해서는 안 됩니다. 재부팅 후 기존 서비스 규칙이 누락될 수 있기 때문입니다.

영구 저장은 두 번째 변경 작업입니다. 롤백을 새로 예약하고, 전체 부팅 설정 파일을 적용한 다음, 별도의 새 로그인 테스트를 반복해야 합니다. 서비스 활성화, 설정 교체, 서버 재부팅을 하나의 명령으로 묶지 마세요. 재부팅 검증은 별도로 계획하고, 규칙과 허용된 출발지의 로그인, 서비스 연결 상태를 확인합니다.

마지막으로 실제 출발지, SSH 포트, 후보 파일, 스냅샷, 검증 결과를 기록합니다. 변경을 확정할 근거는 “명령이 성공을 반환했다”는 사실이 아닙니다. 새 연결이 정상적으로 성립하고, 차단 동작이 예상대로 이루어지며, 부팅 설정으로 규칙을 재현할 수 있어야 합니다. 이 세 조건이 모두 충족되어야 합니다.

자주 묻는 질문

기존 SSH 세션이 정상이라면 롤백을 취소해도 되나요?

아닙니다. 연결 추적 규칙 때문에 기존 세션만 유지될 수 있습니다. 허용된 출발지에서 독립적인 새 연결을 열고 인증에 성공한 뒤 롤백 타이머를 중지해야 합니다.

예제 규칙이 업무 서비스 포트도 차단하나요?

아닙니다. 실제 SSH 포트로 들어오는 새 연결만 제한합니다. 다른 포트에는 기존 규칙이 계속 적용되며, 이 예제는 전체 서버에 대한 기본 차단 정책이 아닙니다.

EPYC 7543P 단독 물리 서버월 $450.2부터

리소스 계획부터 서버 구성까지

서버 모델과 추가 옵션은 월 단위로 합산되며, 구성 페이지에서 전체 내역을 확인할 수 있습니다. 연결 정보는 서버 인도 후 콘솔에서 확인할 수 있습니다.