code0823a

Young-Kyoo Kim·2025년 8월 22일
---
- name: Service Status Check and Conditional Disable
  hosts: all
  gather_facts: yes
  become: yes
  serial: 5  # 5개 노드씩 순차 처리하여 안정성 확보
  
  vars:
    # 비활성화 대상 서비스 설정 (기본값: false로 안전하게 설정)
    disable_auditd: false
    disable_mlocate: false
    disable_ntp_services: false
    
    # 시간 동기화 관련 설정
    max_time_drift_seconds: 30
    ntp_server_pool: "pool.ntp.org"
    
    # 백업 및 로그 설정
    backup_configs: true
    log_changes: true
    
  tasks:
    # Phase 0: 컨트롤러 노드에서 기준 시간 설정 (다른 호스트들이 참조)
    - name: Set time_reference from controller node
      ansible.builtin.set_fact:
        time_reference: "{{ ansible_date_time.iso8601 }}"
      run_once: true
      delegate_to: localhost

    # Phase 1: 현재 상태 체크 (기존 플레이북 재사용)
    - name: Include service status check
      ansible.builtin.include_tasks: service_status_check_complete.yml
      
    # Phase 2: 조건부 서비스 비활성화
    - name: Include conditional service disable tasks
      ansible.builtin.include_tasks: conditional_service_disable.yml
      
    # Phase 3: 시간 동기화 처리
    - name: Include time synchronization tasks
      ansible.builtin.include_tasks: time_sync_correction.yml
      when: node_result.ntp.ntp_synchronized == false or 
            (time_reference is defined and 
             ((node_result.current_time | to_datetime('%Y-%m-%dT%H:%M:%SZ')) - 
              (time_reference | to_datetime('%Y-%m-%dT%H:%M:%SZ'))).total_seconds() | abs > max_time_drift_seconds)
              
    # Phase 4: 최종 검증 및 보고
    - name: Include final verification
      ansible.builtin.include_tasks: final_verification.yml
      
      
      
---
# conditional_service_disable.yml
# 조건부 서비스 비활성화 태스크
---
# ========================================
# AUDITD 서비스 비활성화
# ========================================

- name: Check if auditd disable is requested and service is active
  ansible.builtin.set_fact:
    should_disable_auditd: "{{ disable_auditd and node_result.auditd.service_exists and node_result.auditd.running }}"

- ansible.builtin.block:
    - name: Create backup of auditd configuration
      ansible.builtin.copy:
        src: /etc/audit/auditd.conf
        dest: /etc/audit/auditd.conf.backup.{{ ansible_date_time.epoch }}
        remote_src: yes
        backup: yes # Ansible 자체 백업 기능도 활성화 (선택 사항)
      when: backup_configs and node_result.auditd.service_exists
      
    - name: Log auditd disable action
      ansible.builtin.lineinfile:
        path: /var/log/ansible_service_changes.log
        create: yes
        line: "{{ ansible_date_time.iso8601 }} - {{ inventory_hostname }} - Disabling auditd service (was {{ node_result.auditd.status }})"
      when: log_changes
      
    - name: Stop auditd service
      ansible.builtin.systemd:
        name: auditd
        state: stopped
        enabled: no
      register: auditd_disable_result
      
    - name: Mask auditd service to prevent accidental restart
      ansible.builtin.systemd:
        name: auditd
        masked: yes
      when: auditd_disable_result is succeeded
      
    - name: Verify auditd is stopped
      ansible.builtin.systemd:
        name: auditd
      register: auditd_final_status
      
    - name: Display auditd disable result
      ansible.builtin.debug:
        msg: |
          ✅ AUDITD DISABLED SUCCESSFULLY on {{ inventory_hostname }}
          - Previous Status: {{ node_result.auditd.status }}
          - Current Status: {{ auditd_final_status.status.ActiveState }}
          - Service Masked: Yes
          {% if backup_configs %}
          - Config Backup: /etc/audit/auditd.conf.backup.{{ ansible_date_time.epoch }}
          {% endif %}
          
  when: should_disable_auditd
  
- name: Skip auditd disable - not requested or not running
  ansible.builtin.debug:
    msg: |
      ⏭️  SKIPPING AUDITD DISABLE on {{ inventory_hostname }}
      - Disable Requested: {{ disable_auditd }}
      - Service Exists: {{ node_result.auditd.service_exists }}
      - Currently Running: {{ node_result.auditd.running }}
      - Current Status: {{ node_result.auditd.status }}
  when: not should_disable_auditd

# ========================================
# MLOCATE 서비스 비활성화
# ========================================

- name: Check if mlocate disable is requested and timer is active
  ansible.builtin.set_fact:
    should_disable_mlocate: "{{ disable_mlocate and node_result.mlocate.timer_exists and node_result.mlocate.timer_running }}"

