RHEL 기초 #7 기본 보안 — firewalld, SSH 하드닝

9 분 소요

지금까지 패키지를 깔고, 서비스를 띄우고, 사용자를 만들고, 디스크를 붙였습니다. 마지막은 외부에서 들어오는 통로를 좁히는 일 — 방화벽 (firewalld) 과 SSH 하드닝. RHEL 머신 한 대를 안전하게 운영하는 데 필요한 마지막 단계입니다.

RHEL 기초 시리즈에서 이번 글의 위치:

firewalld — RHEL의 방화벽 추상화 #

리눅스 커널의 패킷 필터링은 netfilter (예전에는 iptables, 지금은 nftables)가 맡습니다. 그 위의 추상화 도구가 RHEL에서는 firewalld입니다. 우분투의 ufw와 같은 역할입니다.

firewalld 상태
$ sudo systemctl status firewalld
● firewalld.service - firewalld - dynamic firewall daemon
     Active: active (running)
       ...

$ sudo firewall-cmd --state
running

RHEL 9에서는 기본으로 켜져 있고 public zone이 활성화돼 있습니다.

zone 모델이 핵심 #

iptables가 단조로운 룰 체인이라면, firewalld인터페이스를 zone에 묶고 zone별로 정책을 정합니다. 같은 머신에 들어오는 패킷이라도 어느 NIC로 들어오느냐에 따라 다른 정책을 받게 할 수 있습니다.

zone 의 모양
   eth0 (외부망)  ──→  public zone   →  엄격한 정책
   eth1 (사내망)  ──→  internal zone →  느슨한 정책
   wg0  (VPN)    ──→  trusted zone  →  거의 다 허용

미리 정의된 zone:

zone설명
drop가장 엄격. 들어오는 모든 트래픽 무응답 차단
blockdrop과 비슷하나 거부 응답을 보냄
public기본 — 일부 서비스만 허용
externalNAT (마스커레이딩) 활성
dmzDMZ 내 서버용
work / home / internal데스크탑 신뢰 수준별
trusted모든 트래픽 허용

현재 zone과 활성 룰 보기 #

zone 확인
$ sudo firewall-cmd --get-default-zone
public

$ sudo firewall-cmd --get-active-zones
public
  interfaces: enp0s1

$ sudo firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s1
  sources:
  services: cockpit dhcpv6-client ssh
  ports:
  protocols:
  forward: yes
  masquerade: no
  forward-ports:
  source-ports:
  rich rules:

여기서 보이는 **services: cockpit dhcpv6-client ssh**가 현재 허용된 서비스입니다. SSH가 기본으로 열려 있어 우리가 #2에서 SSH로 들어올 수 있었던 것입니다.

firewall-cmd — 영구와 임시 #

가장 자주 헷갈리는 부분부터 봅시다. firewalld의 변경에는 두 가지 차원이 있습니다.

옵션어디에 적용
(없음)현재 동작 중인 런타임에만 — 재부팅이나 reload 시 사라짐
--permanent영구 설정 파일에만 — 적용은 reload 후

운영 패턴은 거의 항상:

패턴
$ sudo firewall-cmd --permanent --add-service=http      # 영구 설정에 추가
$ sudo firewall-cmd --reload                            # 영구 설정을 런타임에 반영

또는 두 번에 나눠 안 쓰고 한 번에:

동시 적용 (자주 씀)
$ sudo firewall-cmd --add-service=http                  # 런타임에 즉시
$ sudo firewall-cmd --runtime-to-permanent              # 런타임 상태를 영구로 복사

--reload는 활성 연결을 끊지 않습니다 — SSH로 작업 중에 reload 해도 끊기지 않습니다. 다만 진행 중인 변경 자체가 정확히 의도한 대로 들어갔는지 한 번 더 --list-all로 확인하는 습관이 좋습니다.

service / port 추가하고 빼기 #

미리 정의된 service #

firewalld는 자주 쓰는 서비스에 이름을 붙여 놓았습니다. ssh (22), http (80), https (443), cockpit (9090) 같은 식.

service 추가 / 제거
$ sudo firewall-cmd --permanent --add-service=http
$ sudo firewall-cmd --permanent --add-service=https
$ sudo firewall-cmd --permanent --remove-service=cockpit
$ sudo firewall-cmd --reload

$ sudo firewall-cmd --list-services
ssh dhcpv6-client http https

미리 정의된 service 목록은:

