본문 바로가기
서버구축 (WEB,DB)

Wazuh & Osquery 보안 에이전트 패키지 배포 내부 yum 저장소 구축

by 날으는물고기 2025. 8. 8.

Wazuh & Osquery 보안 에이전트 패키지 배포 내부 yum 저장소 구축

728x90

osquery와 wazuh agent 패키지를 로컬 시스템에 미러링하고, 내부 시스템에서 yum을 통해 설치/업데이트할 수 있는 도커 기반 환경을 구축하는 방법입니다.

패키지 미러링 시스템 아키텍처

osquery & Wazuh Agent 내부 저장소 구성

디렉토리 구조

repo-mirror/
├── docker-compose.yml
├── nginx/
│   ├── Dockerfile
│   └── nginx.conf
├── sync/
│   ├── Dockerfile
│   ├── sync-repos.sh
│   └── crontab
├── data/
│   ├── repos/
│   │   ├── wazuh/
│   │   └── osquery/
│   └── gpg-keys/
└── scripts/
    └── init-repos.sh
300x250

Docker Compose 구성

docker-compose.yml - 전체 서비스 구성

version: '3.8'

services:
  # Nginx 웹서버 - YUM 저장소 제공
  nginx:
    build: ./nginx
    container_name: repo-nginx
    ports:
      - "8080:80"
    volumes:
      - ./data/repos:/usr/share/nginx/html/repos:ro
      - ./data/gpg-keys:/usr/share/nginx/html/gpg-keys:ro
    restart: unless-stopped
    networks:
      - repo-network

  # 동기화 스케줄러
  sync-scheduler:
    build: ./sync
    container_name: repo-sync
    volumes:
      - ./data/repos:/data/repos
      - ./data/gpg-keys:/data/gpg-keys
      - ./sync/sync-repos.sh:/scripts/sync-repos.sh:ro
    environment:
      - TZ=Asia/Seoul
    restart: unless-stopped
    networks:
      - repo-network

  # 초기 설정 및 메타데이터 생성
  init-repos:
    image: centos:7
    container_name: repo-init
    volumes:
      - ./data/repos:/data/repos
      - ./scripts/init-repos.sh:/scripts/init-repos.sh:ro
    command: /scripts/init-repos.sh
    networks:
      - repo-network

networks:
  repo-network:
    driver: bridge

Nginx 설정

nginx/Dockerfile - Nginx 컨테이너 설정

FROM nginx:alpine

# 필요한 패키지 설치
RUN apk add --no-cache tzdata

# 시간대 설정
ENV TZ=Asia/Seoul
RUN cp /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# Nginx 설정 복사
COPY nginx.conf /etc/nginx/nginx.conf

# 디렉토리 생성
RUN mkdir -p /usr/share/nginx/html/repos/wazuh \
             /usr/share/nginx/html/repos/osquery \
             /usr/share/nginx/html/gpg-keys

EXPOSE 80

nginx/nginx.conf - Nginx 웹서버 설정

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    tcp_nopush on;
    keepalive_timeout 65;
    autoindex on;
    autoindex_exact_size off;
    autoindex_localtime on;

    server {
        listen 80;
        server_name _;
        root /usr/share/nginx/html;

        # Wazuh 저장소
        location /repos/wazuh/ {
            alias /usr/share/nginx/html/repos/wazuh/;
            autoindex on;
        }

        # osquery 저장소
        location /repos/osquery/ {
            alias /usr/share/nginx/html/repos/osquery/;
            autoindex on;
        }

        # GPG 키
        location /gpg-keys/ {
            alias /usr/share/nginx/html/gpg-keys/;
            autoindex on;
        }

        # 보안 헤더 추가
        add_header X-Content-Type-Options nosniff;
        add_header X-Frame-Options DENY;
        add_header X-XSS-Protection "1; mode=block";
    }
}

동기화 스크립트

sync/Dockerfile - 동기화 컨테이너 설정

FROM centos:7

# 필요한 패키지 설치
RUN yum install -y epel-release && \
    yum install -y \
        wget \
        rsync \
        createrepo \
        yum-utils \
        cronie \
        curl \
    && yum clean all

# 시간대 설정
ENV TZ=Asia/Seoul
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime

# 스크립트 디렉토리 생성
RUN mkdir -p /scripts /data/repos /data/gpg-keys

# crontab 설정 복사
COPY crontab /etc/cron.d/sync-repos
RUN chmod 0644 /etc/cron.d/sync-repos && \
    crontab /etc/cron.d/sync-repos