- ansible.builtin.block:
    - name: Log mlocate disable action
      ansible.builtin.lineinfile:
        path: /var/log/ansible_service_changes.log
        create: yes
        line: "{{ ansible_date_time.iso8601 }} - {{ inventory_hostname }} - Disabling mlocate timer (was {{ node_result.mlocate.timer_status }})"
      when: log_changes
      
    - name: Stop mlocate updatedb timer
      ansible.builtin.systemd:
        name: "{{ node_result.mlocate.timer_service }}"
        state: stopped
        enabled: no
      register: mlocate_disable_result
      
    - name: Mask mlocate timer to prevent accidental restart
      ansible.builtin.systemd:
        name: "{{ node_result.mlocate.timer_service }}"
        masked: yes
      when: mlocate_disable_result is succeeded
      
    - name: Remove mlocate database if requested
      ansible.builtin.file:
        path: "{{ node_result.mlocate.db_path }}"
        state: absent
      when: mlocate_disable_result is succeeded and node_result.mlocate.db_exists
      register: mlocate_db_removed
      
    - name: Verify mlocate timer is stopped
      ansible.builtin.systemd:
        name: "{{ node_result.mlocate.timer_service }}"
      register: mlocate_final_status
      
    - name: Display mlocate disable result
      ansible.builtin.debug:
        msg: |
          ✅ MLOCATE DISABLED SUCCESSFULLY on {{ inventory_hostname }}
          - Timer Service: {{ node_result.mlocate.timer_service }}
          - Previous Status: {{ node_result.mlocate.timer_status }}
          - Current Status: {{ mlocate_final_status.status.ActiveState }}
          - Service Masked: Yes
          {% if mlocate_db_removed is defined and mlocate_db_removed.changed %}
          - Database Removed: {{ node_result.mlocate.db_path }}
          {% endif %}
          
  when: should_disable_mlocate
  
- name: Skip mlocate disable - not requested or not running
  ansible.builtin.debug:
    msg: |
      ⏭️  SKIPPING MLOCATE DISABLE on {{ inventory_hostname }}
      - Disable Requested: {{ disable_mlocate }}
      - Timer Exists: {{ node_result.mlocate.timer_exists }}
      - Timer Running: {{ node_result.mlocate.timer_running }}
      - Timer Status: {{ node_result.mlocate.timer_status }}
  when: not should_disable_mlocate

# ========================================
# NTP 서비스 비활성화 (선택적)
# ========================================

- name: Check if NTP disable is requested and service is active
  ansible.builtin.set_fact:
    should_disable_ntp: "{{ disable_ntp_services and node_result.ntp.active_service != 'none' }}"

- ansible.builtin.block:
    - name: Create backup of time service configurations
      ansible.builtin.copy:
        src: "{{ item.src }}"
        dest: "{{ item.dest }}"
        remote_src: yes
        backup: yes
      loop:
        - { src: "/etc/chrony.conf", dest: "/etc/chrony.conf.backup.{{ ansible_date_time.epoch }}" }
        - { src: "/etc/ntp.conf", dest: "/etc/ntp.conf.backup.{{ ansible_date_time.epoch }}" }
      when: backup_configs
      failed_when: false  # 파일이 없을 수 있으므로 실패 무시
      
    - name: Log NTP disable action
      ansible.builtin.lineinfile:
        path: /var/log/ansible_service_changes.log
        create: yes
        line: "{{ ansible_date_time.iso8601 }} - {{ inventory_hostname }} - Disabling {{ node_result.ntp.active_service }} service"
      when: log_changes
      
    - name: Stop active NTP service
      ansible.builtin.systemd:
        name: "{{ node_result.ntp.active_service }}"
        state: stopped
        enabled: no
      register: ntp_disable_result
      
    - name: Mask NTP service to prevent accidental restart
      ansible.builtin.systemd:
        name: "{{ node_result.ntp.active_service }}"
        masked: yes
      when: ntp_disable_result is succeeded
      
    - name: Verify NTP service is stopped
      ansible.builtin.systemd:
        name: "{{ node_result.ntp.active_service }}"
      register: ntp_final_status
      
    - name: Display NTP disable result
      ansible.builtin.debug:
        msg: |
          ✅ NTP SERVICE DISABLED SUCCESSFULLY on {{ inventory_hostname }}
          - Service: {{ node_result.ntp.active_service }}
          - Current Status: {{ ntp_final_status.status.ActiveState }}
          - Service Masked: Yes
          ⚠️  WARNING: Time synchronization is now disabled!
          
  when: should_disable_ntp
  
- name: Skip NTP disable - not requested or no active service
  ansible.builtin.debug:
    msg: |
      ⏭️  SKIPPING NTP DISABLE on {{ inventory_hostname }}
      - Disable Requested: {{ disable_ntp_services }}
      - Active Service: {{ node_result.ntp.active_service }}
  when: not should_disable_ntp

