G-1: 메신저 Webhook Relay + _send_to_room 실제 httpx 호출 구현 G-2: POST /api/tasks/bulk SR 대량작업 엔드포인트 (최대 100건) G-3: 라이선스 만료 알림 스케줄러 (매일 09:00 KST) G-4: 체험판 upgrade_banner 필드 + license.py 배너 로직 G-5: core/auto_rca.py + incidents/problem auto-rca 엔드포인트 G-6: core/deploy_impact.py + vibe impact-analysis 엔드포인트 G-7: core/ticket_classifier.py + SR 생성 시 AI 분류 + ai-suggestion API G-8: VulnPatchRecord 모델 + vuln_scan 패치추적 4개 엔드포인트 G-9: core/jira_sync.py + gateway Jira/Confluence 연동 엔드포인트 G-10: core/push_notify.py + routers/push.py + PushSubscription 모델 G-11: approvals 다중승인 (위임/서명/기한초과/마감연장) G-12: alembic.ini + migrations/ + cicd/migrate_to_postgres.sh 하네스: guardia-orchestrator 확장기능 Phase 반영 봇명령어: /sr /status /license /bulk 슬래시 명령어 추가 설치스크립트: setup/ (Ubuntu, CentOS, RHEL, Windows) --test 옵션 포함 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
203 lines
7.9 KiB
Bash
203 lines
7.9 KiB
Bash
#!/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 <<EOF
|
|
{
|
|
"event": "ssl_renew",
|
|
"status": "${status}",
|
|
"message": "${message}",
|
|
"renewed_domains": "${renewed_domains}",
|
|
"web_server": "${WEB_SERVER}",
|
|
"renew_days": ${RENEW_DAYS},
|
|
"exit_code": ${exit_code},
|
|
"log_file": "${LOG_FILE}",
|
|
"timestamp": "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
|
}
|
|
EOF
|
|
)"
|
|
|
|
# curl: 연결 실패 시 오류 무시 (ITSM가 다운돼도 갱신 스크립트는 계속 진행)
|
|
if curl -sf \
|
|
-X POST \
|
|
-H "Authorization: Bearer ${ITSM_TOKEN}" \
|
|
-H "Content-Type: application/json; charset=utf-8" \
|
|
--data "${payload}" \
|
|
--max-time 10 \
|
|
"${ITSM_URL}/api/ssl/renew-callback" \
|
|
> /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
|