# 동기화 스크립트 복사
COPY sync-repos.sh /scripts/sync-repos.sh
RUN chmod +x /scripts/sync-repos.sh

# 초기 동기화 실행
RUN /scripts/sync-repos.sh

# cron 실행
CMD ["crond", "-n"]

sync/sync-repos.sh - 패키지 동기화 스크립트

#!/bin/bash

# 로그 설정
LOG_DIR="/var/log/repo-sync"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/sync-$(date +%Y%m%d).log"

# 함수: 로그 기록
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# 함수: 에러 처리
error_exit() {
    log "ERROR: $1"
    exit 1
}

# 디렉토리 설정
REPO_BASE="/data/repos"
GPG_KEY_DIR="/data/gpg-keys"

# Wazuh 설정
WAZUH_REPO_URL="https://packages.wazuh.com/4.x/yum/"
WAZUH_GPG_URL="https://packages.wazuh.com/key/GPG-KEY-WAZUH"
WAZUH_LOCAL_DIR="$REPO_BASE/wazuh"

# osquery 설정
OSQUERY_REPO_URL="https://pkg.osquery.io/rpm/"
OSQUERY_GPG_URL="https://pkg.osquery.io/rpm/GPG"
OSQUERY_LOCAL_DIR="$REPO_BASE/osquery"

# 디렉토리 생성
mkdir -p "$WAZUH_LOCAL_DIR" "$OSQUERY_LOCAL_DIR" "$GPG_KEY_DIR"

log "========== 저장소 동기화 시작 =========="

# Wazuh 동기화
log "Wazuh 패키지 동기화 시작..."
cd "$WAZUH_LOCAL_DIR" || error_exit "Wazuh 디렉토리 접근 실패"

# Wazuh GPG 키 다운로드
log "Wazuh GPG 키 다운로드..."
curl -sS -o "$GPG_KEY_DIR/GPG-KEY-WAZUH" "$WAZUH_GPG_URL" || error_exit "Wazuh GPG 키 다운로드 실패"

# Wazuh 패키지 동기화
log "Wazuh 패키지 미러링..."
wget -r -np -nH --cut-dirs=3 \
     -R "index.html*" \
     --reject-regex ".*\?.*" \
     --timeout=30 \
     --tries=3 \
     "$WAZUH_REPO_URL" 2>&1 | tee -a "$LOG_FILE"

# repodata 업데이트
log "Wazuh repodata 생성..."
createrepo --update "$WAZUH_LOCAL_DIR" || error_exit "Wazuh createrepo 실패"

# osquery 동기화
log "osquery 패키지 동기화 시작..."
cd "$OSQUERY_LOCAL_DIR" || error_exit "osquery 디렉토리 접근 실패"

# osquery GPG 키 다운로드
log "osquery GPG 키 다운로드..."
curl -sS -o "$GPG_KEY_DIR/GPG-KEY-OSQUERY" "$OSQUERY_GPG_URL" || error_exit "osquery GPG 키 다운로드 실패"

# osquery 패키지 동기화
log "osquery 패키지 미러링..."
wget -r -np -nH --cut-dirs=1 \
     -R "index.html*" \
     --reject-regex ".*\?.*" \
     --timeout=30 \
     --tries=3 \
     "$OSQUERY_REPO_URL" 2>&1 | tee -a "$LOG_FILE"

# repodata 업데이트
log "osquery repodata 생성..."
createrepo --update "$OSQUERY_LOCAL_DIR" || error_exit "osquery createrepo 실패"

# 디스크 사용량 확인
log "디스크 사용량 확인:"
df -h "$REPO_BASE" | tee -a "$LOG_FILE"
du -sh "$WAZUH_LOCAL_DIR" "$OSQUERY_LOCAL_DIR" | tee -a "$LOG_FILE"

# 오래된 로그 파일 정리 (30일 이상)
find "$LOG_DIR" -name "sync-*.log" -mtime +30 -delete

log "========== 저장소 동기화 완료 =========="

sync/crontab - 크론 스케쥴 설정

# 매일 새벽 2시에 저장소 동기화 실행
0 2 * * * /scripts/sync-repos.sh >> /var/log/repo-sync/cron.log 2>&1

# 매 6시간마다 간단한 메타데이터 업데이트
0 */6 * * * cd /data/repos/wazuh && createrepo --update . >> /var/log/repo-sync/cron.log 2>&1
0 */6 * * * cd /data/repos/osquery && createrepo --update . >> /var/log/repo-sync/cron.log 2>&1