# ========================================
# 서비스 비활성화 요약
# ========================================

- name: Create disable action summary
  ansible.builtin.set_fact:
    disable_summary:
      hostname: "{{ inventory_hostname }}"
      auditd_disabled: "{{ should_disable_auditd | default(false) }}"
      mlocate_disabled: "{{ should_disable_mlocate | default(false) }}"
      ntp_disabled: "{{ should_disable_ntp | default(false) }}"
      timestamp: "{{ ansible_date_time.iso8601 }}"
      actions_taken:
        - "{{ 'auditd stopped and masked' if should_disable_auditd else 'auditd unchanged' }}"
        - "{{ 'mlocate timer stopped and masked' if should_disable_mlocate else 'mlocate unchanged' }}"
        - "{{ node_result.ntp.active_service + ' stopped and masked' if should_disable_ntp else 'NTP unchanged' }}"

- name: Display per-node disable summary
  ansible.builtin.debug:
    msg: |
      
      ================================================
      🔧 SERVICE DISABLE SUMMARY - {{ inventory_hostname }}
      ================================================
      Timestamp: {{ disable_summary.timestamp }}
      
      📊 Actions Taken:
      {% for action in disable_summary.actions_taken %}
      - {{ action }}
      {% endfor %}
      
      📋 Current Service States:
      {% if disable_summary.auditd_disabled %}
      ✅ auditd: DISABLED AND MASKED
      {% else %}
      ⏭️  auditd: {{ node_result.auditd.status }} (unchanged)
      {% endif %}
      
      {% if disable_summary.mlocate_disabled %}
      ✅ mlocate: DISABLED AND MASKED
      {% else %}
      ⏭️  mlocate: {{ node_result.mlocate.timer_status }} (unchanged)
      {% endif %}
      
      {% if disable_summary.ntp_disabled %}
      ⚠️  {{ node_result.ntp.active_service }}: DISABLED AND MASKED
      {% else %}
      ⏭️  NTP: {{ node_result.ntp.active_service }} (unchanged)
      {% endif %}

- name: Save disable action log
  ansible.builtin.copy:
    content: "{{ disable_summary | to_nice_json }}"
    dest: "/tmp/disable_actions_{{ inventory_hostname }}.json"
  delegate_to: localhost
  
  
  
 
---
# time_sync_correction.yml
# 시간 동기화 문제 해결 태스크
---
# ========================================
# 시간 차이 분석 및 교정
# ========================================

- name: Calculate time drift from reference
  ansible.builtin.set_fact:
    time_drift_seconds: |
      {% if time_reference is defined %}
      {% set ref_time = time_reference | to_datetime('%Y-%m-%dT%H:%M:%SZ') %}
      {% set node_time = node_result.current_time | to_datetime('%Y-%m-%dT%H:%M:%SZ') %}
      {{ (node_time - ref_time).total_seconds() }}
      {% else %}
      0
      {% endif %}
  
- name: Determine time correction strategy
  ansible.builtin.set_fact:
    time_correction_needed: "{{ (time_drift_seconds | float | abs) > max_time_drift_seconds }}"
    drift_level: |
      {% set drift = time_drift_seconds | float | abs %}
      {% if drift > 300 %}
      CRITICAL
      {% elif drift > 60 %}
      HIGH
      {% elif drift > 30 %}
      MODERATE
      {% else %}
      MINOR
      {% endif %}

- name: Display time analysis
  ansible.builtin.debug:
    msg: |
      🕐 TIME DRIFT ANALYSIS - {{ inventory_hostname }}
      ================================================
      Current Time: {{ node_result.current_time }}
      Reference Time: {{ time_reference | default('N/A') }}
      Time Drift: {{ time_drift_seconds | float | round(2) }}s
      Drift Level: {{ drift_level }}
      Correction Needed: {{ time_correction_needed }}
      NTP Synchronized: {{ node_result.ntp.ntp_synchronized }}
      Active NTP Service: {{ node_result.ntp.active_service }}

# ========================================
# NTP 서비스 상태 확인 및 수정
# ========================================

