안녕하세요, 성장하는 개발자 여러분!
“서버 보안”이라고 하면 뭔가 복잡하고 어려운 것처럼 느껴지시나요? 하지만 실제로는 몇 가지 핵심 원칙만 제대로 지키면 90%의 공격을 막을 수 있습니다. 오늘은 가장 기본이면서도 강력한 두 가지 보안 도구인 방화벽과 OpenSSH를 활용해 서버를 안전하게 지키는 방법을 알려드리겠습니다.
실무에서 제가 직접 겪었던 보안 사고들과 그 해결 과정을 통해 배운 실전 노하우를 함께 공유하니, 끝까지 집중해서 읽어보시길 바랍니다! 🔒
1. 서버 보안의 기본 원칙: 최소 권한과 다층 방어
보안의 황금률
- 최소 권한 원칙: 꼭 필요한 것만 열어두기
- 다층 방어: 하나의 보안 장치에 의존하지 않기
- 지속적인 모니터링: 로그를 통한 이상 징후 감지
- 정기적인 업데이트: 보안 패치는 최우선
실무에서 자주 발생하는 보안 위협들
# 실제 공격 시도 로그 예시
grep "Failed password" /var/log/secure | tail -5
# Jan 15 14:32:11 server sshd[1234]: Failed password for root from 192.168.1.100 port 22
# Jan 15 14:32:13 server sshd[1235]: Failed password for admin from 192.168.1.100 port 22
# Jan 15 14:32:15 server sshd[1236]: Failed password for user from 192.168.1.100 port 22
# 의심스러운 접속 시도 확인
journalctl -u sshd | grep "authentication failure" | tail -10
이런 로그들이 보이시나요? 이미 여러분의 서버가 공격받고 있을 수 있습니다!
2. Firewalld: 현대적인 방화벽 관리의 핵심
Firewalld vs iptables: 왜 Firewalld를 써야 할까?
Firewalld의 장점:
- 동적 설정 변경: 서비스 재시작 없이 즉시 적용
- Zone 기반 관리: 네트워크 환경별 정책 분리
- GUI 지원: 명령어뿐만 아니라 그래픽 도구도 제공
- XML 설정: 사람이 읽기 쉬운 설정 파일
기본 설정과 상태 확인
# 1. Firewalld 상태 확인
sudo systemctl status firewalld
sudo firewall-cmd --state
# 2. 현재 설정 확인
sudo firewall-cmd --list-all
# 3. 활성 영역(Zone) 확인
sudo firewall-cmd --get-active-zones
# 4. 기본 영역 확인
sudo firewall-cmd --get-default-zone
Zone 개념 이해하기
Firewalld의 가장 강력한 기능 중 하나가 Zone입니다:
# 사용 가능한 모든 Zone 확인
sudo firewall-cmd --get-zones
# block dmz drop external home internal public trusted work
# 각 Zone의 특성
# - drop: 모든 들어오는 패킷 차단 (가장 엄격)
# - block: 들어오는 패킷 거부 응답
# - public: 기본 Zone, SSH와 DHCP 클라이언트만 허용
# - external: 외부 네트워크용, NAT 환경에서 사용
# - internal: 내부 네트워크용, 더 많은 서비스 허용
# - home: 홈 네트워크용, 파일 공유 등 허용
# - work: 직장 네트워크용
# - trusted: 모든 패킷 허용 (가장 관대)
실무 시나리오별 방화벽 설정
시나리오 1: 웹 서버 보안 설정
# 기본 Zone을 public으로 설정 (이미 기본값)
sudo firewall-cmd --set-default-zone=public
# HTTP/HTTPS 서비스 영구 허용
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
# 커스텀 포트 허용 (예: 8080)
sudo firewall-cmd --permanent --add-port=8080/tcp
# 설정 적용
sudo firewall-cmd --reload
# 확인
sudo firewall-cmd --list-services
sudo firewall-cmd --list-ports
시나리오 2: 데이터베이스 서버 보안 강화
# 특정 IP에서만 MySQL 접근 허용
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="3306" accept'
# 특정 서버에서만 PostgreSQL 접근 허용
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.100" port protocol="tcp" port="5432" accept'
# 모든 외부 접근 차단하고 내부 네트워크만 허용
sudo firewall-cmd --permanent --zone=internal --add-source=192.168.1.0/24
sudo firewall-cmd --permanent --zone=internal --add-service=mysql
sudo firewall-cmd --permanent --zone=internal --add-service=postgresql
sudo firewall-cmd --reload
시나리오 3: 개발 서버 임시 포트 열기
# 임시로 포트 열기 (재부팅 시 삭제됨)
sudo firewall-cmd --add-port=3000/tcp
sudo firewall-cmd --add-port=8080/tcp
# 확인
sudo firewall-cmd --list-ports
# 영구 설정으로 변경 (필요한 경우)
sudo firewall-cmd --runtime-to-permanent
Rich Rules: 고급 방화벽 정책
# 1. 특정 시간대에만 SSH 접근 허용 (고급 설정)
# 평일 9시-18시에만 특정 IP에서 SSH 접근 허용
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.100" service name="ssh" log prefix="SSH Access: " level="info" limit value="3/m" accept'
# 2. 무차별 대입 공격 방지
# 동일 IP에서 5번 이상 실패 시 10분간 차단
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" service name="ssh" log prefix="SSH Brute Force: " level="warning" limit value="5/m" drop'
# 3. 포트 스캔 감지 및 차단
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="22" protocol="tcp" log prefix="SSH Scan: " level="warning" limit value="1/m" accept'
방화벽 로그 모니터링
# 방화벽 로그 실시간 모니터링
sudo journalctl -u firewalld -f
# 특정 키워드로 로그 필터링
sudo journalctl -u firewalld | grep "SSH"
sudo journalctl -u firewalld | grep "DROP"
# 로그 분석 스크립트
#!/bin/bash
echo "=== 오늘의 방화벽 로그 요약 ==="
echo "총 차단된 연결 수:"
sudo journalctl -u firewalld --since today | grep -c "DROP"
echo "SSH 접근 시도:"
sudo journalctl -u firewalld --since today | grep -c "SSH"
echo "가장 많이 시도한 IP:"
sudo journalctl -u firewalld --since today | grep -oP 'from K[0-9.]+' | sort | uniq -c | sort -nr | head -5
3. OpenSSH: 안전한 원격 접속의 모든 것
SSH 기본 보안 강화
1. SSH 설정 파일 최적화
# SSH 설정 파일 백업
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
# 주요 보안 설정
sudo nano /etc/ssh/sshd_config
필수 보안 설정들:
# /etc/ssh/sshd_config 권장 설정
# 1. 기본 포트 변경 (선택사항)
Port 2222
# 2. Root 로그인 완전 차단
PermitRootLogin no
# 3. 패스워드 로그인 비활성화 (키 인증만 허용)
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no
# 4. 빈 패스워드 금지
PermitEmptyPasswords no
# 5. X11 포워딩 비활성화 (필요없는 경우)
X11Forwarding no
# 6. 로그인 시간 제한
LoginGraceTime 30
# 7. 최대 동시 세션 제한
MaxSessions 3
# 8. 특정 사용자만 SSH 접근 허용
AllowUsers developer admin
# 9. 특정 그룹만 SSH 접근 허용
AllowGroups ssh-users
# 10. 연결 유지 설정
ClientAliveInterval 300
ClientAliveCountMax 2
설정 후 서비스 재시작:
# 설정 파일 문법 검사
sudo sshd -t
# SSH 서비스 재시작
sudo systemctl restart sshd
# 상태 확인
sudo systemctl status sshd
SSH 키 기반 인증: 패스워드보다 1000배 안전한 방법
1. SSH 키 쌍 생성
# RSA 키 생성 (4096비트, 더 안전)
ssh-keygen -t rsa -b 4096 -C "your-email@domain.com"
# 또는 더 최신의 Ed25519 키 (권장)
ssh-keygen -t ed25519 -C "your-email@domain.com"
# 키 생성 시 옵션들
# -t: 키 타입 (rsa, ed25519)
# -b: 키 비트 수
# -C: 코멘트 (보통 이메일)
# -f: 키 파일 이름 지정
2. 공개키 서버에 복사
# 자동으로 공개키 복사
ssh-copy-id username@server-ip
# 커스텀 포트 사용 시
ssh-copy-id -p 2222 username@server-ip
# 특정 키 파일 지정
ssh-copy-id -i ~/.ssh/custom_key.pub username@server-ip
# 수동으로 복사 (ssh-copy-id 사용 불가 시)
cat ~/.ssh/id_rsa.pub | ssh username@server-ip "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
3. SSH 키 관리 및 보안
# 키 권한 설정 (매우 중요!)
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub
chmod 600 ~/.ssh/authorized_keys
# 여러 키 관리를 위한 SSH config 설정
nano ~/.ssh/config
SSH Config 예시:
# ~/.ssh/config
Host prod-server
    HostName 192.168.1.100
    Port 2222
    User developer
    IdentityFile ~/.ssh/prod_server_key