초기화 스크립트

scripts/init-repos.sh - 초기 설정 스크립트

#!/bin/bash

echo "저장소 초기화 시작..."

# 디렉토리 생성
mkdir -p /data/repos/wazuh /data/repos/osquery /data/gpg-keys

# 권한 설정
chmod -R 755 /data/repos
chmod -R 755 /data/gpg-keys

echo "초기화 완료!"

클라이언트 설정 파일

클라이언트 YUM 저장소 설정 파일

# /etc/yum.repos.d/wazuh-internal.repo
[wazuh-internal]
name=Internal Wazuh Repository
baseurl=http://your-internal-server:8080/repos/wazuh/
enabled=1
gpgcheck=1
gpgkey=http://your-internal-server:8080/gpg-keys/GPG-KEY-WAZUH
protect=1

# /etc/yum.repos.d/osquery-internal.repo
[osquery-internal]
name=Internal osquery Repository
baseurl=http://your-internal-server:8080/repos/osquery/
enabled=1
gpgcheck=1
gpgkey=http://your-internal-server:8080/gpg-keys/GPG-KEY-OSQUERY
protect=1

운영 관리 스크립트

운영 관리를 위한 유틸리티 스크립트

#!/bin/bash
# manage-repo.sh - 저장소 관리 스크립트

COMPOSE_FILE="docker-compose.yml"

# 함수: 도움말 표시
show_help() {
    cat << EOF
사용법: $0 [명령]

명령:
    start       - 모든 서비스 시작
    stop        - 모든 서비스 중지
    restart     - 모든 서비스 재시작
    sync        - 수동으로 저장소 동기화
    status      - 서비스 상태 확인
    logs        - 로그 확인
    clean       - 오래된 패키지 정리
    backup      - 저장소 백업
    restore     - 저장소 복원
EOF
}

# 서비스 시작
start_services() {
    echo "서비스 시작중..."
    docker-compose -f $COMPOSE_FILE up -d
}

# 서비스 중지
stop_services() {
    echo "서비스 중지중..."
    docker-compose -f $COMPOSE_FILE down
}

# 서비스 재시작
restart_services() {
    stop_services
    start_services
}

# 수동 동기화
manual_sync() {
    echo "수동 동기화 시작..."
    docker-compose -f $COMPOSE_FILE exec sync-scheduler /scripts/sync-repos.sh
}