- name: Fix NTP synchronization issues
  ansible.builtin.block:
    - name: Install chrony if no time service is available
      ansible.builtin.package:
        name: chrony
        state: present
      when: node_result.ntp.active_service == 'none'
      register: chrony_installed
      
    - name: Configure chrony with reliable NTP servers
      ansible.builtin.template:
        src: chrony.conf.j2
        dest: /etc/chrony.conf
        backup: yes
      notify: restart chronyd
      when: node_result.ntp.chronyd_exists or chrony_installed is defined
      
    - name: Start and enable chronyd
      ansible.builtin.systemd:
        name: chronyd
        state: started
        enabled: yes
      when: node_result.ntp.chronyd_exists or chrony_installed is defined
      register: chronyd_started
      
    - name: Force immediate time synchronization (gradual) using chronyc
      ansible.builtin.command: chronyc bursts
      when: time_correction_needed and drift_level in ['MINOR', 'MODERATE'] and (node_result.ntp.chronyd_exists or chrony_installed is defined)
      register: gradual_sync_result
      failed_when: false # `chronyc bursts`는 0이 아닌 exit code를 반환할 수 있으므로 실패 무시
      
    - name: Force immediate time synchronization (step for critical drift)
      ansible.builtin.block:
        - name: Stop chronyd for manual time step
          ansible.builtin.systemd:
            name: chronyd
            state: stopped
            
        - name: Force time step with chronyd
          ansible.builtin.command: chronyd -q 'server {{ ntp_server_pool }} iburst'
          register: step_sync_result
          
        - name: Start chronyd after manual sync
          ansible.builtin.systemd:
            name: chronyd
            state: started
            
      when: time_correction_needed and drift_level == 'CRITICAL' and (node_result.ntp.chronyd_exists or chrony_installed is defined)
      
    - name: Wait for synchronization to stabilize using chronyc waitsync
      ansible.builtin.command: chronyc waitsync 15 # 15초 동안 동기화 기다림
      register: waitsync_result
      until: waitsync_result.rc == 0
      retries: 4 # 총 60초 기다림
      delay: 15
      failed_when: false # 동기화 실패 시 바로 태스크 실패로 처리하지 않음
      when: (chronyd_started is changed or gradual_sync_result is defined or step_sync_result is defined) and (node_result.ntp.chronyd_exists or chrony_installed is defined)

    - name: Verify time synchronization after correction with timedatectl
      ansible.builtin.command: timedatectl status
      register: time_verify_result
      
    - name: Get updated chrony tracking status
      ansible.builtin.command: chronyc tracking
      register: chrony_tracking_updated
      failed_when: false
      when: node_result.ntp.chronyd_exists or chrony_installed is defined
      
    - name: Display time correction results
      ansible.builtin.debug:
        msg: |
          ✅ TIME SYNCHRONIZATION CORRECTION COMPLETED
          ================================================
          Previous Drift: {{ time_drift_seconds | float | round(2) }}s
          Correction Method: {{ 'Step' if drift_level == 'CRITICAL' else 'Gradual' }}
          
          Updated Status:
          {{ time_verify_result.stdout }}
          
          {% if chrony_tracking_updated.stdout is defined %}
          Chrony Tracking:
          {{ chrony_tracking_updated.stdout }}
          {% endif %}
          
  when: time_correction_needed or not node_result.ntp.ntp_synchronized

# ========================================
# 시간 동기화 모니터링 설정
# ========================================

- name: Setup time synchronization monitoring
  ansible.builtin.block:
    - name: Create time sync monitoring script
      ansible.builtin.copy:
        content: |
          #!/bin/bash
          # Time synchronization monitoring script
          # Generated by Ansible on {{ ansible_date_time.iso8601 }}
          
          LOG_FILE="/var/log/time_sync_monitor.log"
          MAX_DRIFT={{ max_time_drift_seconds }} # 플레이북 변수 사용
          NTP_POOL="{{ ntp_server_pool }}" # 플레이북 변수 사용
          
          # Get current time sync status
          TIMEDATECTL_OUTPUT=$(timedatectl status)
          NTP_SYNC=$(echo "$TIMEDATECTL_OUTPUT" | grep "NTP synchronized" | awk '{print $3}')
          
          if [[ "$NTP_SYNC" != "yes" ]]; then
              echo "$(date): WARNING - NTP not synchronized, attempting restart" >> $LOG_FILE
              systemctl restart chronyd || echo "$(date): ERROR - Failed to restart chronyd" >> $LOG_FILE
          fi
          
          # Check chrony tracking if available
          if command -v chronyc &> /dev/null; then
              CHRONY_TRACKING=$(chronyc tracking)
              OFFSET=$(echo "$CHRONY_TRACKING" | grep "Last offset" | awk '{print $4}')
              
              # Ensure OFFSET is a valid number before comparison
              if [[ -n "$OFFSET" && "$OFFSET" =~ ^[+-]?[0-9]*\.?[0-9]+$ ]]; then
                  if (( $(echo "$OFFSET > $MAX_DRIFT" | bc -l) )); then
                      echo "$(date): WARNING - Time offset too large: ${OFFSET}s, forcing burst sync" >> $LOG_FILE
                      chronyc bursts || echo "$(date): ERROR - Failed to perform chronyc bursts" >> $LOG_FILE
                  fi
              fi
          fi
        dest: /usr/local/bin/time_sync_monitor.sh
        mode: '0755'
        
    # cron job 대신 systemd timer를 선호
    # - name: Create cron job for time sync monitoring
    #   ansible.builtin.cron:
    #     name: "Time synchronization monitoring"
    #     minute: "*/15"
    #     job: "/usr/local/bin/time_sync_monitor.sh"
    #     user: root
        
    - name: Create systemd timer for time sync monitoring
      ansible.builtin.copy:
        content: |
          [Unit]
          Description=Time Sync Monitor Timer
          
          [Timer]
          OnCalendar=*:0/15
          Persistent=true
          
          [Install]
          WantedBy=timers.target
        dest: /etc/systemd/system/time-sync-monitor.timer
        
    - name: Create systemd service for time sync monitoring
      ansible.builtin.copy:
        content: |
          [Unit]
          Description=Time Sync Monitor Service
          
          [Service]
          Type=oneshot
          ExecStart=/usr/local/bin/time_sync_monitor.sh
          User=root
        dest: /etc/systemd/system/time-sync-monitor.service
        
    - name: Enable and start time sync monitoring timer
      ansible.builtin.systemd:
        name: time-sync-monitor.timer
        enabled: yes
        state: started
        daemon_reload: yes
        
  # 시간 동기화 문제가 있었거나 동기화 상태가 아니었을 경우 모니터링 설정
  when: time_correction_needed or not node_result.ntp.ntp_synchronized