Host dev-server
    HostName 192.168.1.200
    Port 22
    User admin
    IdentityFile ~/.ssh/dev_server_key
Host git-server
    HostName github.com
    User git
    IdentityFile ~/.ssh/github_key
# 모든 서버에 대한 기본 설정
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    StrictHostKeyChecking ask
    UserKnownHostsFile ~/.ssh/known_hosts
사용법:
# 간단하게 접속
ssh prod-server
ssh dev-server
# 파일 복사도 편리하게
scp myfile.txt prod-server:~/
SSH 터널링: 안전한 포트 포워딩
로컬 포트 포워딩
# 원격 서버의 MySQL을 로컬에서 접근
ssh -L 3306:localhost:3306 username@server-ip
# 원격 서버의 웹 애플리케이션을 로컬에서 접근
ssh -L 8080:localhost:80 username@server-ip
# 백그라운드 실행
ssh -f -N -L 3306:localhost:3306 username@server-ip
리모트 포트 포워딩
# 로컬의 서비스를 원격에서 접근 가능하게 (주의 필요)
ssh -R 8080:localhost:3000 username@server-ip
# 백그라운드 실행
ssh -f -N -R 8080:localhost:3000 username@server-ip
SOCKS 프록시
# SOCKS 프록시 생성
ssh -D 1080 username@server-ip
# 브라우저에서 localhost:1080을 SOCKS 프록시로 설정하면
# 모든 트래픽이 서버를 통해 전송됨
4. 실전 보안 모니터링과 자동화
SSH 접속 로그 분석
# 성공한 SSH 로그인 확인
sudo grep "Accepted" /var/log/secure
# 실패한 SSH 로그인 확인
sudo grep "Failed password" /var/log/secure
# 특정 IP의 접속 시도 분석
sudo grep "192.168.1.100" /var/log/secure
# 오늘의 SSH 활동 요약
sudo journalctl -u sshd --since today | grep -E "(Accepted|Failed)"
자동 보안 모니터링 스크립트
#!/bin/bash
# ssh_monitor.sh - SSH 보안 모니터링 스크립트
LOG_FILE="/var/log/ssh_monitor.log"
SECURE_LOG="/var/log/secure"
ALERT_THRESHOLD=5
echo "=== SSH 보안 모니터링 시작: $(date) ===" >> $LOG_FILE
# 1. 실패한 로그인 시도 카운트
FAILED_ATTEMPTS=$(grep "Failed password" $SECURE_LOG | grep "$(date +%b %d)" | wc -l)
if [ $FAILED_ATTEMPTS -gt $ALERT_THRESHOLD ]; then
    echo "⚠️  경고: 오늘 $FAILED_ATTEMPTS 번의 로그인 실패가 발생했습니다!" >> $LOG_FILE
    # 가장 많이 시도한 IP 찾기
    TOP_ATTACKERS=$(grep "Failed password" $SECURE_LOG | grep "$(date +%b %d)" | 
                   grep -oP 'from K[0-9.]+' | sort | uniq -c | sort -nr | head -3)
    echo "주요 공격 IP들:" >> $LOG_FILE
    echo "$TOP_ATTACKERS" >> $LOG_FILE
    # 자동 차단 (선택사항)
    echo "$TOP_ATTACKERS" | while read count ip; do
        if [ $count -gt 10 ]; then
            echo "IP $ip 자동 차단 (시도 횟수: $count)" >> $LOG_FILE
            sudo firewall-cmd --add-rich-rule="rule family='ipv4' source address='$ip' drop"
        fi
    done