목록
$ sudo firewall-cmd --get-services | tr ' ' '\n' | head -20
RH-Satellite-6
RH-Satellite-6-capsule
amanda-client
amanda-k5-client
amqp
...

포트 직접 열기 #

표준에 없는 포트는 직접:

포트 추가 / 제거
$ sudo firewall-cmd --permanent --add-port=8000/tcp
$ sudo firewall-cmd --permanent --add-port=5000-5010/tcp     # 범위
$ sudo firewall-cmd --permanent --remove-port=8000/tcp
$ sudo firewall-cmd --reload

$ sudo firewall-cmd --list-ports
8000/tcp 5000-5010/tcp

특정 zone 에만 #

옵션의 어느 위치에든 --zone=을 끼워 넣을 수 있습니다.

internal zone 에만 mysql
$ sudo firewall-cmd --permanent --zone=internal --add-service=mysql
$ sudo firewall-cmd --reload

zone을 명시하지 않으면 default zone (보통 public) 에 적용.

Rich Rule — 한 단계 더 세밀하게 #

기본 service/port 룰은 “허용 / 차단” 의 두 상태뿐이지만, 특정 IP 에게만 / 특정 시간에만 / 로그를 남기면서 같은 경우엔 부족합니다. 그때 쓰는 게 Rich Rule.

특정 IP 만 SSH 허용
$ sudo firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4"
  source address="192.168.64.0/24"
  service name="ssh"
  accept'
$ sudo firewall-cmd --reload
특정 IP 거부
$ sudo firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4"
  source address="203.0.113.50"
  reject'
포트를 열되 로그 남기기
$ sudo firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4"
  port port="8443" protocol="tcp"
  log prefix="HTTPS-ALT: " level="info"
  accept'

Rich rule은 명령이 길어서 운영에서는 텍스트 파일에 정리해두고 한 줄씩 적용합니다.

iptables를 잠깐 #

옛 자료엔 iptables -A INPUT ... 같은 명령이 자주 나옵니다. RHEL 9 에선 firewalld가 그 위 추상화이고, 직접 iptables 명령으로 룰을 추가해도 firewalld와 충돌해 사라집니다. 새로 익히는 분은 firewalld만으로.

운영 자동화에서 firewalld가 너무 무겁다면 nftables를 직접 쓰는 길도 있고, 클라우드 환경에선 호스트 firewalld보다 AWS Security Group / GCP Firewall 같은 클라우드 방화벽이 훨씬 자주 손이 갑니다. 그래도 머신 안에서의 보호 한 층으로 firewalld를 같이 켜두는 게 이중 잠금입니다.

SSH 하드닝 — 표준 4 종 #

SSH는 외부에서 가장 많이 노리는 표적입니다. 인터넷에 공개된 22 포트엔 평균 분당 수백 번의 자동화 시도가 들어옵니다. 다음 네 가지를 한 번에 처리해두는 게 표준.

1) 키 인증 만들고 등록 — 비밀번호 로그인 닫기 전에 #

먼저 호스트(작업하는 노트북)에서 SSH 키를 만들어 둡니다. 이미 있으면 건너뛰어도 OK.

호스트에서 — 키 생성
$ ssh-keygen -t ed25519 -C "curtis@laptop"
Generating public/private ed25519 key pair.
Enter file in which to save the key (~/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):  ← 강한 passphrase 권장
...
Your identification has been saved in ~/.ssh/id_ed25519
Your public key has been saved in ~/.ssh/id_ed25519.pub

ed25519가 현재 권장 알고리즘. RSA가 익숙하다면 -t rsa -b 4096도 OK.

공개키를 RHEL 머신에 복사:

호스트에서 — 복사
$ ssh-copy-id curtis@192.168.64.15

ssh-copy-id가 자동으로 ~/.ssh/authorized_keys에 공개키를 추가하고 권한도 맞춰줍니다 (#5의 SSH 권한 부분).

2) sshd_config.d 분리 #

RHEL 9의 sshd는 /etc/ssh/sshd_config.d/ 안의 .conf 파일들을 메인 설정보다 먼저 읽습니다. 메인 파일을 안 건드리고 우리 설정만 분리해 두면, 패키지 업데이트 시 충돌이 없습니다.

/etc/ssh/sshd_config.d/01-hardening.conf
# 비밀번호 인증 비활성 — 키 인증만
PasswordAuthentication no
KbdInteractiveAuthentication no

# root 로의 SSH 로그인 차단
PermitRootLogin no

