13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] CIS 벤치마크

  • [보안] CIS 벤치마크 기반 서버 보안 강화: 실제 적용 사례와 효과

    [보안] CIS 벤치마크 기반 서버 보안 강화: 실제 적용 사례와 효과

    CIS 벤치마크 기반 서버 보안 강화: 실제 적용 사례와 효과

    서버를 오래 운영하다 보면 한 번쯤은 이런 순간이 옵니다. 서비스는 잘 돌아가는데, 막상 CIS 벤치마크 서버 보안 관점에서 보면 기본 설정이 생각보다 허술한 경우가 있거든요. 저도 홈랩(Home Lab, 개인 실험용 인프라 환경)과 업무 환경에서 Linux(리눅스) 서버를 같이 만지다 보니, “기능은 되는데 보안 감사(Security Audit, 보안 점검)에서 계속 걸리네?” 싶은 날이 있었습니다. 특히 계정 정책, SSH(Secure Shell, 원격 접속), 파일 권한, 로그 설정 같은 항목은 평소엔 문제 없어 보여도 실제 감사 시점에는 바로 드러나더라고요. 그래서 이번 글에서는 제가 직접 정리해서 적용했던 CIS 벤치마크 기반 서버 보안 강화 과정을 사례 중심으로 풀어보겠습니다.

    이 글은 특정 제품 홍보가 아니라, 실제로 많이 부딪히는 Linux 보안 강화 흐름을 기준으로 적었습니다. 보안 감사 대응이 필요하신 분, 서버 설정을 체계적으로 정리하고 싶은 분, 그리고 “어디서부터 손대야 하지?” 막막하신 분께 특히 도움이 될 겁니다.

    CIS 벤치마크 서버 보안 전체 아키텍처 개요 이미지

    기본 서버에서 계정 정책, SSH 설정, 로그 수집, 감사 항목을 단계적으로 강화하는 전체 흐름을 보여주는 이미지입니다.

    CIS 벤치마크 서버 보안, 쉽게 말하면 뭘까요?

    CIS Benchmarks(CIS 벤치마크, Center for Internet Security에서 제공하는 보안 설정 권고안)는 운영체제와 소프트웨어를 보다 안전하게 설정하기 위한 기준 모음입니다. 쉽게 말해, “이 정도는 최소한 맞춰두면 보안 사고 가능성을 꽤 줄일 수 있습니다”라는 체크리스트에 가깝습니다.

    처음엔 저도 이게 뭔가 싶었습니다. 문서를 보면 항목이 많고, 다 적용하면 서비스가 멈플 것 같아 겁부터 나거든요. 근데 실제로 써보니까 핵심은 단순합니다. 모든 항목을 기계적으로 넣는 게 아니라, 서비스 영향도를 보면서 우선순위를 나눠 적용하는 것이더라고요.

    현장에서 특히 많이 보는 점검 항목

    • 계정 및 인증(Authentication, 사용자 신원 확인): 패스워드 정책, 불필요한 계정 정리, sudo 권한 제한
    • SSH 설정: root 직접 로그인 차단, 인증 방식 점검, 접속 가능한 사용자 제한
    • 파일 권한(File Permission, 접근 권한): 중요한 설정 파일의 소유자와 권한 조정
    • 로그와 감사(Auditing, 행위 기록 추적): auth 로그, sudo 로그, 변경 이력 보관
    • 네트워크 최소화: 불필요한 포트와 서비스 비활성화
    구분 적용 전 적용 후
    계정 관리 오래된 계정 방치 불필요 계정 잠금 또는 제거
    SSH 접속 기본값 위주 운영 root 로그인 차단, 접근 사용자 제한
    로그 추적 기본 로그만 확인 감사 기준에 맞춰 로그 보강
    설정 일관성 서버마다 편차 큼 표준 설정 베이스 확보

    여기서 중요한 포인트! CIS 벤치마크 서버 보안은 만능 보안 솔루션이 아닙니다. 다만 서버 설정의 기준선을 맞춰주기 때문에, 보안 감사나 운영 안정성 측면에서 체감 효과가 꽤 큽니다.

    제가 적용할 때 잡았던 기준: 한 번에 다 하지 않기

    실전에서는 보통 신규 서버보다 기존 운영 서버가 더 문제입니다. 이미 서비스가 돌아가고 있으니 설정 하나 잘못 건드리면 장애로 이어질 수 있거든요. 저도 처음엔 의욕이 앞서서 SSH, PAM(Pluggable Authentication Modules, 인증 모듈), sysctl 커널 파라미터까지 한 번에 바꾸려다가 접속 정책 꼬여서 삽질 좀 했습니다 ㅎㅎ

    그래서 이후부터는 아래 순서로 정리했습니다.

    1. 현재 상태 수집
    2. 위험도 높은 항목 우선 적용
    3. 변경 전 백업
    4. 적용 후 즉시 검증
    5. 운영 표준 문서화

    이 순서가 별거 아닌 것 같아도 진짜 중요합니다. 특히 보안 감사 대응에서는 “무엇을 바꿨는지”보다 “왜 이렇게 바꿨고, 어떻게 검증했는지”가 더 중요할 때가 많더라고요.

    실전 구현 1: 현재 서버 설정과 계정 상태 점검

    가장 먼저 한 일은 현황 파악이었습니다. 서버 설정을 바꾸기 전에 지금 어떤 계정이 있고, SSH가 어떻게 열려 있고, 권한이 어떤지 보는 거죠. 아래 명령어는 배포판에 크게 구애받지 않고 기본 점검에 쓸 수 있습니다.

    # 접속 중인 사용자 확인
    who
    
    # 최근 로그인 이력 확인
    last -a | head
    
    # sudo 권한 그룹 확인
    getent group sudo
    getent group wheel
    
    # root 원격 로그인 설정 확인
    grep -Ei '^PermitRootLogin' /etc/ssh/sshd_config
    
    # 패스워드 정책 관련 기본 설정 확인
    grep -E 'PASS_MAX_DAYS|PASS_MIN_DAYS|PASS_WARN_AGE' /etc/login.defs
    
    # 중요 파일 권한 확인
    ls -l /etc/passwd /etc/shadow /etc/group /etc/ssh/sshd_config

    제가 직접 해보니 여기서 이미 문제점이 꽤 보였습니다. 예전 작업용 계정이 남아 있거나, SSH 설정 파일에 주석만 있고 실설정은 애매한 경우도 있었고요. 이런 상태에서 바로 강화 설정부터 넣으면, 나중에 왜 접속이 막혔는지 추적이 어려워집니다.

    CIS 벤치마크 서버 보안 점검을 위한 리눅스 터미널 이미지

    계정 상태, SSH 설정, 파일 권한을 터미널에서 점검하는 실전 분위기의 이미지입니다.

    실전 구현 2: SSH와 계정 정책부터 우선 강화

    초기 단계에서는 영향도가 크지만 효과도 분명한 항목부터 만졌습니다. 제 경험상 SSH와 계정 정책만 정리해도 보안 감사 결과가 꽤 달라지더라고요.

    1. root 직접 로그인 차단

    sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
    sudo vi /etc/ssh/sshd_config
    PermitRootLogin no
    PasswordAuthentication yes
    X11Forwarding no
    MaxAuthTries 4
    ClientAliveInterval 300
    ClientAliveCountMax 0
    sudo sshd -t
    sudo systemctl reload sshd

    여기서 중요한 건 reload 전에 반드시 문법 검증입니다. 저도 예전에 오타 하나로 SSH 데몬이 reload 실패해서 콘솔로 붙었던 적이 있습니다. 원격 서버면 정말 식은땀 납니다.

    2. 접속 허용 사용자 제한

    # /etc/ssh/sshd_config 예시
    AllowUsers adminuser deployuser

    운영 서버는 접속 대상이 많을수록 관리가 어렵습니다. 그래서 관리 계정은 분리하고, 실제 접속이 필요한 사용자만 명시하는 편이 훨씬 낫더라고요.

    3. 패스워드 정책 정리

    sudo vi /etc/login.defs
    PASS_MAX_DAYS   90
    PASS_MIN_DAYS   1
    PASS_WARN_AGE   7

    환경에 따라 MFA(Multi-Factor Authentication, 다중 인증)나 키 기반 인증 위주로 운영하는 경우도 있겠지만, 기본 정책이 비어 있으면 보안 감사에서 자주 지적됩니다. 그래서 사용 여부와 별개로 기준은 잡아두는 편이 좋더라고요.

    실전 구현 3: 파일 권한과 감사 로그 보강

    그다음은 Linux 보안에서 기본 중의 기본인 파일 권한과 로그입니다. 평소엔 잘 눈에 안 띄는데, 사고 나면 가장 먼저 후회하는 부분이기도 하죠.

    중요 파일 권한 조정

    sudo chown root:root /etc/ssh/sshd_config
    sudo chmod 600 /etc/ssh/sshd_config
    sudo chown root:root /etc/shadow
    sudo chmod 000 /etc/shadow
    sudo chmod 644 /etc/passwd /etc/group

    배포판 기본값과 다를 수 있으니 적용 전 확인은 필수입니다. 특히 자동화 도구나 이미지 템플릿이 이미 정책을 넣어둔 경우가 있어서, 무작정 덮어쓰면 오히려 표준에서 벗어날 수 있습니다.

    sudo 로그 남기기

    sudo visudo
    Defaults logfile="/var/log/sudo.log"

    이 설정은 나중에 보안 감사뿐 아니라 내부 추적에도 꽤 유용했습니다. 누가 언제 어떤 권한 명령을 쳤는지 보는 것만으로도 원인 파악 속도가 많이 달라지거든요.

    불필요 서비스 점검

    systemctl list-unit-files --type=service --state=enabled
    ss -tulpen

    여기서 안 쓰는 서비스가 떠 있으면 정리합니다. 예를 들어 테스트용으로 잠깐 열어둔 데몬이 계속 살아 있는 경우가 꽤 있습니다. 보안은 거창한 장비보다도 이런 기본 서버 설정 정리에서 차이가 납니다.

    CIS 벤치마크 서버 보안을 위한 SSH 설정과 감사 로그 구성 이미지

    SSH 접근 제어와 sudo 로그 기록 흐름을 한눈에 보여주는 구성 다이어그램 이미지입니다.

    ⚠️ 실제로 겪었던 문제와 트러블슈팅

    이 부분이 제일 중요할 수도 있습니다. 문서만 보면 다 간단해 보이는데, 실제 적용하면 꼭 한두 군데서 걸립니다.

    문제 1. SSH 설정 반영 후 접속 실패

    원인은 대부분 두 가지였습니다. 하나는 AllowUsers에 현재 운영 계정을 빼먹은 경우, 다른 하나는 sshd_config 오타였습니다.

    • 해결 방법 1: 설정 변경 전 현재 세션은 유지한 상태에서 새 세션으로 접속 테스트
    • 해결 방법 2: sshd -t로 문법 검증 후 reload
    • 해결 방법 3: 가능하면 콘솔 접속 경로 확보 후 작업

    문제 2. 보안 감사는 통과했는데 운영팀이 불편해함

    이것도 많이 겪습니다. 정책은 강화됐는데 현업이 쓰기 불편하면 결국 예외 요청이 쌓이거든요. 그래서 저는 서버 역할별로 기준을 나눴습니다. 배치 서버, 운영 웹 서버, 점프 서버(Jump Server, 중간 접속용 서버)를 같은 기준으로 묶지 않았습니다.

    서버 유형 강화 우선 항목 주의점
    운영 웹 서버 SSH 제한, 로그 강화, 불필요 서비스 제거 배포 계정 접근 경로 확인
    점프 서버 계정 감사, sudo 로그, 접속 기록 보관 관리자 편의성과 통제 균형
    배치 서버 계정 만료 정책, 스크립트 권한 점검 자동 실행 계정 영향 확인

    문제 3. 자동화 스크립트가 갑자기 실패

    패스워드 정책이나 파일 권한을 강화하고 나면 기존 스크립트가 가정하고 있던 조건이 깨질 수 있습니다. 저도 키 파일 권한을 조정한 뒤 일부 배포 스크립트가 실패한 적이 있었는데, 원인을 찾고 보니 권한 검사가 더 엄격해졌더라고요. 드디어 됐다 싶다가 다시 되돌아가서 확인하는 과정, 다들 한 번쯤 있으시죠.

    검증과 결과: 무엇이 달라졌나

    설정 적용 후에는 꼭 검증을 했습니다. 그냥 파일만 바뀌었다고 끝내면 안 됩니다. 실제 접속, 권한, 로그, 서비스 상태를 같이 봐야 하거든요.

    # SSH 설정 최종 확인
    sshd -T | egrep 'permitrootlogin|maxauthtries|clientaliveinterval|clientalivecountmax'
    
    # sudo 로그 확인
    sudo tail -n 20 /var/log/sudo.log
    
    # 열려 있는 포트 점검
    ss -tulpen
    
    # 최근 인증 로그 확인
    sudo tail -n 50 /var/log/auth.log

    검증 단계에서 제가 체감한 효과는 크게 네 가지였습니다.

    1. 보안 감사 대응 속도 향상: 항목별 근거를 바로 제시하기 쉬워졌습니다.
    2. 설정 표준화: 서버마다 제각각이던 서버 설정이 어느 정도 정리됐습니다.
    3. 추적성 확보: sudo와 인증 로그가 정리되니 사고 분석이 편해졌습니다.
    4. 운영 리스크 감소: root 직접 로그인 같은 명백한 위험 요소를 줄였습니다.

    물론 한 번 적용했다고 끝은 아닙니다. 하지만 CIS 벤치마크 서버 보안 기준으로 최소선만 맞춰도, 평소 불안하게 남아 있던 부분이 꽤 정리됩니다. 특히 보안 감사 준비할 때 체감 차이가 큽니다.

    CIS 벤치마크 서버 보안 적용 후 보안 감사 검증 대시보드 이미지

    적용 전후 점검 항목과 로그 확인 결과를 시각적으로 보여주는 대시보드 형태의 이미지입니다.

    정리: 제가 배운 점과 다음 단계

    이번 적용에서 다시 느낀 건, 보안은 대단한 도구보다도 기본을 얼마나 꾸준히 맞추느냐가 훨씬 중요하다는 점이었습니다. Linux 보안 강화는 막연히 어렵게 느껴지지만, 실제로는 계정 정책, SSH, 권한, 로그처럼 반복해서 나오는 핵심 축이 있습니다. 저도 처음엔 항목이 많아서 겁먹었는데, 하나씩 잘라서 적용하니 생각보다 정리가 되더라고요.

    혹시 지금 운영 중인 서버가 있고, 보안 감사 일정이 다가오고 있다면 이렇게 시작해보세요.

    1. 현재 계정과 SSH 설정부터 점검합니다.
    2. root 로그인 차단과 접근 사용자 제한을 우선 적용합니다.
    3. 중요 파일 권한과 로그 기록을 보강합니다.
    4. 변경 직후 검증 명령으로 바로 확인합니다.
    5. 서버 역할별 예외 정책을 문서화합니다.

    이 흐름만 잡혀도 서버 설정 품질이 눈에 띄게 좋아집니다. 다음 글에서는 Ansible(앤서블, 자동화 도구) 같은 구성 관리 방식으로 이 작업을 반복 가능하게 만드는 방법도 다뤄볼 예정입니다. 이전 글에서 다뤘던 로그 관리나 계정 표준화 주제와도 연결해서 보시면 더 이해가 쉬우실 거예요.

    CIS 벤치마크 서버 보안 적용 전후 비교 요약 이미지

    적용 전후 차이, 핵심 점검 항목, 다음 단계 체크리스트를 요약한 인포그래픽 이미지입니다.

    자주 묻는 질문

    Q1. CIS 벤치마크 항목을 모두 적용해야 하나요?

    아닙니다. 서비스 특성과 운영 환경에 따라 예외가 생길 수 있습니다. 중요한 건 근거 없이 빼는 게 아니라, 왜 제외했는지 설명 가능해야 한다는 점입니다.

    Q2. 기존 운영 서버에도 바로 적용해도 될까요?

    가능은 하지만, 반드시 백업과 검증 경로를 확보한 뒤 단계적으로 적용하는 걸 권장합니다. 특히 원격 접속 정책은 새 세션으로 꼭 테스트해보세요.

    Q3. 보안 감사 대응에 실제 도움이 되나요?

    네, 꽤 도움이 됩니다. 적어도 계정 정책, 접근 통제, 로그 추적성 같은 기본 항목에서 설명이 훨씬 쉬워집니다. 저도 실제로 써보니까 이 부분이 가장 체감되더라고요.

  • [Cloud] Ansible 클라우드 보안 자동화: 취약점 관리 및 규정 준수 가이드

    클라우드 보안, 손으로 하나하나 설정하다가 사고 날 뻔했습니다

    솔직히 말씀드리면, 저도 처음엔 보안 설정을 수작업으로 했어요. EC2 인스턴스 하나하나 들어가서 패키지 업데이트 확인하고, 보안 그룹 규칙 검토하고… 인스턴스가 10개쯤 됐을 때까진 그나마 버텼는데, 50개 넘어가니까 진짜 손이 두 개로는 부족하더라고요. 어느 날 감사(Audit) 리포트 보다가 패치 안 된 서버가 12대나 있다는 걸 발견했을 때 등에 식은땀이 흘렀습니다.

    그때부터 Ansible 보안 자동화를 본격적으로 파기 시작했어요. 취약점 관리(Vulnerability Management)부터 규정 준수(Compliance) 검증까지, Ansible로 어떻게 자동화할 수 있는지 실무 경험을 바탕으로 풀어드릴게요. 클라우드 환경에서 보안을 체계적으로 관리하고 싶으신 분들께 실질적인 도움이 됐으면 합니다.

    ▲ Ansible을 중심으로 한 클라우드 보안 자동화 전체 흐름 — 인벤토리 수집부터 취약점 패치, 규정 준수 보고까지 한눈에


    Ansible 보안 자동화, 이게 왜 필요한가요?

    쉽게 말해서, 클라우드 환경은 서버가 수시로 생겼다 없어지거든요. 온프레미스(자체 서버실)처럼 고정된 인프라가 아니에요. 오늘 오토스케일링으로 인스턴스 10개 생겼다가 내일 5개로 줄어들 수 있잖아요. 이런 환경에서 수작업 보안 관리는 현실적으로 불가능합니다.

    Ansible 보안 자동화가 필요한 이유를 정리해보면:

    • 일관성(Consistency): 모든 서버에 동일한 보안 정책 적용 — 사람이 하면 실수가 생기지만 플레이북(Playbook)은 실수 안 함
    • 속도(Speed): 수백 대 서버에 패치 적용을 몇 분 안에 완료
    • 감사 추적(Audit Trail): 언제, 어떤 변경이 있었는지 코드로 기록됨
    • 반복 가능성(Repeatability): 같은 작업을 언제든 동일하게 재현 가능
    • 드리프트 감지(Configuration Drift Detection): 설정이 원하는 상태에서 벗어나면 자동으로 교정

    근데 여기서 중요한 포인트! Ansible은 에이전트리스(Agentless) 방식이에요. 관리 대상 서버에 별도 소프트웨어를 설치할 필요가 없고, SSH(또는 WinRM)만 열려 있으면 됩니다. 클라우드 환경에서 이게 엄청난 장점이거든요.


    Ansible Vault로 민감 정보 보호하기

    보안 자동화를 하면서 제일 먼저 부딪히는 문제가 뭔지 아세요? 바로 시크릿(Secret) 관리입니다. API 키, 데이터베이스 패스워드, 클라우드 자격증명… 이걸 플레이북 파일에 평문으로 넣으면 안 되잖아요.

    Ansible에는 Ansible Vault라는 내장 암호화 도구가 있어요. 파일이나 변수를 AES-256으로 암호화해서 Git에 안전하게 올릴 수 있게 해줍니다. 제가 실제로 쓰는 방식을 보여드릴게요.

    Vault 파일 생성 및 사용

    # vault 암호화 파일 생성
    ansible-vault create secrets/cloud_credentials.yml
    
    # 기존 파일 암호화
    ansible-vault encrypt vars/sensitive_vars.yml
    
    # 암호화된 파일 내용 확인
    ansible-vault view secrets/cloud_credentials.yml
    
    # 암호화된 파일 수정
    ansible-vault edit secrets/cloud_credentials.yml
    
    # 플레이북 실행 시 vault 패스워드 파일 사용
    ansible-playbook security_hardening.yml --vault-password-file ~/.vault_pass.txt
    # secrets/cloud_credentials.yml (암호화 전 내용 예시)
    ---
    aws_access_key: "AKIAIOSFODNN7EXAMPLE"
    aws_secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
    db_password: "super_secret_password_here"
    api_token: "your_api_token_here"

    💡 팁: 운영 환경에서는 vault 패스워드 파일을 직접 관리하는 것보다 HashiCorp Vault나 AWS Secrets Manager 같은 전용 시크릿 관리 서비스와 연동하는 게 훨씬 안전합니다.


    취약점 관리 자동화 실전

    이제 본격적으로 들어가볼게요. Ansible 취약점 관리의 핵심은 세 가지입니다: 취약 패키지 탐지 → 패치 적용 → 결과 보고. 제가 실제 환경에서 쓰는 플레이북 구조를 공유할게요.

    1단계: 인벤토리 동적 구성 (Dynamic Inventory)

    클라우드 환경에서는 정적 인벤토리(Static Inventory)가 아니라 동적 인벤토리(Dynamic Inventory)를 써야 해요. AWS 기준으로 보면:

    # inventory/aws_ec2.yml
    ---
    plugin: amazon.aws.aws_ec2
    regions:
      - ap-northeast-2  # 서울 리전
    filters:
      instance-state-name: running
      tag:Environment:
        - production
        - staging
    keyed_groups:
      - key: tags.Role
        prefix: role
      - key: tags.Environment
        prefix: env
    hostname_source: private-ip-address
    compose:
      ansible_host: private_ip_address
    # 동적 인벤토리 확인
    ansible-inventory -i inventory/aws_ec2.yml --list
    ansible-inventory -i inventory/aws_ec2.yml --graph

    2단계: 취약점 스캔 및 패치 플레이북

    # playbooks/vulnerability_management.yml
    ---
    - name: 취약점 스캔 및 보안 패치 자동화
      hosts: all
      become: yes
      gather_facts: yes
      vars_files:
        - ../secrets/cloud_credentials.yml
    
      vars:
        patch_reboot_required: false
        security_only: true
        report_path: "/var/log/ansible/security_report"
    
      tasks:
        - name: 리포트 디렉토리 생성
          file:
            path: "{{ report_path }}"
            state: directory
            mode: '0750'
            owner: root
            group: root
    
        - name: 패키지 캐시 업데이트 (Debian/Ubuntu)
          apt:
            update_cache: yes
            cache_valid_time: 3600
          when: ansible_os_family == "Debian"
    
        - name: 패키지 캐시 업데이트 (RHEL/CentOS/Amazon Linux)
          yum:
            update_cache: yes
          when: ansible_os_family == "RedHat"
    
        - name: 보안 업데이트 목록 확인 (Debian/Ubuntu)
          shell: apt list --upgradeable 2>/dev/null | grep -i security
          register: security_updates_deb
          changed_when: false
          failed_when: false
          when: ansible_os_family == "Debian"
    
        - name: 보안 업데이트 목록 확인 (RHEL 계열)
          shell: yum check-update --security 2>/dev/null | tail -n +3
          register: security_updates_rhel
          changed_when: false
          failed_when: security_updates_rhel.rc not in [0, 100]
          when: ansible_os_family == "RedHat"
    
        - name: 보안 패치 적용 (Debian/Ubuntu)
          apt:
            upgrade: dist
            update_cache: yes
            only_upgrade: yes
          when:
            - ansible_os_family == "Debian"
            - security_only | bool
          register: apt_upgrade_result
    
        - name: 보안 패치 적용 (RHEL 계열)
          yum:
            name: "*"
            state: latest
            security: yes
          when:
            - ansible_os_family == "RedHat"
            - security_only | bool
          register: yum_upgrade_result
    
        - name: 재부팅 필요 여부 확인 (Ubuntu)
          stat:
            path: /var/run/reboot-required
          register: reboot_required_file
          when: ansible_os_family == "Debian"
    
        - name: 결과 리포트 생성
          template:
            src: ../templates/security_report.j2
            dest: "{{ report_path }}/report_{{ ansible_date_time.date }}.txt"
            mode: '0640'
          vars:
            hostname: "{{ ansible_hostname }}"
            os_family: "{{ ansible_os_family }}"
            kernel_version: "{{ ansible_kernel }}"
            reboot_needed: "{{ reboot_required_file.stat.exists | default(false) }}"
    
        - name: 재부팅 필요 시 알림
          debug:
            msg: "⚠️ {{ ansible_hostname }} 서버는 패치 적용 후 재부팅이 필요합니다!"
          when:
            - ansible_os_family == "Debian"
            - reboot_required_file.stat.exists | default(false)

    ▲ Ansible 플레이북 실행 결과 — 각 호스트별 패치 적용 현황과 재부팅 필요 여부가 한눈에 표시됨

    3단계: 리포트 템플릿 (Jinja2)

    # templates/security_report.j2
    === 보안 패치 리포트 ===
    호스트명: {{ hostname }}
    OS 계열: {{ os_family }}
    커널 버전: {{ kernel_version }}
    실행 일시: {{ ansible_date_time.iso8601 }}
    재부팅 필요: {{ reboot_needed }}
    
    {% if ansible_os_family == 'Debian' %}
    적용된 업데이트:
    {{ apt_upgrade_result.stdout | default('변경 없음') }}
    {% elif ansible_os_family == 'RedHat' %}
    적용된 업데이트:
    {{ yum_upgrade_result.results | default('변경 없음') }}
    {% endif %}
    ========================

    규정 준수 자동화 — CIS 벤치마크 적용

    취약점 패치만큼 중요한 게 바로 Ansible 규정 준수 자동화예요. CIS(Center for Internet Security) 벤치마크나 NIST 프레임워크 같은 보안 기준을 서버에 자동으로 적용하고 검증하는 거죠.

    저는 주로 CIS 리눅스 벤치마크를 기반으로 하드닝(보안 강화) 플레이북을 만들어서 씁니다. 핵심 항목들을 추려서 보여드릴게요.

    # playbooks/cis_compliance.yml
    ---
    - name: CIS 벤치마크 기반 보안 하드닝
      hosts: "{{ target_hosts | default('all') }}"
      become: yes
      gather_facts: yes
    
      vars:
        cis_level: 1  # 1 또는 2
        disable_unused_services: true
        configure_auditd: true
    
      tasks:
        # === 파일시스템 설정 ===
        - name: "CIS 1.1.1 - /tmp 파티션 nodev 마운트 옵션 설정"
          mount:
            name: /tmp
            src: tmpfs
            fstype: tmpfs
            opts: "defaults,rw,nosuid,nodev,noexec,relatime"
            state: mounted
          tags: [filesystem, cis_1_1]
    
        # === SSH 보안 설정 ===
        - name: "CIS 5.2 - SSH 보안 설정 강화"
          lineinfile:
            path: /etc/ssh/sshd_config
            regexp: "{{ item.regexp }}"
            line: "{{ item.line }}"
            state: present
            backup: yes
          loop:
            - { regexp: '^#?Protocol', line: 'Protocol 2' }
            - { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin no' }
            - { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' }
            - { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' }
            - { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 4' }
            - { regexp: '^#?PermitEmptyPasswords', line: 'PermitEmptyPasswords no' }
            - { regexp: '^#?ClientAliveInterval', line: 'ClientAliveInterval 300' }
            - { regexp: '^#?ClientAliveCountMax', line: 'ClientAliveCountMax 0' }
            - { regexp: '^#?LoginGraceTime', line: 'LoginGraceTime 60' }
          notify: restart sshd
          tags: [ssh, cis_5_2]
    
        # === 감사 로그(Auditd) 설정 ===
        - name: "CIS 4.1 - auditd 설치 및 활성화"
          package:
            name: auditd
            state: present
          when: configure_auditd | bool
          tags: [auditd, cis_4_1]
    
        - name: "CIS 4.1 - auditd 규칙 설정"
          copy:
            content: |
              # 시스템 콜 감사 규칙
              -a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time-change
              -a always,exit -F arch=b32 -S adjtimex -S settimeofday -S stime -k time-change
              -w /etc/localtime -p wa -k time-change
              -w /etc/group -p wa -k identity
              -w /etc/passwd -p wa -k identity
              -w /etc/gshadow -p wa -k identity
              -w /etc/shadow -p wa -k identity
              -w /etc/sudoers -p wa -k scope
              -w /var/log/sudo.log -p wa -k actions
              -w /sbin/insmod -p x -k modules
              -w /sbin/rmmod -p x -k modules
              -w /sbin/modprobe -p x -k modules
              -e 2
            dest: /etc/audit/rules.d/cis_hardening.rules
            mode: '0640'
            owner: root
            group: root
          notify: reload auditd
          when: configure_auditd | bool
          tags: [auditd, cis_4_1]
    
        # === 패스워드 정책 ===
        - name: "CIS 5.4.1 - 패스워드 만료 정책 설정"
          lineinfile:
            path: /etc/login.defs
            regexp: "{{ item.regexp }}"
            line: "{{ item.line }}"
          loop:
            - { regexp: '^PASS_MAX_DAYS', line: 'PASS_MAX_DAYS   90' }
            - { regexp: '^PASS_MIN_DAYS', line: 'PASS_MIN_DAYS   7' }
            - { regexp: '^PASS_WARN_AGE', line: 'PASS_WARN_AGE   14' }
          tags: [password_policy, cis_5_4]
    
        # === 불필요한 서비스 비활성화 ===
        - name: "CIS 2.2 - 불필요한 서비스 비활성화"
          service:
            name: "{{ item }}"
            state: stopped
            enabled: no
          loop:
            - telnet
            - rsh
            - rlogin
            - rexec
            - tftp
            - xinetd
          failed_when: false
          when: disable_unused_services | bool
          tags: [services, cis_2_2]
    
      handlers:
        - name: restart sshd
          service:
            name: sshd
            state: restarted
    
        - name: reload auditd
          service:
            name: auditd
            state: restarted

    ⚠️ 삽질 경험: 이런 실수 하지 마세요

    저도 처음엔 꽤 고생했거든요. 실제로 겪은 문제들을 공유할게요.

    실수 1: 프로덕션에 –check 없이 바로 실행

    이거 진짜 아찔했습니다. 테스트 환경에서 잘 돌아가던 플레이북을 프로덕션에 그냥 실행했다가 SSH 설정 변경으로 일부 서버 접근이 막힌 적이 있어요. 지금은 반드시 이 순서를 지킵니다:

    # 1단계: 드라이런 — 실제 변경 없이 시뮬레이션
    ansible-playbook cis_compliance.yml -i inventory/aws_ec2.yml --check --diff
    
    # 2단계: 특정 태그만 먼저 테스트
    ansible-playbook cis_compliance.yml -i inventory/aws_ec2.yml --tags ssh --check
    
    # 3단계: 한 대만 먼저 적용
    ansible-playbook cis_compliance.yml -i inventory/aws_ec2.yml --limit "specific_host_ip" 
    
    # 4단계: 전체 적용
    ansible-playbook cis_compliance.yml -i inventory/aws_ec2.yml

    실수 2: 멱등성(Idempotency) 무시

    Ansible의 핵심 철학이 멱등성(같은 작업을 여러 번 실행해도 결과가 동일)인데, 초반에 shell 모듈을 너무 남발했어요. shell로 스크립트 실행하면 매번 “changed” 상태가 되거든요. 가능하면 Ansible 내장 모듈(file, lineinfile, service 등)을 쓰는 게 맞습니다.

    실수 3: Vault 패스워드 관리 소홀

    팀원 여러 명이 같은 vault 패스워드를 공유하다가, 퇴사자 발생 시 전체 재암호화해야 하는 상황이 생겼어요. 지금은 CI/CD 파이프라인에서 환경변수로 관리하고, 팀원별 접근은 AWS IAM으로 통제합니다.

    문제 상황 잘못된 방법 올바른 방법
    민감 정보 관리 평문으로 vars 파일에 저장 Ansible Vault + Secrets Manager 연동
    플레이북 테스트 프로덕션에 직접 실행 –check –diff로 드라이런 먼저
    명령 실행 shell/command 모듈 남용 전용 모듈 우선 사용 (멱등성 보장)
    대규모 적용 전체 호스트에 한 번에 실행 –limit으로 단계적 적용
    에러 처리 failed_when 미설정 적절한 failed_when/ignore_errors 설정

    규정 준수 검증 및 리포팅 자동화

    보안 설정을 적용했으면, 실제로 잘 됐는지 검증하고 리포트를 뽑아야죠. 저는 Ansible과 함께 OpenSCAP(보안 설정 자동화 프로토콜 기반 도구)을 연동해서 씁니다.

    # playbooks/compliance_report.yml
    ---
    - name: 규정 준수 검증 및 HTML 리포트 생성
      hosts: all
      become: yes
      gather_facts: yes
    
      vars:
        report_dir: "/var/log/compliance_reports"
        scap_profile: "xccdf_org.ssgproject.content_profile_cis"
    
      tasks:
        - name: OpenSCAP 및 SCAP 보안 가이드 설치 (RHEL 계열)
          yum:
            name:
              - openscap-scanner
              - scap-security-guide
            state: present
          when: ansible_os_family == "RedHat"
    
        - name: 리포트 디렉토리 생성
          file:
            path: "{{ report_dir }}"
            state: directory
            mode: '0750'
    
        - name: SCAP 스캔 실행 및 HTML 리포트 생성
          command: >
            oscap xccdf eval
            --profile {{ scap_profile }}
            --results {{ report_dir }}/results_{{ ansible_hostname }}_{{ ansible_date_time.date }}.xml
            --report {{ report_dir }}/report_{{ ansible_hostname }}_{{ ansible_date_time.date }}.html
            /usr/share/xml/scap/ssg/content/ssg-rhel8-ds.xml
          register: scap_result
          failed_when: scap_result.rc not in [0, 2]
          when: ansible_os_family == "RedHat"
    
        - name: 스캔 결과 요약 출력
          debug:
            msg: "{{ ansible_hostname }} 스캔 완료 — 리포트: {{ report_dir }}/report_{{ ansible_hostname }}_{{ ansible_date_time.date }}.html"
    
        - name: 리포트 파일 로컬로 가져오기
          fetch:
            src: "{{ report_dir }}/report_{{ ansible_hostname }}_{{ ansible_date_time.date }}.html"
            dest: "./compliance_reports/"
            flat: no

    ▲ 규정 준수 검증 결과 대시보드 — 각 CIS 벤치마크 항목별 통과/실패/해당없음 현황을 서버별로 한눈에 파악 가능

    이렇게 하면 매주 자동으로 각 서버의 규정 준수 현황을 HTML 리포트로 뽑을 수 있어요. 경영진이나 감사팀에 제출할 때도 편하고, 뭐가 문제인지 한눈에 보이거든요.


    CI/CD 파이프라인과 보안 자동화 연동

    사실 이게 제일 강력한 부분이에요. 깃허브 액션(GitHub Actions)이나 젠킨스(Jenkins)와 연동하면, 코드 변경이 있을 때마다 자동으로 보안 검증이 돌아가거든요.

    # .github/workflows/security_automation.yml
    name: 보안 자동화 파이프라인
    
    on:
      schedule:
        - cron: '0 2 * * *'  # 매일 새벽 2시 실행
      push:
        branches: [main]
        paths:
          - 'playbooks/**'
          - 'inventory/**'
    
    jobs:
      security-scan:
        runs-on: ubuntu-latest
        steps:
          - name: 코드 체크아웃
            uses: actions/checkout@v3
    
          - name: Python 및 Ansible 설치
            run: |
              pip install ansible ansible-lint
              ansible-galaxy collection install amazon.aws community.general
    
          - name: Ansible Lint 실행 (플레이북 문법/베스트프랙티스 검사)
            run: ansible-lint playbooks/
    
          - name: Vault 패스워드 설정
            run: echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > ~/.vault_pass.txt
    
          - name: 드라이런 실행 (실제 변경 없이 검증)
            run: |
              ansible-playbook playbooks/vulnerability_management.yml \
                -i inventory/aws_ec2.yml \
                --vault-password-file ~/.vault_pass.txt \
                --check
            env:
              AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
              AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    
          - name: 승인 후 실제 적용 (main 브랜치만)
            if: github.ref == 'refs/heads/main'
            run: |
              ansible-playbook playbooks/vulnerability_management.yml \
                -i inventory/aws_ec2.yml \
                --vault-password-file ~/.vault_pass.txt
            env:
              AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
              AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

    이렇게 구성하면 드리프트(원하는 상태에서 벗어남)가 발생해도 다음 날 새벽에 자동으로 교정이 되거든요. 정말 마음이 편해집니다.


    정리: Ansible 클라우드 보안 자동화 핵심 체크리스트

    ▲ Ansible 클라우드 보안 자동화 핵심 구성 요소 요약 — Vault, 취약점 관리, 규정 준수, CI/CD 연동의 4가지 축

    실무에서 느낀 건, 보안은 한 번 설정하고 끝나는 게 아니라는 거예요. 지속적으로 검증하고, 자동화해야 실수가 없습니다. Ansible 보안 자동화로 구성한 체계를 정리해보면:

    1. ✅ Ansible Vault로 모든 민감 정보 암호화 — 평문 시크릿은 절대 금지
    2. ✅ 동적 인벤토리(Dynamic Inventory)로 클라우드 환경 자동 탐지
    3. ✅ 취약점 관리 플레이북으로 정기적 보안 패치 자동화
    4. ✅ CIS 벤치마크 기반 하드닝으로 일관된 보안 기준 적용
    5. ✅ OpenSCAP 연동으로 규정 준수 검증 및 리포트 자동 생성
    6. ✅ CI/CD 파이프라인 연동으로 드리프트 자동 교정
    7. ✅ 드라이런(–check –diff)을 습관화해서 실수 방지

    혹시 이 글을 보고 궁금한 점이 생기셨나요? 다음 글에서는 HashiCorp Vault와 Ansible 연동으로 더 고도화된 시크릿 관리 방법을 다뤄볼 예정이에요. Ansible 기초 인프라 자동화도 함께 보시면 이해가 더 쉬울 거예요.

    뭔가 막히는 부분 있으시면 댓글로 편하게 물어보세요. 저도 삽질하면서 배운 거라, 같이 고민해드릴 수 있습니다.


    자주 묻는 질문 (FAQ)

    Q. Ansible로 Windows 서버 보안도 자동화할 수 있나요?

    네, 가능합니다. Windows는 SSH 대신 WinRM(Windows Remote Management)을 통해 연결하고, win_updates, win_service 같은 Windows 전용 모듈을 사용합니다. 다만 설정이 리눅스보다 조금 더 복잡한 편이에요.

    Q. Ansible Tower(AWX)를 써야 하나요?

    소규모 환경(서버 50대 이하)이면 CLI만으로도 충분합니다. 팀 규모가 커지고, 역할 기반 접근 제어(RBAC)나 스케줄링, 웹 UI가 필요해지면 오픈소스인 AWX나 상용 버전인 Ansible Automation Platform을 고려해보세요.

    Q. 플레이북 실행 중 에러가 나면 어떻게 되나요?

    기본적으로 에러가 발생한 호스트에서 플레이북 실행이 중단됩니다. ignore_errors: yes나 failed_when 조건을 적절히 설정해서 에러 처리를 세밀하게 제어할 수 있어요. 중요한 보안 작업은 에러 발생 시 즉시 알림을 받도록 Slack이나 이메일 핸들러를 연동하는 걸 권장합니다.