# ========================================
# 시간 동기화 권장사항
# ========================================

- name: Generate time sync recommendations
  ansible.builtin.set_fact:
    time_recommendations: |
      {% set drift = time_drift_seconds | float | abs %}
      {% if drift > 300 %}
      CRITICAL TIME DRIFT ({{ drift | round(2) }}s):
      1. 즉시 수동 시간 동기화 수행
      2. NTP 서버 연결 상태 확인
      3. 방화벽 설정 점검 (UDP 123 포트)
      4. 네트워크 연결성 확인
      5. 시스템 클럭 하드웨어 점검 권장
      {% elif drift > 60 %}
      HIGH TIME DRIFT ({{ drift | round(2) }}s):
      1. NTP 서비스 재시작 수행
      2. 더 가까운 NTP 서버로 변경 고려
      3. 정기적인 시간 동기화 모니터링 설정
      {% elif drift > 30 %}
      MODERATE TIME DRIFT ({{ drift | round(2) }}s):
      1. NTP 동기화 강제 수행
      2. 시간 동기화 빈도 증가
      {% else %}
      MINOR TIME DRIFT ({{ drift | round(2) }}s):
      1. 정상 범위 내 차이
      2. 정기 모니터링 유지
      {% endif %}

- name: Display time sync recommendations
  ansible.builtin.debug:
    msg: |
      
      ================================================
      🔧 TIME SYNCHRONIZATION RECOMMENDATIONS
      ================================================
      Host: {{ inventory_hostname }}
      {{ time_recommendations }}
      
      추가 점검 사항:
      - 네트워크 지연: ping {{ ntp_server_pool }}
      - NTP 포트 확인: netstat -ulnp | grep :123
      - 시스템 로그: journalctl -u chronyd -f
      - 하드웨어 클럭: hwclock --show
  when: time_correction_needed # 시간 교정이 필요했을 경우에만 권장사항 표시
  
  
  
  ---
# final_verification.yml
# 최종 검증 및 보고서 생성
---
# ========================================
# 변경 후 서비스 상태 재검증
# ========================================

- name: Re-check auditd status after changes
  ansible.builtin.systemd:
    name: auditd
  register: auditd_final_check
  failed_when: false
  when: node_result.auditd.service_exists

- name: Re-check mlocate timer status after changes
  ansible.builtin.systemd:
    name: "{{ node_result.mlocate.timer_service }}"
  register: mlocate_final_check
  failed_when: false
  when: node_result.mlocate.timer_exists

- name: Re-check NTP status after changes
  ansible.builtin.command: timedatectl status
  register: ntp_final_check
  failed_when: false

- name: Get updated chrony tracking status
  ansible.builtin.command: chronyc tracking
  register: chrony_final_check
  failed_when: false
  when: node_result.ntp.chronyd_exists or (node_result.ntp.active_service == 'none' and 'chrony_installed' in hostvars[inventory_hostname] and hostvars[inventory_hostname].chrony_installed.changed) # chrony가 설치되었을 경우 확인

- name: Get current time after changes
  ansible.builtin.setup:
    filter: ansible_date_time
  register: final_time_info

# ========================================
# 최종 상태 분석
# ========================================

