RHEL 기초 #7 기본 보안 — firewalld, SSH 하드닝
지금까지 패키지를 깔고, 서비스를 띄우고, 사용자를 만들고, 디스크를 붙였습니다. 마지막은 외부에서 들어오는 통로를 좁히는 일 — 방화벽 (firewalld) 과 SSH 하드닝. RHEL 머신 한 대를 안전하게 운영하는 데 필요한 마지막 단계입니다.
RHEL 기초 시리즈에서 이번 글의 위치:
- #1 RHEL이란 — Fedora에서 RHEL까지, 그리고 AlmaLinux와 Rocky Linux
- #2 셋업 — RHEL 9 설치, Subscription Manager, 첫 로그인
- #3 dnf와 패키지 관리 — repo, modules, AppStream
- #4 systemd 입문 — 서비스, target, journalctl
- #5 사용자/그룹/권한 — UID/GID, sudo, ACL
- #6 파일 시스템 기본 — XFS, mount, /etc/fstab
- #7 기본 보안 — firewalld, SSH 하드닝 ← 이번 글
firewalld — RHEL의 방화벽 추상화 #
리눅스 커널의 패킷 필터링은 netfilter (예전에는 iptables, 지금은 nftables)가 맡습니다. 그 위의 추상화 도구가 RHEL에서는 firewalld입니다. 우분투의 ufw와 같은 역할입니다.
$ sudo systemctl status firewalld
● firewalld.service - firewalld - dynamic firewall daemon
Active: active (running)
...
$ sudo firewall-cmd --state
runningRHEL 9에서는 기본으로 켜져 있고 public zone이 활성화돼 있습니다.
zone 모델이 핵심 #
iptables가 단조로운 룰 체인이라면, firewalld는 인터페이스를 zone에 묶고 zone별로 정책을 정합니다. 같은 머신에 들어오는 패킷이라도 어느 NIC로 들어오느냐에 따라 다른 정책을 받게 할 수 있습니다.
eth0 (외부망) ──→ public zone → 엄격한 정책
eth1 (사내망) ──→ internal zone → 느슨한 정책
wg0 (VPN) ──→ trusted zone → 거의 다 허용미리 정의된 zone:
| zone | 설명 |
|---|---|
drop | 가장 엄격. 들어오는 모든 트래픽 무응답 차단 |
block | drop과 비슷하나 거부 응답을 보냄 |
public | 기본 — 일부 서비스만 허용 |
external | NAT (마스커레이딩) 활성 |
dmz | DMZ 내 서버용 |
work / home / internal | 데스크탑 신뢰 수준별 |
trusted | 모든 트래픽 허용 |
현재 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) 같은 식.
$ 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=을 끼워 넣을 수 있습니다.
$ sudo firewall-cmd --permanent --zone=internal --add-service=mysql
$ sudo firewall-cmd --reloadzone을 명시하지 않으면 default zone (보통 public) 에 적용.
Rich Rule — 한 단계 더 세밀하게 #
기본 service/port 룰은 “허용 / 차단” 의 두 상태뿐이지만, 특정 IP 에게만 / 특정 시간에만 / 로그를 남기면서 같은 경우엔 부족합니다. 그때 쓰는 게 Rich Rule.
$ 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$ 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.pubed25519가 현재 권장 알고리즘. RSA가 익숙하다면 -t rsa -b 4096도 OK.
공개키를 RHEL 머신에 복사:
$ ssh-copy-id curtis@192.168.64.15ssh-copy-id가 자동으로 ~/.ssh/authorized_keys에 공개키를 추가하고 권한도 맞춰줍니다 (#5의 SSH 권한 부분).
2) sshd_config.d 분리
#
RHEL 9의 sshd는 /etc/ssh/sshd_config.d/ 안의 .conf 파일들을 메인 설정보다 먼저 읽습니다. 메인 파일을 안 건드리고 우리 설정만 분리해 두면, 패키지 업데이트 시 충돌이 없습니다.
# 비밀번호 인증 비활성 — 키 인증만
PasswordAuthentication no
KbdInteractiveAuthentication no
# root 로의 SSH 로그인 차단
PermitRootLogin no
# (선택) 표준 22 포트 변경 — 자동 봇 절반 줄어듦
# Port 2222
# 빈 비밀번호 차단
PermitEmptyPasswords no
# X11 / 에이전트 포워딩 — 필요 없으면 닫기
X11Forwarding no
AllowAgentForwarding no
# 인증 시도 횟수 / 동시 미인증 연결 수
MaxAuthTries 3
MaxStartups 10:30:603) 적용 전 검증과 적용 #
설정 한 줄을 잘못 적으면 현재 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도 같이 손봐야 합니다.
# 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 그룹
- #3 —
dnf 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 또는 인스턴스 정지 후 디스크 마운트 해 수정. 그래서 반드시 새 터미널로 검증 후 기존 세션 닫기.
“포트는 열었는데 접근 안 됨” #
세 곳을 함께 보세요.
- firewalld —
firewall-cmd --list-all - SELinux —
semanage port -l | grep <포트>로 등록 여부 - 앱 자체가 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-zones | zone |
firewall-cmd --permanent --add-service=http | service 추가 |
firewall-cmd --permanent --add-port=8000/tcp | port 추가 |
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 -t | sshd 설정 문법 검사 |
systemctl reload sshd | sshd 재적용 (연결 안 끊김) |
semanage port -a -t ssh_port_t -p tcp 2222 | SELinux에 포트 등록 |
정리 #
이번 글에서 잡은 그림:
- 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 머신 한 대를 띄우고 등록까지
- #3 —
dnf로 패키지를 깔고 지우고 롤백 / AppStream과 modules - #4 —
systemd로 서비스를 띄우고 / 첫 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 | 자격증 트랙 — 시험 도메인 기반 |
이 시리즈가 다음 트랙들의 출발점입니다. 본인 환경의 필요에 따라 골라 따라오세요.