# 상태 확인
check_status() {
    echo "=== 서비스 상태 ==="
    docker-compose -f $COMPOSE_FILE ps

    echo -e "\n=== 디스크 사용량 ==="
    du -sh data/repos/*

    echo -e "\n=== 최근 동기화 로그 ==="
    docker-compose -f $COMPOSE_FILE exec sync-scheduler tail -n 20 /var/log/repo-sync/sync-$(date +%Y%m%d).log
}

# 로그 확인
show_logs() {
    docker-compose -f $COMPOSE_FILE logs -f --tail=100
}

# 오래된 패키지 정리
clean_old_packages() {
    echo "30일 이상 된 패키지 정리중..."
    find data/repos -name "*.rpm" -mtime +30 -delete

    # repodata 재생성
    docker-compose -f $COMPOSE_FILE exec sync-scheduler bash -c "
        createrepo --update /data/repos/wazuh
        createrepo --update /data/repos/osquery
    "
}

# 백업
backup_repos() {
    BACKUP_DIR="backups/$(date +%Y%m%d_%H%M%S)"
    mkdir -p "$BACKUP_DIR"

    echo "백업 시작: $BACKUP_DIR"
    tar czf "$BACKUP_DIR/repos.tar.gz" data/repos/
    tar czf "$BACKUP_DIR/gpg-keys.tar.gz" data/gpg-keys/

    echo "백업 완료!"
}

# 복원
restore_repos() {
    if [ -z "$1" ]; then
        echo "사용법: $0 restore [백업 디렉토리]"
        exit 1
    fi

    BACKUP_DIR="$1"

    if [ ! -d "$BACKUP_DIR" ]; then
        echo "백업 디렉토리를 찾을 수 없습니다: $BACKUP_DIR"
        exit 1
    fi

    echo "복원 시작: $BACKUP_DIR"
    tar xzf "$BACKUP_DIR/repos.tar.gz"
    tar xzf "$BACKUP_DIR/gpg-keys.tar.gz"

    echo "복원 완료!"
}

# 메인 로직
case "$1" in
    start)
        start_services
        ;;
    stop)
        stop_services
        ;;
    restart)
        restart_services
        ;;
    sync)
        manual_sync
        ;;
    status)
        check_status
        ;;
    logs)
        show_logs
        ;;
    clean)
        clean_old_packages
        ;;
    backup)
        backup_repos
        ;;
    restore)
        restore_repos "$2"
        ;;
    *)
        show_help
        ;;
esac

보안 강화 설정

보안 강화를 위한 추가 설정

# nginx/nginx-secure.conf - IP 기반 접근 제어 추가

# ... (기존 설정)

http {
    # ... (기존 설정)

    # IP 접근 제어를 위한 geo 블록
    geo $allowed_ips {
        default 0;
        10.0.0.0/8 1;      # 내부 네트워크
        172.16.0.0/12 1;   # 내부 네트워크
        192.168.0.0/16 1;  # 내부 네트워크
    }

    # Rate limiting
    limit_req_zone $binary_remote_addr zone=repo_limit:10m rate=30r/s;

    server {
        listen 80;
        server_name _;
        root /usr/share/nginx/html;

        # IP 기반 접근 제어
        if ($allowed_ips = 0) {
            return 403;
        }

        # Rate limiting 적용
        limit_req zone=repo_limit burst=20 nodelay;

        # Basic Authentication (선택사항)
        # auth_basic "Internal Repository";
        # auth_basic_user_file /etc/nginx/.htpasswd;

        # ... (나머지 설정)
    }
}

---

# docker-compose-secure.yml - 보안 강화 버전

version: '3.8'

services:
  nginx:
    build: ./nginx
    container_name: repo-nginx
    ports:
      - "127.0.0.1:8080:80"  # 로컬호스트에서만 접근
    volumes:
      - ./data/repos:/usr/share/nginx/html/repos:ro
      - ./data/gpg-keys:/usr/share/nginx/html/gpg-keys:ro
      - ./nginx/.htpasswd:/etc/nginx/.htpasswd:ro  # Basic Auth
    restart: unless-stopped
    networks:
      - repo-network
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /var/cache/nginx
      - /var/run

  sync-scheduler:
    build: ./sync
    container_name: repo-sync
    volumes:
      - ./data/repos:/data/repos
      - ./data/gpg-keys:/data/gpg-keys
      - ./sync/sync-repos.sh:/scripts/sync-repos.sh:ro
    environment:
      - TZ=Asia/Seoul
    restart: unless-stopped
    networks:
      - repo-network
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - DAC_OVERRIDE
      - CHOWN

networks:
  repo-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

모니터링 및 알림

monitor-repo.sh - 저장소 상태 모니터링

#!/bin/bash

# 설정
REPO_URL="http://localhost:8080"
SLACK_WEBHOOK="YOUR_SLACK_WEBHOOK_URL"
EMAIL_TO="admin@example.com"
DISK_THRESHOLD=80  # 디스크 사용률 임계값 (%)

# 함수: Slack 알림
send_slack() {
    local message="$1"
    curl -X POST $SLACK_WEBHOOK \
        -H 'Content-type: application/json' \
        --data "{\"text\":\"🚨 Repo Mirror Alert: $message\"}"
}

# 함수: 이메일 알림
send_email() {
    local subject="$1"
    local body="$2"
    echo "$body" | mail -s "$subject" $EMAIL_TO
}

# 1. 웹서버 상태 확인
check_webserver() {
    if ! curl -s -o /dev/null -w "%{http_code}" "$REPO_URL" | grep -q "200"; then
        send_slack "웹서버가 응답하지 않습니다!"
        return 1
    fi
    echo "✅ 웹서버 정상"
}

# 2. 저장소 접근성 확인
check_repo_access() {
    for repo in wazuh osquery; do
        if ! curl -s "$REPO_URL/repos/$repo/repodata/repomd.xml" > /dev/null; then
            send_slack "$repo 저장소 메타데이터에 접근할 수 없습니다!"
            return 1
        fi
    done
    echo "✅ 저장소 접근성 정상"
}

# 3. 디스크 사용량 확인
check_disk_usage() {
    local usage=$(df -h /data/repos | awk 'NR==2 {print $5}' | sed 's/%//')
    if [ "$usage" -gt "$DISK_THRESHOLD" ]; then
        send_slack "디스크 사용량 경고: ${usage}% (임계값: ${DISK_THRESHOLD}%)"
        return 1
    fi
    echo "✅ 디스크 사용량 정상: ${usage}%"
}

# 4. 동기화 상태 확인
check_sync_status() {
    local last_sync=$(find /var/log/repo-sync -name "sync-*.log" -mtime -1 | wc -l)
    if [ "$last_sync" -eq 0 ]; then
        send_slack "24시간 내 동기화 로그가 없습니다!"
        return 1
    fi
    echo "✅ 동기화 상태 정상"
}

# 5. 패키지 무결성 확인
check_package_integrity() {
    # GPG 키 확인
    for key in GPG-KEY-WAZUH GPG-KEY-OSQUERY; do
        if [ ! -f "/data/gpg-keys/$key" ]; then
            send_slack "GPG 키가 없습니다: $key"
            return 1
        fi
    done

    # 최신 패키지 확인
    local wazuh_count=$(find /data/repos/wazuh -name "*.rpm" -mtime -7 | wc -l)
    local osquery_count=$(find /data/repos/osquery -name "*.rpm" -mtime -7 | wc -l)

    echo "✅ 패키지 무결성 정상 (최근 7일 패키지: Wazuh=$wazuh_count, osquery=$osquery_count)"
}

# 메인 실행
echo "========== 저장소 모니터링 시작: $(date) =========="

check_webserver
check_repo_access
check_disk_usage
check_sync_status
check_package_integrity

echo "========== 모니터링 완료 =========="

실행 가이드

1. 초기 설정 및 실행

# 1. 프로젝트 디렉토리 생성
mkdir -p repo-mirror && cd repo-mirror

# 2. 위의 파일들을 각각 생성
# (docker-compose.yml, Dockerfiles, 스크립트 등)

# 3. 실행 권한 부여
chmod +x sync/sync-repos.sh
chmod +x scripts/init-repos.sh
chmod +x manage-repo.sh
chmod +x monitor-repo.sh

# 4. 서비스 시작
./manage-repo.sh start

# 5. 초기 동기화 확인
./manage-repo.sh status

2. 클라이언트 설정

# 클라이언트 서버에서
# 1. 기존 저장소 비활성화
yum-config-manager --disable wazuh osquery

# 2. 내부 저장소 설정 추가
cat > /etc/yum.repos.d/internal-repos.repo << EOF
[wazuh-internal]
name=Internal Wazuh Repository
baseurl=http://your-repo-server:8080/repos/wazuh/
enabled=1
gpgcheck=1
gpgkey=http://your-repo-server:8080/gpg-keys/GPG-KEY-WAZUH

[osquery-internal]
name=Internal osquery Repository
baseurl=http://your-repo-server:8080/repos/osquery/
enabled=1
gpgcheck=1
gpgkey=http://your-repo-server:8080/gpg-keys/GPG-KEY-OSQUERY
EOF

# 3. 패키지 설치
yum clean all
yum makecache
yum install wazuh-agent osquery

3. 운영 관리

# 수동 동기화
./manage-repo.sh sync

# 로그 확인
./manage-repo.sh logs

# 상태 모니터링
./monitor-repo.sh

# 백업
./manage-repo.sh backup

# 오래된 패키지 정리
./manage-repo.sh clean

운영 체크리스트

항목 주기 명령/확인사항
서비스 상태 확인 일일 docker-compose ps
동기화 로그 확인 일일 ./manage-repo.sh logs
디스크 사용량 주간 df -h data/
패키지 무결성 주간 ./monitor-repo.sh
백업 주간 ./manage-repo.sh backup
오래된 패키지 정리 월간 ./manage-repo.sh clean
보안 패치 확인 월간 컨테이너 이미지 업데이트

문제 해결

1. 동기화 실패 시

# 로그 확인
docker-compose logs sync-scheduler

# 수동 동기화 시도
docker exec -it repo-sync /scripts/sync-repos.sh

# 네트워크 연결 확인
docker exec -it repo-sync curl -I https://packages.wazuh.com

2. 클라이언트 연결 실패 시

# 방화벽 확인
firewall-cmd --list-ports

# SELinux 확인
getenforce

# 저장소 메타데이터 확인
curl http://your-repo-server:8080/repos/wazuh/repodata/repomd.xml

3. 디스크 공간 부족 시

# 오래된 패키지 정리
./manage-repo.sh clean

# 로그 정리
find data/logs -name "*.log" -mtime +30 -delete

이 구성을 통해 osquery와 wazuh agent 패키지를 안정적으로 미러링하고 내부 시스템에서 쉽게 설치/업데이트할 수 있습니다.

728x90
그리드형(광고전용)

댓글