# (선택) 표준 22 포트 변경 — 자동 봇 절반 줄어듦
# Port 2222

# 빈 비밀번호 차단
PermitEmptyPasswords no

# X11 / 에이전트 포워딩 — 필요 없으면 닫기
X11Forwarding no
AllowAgentForwarding no

# 인증 시도 횟수 / 동시 미인증 연결 수
MaxAuthTries 3
MaxStartups 10:30:60

3) 적용 전 검증과 적용 #

설정 한 줄을 잘못 적으면 현재 SSH 세션은 유지되지만 새 SSH가 안 붙어 잠금 상태가 됩니다 (콘솔 접근이 없으면 답이 없습니다). 적용 전에 반드시 검증.

문법 검사
$ sudo sshd -t       # 출력 없으면 OK
적용
$ sudo systemctl reload sshd

현재 SSH 세션은 그대로 두고, 새 터미널에서 한 번 더 SSH로 붙어 동작 확인. 안 붙으면 현재 세션에서 설정을 되돌립니다. 새 SSH가 잘 붙으면 안전.

확인
$ ssh curtis@192.168.64.15           # 새 터미널에서
[curtis@rhel9-lab ~]$                # 키 인증으로 들어옴 (비밀번호 안 물음)

4) 포트 변경 시 — firewalld도 같이 #

포트를 바꿨다면 firewalld와 SELinux도 같이 손봐야 합니다.

포트 2222 로 바꾼 경우
# 1) sshd_config.d 에 'Port 2222' 적기

# 2) SELinux 에 새 포트를 ssh 용으로 알리기
$ sudo dnf install -y policycoreutils-python-utils
$ sudo semanage port -a -t ssh_port_t -p tcp 2222

# 3) firewalld 에 열기 (기존 22 는 잠깐 같이 두고 검증 후 닫기)
$ sudo firewall-cmd --permanent --add-port=2222/tcp
$ sudo firewall-cmd --reload

# 4) sshd reload + 새 터미널에서 확인
$ sudo systemctl reload sshd
$ ssh -p 2222 curtis@192.168.64.15

# 5) 새 포트 정상 동작 확인 후 22 닫기
$ sudo firewall-cmd --permanent --remove-service=ssh
$ sudo firewall-cmd --reload

중요 — 포트 변경은 보안의 본질이 아니라 자동화된 봇의 잡음을 줄이는 정도. 진짜 보호는 키 인증 + 비밀번호 차단 + root 차단 세 가지에서 옵니다. 포트 변경은 선택.

fail2ban — 한 단계 더 #

자동화 봇이 끊임없이 두드리는 게 거슬리면 **fail2ban**으로 자동 차단을 둘 수 있습니다. EPEL에서.

설치 / 켜기
$ sudo dnf install -y fail2ban
$ sudo systemctl enable --now fail2ban

기본 정책은 SSH에 5번 실패하면 그 IP를 10분 차단. 자세한 튜닝은 고급 #5에서.

그 외에 손볼 부분 #

기초의 마지막인 만큼 여기까지 하면 충분 한 부분과 다음 시리즈에서 다룰 부분을 갈라 둡니다.

이번 시리즈에서 닿은 범위 (= 충분) #

  • #5 — 일상 작업용 사용자 만들기, root로 일하지 않기, sudo의 wheel 그룹
  • #3dnf update로 보안 패치 정기 적용
  • #7 (이번 글) — firewalld + SSH 하드닝

다음 시리즈에서 #

주제시리즈
SELinux 깊이 — 라벨, 부울값, 정책중급 #1
auditd / OpenSCAP / FIPS 컴플라이언스고급 #5
자동 보안 패치 (dnf-automatic)중급 #6
TLS 인증서 운영실전 #1

SELinux 한 줄만 — RHEL 9에서 SELinux는 기본 Enforcing. 보안의 마지막 한 층이고, 끄지 말아야 합니다. “왜 nginx가 80에서 안 떠?“의 절반이 SELinux 라벨 문제고, 중급 #1에서 트러블슈팅 패턴을 정리합니다. 임시로 끄고 싶을 때조차 setenforce 0(Permissive 모드) 정도로만 — 완전 비활성(/etc/selinux/config에서 disabled)은 운영에서 절대 금지.

AlmaLinux / Rocky 차이 #

이번 글의 모든 명령이 그대로 동작 합니다. firewalld / sshd / SELinux는 RHEL의 패키지를 그대로 가져온 것이라 차이가 없습니다. fail2ban도 EPEL에서 동일.