- name: Analyze final service states
  ansible.builtin.set_fact:
    final_verification:
      hostname: "{{ inventory_hostname }}"
      timestamp: "{{ final_time_info.ansible_facts.ansible_date_time.iso8601 }}"
      verification_status:
        auditd:
          requested_disable: "{{ disable_auditd | default(false) }}"
          current_status: "{{ auditd_final_check.status.ActiveState if auditd_final_check.status is defined else 'N/A' }}"
          is_masked: "{{ auditd_final_check.status.UnitFileState == 'masked' if auditd_final_check.status is defined else false }}"
          disable_successful: "{{ (disable_auditd | default(false)) and (auditd_final_check.status.ActiveState == 'inactive' if auditd_final_check.status is defined else false) }}"
        mlocate:
          requested_disable: "{{ disable_mlocate | default(false) }}"
          current_status: "{{ mlocate_final_check.status.ActiveState if mlocate_final_check.status is defined else 'N/A' }}"
          is_masked: "{{ mlocate_final_check.status.UnitFileState == 'masked' if mlocate_final_check.status is defined else false }}"
          disable_successful: "{{ (disable_mlocate | default(false)) and (mlocate_final_check.status.ActiveState == 'inactive' if mlocate_final_check.status is defined else false) }}"
        ntp:
          sync_status: "{{ 'synchronized' if 'NTP synchronized: yes' in ntp_final_check.stdout else 'not synchronized' }}"
          current_time: "{{ final_time_info.ansible_facts.ansible_date_time.iso8601 }}"
          chrony_tracking: "{{ chrony_final_check.stdout if chrony_final_check.stdout is defined else 'N/A' }}"

# ========================================
# 최종 검증 보고서
# ========================================

- name: Display final verification report
  ansible.builtin.debug:
    msg: |
      
      ================================================
      📋 FINAL VERIFICATION REPORT
      ================================================
      Host: {{ final_verification.hostname }}
      Verification Time: {{ final_verification.timestamp }}
      
      🔍 AUDITD VERIFICATION:
      {% if final_verification.verification_status.auditd.requested_disable %}
      - Disable Requested: ✅ YES
      - Current Status: {{ final_verification.verification_status.auditd.current_status }}
      - Service Masked: {{ '✅ YES' if final_verification.verification_status.auditd.is_masked else '❌ NO' }}
      - Disable Success: {{ '✅ SUCCESS' if final_verification.verification_status.auditd.disable_successful else '❌ FAILED' }}
      {% else %}
      - Disable Requested: ⏭️  NO (Service Unchanged)
      - Current Status: {{ final_verification.verification_status.auditd.current_status }}
      {% endif %}
      
      📍 MLOCATE VERIFICATION:
      {% if final_verification.verification_status.mlocate.requested_disable %}
      - Disable Requested: ✅ YES
      - Current Status: {{ final_verification.verification_status.mlocate.current_status }}
      - Service Masked: {{ '✅ YES' if final_verification.verification_status.mlocate.is_masked else '❌ NO' }}
      - Disable Success: {{ '✅ SUCCESS' if final_verification.verification_status.mlocate.disable_successful else '❌ FAILED' }}
      {% else %}
      - Disable Requested: ⏭️  NO (Service Unchanged)
      - Current Status: {{ final_verification.verification_status.mlocate.current_status }}
      {% endif %}
      
      🕐 NTP/TIME VERIFICATION:
      - Current Time: {{ final_verification.verification_status.ntp.current_time }}
      - Sync Status: {{ '✅ SYNCHRONIZED' if final_verification.verification_status.ntp.sync_status == 'synchronized' else '⚠️ NOT SYNCHRONIZED' }}
      {% if final_verification.verification_status.ntp.chrony_tracking != 'N/A' %}
      - Chrony Details:
      {{ final_verification.verification_status.ntp.chrony_tracking | indent(4) }}
      {% endif %}

- name: Save final verification report
  ansible.builtin.copy:
    content: "{{ final_verification | to_nice_json }}"
    dest: "/tmp/final_verification_{{ inventory_hostname }}.json"
  delegate_to: localhost

# ========================================
# 전체 클러스터 최종 요약 (run_once)
# ========================================

- name: Collect all verification reports
  ansible.builtin.find:
    paths: /tmp
    patterns: "final_verification_*.json"
  register: verification_files
  delegate_to: localhost
  run_once: true

- name: Read all verification files
  ansible.builtin.slurp:
    src: "{{ item.path }}"
  register: verification_contents
  loop: "{{ verification_files.files }}"
  delegate_to: localhost
  run_once: true

- name: Combine verification results
  ansible.builtin.set_fact:
    all_verifications: "{{ all_verifications | default([]) + [item.content | b64decode | from_json] }}"
  loop: "{{ verification_contents.results }}"
  delegate_to: localhost
  run_once: true

