zioinfo-mail/docs/security_governance.md
DESKTOP-TKLFCPR\ython 45f96176a6 Initial commit: GUARDiA project setup
- CLAUDE.md: project context and architecture spec
- docs/: system specs, DB schema, messenger integration, deployment engine
- skills/: guardia-deploy, guardia-agent, guardia-messenger
- .claude/settings.json: project-level permissions
- .gitignore: Python/FastAPI project

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 18:50:19 +09:00

6.3 KiB

[Specification] 보안 & 거버넌스 정책


1. RBAC 권한 체계

1.1. 역할 정의

역할 권한 담당
Developer 소관 사이트 배포 요청 파일 전송 + 재기동
SM 운영자 로그 조회, 자원 정비, 크론 관리 운영 유지보수
팀장 (Owner) 1차 배포 승인 소관 시스템
대리 (Deputy) 팀장 부재 시 대리 승인
PM 최종 HITL 검증, 거버넌스 규칙 수정 전체 통제
보안책임자 인프라 변경/보안 작업 3차 승인

1.2. 다단계 승인 워크플로우

APPROVAL_POLICY = {
    "MAINTENANCE":   {"steps": 1, "approvers": ["OWNER"]},
    "DEPLOY_NORMAL": {"steps": 2, "approvers": ["OWNER", "PM"]},
    "DEPLOY_SECURE": {"steps": 3, "approvers": ["OWNER", "PM", "SECURITY"]},
}

def get_approval_policy(task_category: str) -> dict:
    if task_category in ["LOG_ANALYSIS", "CRON_MANAGE"]:
        return APPROVAL_POLICY["MAINTENANCE"]
    elif task_category in ["DEPLOY", "DATA_CLEAN"]:
        return APPROVAL_POLICY["DEPLOY_NORMAL"]
    else:  # SSL, INFRA_CHANGE
        return APPROVAL_POLICY["DEPLOY_SECURE"]

2. 계층적 승인 엔진 (에스컬레이션)

def get_approver_chain(system_id: str) -> str:
    """SLA 초과 시 자동 에스컬레이션"""
    info = db.execute(
        "SELECT owner_id, deputy_id, supervisor_id, sla_minutes, is_active "
        "FROM TB_SYSTEM_OWNER WHERE system_id = %s", (system_id,)
    ).fetchone()

    if info["is_active"] == "Y":
        return info["owner_id"]       # 1순위: 담당 팀장
    elif info["deputy_id"]:
        return info["deputy_id"]      # 2순위: 대리 승인자
    else:
        return info["supervisor_id"]  # 3순위: 상위 관리자

def check_escalation(task_id: str):
    """SLA 초과 여부 체크 → 자동 이관"""
    task = db.get_task(task_id)
    owner_info = db.get_system_owner(task["system_id"])

    elapsed = (datetime.now() - task["created_at"]).seconds / 60
    if elapsed > owner_info["sla_minutes"] and task["status"] == "PENDING_APPROVAL":
        next_approver = get_approver_chain(task["system_id"])
        db.update_task_approver(task_id, next_approver, "ESCALATED")
        messenger.send_urgent_notification(next_approver, f"[긴급] {task_id} 승인 대기")
        audit.log_action(task_id, f"Escalated to {next_approver}")

3. 자격증명 보안

3.1. 암호화 저장 정책

- SSH/FTP 비밀번호: AES-256 암호화 → TB_SERVER_INFO.os_pw_enc
- 실행 시점에만 메모리 로드, 로그 출력 금지
- 메신저 응답창 노출 절대 금지
- 주기적 비밀번호 갱신 시 DB 암호화 업데이트

3.2. SSH 접속 보안

# opsagent 계정 sudoers 설정 (대상 서버에 1회 설정)
# /etc/sudoers.d/opsagent
opsagent ALL=(ALL) NOPASSWD: /usr/bin/kill, /usr/bin/systemctl restart *, /app/scripts/shutdown.sh, /app/scripts/startup.sh

4. 감사 로그 해시 체이닝

import hashlib, json

def compute_audit_hash(log_data: dict, prev_hash: str) -> str:
    """SHA-256 해시 체인 생성"""
    content = json.dumps(log_data, ensure_ascii=False, sort_keys=True)
    combined = f"{content}{prev_hash}"
    return hashlib.sha256(combined.encode()).hexdigest()

def log_execution(task_id: str, server_ip: str, command: str, exit_code: int, result: str):
    """감사 로그 저장 — 이전 로그의 해시를 체인에 포함"""
    prev = db.get_last_audit_log()
    prev_hash = prev["result_hash"] if prev else "GENESIS"

    log_data = {
        "task_id": task_id,
        "server_ip": server_ip,
        "command": command,
        "exit_code": exit_code,
        "exec_time": datetime.utcnow().isoformat()
    }
    new_hash = compute_audit_hash(log_data, prev_hash)

    db.insert_audit_log({**log_data, "result": result, "result_hash": new_hash})

def verify_audit_integrity() -> bool:
    """해시 체인 무결성 검증 — 위변조 탐지"""
    logs = db.get_all_audit_logs_ordered()
    prev_hash = "GENESIS"
    for log in logs:
        expected_hash = compute_audit_hash(
            {k: v for k, v in log.items() if k != "result_hash"}, prev_hash
        )
        if expected_hash != log["result_hash"]:
            alert_security_breach(log["log_id"])
            return False
        prev_hash = log["result_hash"]
    return True

5. Kill-Switch (비상 정지)

@app.post("/api/emergency/kill")
async def emergency_kill(operator_id: str, reason: str):
    """메신저 '정지' 명령 → 전체 배포 프로세스 즉시 중단"""
    # 진행 중인 모든 IN_PROGRESS 태스크 중단
    active_tasks = db.get_tasks_by_status("IN_PROGRESS")
    for task in active_tasks:
        task_manager.abort(task["task_id"])
        trigger_rollback(task["task_id"])

    # 감사 로그
    log_execution("EMERGENCY", "ALL", f"KILL_SWITCH by {operator_id}", 0, reason)

    # PM에게 알림
    messenger.send_to_all_pm(f"🚨 비상 정지 실행: {operator_id}{reason}")

    return {"status": "KILLED", "aborted_tasks": len(active_tasks)}

6. 보안 감사 체크리스트 (GS 인증 대응)

항목 검증 방법 주기
로그 무결성 해시 체인 전체 역추적 매일 00:00
계정 잠금 탐지 faillock 명령어 원격 스캔 1시간마다
설정 파일 변조 SHA-256 Checksum 비교 1시간마다
인증서 만료 openssl enddate 파싱 매일 04:00
디스크 임계치 df -h 자동 스캔 5분마다
비인가 포트 ss -tlnp 스캔 1시간마다

7. 데이터 무결성 보호 (배포 중 롤백)

class IntegrityShield:
    def protect_rollback(self, task_id: str, executor: SSHExecutor):
        """롤백 수행 시에도 감사 기록 보존"""
        # 롤백 직전 현재 해시 상태 기록
        log_execution(task_id, executor.host, "ROLLBACK_START", 0, "롤백 시작")
        # 실제 롤백
        result = executor.execute_command("sh /app/scripts/rollback.sh")
        if result["exit_code"] != 0:
            # 롤백 실패 → 수동 점검 필요 상태로 변경 (다음 배포 차단)
            db.update_task_status(task_id, "MANUAL_INTERVENTION_REQUIRED")
            messenger.send_critical_alert(f"🔴 {task_id} 롤백 실패 — 수동 점검 필요")