#!/bin/bash # ============================================================================== # GUARDiA ITSM — SSL 인증서 자동 갱신 스크립트 (certbot) # 경로: /opt/guardia/scripts/ssl/ssl_auto_renew.sh # # 기능: # 1. certbot renew --dry-run 으로 사전 검증 # 2. certbot renew 실행 (갱신 대상 인증서만) # 3. nginx/apache 서비스 재시작 (갱신 성공 시) # 4. GUARDiA ITSM API 콜백 — 갱신 결과 전달 (SR 자동 생성) # # 환경변수 (실행 전 .env 또는 시스템에 설정): # GUARDIA_ITSM_URL — ITSM 서버 URL (기본: http://localhost:8000) # GUARDIA_ITSM_TOKEN — ITSM API Bearer 토큰 # SSL_WEB_SERVER — nginx 또는 apache2 (기본: nginx) # SSL_CERT_EMAIL — certbot 알림 이메일 # SSL_DOMAINS — 갱신할 도메인 (쉼표 구분, 미설정 시 certbot 전체 갱신) # SSL_RENEW_DAYS — 만료 N일 이내 갱신 시도 (기본: 30) # SSL_DRY_RUN_ONLY — true 이면 dry-run 만 수행 (테스트용, 기본: false) # SSL_RELOAD_CMD — 서비스 재시작 명령어 오버라이드 # # 반환 코드: # 0 — 성공 (갱신 없음 포함) # 1 — 갱신 실패 # 2 — 사전 점검 실패 (dry-run 오류) # 3 — 설치 환경 오류 (certbot 미설치 등) # ============================================================================== set -euo pipefail IFS=$'\n\t' # ── 설정 ────────────────────────────────────────────────────────────────────── ITSM_URL="${GUARDIA_ITSM_URL:-http://localhost:8000}" ITSM_TOKEN="${GUARDIA_ITSM_TOKEN:-}" WEB_SERVER="${SSL_WEB_SERVER:-nginx}" CERT_EMAIL="${SSL_CERT_EMAIL:-}" DOMAINS="${SSL_DOMAINS:-}" RENEW_DAYS="${SSL_RENEW_DAYS:-30}" DRY_RUN_ONLY="${SSL_DRY_RUN_ONLY:-false}" LOG_DIR="/var/log/guardia/ssl" LOG_FILE="${LOG_DIR}/renew_$(date +%Y%m%d_%H%M%S).log" SCRIPT_NAME="$(basename "$0")" # certbot 재시작 후크 우선순위: 환경변수 > 자동 감지 if [ -n "${SSL_RELOAD_CMD:-}" ]; then RELOAD_CMD="${SSL_RELOAD_CMD}" elif command -v systemctl &>/dev/null; then RELOAD_CMD="systemctl reload ${WEB_SERVER}" else RELOAD_CMD="service ${WEB_SERVER} reload" fi # ── 로그 함수 ───────────────────────────────────────────────────────────────── mkdir -p "${LOG_DIR}" exec > >(tee -a "${LOG_FILE}") 2>&1 log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*"; } warn() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARN] $*"; } err() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" >&2; } log "=== ${SCRIPT_NAME} 시작 ===" log "ITSM URL : ${ITSM_URL}" log "WEB SERVER : ${WEB_SERVER}" log "RENEW DAYS : ${RENEW_DAYS}" log "DRY RUN ONLY : ${DRY_RUN_ONLY}" log "LOG FILE : ${LOG_FILE}" # ── certbot 설치 확인 ───────────────────────────────────────────────────────── if ! command -v certbot &>/dev/null; then err "certbot 명령어를 찾을 수 없습니다." err "설치: apt install certbot 또는 yum install certbot" _itsm_callback "ERROR" "certbot 미설치" "" 3 exit 3 fi CERTBOT_VERSION="$(certbot --version 2>&1 | head -1)" log "certbot 버전: ${CERTBOT_VERSION}" # ── ITSM API 콜백 함수 ──────────────────────────────────────────────────────── # 보안: ITSM 서버 주소만 포함 — IP·계정·비밀번호 전송 금지 _itsm_callback() { local status="$1" # SUCCESS | FAILURE | DRY_RUN_OK | DRY_RUN_FAIL local message="$2" local renewed_domains="${3:-}" local exit_code="${4:-0}" if [ -z "${ITSM_TOKEN}" ]; then warn "GUARDIA_ITSM_TOKEN 미설정 — ITSM 콜백 건너뜀" return 0 fi local payload payload="$(cat < /dev/null 2>&1; then log "ITSM 콜백 전송 성공: status=${status}" else warn "ITSM 콜백 전송 실패 (서버 응답 없음 — 무시하고 계속 진행)" fi } # ── 사전 점검: dry-run ──────────────────────────────────────────────────────── log "--- 사전 점검 (dry-run) 시작 ---" CERTBOT_ARGS=( renew --non-interactive --agree-tos "--deploy-hook" "${RELOAD_CMD}" "--days" "${RENEW_DAYS}" ) if [ -n "${CERT_EMAIL}" ]; then CERTBOT_ARGS+=("--email" "${CERT_EMAIL}") fi if [ -n "${DOMAINS}" ]; then # 도메인 개별 지정 시 각 도메인을 --domain 으로 전달 IFS=',' read -ra DOMAIN_LIST <<< "${DOMAINS}" for domain in "${DOMAIN_LIST[@]}"; do domain="$(echo "${domain}" | tr -d ' ')" [ -n "${domain}" ] && CERTBOT_ARGS+=("--domain" "${domain}") done fi if ! certbot "${CERTBOT_ARGS[@]}" --dry-run >> "${LOG_FILE}" 2>&1; then err "dry-run 실패 — 실제 갱신을 중단합니다." _itsm_callback "DRY_RUN_FAIL" "certbot dry-run 실패. 로그: ${LOG_FILE}" "" 2 exit 2 fi log "dry-run 성공." if [ "${DRY_RUN_ONLY}" = "true" ]; then log "DRY_RUN_ONLY=true — 실제 갱신 건너뜀." _itsm_callback "DRY_RUN_OK" "dry-run 전용 실행 완료." "" 0 exit 0 fi # ── 실제 갱신 ───────────────────────────────────────────────────────────────── log "--- 실제 갱신 시작 ---" RENEW_OUTPUT="$(certbot "${CERTBOT_ARGS[@]}" 2>&1)" || { err "certbot renew 실패." err "${RENEW_OUTPUT}" _itsm_callback "FAILURE" "certbot renew 실패. 상세: ${LOG_FILE}" "" 1 exit 1 } log "${RENEW_OUTPUT}" # 갱신된 인증서 도메인 추출 RENEWED="" if echo "${RENEW_OUTPUT}" | grep -q "Successfully renewed"; then RENEWED="$(echo "${RENEW_OUTPUT}" \ | grep -oP '(?<=Successfully renewed certificate for )[^\s]+' \ | paste -sd ',' -)" log "갱신 완료 도메인: ${RENEWED:-없음}" else log "갱신 대상 없음 (모든 인증서가 유효 기간 내)." fi # ── 웹서버 재시작 (갱신 발생 시) ────────────────────────────────────────────── if [ -n "${RENEWED}" ]; then log "웹서버 재시작 중: ${RELOAD_CMD}" if eval "${RELOAD_CMD}" >> "${LOG_FILE}" 2>&1; then log "웹서버 재시작 성공." else warn "웹서버 재시작 실패 — 수동 확인 필요: ${RELOAD_CMD}" fi fi # ── ITSM 콜백 ───────────────────────────────────────────────────────────────── if [ -n "${RENEWED}" ]; then _itsm_callback "SUCCESS" "인증서 갱신 완료." "${RENEWED}" 0 else _itsm_callback "SUCCESS" "갱신 대상 없음 (모두 유효)." "" 0 fi log "=== ${SCRIPT_NAME} 완료 ===" exit 0