- name: Calculate final statistics
  ansible.builtin.set_fact:
    final_stats:
      total_nodes: "{{ all_verifications | length }}"
      auditd:
        disable_requested: "{{ all_verifications | selectattr('verification_status.auditd.requested_disable', 'equalto', true) | list | length }}"
        disable_successful: "{{ all_verifications | selectattr('verification_status.auditd.disable_successful', 'equalto', true) | list | length }}"
        disable_failed: "{{ all_verifications | selectattr('verification_status.auditd.requested_disable', 'equalto', true) | selectattr('verification_status.auditd.disable_successful', 'equalto', false) | list | length }}"
      mlocate:
        disable_requested: "{{ all_verifications | selectattr('verification_status.mlocate.requested_disable', 'equalto', true) | list | length }}"
        disable_successful: "{{ all_verifications | selectattr('verification_status.mlocate.disable_successful', 'equalto', true) | list | length }}"
        disable_failed: "{{ all_verifications | selectattr('verification_status.mlocate.requested_disable', 'equalto', true) | selectattr('verification_status.mlocate.disable_successful', 'equalto', false) | list | length }}"
      ntp:
        synchronized_nodes: "{{ all_verifications | selectattr('verification_status.ntp.sync_status', 'equalto', 'synchronized') | list | length }}"
        unsynchronized_nodes: "{{ all_verifications | selectattr('verification_status.ntp.sync_status', 'equalto', 'not synchronized') | list | length }}"
  delegate_to: localhost
  run_once: true

- name: Get failed operations details
  ansible.builtin.set_fact:
    failed_operations:
      auditd_failed_nodes: "{{ all_verifications | selectattr('verification_status.auditd.requested_disable', 'equalto', true) | selectattr('verification_status.auditd.disable_successful', 'equalto', false) | map(attribute='hostname') | list }}"
      mlocate_failed_nodes: "{{ all_verifications | selectattr('verification_status.mlocate.requested_disable', 'equalto', true) | selectattr('verification_status.mlocate.disable_successful', 'equalto', false) | map(attribute='hostname') | list }}"
      time_unsync_nodes: "{{ all_verifications | selectattr('verification_status.ntp.sync_status', 'equalto', 'not synchronized') | map(attribute='hostname') | list }}"
  delegate_to: localhost
  run_once: true

- name: Display cluster-wide final summary
  ansible.builtin.debug:
    msg: |
      
      ================================================
      🎯 CLUSTER-WIDE FINAL SUMMARY
      ================================================
      Total Nodes Processed: {{ final_stats.total_nodes }}
      
      🔍 AUDITD DISABLE OPERATIONS:
      ├─ Disable Requested: {{ final_stats.auditd.disable_requested }} nodes
      ├─ Successfully Disabled: {{ final_stats.auditd.disable_successful }} nodes
      ├─ Failed Disables: {{ final_stats.auditd.disable_failed }} nodes
      {% if failed_operations.auditd_failed_nodes | length > 0 %}
      └─ Failed Nodes: {{ failed_operations.auditd_failed_nodes | join(', ') }}
      {% else %}
      └─ All Operations Successful ✅
      {% endif %}
      
      📍 MLOCATE DISABLE OPERATIONS:
      ├─ Disable Requested: {{ final_stats.mlocate.disable_requested }} nodes
      ├─ Successfully Disabled: {{ final_stats.mlocate.disable_successful }} nodes
      ├─ Failed Disables: {{ final_stats.mlocate.disable_failed }} nodes
      {% if failed_operations.mlocate_failed_nodes | length > 0 %}
      └─ Failed Nodes: {{ failed_operations.mlocate_failed_nodes | join(', ') }}
      {% else %}
      └─ All Operations Successful ✅
      {% endif %}
      
      🕐 TIME SYNCHRONIZATION STATUS:
      ├─ Synchronized Nodes: {{ final_stats.ntp.synchronized_nodes }} nodes
      ├─ Unsynchronized Nodes: {{ final_stats.ntp.unsynchronized_nodes }} nodes
      {% if failed_operations.time_unsync_nodes | length > 0 %}
      └─ Needs Attention: {{ failed_operations.time_unsync_nodes | join(', ') }}
      {% else %}
      └─ All Nodes Synchronized ✅
      {% endif %}
  delegate_to: localhost
  run_once: true