자주 만나는 함정 #

“firewalld 룰을 적용했는데 사라져요” #

--permanent 안 붙이고 --reload 한 경우. 영구로 가려면 --permanent--reload가 한 묶음.

“SSH가 끊긴 채로 안 붙습니다” #

설정 변경 후 콘솔 접근 없이 잘못된 sshd 설정으로 reload 한 경우. 가상화 환경이라면 VM 콘솔로 들어가 되돌립니다. 클라우드라면 Serial Console 또는 인스턴스 정지 후 디스크 마운트 해 수정. 그래서 반드시 새 터미널로 검증 후 기존 세션 닫기.

“포트는 열었는데 접근 안 됨” #

세 곳을 함께 보세요.

  1. firewalldfirewall-cmd --list-all
  2. SELinuxsemanage port -l | grep <포트>로 등록 여부
  3. 앱 자체가 0.0.0.0이 아닌 127.0.0.1에 바인드ss -tlnp로 확인

세 곳 중 하나가 빠지면 외부에서 못 들어옵니다.

“키 인증 끄고 비밀번호도 끄고 잠금 됐습니다” #

sshd_config.d/01-hardening.conf가 적용 직전 — 반드시 다른 새 터미널에서 키 인증으로 한 번 들어가 동작을 확인한 뒤 reload. 이게 SSH 작업의 황금률.

자주 쓰는 명령 한 표 #

명령하는 일
firewall-cmd --state / --list-all상태 / 현재 룰
firewall-cmd --get-default-zone / --get-active-zoneszone
firewall-cmd --permanent --add-service=httpservice 추가
firewall-cmd --permanent --add-port=8000/tcpport 추가
firewall-cmd --permanent --add-rich-rule='...'rich rule
firewall-cmd --reload영구 설정을 런타임에
firewall-cmd --runtime-to-permanent런타임을 영구에
ssh-keygen -t ed25519키 생성
ssh-copy-id user@host공개키 등록
sshd -tsshd 설정 문법 검사
systemctl reload sshdsshd 재적용 (연결 안 끊김)
semanage port -a -t ssh_port_t -p tcp 2222SELinux에 포트 등록

정리 #

이번 글에서 잡은 그림:

  • RHEL의 방화벽 추상화는 firewalld, 인터페이스를 zone에 묶고 zone 별로 정책
  • firewall-cmd의 두 차원 — --permanent (영구 설정) + --reload (런타임 반영). 또는 --runtime-to-permanent
  • service / port / rich rule의 세 단계로 점점 세밀하게
  • SSH 하드닝 표준 4 종 — 키 인증 등록 → 비밀번호 차단 → root 차단 → (선택) 포트 변경
  • 모든 sshd 변경은 sshd -t 검증 → reload → 새 터미널로 확인의 흐름
  • SELinux는 끄지 않기. 깊이는 중급 #1
  • 자동 봇 차단은 EPEL의 fail2ban으로 한 줄

시리즈 마무리 #

이 7 편으로 잡은 것들:

  • #1 — 레드햇 계열 지도 / RHEL의 위치 / AlmaLinux/Rocky
  • #2 — RHEL 머신 한 대를 띄우고 등록까지
  • #3dnf로 패키지를 깔고 지우고 롤백 / AppStream과 modules
  • #4systemd로 서비스를 띄우고 / 첫 unit 직접 작성 / journalctl
  • #5 — 사용자/그룹/권한 모델 / chmod / ACL / sudo
  • #6 — XFS 위에 새 디스크를 붙여 마운트하고 /etc/fstab에 영구 등록
  • #7 — firewalld의 zone 모델과 SSH 하드닝

여기까지가 혼자 RHEL 머신 한 대를 운영할 수 있는 수준 입니다. 회사 서버 앞에 앉아 SSH로 들어가도 기본 동작에서 막히지 않습니다.

다음 단계는 깊이로 들어가는 시리즈입니다.

시리즈무엇
RHEL 중급SELinux 깊이, LVM, Stratis, NFS, NetworkManager, Podman 입문
RHEL 고급부팅 / 커널 튜닝 / 성능 분석 / OpenSCAP / Cockpit
RHEL 실전웹,DB,Podman 운영, Cockpit, Ansible 자동화
RHCSA / RHCE자격증 트랙 — 시험 도메인 기반

이 시리즈가 다음 트랙들의 출발점입니다. 본인 환경의 필요에 따라 골라 따라오세요.

X