fi
# 2. 새로운 SSH 키 추가 감지
AUTHORIZED_KEYS="/home/*/.ssh/authorized_keys"
for auth_file in $AUTHORIZED_KEYS; do
    if [ -f "$auth_file" ]; then
        KEY_COUNT=$(wc -l < "$auth_file")
        echo "$(dirname $auth_file): $KEY_COUNT 개의 SSH 키" >> $LOG_FILE
    fi
done
# 3. Root 로그인 시도 감지
ROOT_ATTEMPTS=$(grep "Failed password for root" $SECURE_LOG | grep "$(date +%b %d)" | wc -l)
if [ $ROOT_ATTEMPTS -gt 0 ]; then
    echo "🚨 위험: Root 계정으로 $ROOT_ATTEMPTS 번의 로그인 시도가 있었습니다!" >> $LOG_FILE
fi
echo "=== 모니터링 완료: $(date) ===" >> $LOG_FILE
echo "" >> $LOG_FILE
Fail2Ban: 자동 침입 차단 시스템
# Fail2Ban 설치
sudo dnf install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# SSH 보호 설정
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
# 차단 시간 (초)
bantime = 3600
# 관찰 시간 (초)
findtime = 600
# 최대 시도 횟수
maxretry = 5
[sshd]
enabled = true
port = ssh
logpath = /var/log/secure
maxretry = 3
bantime = 7200
# Fail2Ban 재시작
sudo systemctl restart fail2ban
# 상태 확인
sudo fail2ban-client status
sudo fail2ban-client status sshd
# 차단된 IP 확인
sudo fail2ban-client get sshd banned
# 수동으로 IP 차단/해제
sudo fail2ban-client set sshd banip 192.168.1.100
sudo fail2ban-client set sshd unbanip 192.168.1.100
5. 보안 점검 체크리스트와 베스트 프랙티스
일일 보안 점검 체크리스트
#!/bin/bash
# daily_security_check.sh
echo "=== 일일 보안 점검 $(date) ==="
# 1. 시스템 업데이트 확인
echo "1. 시스템 업데이트 상태:"
dnf check-update | wc -l
# 2. 방화벽 상태 확인
echo "2. 방화벽 상태:"
sudo systemctl is-active firewalld
# 3. SSH 서비스 상태 확인
echo "3. SSH 서비스 상태:"
sudo systemctl is-active sshd
# 4. 활성 네트워크 연결 확인
echo "4. 현재 활성 연결 (상위 10개):"
sudo ss -tuln | head -10
# 5. 마지막 로그인 확인
echo "5. 최근 로그인 기록:"
last -n 5
# 6. 수상한 프로세스 확인
echo "6. 높은 CPU 사용 프로세스:"
ps aux --sort=-%cpu | head -5
# 7. 디스크 사용량 확인
echo "7. 디스크 사용량:"
df -h | grep -v tmpfs
# 8. 실패한 로그인 시도
echo "8. 오늘의 실패한 로그인 시도:"
sudo grep "Failed password" /var/log/secure | grep "$(date +%b %d)" | wc -l
echo "=== 점검 완료 ==="
월간 보안 리뷰 체크리스트
- [ ] 사용자 계정 검토: 불필요한 계정 제거
- [ ] SSH 키 정리: 사용하지 않는 키 제거
- [ ] 방화벽 규칙 점검: 불필요한 규칙 정리
- [ ] 로그 분석: 수상한 활동 패턴 확인
- [ ] 보안 패치 적용: 최신 보안 업데이트
- [ ] 백업 확인: 설정 파일 백업 상태 점검
보안 사고 대응 절차
1. 즉시 대응 (5분 이내)
# 의심스러운 IP 즉시 차단
sudo firewall-cmd --add-rich-rule="rule family='ipv4' source address='SUSPICIOUS_IP' drop"
# 모든 SSH 연결 확인
who
w
# 의심스러운 세션 강제 종료
sudo pkill -u suspicious_user
# 긴급 상황 시 SSH 포트 임시 변경
sudo sed -i 's/Port 22/Port 2222/' /etc/ssh/sshd_config
sudo systemctl restart sshd
2. 조사 및 분석 (30분 이내)
# 상세 로그 분석
sudo grep -E "($(date +%b %d)|$(date -d yesterday +%b %d))" /var/log/secure > security_incident_$(date +%Y%m%d).log
# 네트워크 연결 상태 저장
sudo ss -tuln > network_status_$(date +%Y%m%d).log
# 프로세스 상태 저장
ps aux > process_status_$(date +%Y%m%d).log
3. 복구 및 강화 (1시간 이내)
# 패스워드 강제 변경
sudo passwd username
# SSH 키 교체
ssh-keygen -t ed25519 -C "incident-response-$(date +%Y%m%d)"
# 방화벽 규칙 강화
sudo firewall-cmd --set-default-zone=drop
sudo firewall-cmd --zone=trusted --add-source=ADMIN_IP
마치며: 보안은 여정이지 목적지가 아닙니다
핵심 원칙 재정리
- 기본을 충실히: 방화벽 + SSH 키 인증만으로도 90% 보안 확보
- 지속적인 모니터링: 로그 분석과 이상 징후 감지
- 정기적인 점검: 보안 설정과 계정 관리 
- 빠른 대응: 사고 발생 시 신속한 차단과 분석
실무에서 기억할 점
- 편의성 vs 보안: 항상 트레이드오프 관계임을 인식
- 문서화: 모든 보안 설정과 대응 절차를 문서로 남기기
- 팀 공유: 보안 지식과 절차를 팀원들과 공유
- 지속적인 학습: 새로운 공격 기법과 방어 방법 학습
다음 단계
오늘 배운 내용을 바탕으로:
- 테스트 서버에서 연습: 실제 운영 서버에 적용하기 전에 충분히 연습
- 모니터링 시스템 구축: 자동화된 보안 모니터링 환경 구성
- 백업 계획 수립: 보안 사고 시 빠른 복구를 위한 백업 전략
- 팀 내 보안 문화 조성: 개발팀 전체가 보안을 의식하는 문화 만들기
기억하세요: 완벽한 보안은 존재하지 않습니다. 하지만 기본적인 보안 수칙을 지키는 것만으로도 대부분의 공격으로부터 서버를 안전하게 지킬 수 있습니다.
보안은 한 번 설정하고 끝나는 것이 아니라 지속적으로 관리하고 개선해야 하는 여정입니다. 오늘부터 여러분도 이 여정에 함께해주세요! 🛡️
다음 포스트에서는 “로그 관리와 모니터링 실전 가이드”에서 더 고급 모니터링 기법들을 다뤄보겠습니다. 기대해 주세요!