- name: Generate comprehensive final report CSV
  ansible.builtin.copy:
    content: |
      Hostname,Timestamp,auditd_Disable_Requested,auditd_Current_Status,auditd_Masked,auditd_Disable_Success,mlocate_Disable_Requested,mlocate_Current_Status,mlocate_Masked,mlocate_Disable_Success,NTP_Sync_Status,Current_Time,Overall_Status
      {% for verification in all_verifications -%}
      {% set auditd_ok = not verification.verification_status.auditd.requested_disable or verification.verification_status.auditd.disable_successful %}
      {% set mlocate_ok = not verification.verification_status.mlocate.requested_disable or verification.verification_status.mlocate.disable_successful %}
      {% set ntp_ok = verification.verification_status.ntp.sync_status == 'synchronized' %}
      {{ verification.hostname }},{{ verification.timestamp }},{{ verification.verification_status.auditd.requested_disable }},{{ verification.verification_status.auditd.current_status }},{{ verification.verification_status.auditd.is_masked }},{{ verification.verification_status.auditd.disable_successful }},{{ verification.verification_status.mlocate.requested_disable }},{{ verification.verification_status.mlocate.current_status }},{{ verification.verification_status.mlocate.is_masked }},{{ verification.verification_status.mlocate.disable_successful }},{{ verification.verification_status.ntp.sync_status }},{{ verification.verification_status.ntp.current_time }},{{ 'SUCCESS' if auditd_ok and mlocate_ok and ntp_ok else 'PARTIAL' if (auditd_ok and mlocate_ok) or ntp_ok else 'FAILED' }}
      {% endfor %}
    dest: "./final_verification_report.csv"
  delegate_to: localhost
  run_once: true

- name: Clean up temporary verification files
  ansible.builtin.file:
    path: "{{ item.path }}"
    state: absent
  loop: "{{ verification_files.files }}"
  delegate_to: localhost
  run_once: true

# ========================================
# 권장 사후 조치
# ========================================

- name: Display post-operation recommendations
  ansible.builtin.debug:
    msg: |
      
      ================================================
      🔧 POST-OPERATION RECOMMENDATIONS
      ================================================
      
      {% if final_stats.auditd.disable_failed > 0 %}
      ❌ AUDITD DISABLE FAILURES:
      - Manual intervention required on: {{ failed_operations.auditd_failed_nodes | join(', ') }}
      - Check service dependencies: systemctl list-dependencies auditd
      - Force stop if needed: systemctl stop auditd --force
      - Check for active audit processes: ps aux | grep audit
      {% endif %}
      
      {% if final_stats.mlocate.disable_failed > 0 %}
      ❌ MLOCATE DISABLE FAILURES:
      - Manual intervention required on: {{ failed_operations.mlocate_failed_nodes | join(', ') }}
      - Check running updatedb processes: ps aux | grep updatedb
      - Kill if necessary: pkill -f updatedb
      - Retry disable: systemctl stop mlocate-updatedb.timer
      {% endif %}
      
      {% if final_stats.ntp.unsynchronized_nodes > 0 %}
      ⚠️  TIME SYNCHRONIZATION ISSUES:
      - Nodes needing attention: {{ failed_operations.time_unsync_nodes | join(', ') }}
      
      Manual troubleshooting steps:
      1. Check NTP service status:
         systemctl status chronyd
      
      2. Verify NTP server connectivity:
         chronyc sources -v
         ping {{ ntp_server_pool }}
      
      3. Check firewall settings:
         firewall-cmd --list-ports | grep 123
         iptables -L | grep 123
      
      4. Force time synchronization:
         chrony -q 'server {{ ntp_server_pool }} iburst'
      
      5. Check system logs:
         journalctl -u chronyd --since="1 hour ago"
      {% endif %}
      
      📊 MONITORING SETUP:
      {% if final_stats.auditd.disable_successful > 0 %}
      - auditd monitoring disabled on {{ final_stats.auditd.disable_successful }} nodes
      - Alternative security monitoring recommended
      {% endif %}
      
      {% if final_stats.mlocate.disable_successful > 0 %}
      - locate database updates disabled on {{ final_stats.mlocate.disable_successful }} nodes
      - Manual updatedb runs required for file indexing
      {% endif %}
      
      📄 Generated Reports:
      - ./final_verification_report.csv (Complete operation results)
      - /var/log/ansible_service_changes.log (Change audit trail on each node)
      - Time sync monitoring active on nodes with corrections
      
      🔄 ROLLBACK PROCEDURES:
      If services need to be re-enabled:
      
      For auditd:
        sudo systemctl unmask auditd
        sudo systemctl enable auditd
        sudo systemctl start auditd
      
      For mlocate:
        sudo systemctl unmask mlocate-updatedb.timer
        sudo systemctl enable mlocate-updatedb.timer
        sudo systemctl start mlocate-updatedb.timer
        sudo updatedb  # Rebuild database
      
      ⚡ IMMEDIATE ACTIONS REQUIRED:
      {% if final_stats.auditd.disable_failed > 0 or final_stats.mlocate.disable_failed > 0 %}
      - Review failed operations and resolve manually
      {% endif %}
      {% if final_stats.ntp.unsynchronized_nodes > 0 %}
      - Fix time synchronization on unsynchronized nodes
      {% endif %}
      {% if final_stats.auditd.disable_successful > 0 %}
      - Implement alternative security monitoring if required
      {% endif %}
  delegate_to: localhost
  run_once: true

0개의 댓글