aim-web2.1.0rc9
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,64 +0,0 @@
|
||||
---
|
||||
- name: "Checkmk | Cleanup agent"
|
||||
hosts: all
|
||||
gather_facts: true
|
||||
|
||||
tasks:
|
||||
- name: "Windows | Remove installed Checkmk agent"
|
||||
when: ansible_facts['os_family'] == 'Windows'
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$Ansible.Changed = $false
|
||||
$uninstallRoots = @(
|
||||
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
|
||||
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
|
||||
)
|
||||
|
||||
$products = foreach ($root in $uninstallRoots) {
|
||||
if (Test-Path -LiteralPath $root) {
|
||||
Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue |
|
||||
ForEach-Object {
|
||||
$product = Get-ItemProperty -LiteralPath $_.PSPath -ErrorAction SilentlyContinue
|
||||
if ($product.DisplayName -like 'Check MK Agent*' -or
|
||||
$product.DisplayName -like 'Checkmk Agent*') {
|
||||
[PSCustomObject]@{
|
||||
ProductCode = $_.PSChildName
|
||||
DisplayName = $product.DisplayName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($product in $products) {
|
||||
if ($product.ProductCode -match '^\{[0-9A-Fa-f-]+\}$') {
|
||||
$process = Start-Process -FilePath 'msiexec.exe' `
|
||||
-ArgumentList "/x $($product.ProductCode) /qn /norestart" `
|
||||
-Wait -PassThru
|
||||
|
||||
if ($process.ExitCode -notin @(0, 1605, 1614, 3010)) {
|
||||
throw "Failed to uninstall $($product.DisplayName). MSI exit code: $($process.ExitCode)"
|
||||
}
|
||||
|
||||
if ($process.ExitCode -in @(0, 3010)) {
|
||||
$Ansible.Changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$Ansible.Result = @{
|
||||
removed_products = @($products.DisplayName)
|
||||
}
|
||||
|
||||
- name: "Debian | Remove Checkmk agent"
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
ansible.builtin.apt:
|
||||
name: check-mk-agent
|
||||
state: absent
|
||||
purge: true
|
||||
|
||||
- name: "RedHat | Remove Checkmk agent"
|
||||
when: ansible_facts['os_family'] == 'RedHat'
|
||||
ansible.builtin.dnf:
|
||||
name: check-mk-agent
|
||||
state: absent
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
# PURPOSE: Preview / clean up Checkmk scripts
|
||||
# DESCRIPTION: List obsolete managed script paths; remove them only with explicit deletion approval.
|
||||
# TARGETS: linux, windows
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# checkmk_cleanup_enabled [bool]: false
|
||||
# checkmk_unifi_mode [choice]: auto
|
||||
# want_linux_check_certificate [bool]: false
|
||||
# want_windows_citrix [bool]: false
|
||||
# want_windows_surebackup [bool]: false
|
||||
# want_windows_backup [bool]: false
|
||||
# want_windows_nsp_mailqueue [bool]: false
|
||||
# want_windows_certificate [bool]: false
|
||||
# want_windows_veeam_cloud_connect [bool]: false
|
||||
# want_windows_veeam_backup [bool]: false
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: Cleanup only touches the documented managed filenames. Preview is the default; enabling deletion requires another confirmation.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/checkmk_cleanup_scripts.yml --limit <host> --vault-id <customer>@prompt
|
||||
- name: Checkmk | Preview or clean up linux
|
||||
hosts: linux
|
||||
gather_facts: true
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
become: true
|
||||
roles:
|
||||
- role: system_detect_roles
|
||||
- role: checkmk_script_plan
|
||||
- role: checkmk_cleanup_scripts
|
||||
- name: Checkmk | Preview or clean up windows
|
||||
hosts: windows
|
||||
gather_facts: true
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
roles:
|
||||
- role: system_detect_roles
|
||||
- role: checkmk_script_plan
|
||||
- role: checkmk_cleanup_scripts
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
- name: "Checkmk | Deploy agent, scripts and configuration"
|
||||
hosts: all
|
||||
gather_facts: true
|
||||
|
||||
roles:
|
||||
- checkmk_agent
|
||||
- server_role_selection
|
||||
- checkmk_scripts
|
||||
- checkmk_agent_config
|
||||
@@ -0,0 +1,136 @@
|
||||
# PURPOSE: Install Checkmk agent
|
||||
# DESCRIPTION: Install staged agent packages, deploy selected checks, render Windows settings and ensure the agent is running.
|
||||
# TARGETS: linux, windows
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# checkmk_unifi_mode [choice]: auto
|
||||
# checkmk_unifi_username [text]: bf-monitoring
|
||||
# checkmk_unifi_password [secret_ref]: vault_checkmk_unifi_password
|
||||
# checkmk_unifi_baseurl [url]: network: https://127.0.0.1:8443; os: https://127.0.0.1:11443
|
||||
# checkmk_unifi_curl_options [text]: --insecure --tlsv1.2
|
||||
# want_linux_check_certificate [bool]: false
|
||||
# want_windows_citrix [bool]: false
|
||||
# want_windows_surebackup [bool]: false
|
||||
# want_windows_backup [bool]: false
|
||||
# want_windows_nsp_mailqueue [bool]: false
|
||||
# want_windows_certificate [bool]: false
|
||||
# want_windows_veeam_cloud_connect [bool]: false
|
||||
# want_windows_veeam_backup [bool]: false
|
||||
# checkmk_unifi_status_provisioning [int]: 1
|
||||
# checkmk_unifi_status_upgrading [int]: 1
|
||||
# checkmk_unifi_status_upgradable [int]: 0
|
||||
# checkmk_unifi_status_heartbeat_missed [int]: 1
|
||||
# checkmk_unifi_status_noautobackup [int]: 0
|
||||
# checkmk_windows_updates_timeout [int]: 3600
|
||||
# checkmk_windows_updates_cache [int]: 43200
|
||||
# checkmk_mk_inventory_timeout [int]: 120
|
||||
# checkmk_plugins_default_timeout [int]: 120
|
||||
# checkmk_plugins_default_cache [int]: 600
|
||||
# checkmk_extra_plugin_patterns [sequence]: []
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: Installs packages and AIM-managed script files. On Windows only the marked plugins section in check_mk.user.yml is replaced; all other user-config sections are preserved. UniFi deployment also removes the alternative UniFi local check; no other cleanup is performed.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/checkmk_install_agent.yml --limit <host> --vault-id <customer>@prompt
|
||||
- name: Checkmk | Install linux
|
||||
hosts: linux
|
||||
gather_facts: true
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in
|
||||
['true', 'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
- name: Load system detect roles
|
||||
ansible.builtin.include_role:
|
||||
name: system_detect_roles
|
||||
- name: Load checkmk script plan
|
||||
ansible.builtin.include_role:
|
||||
name: checkmk_script_plan
|
||||
- name: Checkmk | Preflight scripts and credentials
|
||||
ansible.builtin.include_role:
|
||||
name: checkmk_deploy_scripts
|
||||
tasks_from: preflight
|
||||
become: true
|
||||
roles:
|
||||
- role: checkmk_agent
|
||||
- role: checkmk_deploy_scripts
|
||||
- role: checkmk_configure_agent
|
||||
- role: checkmk_manage_service
|
||||
post_tasks:
|
||||
- name: Checkmk | Observe installed agent
|
||||
ansible.builtin.include_role:
|
||||
name: checkmk_report
|
||||
- name: AIM | Publish operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: checkmk_agent_state_v1
|
||||
data: '{{ _aim_checkmk_state }}'
|
||||
- name: Checkmk | Install windows
|
||||
hosts: windows
|
||||
gather_facts: true
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in
|
||||
['true', 'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
- name: Load system detect roles
|
||||
ansible.builtin.include_role:
|
||||
name: system_detect_roles
|
||||
- name: Load checkmk script plan
|
||||
ansible.builtin.include_role:
|
||||
name: checkmk_script_plan
|
||||
- name: Checkmk | Preflight scripts and credentials
|
||||
ansible.builtin.include_role:
|
||||
name: checkmk_deploy_scripts
|
||||
tasks_from: preflight
|
||||
roles:
|
||||
- role: checkmk_agent
|
||||
- role: checkmk_deploy_scripts
|
||||
- role: checkmk_configure_agent
|
||||
- role: checkmk_manage_service
|
||||
post_tasks:
|
||||
- name: Checkmk | Observe installed agent
|
||||
ansible.builtin.include_role:
|
||||
name: checkmk_report
|
||||
- name: AIM | Publish operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: checkmk_agent_state_v1
|
||||
data: '{{ _aim_checkmk_state }}'
|
||||
@@ -0,0 +1,115 @@
|
||||
# PURPOSE: Read current Windows Checkmk user configuration
|
||||
# DESCRIPTION: Display the current check_mk.user.yml from Windows without modifying the host.
|
||||
# TARGETS: windows
|
||||
# INPUTS (omitted values inherit inventory / playbook defaults):
|
||||
# aim_debug [bool]: false
|
||||
# checkmk_windows_user_cfg [text]: C:\ProgramData\checkmk\agent\check_mk.user.yml
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: none; this playbook is read-only.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/checkmk_read_windows_config.yml --limit <host> --vault-id <customer>@prompt
|
||||
|
||||
- name: Checkmk | Read Windows user configuration
|
||||
hosts: windows
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in
|
||||
['true', 'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
|
||||
- name: AIM | Validate Checkmk configuration path
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (checkmk_windows_user_cfg | default('C:\\ProgramData\\checkmk\\agent\\check_mk.user.yml')) is
|
||||
string
|
||||
- (checkmk_windows_user_cfg | default('C:\\ProgramData\\checkmk\\agent\\check_mk.user.yml') | trim
|
||||
| length) > 0
|
||||
fail_msg: checkmk_windows_user_cfg must be a non-empty Windows path.
|
||||
quiet: true
|
||||
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
configuration: "{{ checkmk_windows_user_cfg | default('C:\\\\ProgramData\\\\checkmk\\\\agent\\\\check_mk.user.yml')
|
||||
}}"
|
||||
mode: Read-only; no Checkmk configuration is modified.
|
||||
when: aim_debug | default(false) | bool
|
||||
|
||||
- name: Windows | Inspect current Checkmk user configuration
|
||||
ansible.windows.win_stat:
|
||||
path: "{{ checkmk_windows_user_cfg | default('C:\\ProgramData\\checkmk\\agent\\check_mk.user.yml') }}"
|
||||
get_checksum: false
|
||||
register: _checkmk_windows_user_config_stat
|
||||
changed_when: false
|
||||
|
||||
- name: Windows | Require the approved Checkmk user filename
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- not (_checkmk_windows_user_config_stat.stat.exists | default(false)) or (_checkmk_windows_user_config_stat.stat.filename | lower) == 'check_mk.user.yml'
|
||||
- not (_checkmk_windows_user_config_stat.stat.exists | default(false)) or (_checkmk_windows_user_config_stat.stat.isreg | default(false))
|
||||
- (_checkmk_windows_user_config_stat.stat.size | default(0) | int) <= 524288
|
||||
fail_msg: Only a regular check_mk.user.yml file up to 512 KiB may be read.
|
||||
quiet: true
|
||||
|
||||
- name: Windows | Read current Checkmk user configuration
|
||||
ansible.windows.slurp:
|
||||
src: "{{ checkmk_windows_user_cfg | default('C:\\ProgramData\\checkmk\\agent\\check_mk.user.yml') }}"
|
||||
register: _checkmk_windows_user_config_slurp
|
||||
when: _checkmk_windows_user_config_stat.stat.exists | default(false)
|
||||
no_log: true
|
||||
|
||||
- name: Windows | Build Checkmk user configuration result
|
||||
ansible.builtin.set_fact:
|
||||
_checkmk_windows_user_config:
|
||||
exists: '{{ _checkmk_windows_user_config_stat.stat.exists | default(false) | bool }}'
|
||||
path: "{{ _checkmk_windows_user_config_stat.stat.path | default(checkmk_windows_user_cfg | default('C:\\ProgramData\\checkmk\\agent\\check_mk.user.yml')) }}"
|
||||
size_bytes: '{{ _checkmk_windows_user_config_stat.stat.size | default(0) | int }}'
|
||||
last_write_time_utc: >-
|
||||
{{ (_checkmk_windows_user_config_stat.stat.lastwritetime | aim_epoch_iso_utc)
|
||||
if (_checkmk_windows_user_config_stat.stat.exists | default(false)) else none }}
|
||||
content: >-
|
||||
{{ (_checkmk_windows_user_config_slurp.content | b64decode)
|
||||
if (_checkmk_windows_user_config_stat.stat.exists | default(false)) else '' }}
|
||||
changed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Windows | Report missing Checkmk user configuration
|
||||
ansible.builtin.debug:
|
||||
msg: |-
|
||||
{{ inventory_hostname }}
|
||||
Checkmk user configuration was not found.
|
||||
Path: {{ _checkmk_windows_user_config.path }}
|
||||
when: not (_checkmk_windows_user_config.exists | bool)
|
||||
|
||||
- name: Windows | Print current Checkmk user configuration
|
||||
ansible.builtin.debug:
|
||||
msg: |-
|
||||
{{ inventory_hostname }}
|
||||
Path: {{ _checkmk_windows_user_config.path }}
|
||||
Size: {{ _checkmk_windows_user_config.size_bytes }} bytes
|
||||
Last write (UTC): {{ _checkmk_windows_user_config.last_write_time_utc }}
|
||||
----- BEGIN check_mk.user.yml -----
|
||||
{{ _checkmk_windows_user_config.content }}
|
||||
----- END check_mk.user.yml -----
|
||||
when: _checkmk_windows_user_config.exists | bool
|
||||
- name: AIM | Publish operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: checkmk_user_config_v1
|
||||
data: '{{ _checkmk_windows_user_config | aim_report_checkmk_config }}'
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
- name: "Checkmk | Update agent configuration"
|
||||
hosts: all
|
||||
gather_facts: true
|
||||
|
||||
roles:
|
||||
- server_role_selection
|
||||
- checkmk_agent_config
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
- name: "Checkmk | Update monitoring scripts"
|
||||
hosts: all
|
||||
gather_facts: true
|
||||
|
||||
roles:
|
||||
- server_role_selection
|
||||
- checkmk_scripts
|
||||
@@ -0,0 +1,135 @@
|
||||
# PURPOSE: Update Checkmk scripts and configuration
|
||||
# DESCRIPTION: Deploy selected checks and Windows configuration without installing agent packages.
|
||||
# TARGETS: linux, windows
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# checkmk_unifi_mode [choice]: auto
|
||||
# checkmk_unifi_username [text]: bf-monitoring
|
||||
# checkmk_unifi_password [secret_ref]: vault_checkmk_unifi_password
|
||||
# checkmk_unifi_baseurl [url]: network: https://127.0.0.1:8443; os: https://127.0.0.1:11443
|
||||
# checkmk_unifi_curl_options [text]: --insecure --tlsv1.2
|
||||
# want_linux_check_certificate [bool]: false
|
||||
# want_windows_citrix [bool]: false
|
||||
# want_windows_surebackup [bool]: false
|
||||
# want_windows_backup [bool]: false
|
||||
# want_windows_nsp_mailqueue [bool]: false
|
||||
# want_windows_certificate [bool]: false
|
||||
# want_windows_veeam_cloud_connect [bool]: false
|
||||
# want_windows_veeam_backup [bool]: false
|
||||
# checkmk_unifi_status_provisioning [int]: 1
|
||||
# checkmk_unifi_status_upgrading [int]: 1
|
||||
# checkmk_unifi_status_upgradable [int]: 0
|
||||
# checkmk_unifi_status_heartbeat_missed [int]: 1
|
||||
# checkmk_unifi_status_noautobackup [int]: 0
|
||||
# checkmk_windows_updates_timeout [int]: 3600
|
||||
# checkmk_windows_updates_cache [int]: 43200
|
||||
# checkmk_mk_inventory_timeout [int]: 120
|
||||
# checkmk_plugins_default_timeout [int]: 120
|
||||
# checkmk_plugins_default_cache [int]: 600
|
||||
# checkmk_extra_plugin_patterns [sequence]: []
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: AIM-managed script files are updated. On Windows only the marked plugins section in check_mk.user.yml is replaced; all other user-config sections are preserved. Only the opposite UniFi check is removed during a UniFi mode transition.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/checkmk_update_scripts_config.yml --limit <host> --vault-id <customer>@prompt
|
||||
- name: Checkmk | Update linux
|
||||
hosts: linux
|
||||
gather_facts: true
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in
|
||||
['true', 'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
- name: Load system detect roles
|
||||
ansible.builtin.include_role:
|
||||
name: system_detect_roles
|
||||
- name: Load checkmk script plan
|
||||
ansible.builtin.include_role:
|
||||
name: checkmk_script_plan
|
||||
- name: Checkmk | Preflight scripts and credentials
|
||||
ansible.builtin.include_role:
|
||||
name: checkmk_deploy_scripts
|
||||
tasks_from: preflight
|
||||
become: true
|
||||
roles:
|
||||
- role: checkmk_deploy_scripts
|
||||
- role: checkmk_configure_agent
|
||||
- role: checkmk_manage_service
|
||||
post_tasks:
|
||||
- &id001
|
||||
name: Checkmk | Collect managed change summary
|
||||
ansible.builtin.set_fact:
|
||||
_aim_checkmk_changes: '{{ _aim_check_copies | default({}) | aim_report_checkmk_changes(_checkmk_selected_scripts,
|
||||
_checkmk_plugins_update | default({}), _aim_unifi_write | default({}), _aim_opposite_remove | default({}),
|
||||
_checkmk_unifi_effective, ansible_check_mode) }}'
|
||||
- name: AIM | Publish operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: checkmk_agent_config_v1
|
||||
data: '{{ _aim_checkmk_changes }}'
|
||||
- name: Checkmk | Update windows
|
||||
hosts: windows
|
||||
gather_facts: true
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in
|
||||
['true', 'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
- name: Load system detect roles
|
||||
ansible.builtin.include_role:
|
||||
name: system_detect_roles
|
||||
- name: Load checkmk script plan
|
||||
ansible.builtin.include_role:
|
||||
name: checkmk_script_plan
|
||||
- name: Checkmk | Preflight scripts and credentials
|
||||
ansible.builtin.include_role:
|
||||
name: checkmk_deploy_scripts
|
||||
tasks_from: preflight
|
||||
roles:
|
||||
- role: checkmk_deploy_scripts
|
||||
- role: checkmk_configure_agent
|
||||
- role: checkmk_manage_service
|
||||
post_tasks:
|
||||
- *id001
|
||||
- name: AIM | Publish operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: checkmk_agent_config_v1
|
||||
data: '{{ _aim_checkmk_changes }}'
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
# PURPOSE: Apply customer Sophos configuration
|
||||
# DESCRIPTION: Apply this customer profile using hostname, network_objects and vlan_interfaces from inventory.
|
||||
# TARGETS: sophosxgs
|
||||
# REQUIRED NETWORK KEYS: derz_lan, derz_sslvpn, facility, guest, lan_old, management, office, server, voip
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: Changes customer firewall configuration. VLAN parent remains Port1 as in the supplied playbooks. Only this customer profile is selected.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/customers/bluuunit/sophos_apply_customer.yml --limit <host> --vault-id <customer>@prompt --ask-pass
|
||||
- name: Sophos | Apply customer configuration | bluuunit
|
||||
hosts: sophosxgs
|
||||
gather_facts: false
|
||||
any_errors_fatal: false
|
||||
tasks:
|
||||
- name: Apply customer firewall policy
|
||||
ansible.builtin.import_role:
|
||||
name: sophos_customer_bluuunit
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
- name: Sophos | Require customer host configuration
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- hostname is defined
|
||||
- hostname is string
|
||||
- hostname | length > 0
|
||||
- network_objects is defined
|
||||
- network_objects is mapping
|
||||
- vlan_interfaces is defined
|
||||
- vlan_interfaces is mapping
|
||||
fail_msg: Set hostname, network_objects and vlan_interfaces in this host inventory. AIM can prepare
|
||||
these fields.
|
||||
quiet: true
|
||||
- name: Sophos | Require profile network keys
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item in network_objects
|
||||
fail_msg: A customer-required network_objects key is missing. See the loop item.
|
||||
quiet: true
|
||||
loop:
|
||||
- derz_lan
|
||||
- derz_sslvpn
|
||||
- facility
|
||||
- guest
|
||||
- lan_old
|
||||
- management
|
||||
- office
|
||||
- server
|
||||
- voip
|
||||
- name: Sophos | Validate network object shape
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value is mapping
|
||||
- item.value.name is defined
|
||||
- item.value.network is defined
|
||||
- item.value.subnetmask is defined
|
||||
fail_msg: Every network object requires name, network and subnetmask.
|
||||
quiet: true
|
||||
loop: '{{ network_objects | dict2items }}'
|
||||
loop_control:
|
||||
label: '{{ item.key }}'
|
||||
- name: Sophos | Validate VLAN shape
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value is mapping
|
||||
- item.value.name is defined
|
||||
- item.value.ip_address is defined
|
||||
- item.value.subnetmask is defined
|
||||
- item.value.vlan_id is defined
|
||||
- item.value.zone_name is defined
|
||||
- item.value.zone_type is defined
|
||||
- item.value.zone_description is defined
|
||||
fail_msg: Each VLAN requires its full interface and zone mapping.
|
||||
quiet: true
|
||||
loop: '{{ vlan_interfaces | dict2items }}'
|
||||
loop_control:
|
||||
label: '{{ item.key }}'
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
# PURPOSE: Apply customer Sophos configuration
|
||||
# DESCRIPTION: Apply this customer profile using hostname, network_objects and vlan_interfaces from inventory.
|
||||
# TARGETS: sophosxgs
|
||||
# REQUIRED NETWORK KEYS: azuregwc_lan, lan_old, management, office
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: Changes customer firewall configuration. VLAN parent remains Port1 as in the supplied playbooks. Only this customer profile is selected.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/customers/formicon/sophos_apply_customer.yml --limit <host> --vault-id <customer>@prompt --ask-pass
|
||||
- name: Sophos | Apply customer configuration | formicon
|
||||
hosts: sophosxgs
|
||||
gather_facts: false
|
||||
any_errors_fatal: false
|
||||
tasks:
|
||||
- name: Apply customer firewall policy
|
||||
ansible.builtin.import_role:
|
||||
name: sophos_customer_formicon
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
- name: Sophos | Require customer host configuration
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- hostname is defined
|
||||
- hostname is string
|
||||
- hostname | length > 0
|
||||
- network_objects is defined
|
||||
- network_objects is mapping
|
||||
- vlan_interfaces is defined
|
||||
- vlan_interfaces is mapping
|
||||
fail_msg: Set hostname, network_objects and vlan_interfaces in this host inventory. AIM can prepare
|
||||
these fields.
|
||||
quiet: true
|
||||
- name: Sophos | Require profile network keys
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item in network_objects
|
||||
fail_msg: A customer-required network_objects key is missing. See the loop item.
|
||||
quiet: true
|
||||
loop:
|
||||
- azuregwc_lan
|
||||
- lan_old
|
||||
- management
|
||||
- office
|
||||
- name: Sophos | Validate network object shape
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value is mapping
|
||||
- item.value.name is defined
|
||||
- item.value.network is defined
|
||||
- item.value.subnetmask is defined
|
||||
fail_msg: Every network object requires name, network and subnetmask.
|
||||
quiet: true
|
||||
loop: '{{ network_objects | dict2items }}'
|
||||
loop_control:
|
||||
label: '{{ item.key }}'
|
||||
- name: Sophos | Validate VLAN shape
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value is mapping
|
||||
- item.value.name is defined
|
||||
- item.value.ip_address is defined
|
||||
- item.value.subnetmask is defined
|
||||
- item.value.vlan_id is defined
|
||||
- item.value.zone_name is defined
|
||||
- item.value.zone_type is defined
|
||||
- item.value.zone_description is defined
|
||||
fail_msg: Each VLAN requires its full interface and zone mapping.
|
||||
quiet: true
|
||||
loop: '{{ vlan_interfaces | dict2items }}'
|
||||
loop_control:
|
||||
label: '{{ item.key }}'
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
# PURPOSE: Apply customer Sophos configuration
|
||||
# DESCRIPTION: Apply this customer profile using hostname, network_objects and vlan_interfaces from inventory.
|
||||
# TARGETS: sophosxgs
|
||||
# REQUIRED NETWORK KEYS: drucker, guest, office, wlan
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: Changes customer firewall configuration. VLAN parent remains Port1 as in the supplied playbooks. Only this customer profile is selected.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/customers/gebhardt_stahl/sophos_apply_customer.yml --limit <host> --vault-id <customer>@prompt --ask-pass
|
||||
- name: Sophos | Apply customer configuration | gebhardt_stahl
|
||||
hosts: sophosxgs
|
||||
gather_facts: false
|
||||
any_errors_fatal: false
|
||||
tasks:
|
||||
- name: Apply customer firewall policy
|
||||
ansible.builtin.import_role:
|
||||
name: sophos_customer_gebhardt_stahl
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
- name: Sophos | Require customer host configuration
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- hostname is defined
|
||||
- hostname is string
|
||||
- hostname | length > 0
|
||||
- network_objects is defined
|
||||
- network_objects is mapping
|
||||
- vlan_interfaces is defined
|
||||
- vlan_interfaces is mapping
|
||||
fail_msg: Set hostname, network_objects and vlan_interfaces in this host inventory. AIM can prepare
|
||||
these fields.
|
||||
quiet: true
|
||||
- name: Sophos | Require profile network keys
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item in network_objects
|
||||
fail_msg: A customer-required network_objects key is missing. See the loop item.
|
||||
quiet: true
|
||||
loop:
|
||||
- drucker
|
||||
- guest
|
||||
- office
|
||||
- wlan
|
||||
- name: Sophos | Validate network object shape
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value is mapping
|
||||
- item.value.name is defined
|
||||
- item.value.network is defined
|
||||
- item.value.subnetmask is defined
|
||||
fail_msg: Every network object requires name, network and subnetmask.
|
||||
quiet: true
|
||||
loop: '{{ network_objects | dict2items }}'
|
||||
loop_control:
|
||||
label: '{{ item.key }}'
|
||||
- name: Sophos | Validate VLAN shape
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value is mapping
|
||||
- item.value.name is defined
|
||||
- item.value.ip_address is defined
|
||||
- item.value.subnetmask is defined
|
||||
- item.value.vlan_id is defined
|
||||
- item.value.zone_name is defined
|
||||
- item.value.zone_type is defined
|
||||
- item.value.zone_description is defined
|
||||
fail_msg: Each VLAN requires its full interface and zone mapping.
|
||||
quiet: true
|
||||
loop: '{{ vlan_interfaces | dict2items }}'
|
||||
loop_control:
|
||||
label: '{{ item.key }}'
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
# PURPOSE: Apply customer Sophos configuration
|
||||
# DESCRIPTION: Apply this customer profile using hostname, network_objects and vlan_interfaces from inventory.
|
||||
# TARGETS: sophosxgs
|
||||
# REQUIRED NETWORK KEYS: facility, guest, management, office, voip
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: Changes customer firewall configuration. VLAN parent remains Port1 as in the supplied playbooks. Only this customer profile is selected.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/customers/hungeling_und_toechter/sophos_apply_customer.yml --limit <host> --vault-id <customer>@prompt --ask-pass
|
||||
- name: Sophos | Apply customer configuration | hungeling_und_toechter
|
||||
hosts: sophosxgs
|
||||
gather_facts: false
|
||||
any_errors_fatal: false
|
||||
tasks:
|
||||
- name: Apply customer firewall policy
|
||||
ansible.builtin.import_role:
|
||||
name: sophos_customer_hungeling_und_toechter
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
- name: Sophos | Require customer host configuration
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- hostname is defined
|
||||
- hostname is string
|
||||
- hostname | length > 0
|
||||
- network_objects is defined
|
||||
- network_objects is mapping
|
||||
- vlan_interfaces is defined
|
||||
- vlan_interfaces is mapping
|
||||
fail_msg: Set hostname, network_objects and vlan_interfaces in this host inventory. AIM can prepare
|
||||
these fields.
|
||||
quiet: true
|
||||
- name: Sophos | Require profile network keys
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item in network_objects
|
||||
fail_msg: A customer-required network_objects key is missing. See the loop item.
|
||||
quiet: true
|
||||
loop:
|
||||
- facility
|
||||
- guest
|
||||
- management
|
||||
- office
|
||||
- voip
|
||||
- name: Sophos | Validate network object shape
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value is mapping
|
||||
- item.value.name is defined
|
||||
- item.value.network is defined
|
||||
- item.value.subnetmask is defined
|
||||
fail_msg: Every network object requires name, network and subnetmask.
|
||||
quiet: true
|
||||
loop: '{{ network_objects | dict2items }}'
|
||||
loop_control:
|
||||
label: '{{ item.key }}'
|
||||
- name: Sophos | Validate VLAN shape
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value is mapping
|
||||
- item.value.name is defined
|
||||
- item.value.ip_address is defined
|
||||
- item.value.subnetmask is defined
|
||||
- item.value.vlan_id is defined
|
||||
- item.value.zone_name is defined
|
||||
- item.value.zone_type is defined
|
||||
- item.value.zone_description is defined
|
||||
fail_msg: Each VLAN requires its full interface and zone mapping.
|
||||
quiet: true
|
||||
loop: '{{ vlan_interfaces | dict2items }}'
|
||||
loop_control:
|
||||
label: '{{ item.key }}'
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
# PURPOSE: Apply customer Sophos configuration
|
||||
# DESCRIPTION: Apply this customer profile using hostname, network_objects and vlan_interfaces from inventory.
|
||||
# TARGETS: sophosxgs
|
||||
# REQUIRED NETWORK KEYS: facility, guest, management, office, server, voip
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: Changes customer firewall configuration. VLAN parent remains Port1 as in the supplied playbooks. Only this customer profile is selected.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/customers/koenig_holding_gmbh/sophos_apply_customer.yml --limit <host> --vault-id <customer>@prompt --ask-pass
|
||||
- name: Sophos | Apply customer configuration | koenig_holding_gmbh
|
||||
hosts: sophosxgs
|
||||
gather_facts: false
|
||||
any_errors_fatal: false
|
||||
tasks:
|
||||
- name: Apply customer firewall policy
|
||||
ansible.builtin.import_role:
|
||||
name: sophos_customer_koenig_holding_gmbh
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
- name: Sophos | Require customer host configuration
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- hostname is defined
|
||||
- hostname is string
|
||||
- hostname | length > 0
|
||||
- network_objects is defined
|
||||
- network_objects is mapping
|
||||
- vlan_interfaces is defined
|
||||
- vlan_interfaces is mapping
|
||||
fail_msg: Set hostname, network_objects and vlan_interfaces in this host inventory. AIM can prepare
|
||||
these fields.
|
||||
quiet: true
|
||||
- name: Sophos | Require profile network keys
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item in network_objects
|
||||
fail_msg: A customer-required network_objects key is missing. See the loop item.
|
||||
quiet: true
|
||||
loop:
|
||||
- facility
|
||||
- guest
|
||||
- management
|
||||
- office
|
||||
- server
|
||||
- voip
|
||||
- name: Sophos | Validate network object shape
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value is mapping
|
||||
- item.value.name is defined
|
||||
- item.value.network is defined
|
||||
- item.value.subnetmask is defined
|
||||
fail_msg: Every network object requires name, network and subnetmask.
|
||||
quiet: true
|
||||
loop: '{{ network_objects | dict2items }}'
|
||||
loop_control:
|
||||
label: '{{ item.key }}'
|
||||
- name: Sophos | Validate VLAN shape
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.value is mapping
|
||||
- item.value.name is defined
|
||||
- item.value.ip_address is defined
|
||||
- item.value.subnetmask is defined
|
||||
- item.value.vlan_id is defined
|
||||
- item.value.zone_name is defined
|
||||
- item.value.zone_type is defined
|
||||
- item.value.zone_description is defined
|
||||
fail_msg: Each VLAN requires its full interface and zone mapping.
|
||||
quiet: true
|
||||
loop: '{{ vlan_interfaces | dict2items }}'
|
||||
loop_control:
|
||||
label: '{{ item.key }}'
|
||||
@@ -0,0 +1,62 @@
|
||||
# PURPOSE: Detect host roles
|
||||
# DESCRIPTION: Report detected AD, DHCP, Hyper-V, Veeam and UniFi capabilities without changing inventory memberships.
|
||||
# TARGETS: linux, windows
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/debug_detect_host_roles.yml --limit <host> --vault-id <customer>@prompt
|
||||
- name: Debug | Detected host roles
|
||||
hosts: linux:windows
|
||||
gather_facts: true
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in
|
||||
['true', 'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
roles:
|
||||
- role: system_detect_roles
|
||||
tasks:
|
||||
- name: Detection summary
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
is_dc: '{{ is_dc | default(false) }}'
|
||||
is_dhcp_server: '{{ is_dhcp_server | default(false) }}'
|
||||
is_hyperv_host: '{{ is_hyperv_host | default(false) }}'
|
||||
has_veeam_vbr: '{{ has_veeam_vbr | default(false) }}'
|
||||
has_veeam_vbo: '{{ has_veeam_vbo | default(false) }}'
|
||||
has_veeam_em: '{{ has_veeam_em | default(false) }}'
|
||||
is_unifi_controller: '{{ is_unifi_controller | default(false) }}'
|
||||
is_unifi_os_server: '{{ is_unifi_os_server | default(false) }}'
|
||||
- name: AIM | Publish operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: host_capabilities_v1
|
||||
data:
|
||||
is_dc: '{{ is_dc | default(false) | bool }}'
|
||||
is_dhcp_server: '{{ is_dhcp_server | default(false) | bool }}'
|
||||
is_hyperv_host: '{{ is_hyperv_host | default(false) | bool }}'
|
||||
has_veeam_vbr: '{{ has_veeam_vbr | default(false) | bool }}'
|
||||
has_veeam_vbo: '{{ has_veeam_vbo | default(false) | bool }}'
|
||||
has_veeam_em: '{{ has_veeam_em | default(false) | bool }}'
|
||||
is_unifi_controller: '{{ is_unifi_controller | default(false) | bool }}'
|
||||
is_unifi_os_server: '{{ is_unifi_os_server | default(false) | bool }}'
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
- name: "Debug | Disk usage"
|
||||
hosts: all
|
||||
gather_facts: true
|
||||
|
||||
tasks:
|
||||
- name: "Linux | Collect filesystem usage"
|
||||
when: ansible_facts['os_family'] != 'Windows'
|
||||
ansible.builtin.command:
|
||||
cmd: df -hP -x tmpfs -x devtmpfs
|
||||
register: disk_usage_linux
|
||||
changed_when: false
|
||||
|
||||
- name: "Linux | Show filesystem usage"
|
||||
when: ansible_facts['os_family'] != 'Windows'
|
||||
ansible.builtin.debug:
|
||||
var: disk_usage_linux.stdout_lines
|
||||
|
||||
- name: "Windows | Collect filesystem drive usage"
|
||||
when: ansible_facts['os_family'] == 'Windows'
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" |
|
||||
Select-Object DeviceID,
|
||||
@{Name='SizeGB';Expression={[math]::Round($_.Size / 1GB, 2)}},
|
||||
@{Name='FreeGB';Expression={[math]::Round($_.FreeSpace / 1GB, 2)}},
|
||||
@{Name='UsedPercent';Expression={
|
||||
if ($_.Size -gt 0) {
|
||||
[math]::Round((($_.Size - $_.FreeSpace) / $_.Size) * 100, 1)
|
||||
} else { 0 }
|
||||
}}
|
||||
register: disk_usage_windows
|
||||
changed_when: false
|
||||
|
||||
- name: "Windows | Show filesystem drive usage"
|
||||
when: ansible_facts['os_family'] == 'Windows'
|
||||
ansible.builtin.debug:
|
||||
var: disk_usage_windows.output
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
- name: "Debug | Ping hosts"
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: "Windows | WinRM ping"
|
||||
when: ansible_connection | default('') == 'winrm'
|
||||
ansible.windows.win_ping:
|
||||
|
||||
- name: "Linux | Ansible ping"
|
||||
when: ansible_connection | default('ssh') != 'winrm'
|
||||
ansible.builtin.ping:
|
||||
@@ -1,87 +0,0 @@
|
||||
---
|
||||
- name: "Debug | Server Role Selection"
|
||||
hosts: all
|
||||
gather_facts: true
|
||||
|
||||
roles:
|
||||
- server_role_selection
|
||||
|
||||
tasks:
|
||||
- name: "Debug | Display detected server roles"
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: "{{ inventory_hostname }}"
|
||||
os_family: "{{ ansible_facts['os_family'] | default('unknown') }}"
|
||||
|
||||
windows_roles:
|
||||
domain_controller: "{{ is_dc | default(false) | bool }}"
|
||||
dhcp_server: "{{ is_dhcp_server | default(false) | bool }}"
|
||||
hyperv_host: "{{ is_hyperv_host | default(false) | bool }}"
|
||||
|
||||
veeam:
|
||||
vbr: "{{ has_veeam_vbr | default(false) | bool }}"
|
||||
vbo: "{{ has_veeam_vbo | default(false) | bool }}"
|
||||
enterprise_manager: "{{ has_veeam_em | default(false) | bool }}"
|
||||
|
||||
linux_roles:
|
||||
unifi_controller: "{{ is_unifi_controller | default(false) | bool }}"
|
||||
|
||||
optional_features:
|
||||
linux_certificate_check: "{{ want_linux_check_certificate | default(false) | bool }}"
|
||||
windows_citrix: "{{ want_windows_citrix | default(false) | bool }}"
|
||||
windows_surebackup: "{{ want_windows_surebackup | default(false) | bool }}"
|
||||
windows_backup: "{{ want_windows_backup | default(false) | bool }}"
|
||||
|
||||
- name: "Debug | Display Checkmk script deployment decisions"
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
linux:
|
||||
unifi_controller:
|
||||
deploy: "{{ is_unifi_controller | default(false) | bool }}"
|
||||
reason: "UniFi Controller detected"
|
||||
scripts:
|
||||
- "check_unifi-controller.sh"
|
||||
- "unifi.cfg"
|
||||
|
||||
certificate_directory:
|
||||
deploy: "{{ want_linux_check_certificate | default(false) | bool }}"
|
||||
reason: "Certificate directory monitoring explicitly enabled"
|
||||
scripts:
|
||||
- "check_certificate_directory.sh"
|
||||
|
||||
windows:
|
||||
check_ping:
|
||||
deploy: "{{ is_dc | default(false) | bool }}"
|
||||
reason: "Domain Controller"
|
||||
scripts:
|
||||
- "check-ping.ps1"
|
||||
|
||||
veeam_config_backup:
|
||||
deploy: "{{ has_veeam_vbr | default(false) | bool }}"
|
||||
reason: "Veeam Backup & Replication detected"
|
||||
scripts:
|
||||
- "veeam_config_backup_status.ps1"
|
||||
|
||||
veeam_o365:
|
||||
deploy: "{{ has_veeam_vbo | default(false) | bool }}"
|
||||
reason: "Veeam Backup for Microsoft 365 detected"
|
||||
scripts:
|
||||
- "veeam_o365_status.ps1"
|
||||
|
||||
citrix_sessions:
|
||||
deploy: "{{ want_windows_citrix | default(false) | bool }}"
|
||||
reason: "Citrix monitoring explicitly enabled"
|
||||
scripts:
|
||||
- "citrix_sessions_customized.ps1"
|
||||
|
||||
veeam_surebackup:
|
||||
deploy: "{{ want_windows_surebackup | default(false) | bool }}"
|
||||
reason: "Veeam SureBackup monitoring explicitly enabled"
|
||||
scripts:
|
||||
- "veeam_surebackup_status.ps1"
|
||||
|
||||
windows_backup:
|
||||
deploy: "{{ want_windows_backup | default(false) | bool }}"
|
||||
reason: "Windows Backup monitoring explicitly enabled"
|
||||
scripts:
|
||||
- "windows-backup.ps1"
|
||||
@@ -0,0 +1,185 @@
|
||||
# PURPOSE: Show disk usage
|
||||
# DESCRIPTION: Report attached Windows volumes and common operational Linux mounts.
|
||||
# TARGETS: windows, linux
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# NOTES:
|
||||
# Windows reports attached storage volumes from community.windows.win_disk_facts; mapped/network drives are excluded.
|
||||
# Linux excludes pseudo/system mounts and reports common operational paths plus network/storage filesystems.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/debug_show_disk_usage.yml --limit <host> --vault-id <customer>@prompt
|
||||
|
||||
- name: Debug | Windows disk usage
|
||||
hosts: windows
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in
|
||||
['true', 'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
|
||||
- name: Windows | Gather attached disk and volume facts
|
||||
community.windows.win_disk_facts:
|
||||
filter:
|
||||
- partitions
|
||||
- volumes
|
||||
changed_when: false
|
||||
|
||||
- name: Windows | Normalize attached volume usage
|
||||
ansible.builtin.set_fact:
|
||||
_windows_disk_report: "{{ ansible_facts.disks | default([]) | aim_report_disks('windows') }}"
|
||||
|
||||
- name: Windows | Print disk usage
|
||||
ansible.builtin.debug:
|
||||
msg: |-
|
||||
{{ inventory_hostname }}
|
||||
{% for d in _windows_disk_report.filesystems | default([]) %}
|
||||
{% if d.status == 'available' %}
|
||||
{{ '%-18s' | format(d.name) }} {{ (d.used_bytes / 1073741824) | round(2) }} GB / {{ (d.total_bytes / 1073741824) | round(2) }} GB | {{ d.used_percent | round(1) }}% used | {{ (d.available_bytes / 1073741824) | round(2) }} GB free | {{ d.filesystem_type | default('unknown', true) }} | {{ d.mount }}
|
||||
{% else %}
|
||||
{{ '%-18s' | format(d.name) }} unavailable | {{ d.mount }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
- name: AIM | Publish operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: filesystem_usage_v1
|
||||
data: "{{ _windows_disk_report }}"
|
||||
- name: Debug | Linux disk usage
|
||||
hosts: linux
|
||||
gather_facts: false
|
||||
become: false
|
||||
vars:
|
||||
_disk_common_mounts:
|
||||
- /
|
||||
- /boot
|
||||
- /boot/efi
|
||||
- /home
|
||||
- /var
|
||||
- /var/log
|
||||
- /tmp
|
||||
- /opt
|
||||
- /srv
|
||||
_disk_network_storage_fstypes:
|
||||
- nfs
|
||||
- nfs4
|
||||
- cifs
|
||||
- smb3
|
||||
- ceph
|
||||
- glusterfs
|
||||
- fuse.sshfs
|
||||
- fuse.glusterfs
|
||||
_disk_excluded_fstypes:
|
||||
- proc
|
||||
- sysfs
|
||||
- devtmpfs
|
||||
- tmpfs
|
||||
- cgroup
|
||||
- cgroup2
|
||||
- overlay
|
||||
- squashfs
|
||||
- nsfs
|
||||
- tracefs
|
||||
- debugfs
|
||||
- securityfs
|
||||
- pstore
|
||||
- configfs
|
||||
- hugetlbfs
|
||||
- mqueue
|
||||
- rpc_pipefs
|
||||
- autofs
|
||||
- fusectl
|
||||
- binfmt_misc
|
||||
tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in
|
||||
['true', 'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
|
||||
- name: Linux | Collect mount facts
|
||||
ansible.builtin.setup:
|
||||
filter:
|
||||
- ansible_mounts
|
||||
gather_subset:
|
||||
- '!all'
|
||||
- '!min'
|
||||
changed_when: false
|
||||
|
||||
- name: Linux | Reset selected mount report
|
||||
ansible.builtin.set_fact:
|
||||
_linux_disk_mounts: []
|
||||
- name: Linux | Select common operational mounts
|
||||
ansible.builtin.set_fact:
|
||||
_linux_disk_mounts: '{{ (_linux_disk_mounts | default([])) + [item] }}'
|
||||
loop: '{{ ansible_facts.mounts | default(ansible_mounts | default([])) }}'
|
||||
loop_control:
|
||||
label: "{{ item.mount | default('?') }}"
|
||||
when:
|
||||
- item.fstype | default('') not in _disk_excluded_fstypes
|
||||
- >-
|
||||
(item.mount | default('')) in _disk_common_mounts
|
||||
or (item.mount | default('')).startswith('/mnt/')
|
||||
or (item.mount | default('')).startswith('/media/')
|
||||
or (item.fstype | default('')) in _disk_network_storage_fstypes
|
||||
|
||||
- name: Linux | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
selected_mounts: "{{ (_linux_disk_mounts | default([])) | map(attribute='mount') | list }}"
|
||||
diagnostics: Enabled; pseudo/system mounts are excluded.
|
||||
when: aim_debug | default(false) | bool
|
||||
|
||||
- name: Linux | Print disk usage
|
||||
ansible.builtin.debug:
|
||||
msg: |-
|
||||
{{ inventory_hostname }}
|
||||
{% for m in (_linux_disk_mounts | default([]) | sort(attribute='mount')) %}
|
||||
{% set total = m.size_total | default(0) | float %}
|
||||
{% set free = m.size_available | default(0) | float %}
|
||||
{% set used = total - free %}
|
||||
{% set pct = ((used / total) * 100) if total > 0 else 0 %}
|
||||
{{ '%-18s' | format(m.mount) }} {{ (used / 1073741824) | round(2) }} GB / {{ (total / 1073741824) | round(2) }} GB | {{ pct | round(1) }}% used | {{ (free / 1073741824) | round(2) }} GB free | {{ m.fstype }}
|
||||
{% endfor %}
|
||||
- name: AIM | Publish operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: filesystem_usage_v1
|
||||
data: "{{ _linux_disk_mounts | aim_report_disks('linux') }}"
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
# PURPOSE: Test Ansible connection
|
||||
# DESCRIPTION: Check Ansible manageability using ping or win_ping; this is not an ICMP ping.
|
||||
# TARGETS: linux, windows
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/debug_test_connection.yml --limit <host> --vault-id <customer>@prompt
|
||||
- name: Debug | Windows manageability
|
||||
hosts: windows
|
||||
gather_facts: false
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
tasks:
|
||||
- name: Windows | Test WinRM
|
||||
ansible.windows.win_ping: {}
|
||||
- name: Debug | Linux manageability
|
||||
hosts: linux
|
||||
gather_facts: false
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
tasks:
|
||||
- name: Linux | Test SSH and Python
|
||||
ansible.builtin.ping: {}
|
||||
Binary file not shown.
@@ -0,0 +1,398 @@
|
||||
"""Report normalization owned by the shipped runbooks, not by the Core API.
|
||||
|
||||
Only deliberately selected public fields leave these functions. Raw registered
|
||||
results, exception text, credentials and command lines are never returned.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from collections.abc import Mapping
|
||||
|
||||
|
||||
def _bool(value):
|
||||
if type(value) is bool: return value
|
||||
if str(value).lower() in ('true','yes','1'): return True
|
||||
if str(value).lower() in ('false','no','0','none',''): return False
|
||||
raise ValueError('Report boolean is not a supported literal')
|
||||
|
||||
|
||||
def epoch_iso_utc(value):
|
||||
return datetime.fromtimestamp(float(value), tz=timezone.utc).isoformat().replace('+00:00','Z')
|
||||
|
||||
def capabilities(value):
|
||||
names = ('is_dc','is_dhcp_server','is_hyperv_host','has_veeam_vbr','has_veeam_vbo',
|
||||
'has_veeam_em','is_unifi_controller','is_unifi_os_server')
|
||||
return {k:_bool(value.get(k, False)) for k in names}
|
||||
|
||||
|
||||
def disks(value, platform):
|
||||
result=[]
|
||||
if platform == 'windows':
|
||||
# community.windows.win_disk_facts returns disks -> partitions -> volumes.
|
||||
# Report attached volumes rather than PowerShell-session/mapped drives.
|
||||
for disk in value or []:
|
||||
for partition in disk.get('partitions', []) or []:
|
||||
drive_letter=partition.get('drive_letter')
|
||||
for volume in partition.get('volumes', []) or []:
|
||||
total=volume.get('size')
|
||||
free=volume.get('size_remaining')
|
||||
good=isinstance(total, (int, float)) and isinstance(free, (int, float)) and total >= 0 and free >= 0
|
||||
if good:
|
||||
total=int(total); free=int(free); used=max(0,total-free)
|
||||
pct=round(used/total*100,2) if total else 0.0
|
||||
else:
|
||||
used=total=free=pct=None
|
||||
native_path=volume.get('path') or volume.get('object_id') or ''
|
||||
if drive_letter:
|
||||
name=f'{drive_letter}:'
|
||||
mount=f'{drive_letter}:\\'
|
||||
else:
|
||||
name=volume.get('label') or native_path or f"volume-disk{disk.get('number','?')}-part{partition.get('number','?')}"
|
||||
mount=native_path or name
|
||||
result.append(dict(name=str(name),mount=str(mount),filesystem_type=volume.get('type'),
|
||||
used_bytes=used,total_bytes=total,available_bytes=free,used_percent=pct,
|
||||
status='available' if good else 'unavailable'))
|
||||
else:
|
||||
for row in value:
|
||||
total,free=int(row.get('size_total',0)),int(row.get('size_available',0))
|
||||
used=max(0,total-free); good=total>0
|
||||
name,mount,fs=row.get('device',row['mount']),row['mount'],row.get('fstype')
|
||||
if not good: used=total=free=pct=None
|
||||
else:
|
||||
used,total,free=(int(x) if x is not None else None for x in (used,total,free))
|
||||
pct=round(used/total*100,2) if total and used is not None else None
|
||||
result.append(dict(name=name,mount=mount,filesystem_type=fs,used_bytes=used,total_bytes=total,
|
||||
available_bytes=free,used_percent=pct,status='available' if good else 'unavailable'))
|
||||
return {'platform':platform,'filesystems':result}
|
||||
|
||||
|
||||
SERVICE_ERRORS={
|
||||
5:('permission_denied','Access was denied when starting the service.'),
|
||||
1053:('start_timeout','The service did not respond to the start request in time.'),
|
||||
1058:('service_disabled','The service is disabled.'),
|
||||
1060:('service_not_found','The service no longer exists.'),
|
||||
1068:('dependency_failed','A required dependency service could not be started.'),
|
||||
1069:('logon_failed','The service account could not log on.'),
|
||||
}
|
||||
|
||||
def service_error(raw):
|
||||
code=raw.get('native_code',raw.get('error_code'))
|
||||
if type(code) is not int: code=None
|
||||
reason,message=SERVICE_ERRORS.get(code,('start_failed','The service start request failed.'))
|
||||
# A bounded fallback for the existing win_service module; no raw text export.
|
||||
text=str(raw.get('msg',''))[:4096].lower()
|
||||
if code is None:
|
||||
signatures=[('access is denied',5),('access denied',5),('dependency',1068),
|
||||
('disabled',1058),('timed out',1053),('does not exist',1060),('logon failure',1069)]
|
||||
for term,num in signatures:
|
||||
if term in text:
|
||||
reason,message=SERVICE_ERRORS[num]; break
|
||||
return reason,message,code
|
||||
|
||||
|
||||
def services(before, attempts, after, include=(), exclude=(), check=False):
|
||||
stopped=sorted(s['name'] for s in before if s.get('state')=='stopped')
|
||||
eligible=sorted(s['name'] for s in before if s.get('state')=='stopped'
|
||||
and s.get('start_mode') in ('auto','delayed') and (not include or s['name'] in include)
|
||||
and s['name'] not in exclude)
|
||||
actual={}
|
||||
for row in attempts:
|
||||
if row.get('skipped'): continue
|
||||
name=row.get('item',{}).get('name')
|
||||
if name in eligible and not check: actual[name]=row
|
||||
states={s['name']:s.get('state','unknown') for s in after}
|
||||
newly=sorted(n for n in stopped if states.get(n)=='started')
|
||||
still=sorted(n for n in stopped if states.get(n)=='stopped')
|
||||
absent=sorted(n for n in stopped if n not in states)
|
||||
failures=[]
|
||||
for name, row in actual.items():
|
||||
if states.get(name)=='started': continue
|
||||
if row.get('failed'):
|
||||
reason,message,code=service_error(row)
|
||||
elif name not in states:
|
||||
reason,message,code='state_unavailable','Post-start service state could not be observed.',None
|
||||
else:
|
||||
reason,message,code='not_running_after_start','The service is not running at the post-start observation.',None
|
||||
failures.append({'name':name,'reason':reason,'message':message,'native_code':code})
|
||||
return dict(mode='check' if check else 'apply',initially_stopped=stopped,eligible=eligible,
|
||||
attempted=sorted(actual),excluded=sorted(set(stopped)-set(eligible)),newly_running=newly,
|
||||
still_stopped=still,unobserved=absent,failed_to_start=failures,
|
||||
started_count=len(set(newly)&set(actual)),failed_count=len(failures),before_count=len(stopped),
|
||||
after_observed=bool(after) or not before)
|
||||
|
||||
|
||||
def package_snapshot(raw, platform):
|
||||
"""Normalize package_facts (preferred) or legacy textual snapshots."""
|
||||
result={}
|
||||
if isinstance(raw, Mapping):
|
||||
for name, rows in raw.items():
|
||||
if not isinstance(rows, list): continue
|
||||
for row in rows:
|
||||
if not isinstance(row, Mapping): continue
|
||||
arch=str(row.get('arch') or 'unknown')
|
||||
version=str(row.get('version') or '')
|
||||
release=row.get('release')
|
||||
epoch=row.get('epoch')
|
||||
if release not in (None,''):
|
||||
version=f'{version}-{release}'
|
||||
if epoch not in (None,'','0',0):
|
||||
version=f'{epoch}:{version}'
|
||||
result.setdefault((str(name),arch),set()).add(version)
|
||||
return result
|
||||
for line in str(raw).splitlines():
|
||||
columns=line.split('\t')
|
||||
if len(columns)!=4: raise ValueError('Invalid package database record')
|
||||
name,arch,version,status=columns
|
||||
if platform=='debian' and status!='installed': continue
|
||||
result.setdefault((name,arch),set()).add(version)
|
||||
return result
|
||||
|
||||
|
||||
def _patch_reboot_fields(reboot_required, rebooted, preexisting, reboot_enabled, delay_minutes, blocked_reason=None):
|
||||
observed = None if reboot_required is None else bool(reboot_required)
|
||||
performed = bool(rebooted)
|
||||
final_required = False if performed else observed
|
||||
deferred = bool(final_required) and not bool(reboot_enabled)
|
||||
return dict(
|
||||
reboot_required=final_required,
|
||||
reboot_required_before=bool(preexisting),
|
||||
reboot_required_after=final_required,
|
||||
reboot_performed=performed,
|
||||
reboot_deferred=deferred,
|
||||
reboot_delay_minutes=int(delay_minutes),
|
||||
blocked_reason=blocked_reason,
|
||||
)
|
||||
|
||||
|
||||
def _reboot_reasons(value):
|
||||
rows=value if isinstance(value,list) else []
|
||||
out=[]
|
||||
for row in rows:
|
||||
if not isinstance(row,Mapping): continue
|
||||
source=str(row.get('source','unknown'))[:128]
|
||||
desc=str(row.get('description',''))[:512]
|
||||
out.append(dict(source=source,description=desc))
|
||||
return out[:32]
|
||||
|
||||
|
||||
def patch_blocked(platform, check=False, delay_minutes=0, rescan_after_reboot=False, reboot_reasons=()):
|
||||
result=dict(platform=str(platform), mode='check' if check else 'apply',
|
||||
evidence='preflight_reboot_state', complete=False, updates=[], updated_count=0,
|
||||
installed_count=0, removed_count=0, pending=[], failed_updates=[])
|
||||
result.update(_patch_reboot_fields(True, False, True, False, delay_minutes,
|
||||
'preexisting_reboot_required'))
|
||||
if str(platform) == 'windows':
|
||||
result.update(rescan_after_reboot=bool(rescan_after_reboot), patch_cycles=0,
|
||||
continuation_required=True, remaining_updates_known=False,
|
||||
reboot_reasons_before=_reboot_reasons(reboot_reasons))
|
||||
return result
|
||||
|
||||
|
||||
def patch_linux(before, after, platform, check=False, complete=True, reboot_required=None, rebooted=False,
|
||||
preexisting=False, reboot_enabled=True, delay_minutes=0):
|
||||
old,new=package_snapshot(before,platform),package_snapshot(after,platform)
|
||||
updates=[]
|
||||
if not check:
|
||||
for name,arch in sorted(set(old)|set(new)):
|
||||
a,b=old.get((name,arch),set()),new.get((name,arch),set())
|
||||
if a==b: continue
|
||||
updates.append(dict(name=name,identifier=None,architecture=arch,old_versions=sorted(a),new_versions=sorted(b),
|
||||
action='updated' if a and b else 'installed' if b else 'removed',kb=[]))
|
||||
result=dict(platform=platform,mode='check' if check else 'apply',evidence='package_snapshots',complete=bool(complete),
|
||||
updates=updates,updated_count=sum(x['action']=='updated' for x in updates),
|
||||
installed_count=sum(x['action']=='installed' for x in updates),removed_count=sum(x['action']=='removed' for x in updates),
|
||||
pending=[],failed_updates=[])
|
||||
result.update(_patch_reboot_fields(reboot_required, rebooted, preexisting, reboot_enabled, delay_minutes))
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def _hresult_u32(code):
|
||||
if type(code) is not int: return None
|
||||
return code & 0xffffffff
|
||||
|
||||
|
||||
WINDOWS_UPDATE_FAILURES={
|
||||
0x80240009:('operation_in_progress','Another conflicting Windows Update operation is in progress.'),
|
||||
0x80240016:('install_not_allowed','Windows Update could not install this update because another installation was active or a mandatory reboot was pending.'),
|
||||
0x80240017:('not_applicable','The update is no longer applicable to this host.'),
|
||||
0x80240019:('exclusive_install_conflict','Windows Update reported an exclusive-install conflict.'),
|
||||
0x8024001B:('self_update_in_progress','The Windows Update Agent is updating itself.'),
|
||||
0x8024001F:('no_connection','Windows Update could not complete because its network connection was unavailable.'),
|
||||
0x80240021:('timeout','Windows Update did not complete before its operation timeout.'),
|
||||
}
|
||||
|
||||
|
||||
def _windows_failure(code):
|
||||
normalized=_hresult_u32(code)
|
||||
reason,message=WINDOWS_UPDATE_FAILURES.get(normalized,('update_failed','Windows Update failed to install this update.'))
|
||||
return normalized,reason,message
|
||||
|
||||
|
||||
def windows_update_result_failed(value):
|
||||
value=value if isinstance(value,Mapping) else {}
|
||||
if value.get('failed') is True: return True
|
||||
if int(value.get('failed_update_count',0) or 0)>0: return True
|
||||
return any(isinstance(row,Mapping) and 'failure_hresult_code' in row for row in value.get('updates',{}).values())
|
||||
|
||||
|
||||
def windows_update_block_reason(value):
|
||||
value=value if isinstance(value,Mapping) else {}
|
||||
for row in value.get('updates',{}).values():
|
||||
if isinstance(row,Mapping) and 'failure_hresult_code' in row:
|
||||
return _windows_failure(row.get('failure_hresult_code'))[1]
|
||||
# ansible_failed_result can carry only a generic module failure. Keep this bounded.
|
||||
return 'update_failed'
|
||||
|
||||
|
||||
def _windows_pending_from_search(value):
|
||||
value=value if isinstance(value,Mapping) else {}
|
||||
pending=[]
|
||||
for ident,row in value.get('updates',{}).items():
|
||||
if not isinstance(row,Mapping): continue
|
||||
pending.append(dict(name=str(row.get('title',ident)),identifier=str(ident),
|
||||
kb=[str(x) for x in row.get('kb',[])]))
|
||||
return pending
|
||||
|
||||
|
||||
def patch_windows_runs(runs, searches=(), check=False, preexisting=False, rebooted=False,
|
||||
reboot_enabled=True, delay_minutes=0, rescan_after_reboot=False,
|
||||
patch_cycles=0, continuation_required=False, remaining_updates_known=False,
|
||||
reboot_deferred=False, reboot_required_after=False, blocked_reason=None,
|
||||
complete_override=True, reboot_reasons_before=()):
|
||||
runs=runs if isinstance(runs,list) else []
|
||||
searches=searches if isinstance(searches,list) else []
|
||||
installed={}; failed={}
|
||||
for entry in runs:
|
||||
if not isinstance(entry,Mapping): continue
|
||||
requested=entry.get('requested',{}) if isinstance(entry.get('requested'),Mapping) else {}
|
||||
value=entry.get('result',{}) if isinstance(entry.get('result'),Mapping) else {}
|
||||
rows=value.get('updates',{}) if isinstance(value.get('updates'),Mapping) else {}
|
||||
if not rows and entry.get('task_failed'):
|
||||
wave=int(entry.get('wave',0) or 0)
|
||||
ident=str(requested.get('identifier') or ('wave-%s' % wave if wave else 'windows-update-wave'))
|
||||
failed[ident]=dict(name=str(requested.get('title') or 'Windows Update patch wave'),identifier=ident,
|
||||
native_code=None,native_code_hex=None,reason='update_failed',
|
||||
message='Windows Update failed before per-update failure details were available.')
|
||||
for ident,row in rows.items():
|
||||
if not isinstance(row,Mapping): continue
|
||||
ident=str(ident); name=str(row.get('title',requested.get('title',ident)))
|
||||
kb=[str(x) for x in row.get('kb',requested.get('kb',[]))]
|
||||
if row.get('installed') is True and not check:
|
||||
installed[ident]=dict(name=name,identifier=ident,architecture=None,old_versions=[],new_versions=[],
|
||||
action='updated',kb=kb)
|
||||
failed.pop(ident,None)
|
||||
if 'failure_hresult_code' in row:
|
||||
code,reason,message=_windows_failure(row.get('failure_hresult_code'))
|
||||
failed[ident]=dict(name=name,identifier=ident,native_code=code,
|
||||
native_code_hex=(f'0x{code:08X}' if code is not None else None),
|
||||
reason=reason,message=message)
|
||||
pending=[]
|
||||
if remaining_updates_known and searches:
|
||||
pending=_windows_pending_from_search(searches[-1])
|
||||
# A final search is authoritative for remaining applicability; do not duplicate
|
||||
# updates that it says are no longer pending.
|
||||
final_required=bool(reboot_required_after)
|
||||
performed=bool(rebooted)
|
||||
deferred=bool(reboot_deferred) or (final_required and not bool(reboot_enabled))
|
||||
complete=bool(complete_override) and not bool(failed)
|
||||
result=dict(platform='windows',mode='check' if check else 'apply',evidence='windows_update_result',
|
||||
complete=complete,updates=list(installed.values()),updated_count=len(installed),installed_count=0,
|
||||
removed_count=0,pending=pending,failed_updates=list(failed.values()),
|
||||
reboot_required=final_required,reboot_required_before=bool(preexisting),
|
||||
reboot_required_after=final_required,reboot_performed=performed,reboot_deferred=deferred,
|
||||
reboot_delay_minutes=int(delay_minutes),blocked_reason=blocked_reason,
|
||||
rescan_after_reboot=bool(rescan_after_reboot),patch_cycles=int(patch_cycles),
|
||||
continuation_required=bool(continuation_required),remaining_updates_known=bool(remaining_updates_known),
|
||||
reboot_reasons_before=_reboot_reasons(reboot_reasons_before))
|
||||
return result
|
||||
|
||||
def cleanup(paths, directory, removed, unifi, unifi_result, enabled=False, check=False):
|
||||
deleted=[r.get('item') for r in removed.get('results',[]) if r.get('changed') and not check]
|
||||
mode='check' if check else 'apply' if enabled else 'preview'
|
||||
state='retained'
|
||||
if unifi=='disabled':
|
||||
state='candidate' if not enabled or check else 'removed' if unifi_result.get('changed') else 'unchanged'
|
||||
return dict(mode=mode,directory=directory,candidates=list(paths),removed=deleted,
|
||||
unifi_configuration=state,complete=True)
|
||||
|
||||
|
||||
_SECRET=re.compile(r'password|passwd|passphrase|secret|token|credential|private.?key|authorization|community',re.I)
|
||||
_COMMAND={'cmd_line','command','command_line','arguments','args','environment','env','passphrase'}
|
||||
|
||||
def checkmk_config(value):
|
||||
"""Read only the named user config; redact secret/command fields before publication."""
|
||||
import yaml
|
||||
path=value['path']
|
||||
if re.split(r'[\\/]',path)[-1].lower()!='check_mk.user.yml':
|
||||
raise ValueError('Only check_mk.user.yml can be published')
|
||||
content=value.get('content','')
|
||||
if len(content.encode('utf-8'))>512*1024:
|
||||
raise ValueError('Checkmk user configuration exceeds report limit')
|
||||
data=yaml.safe_load(content) if value.get('exists') else {}
|
||||
if data is None: data={}
|
||||
if not isinstance(data,dict): raise ValueError('Checkmk configuration must be a mapping')
|
||||
redactions=[]; seen=set(); budget=[0]
|
||||
def walk(item,path,depth=0):
|
||||
budget[0]+=1
|
||||
if depth>18 or budget[0]>50000: raise ValueError('Configuration structure exceeds limit')
|
||||
if isinstance(item,(dict,list)):
|
||||
if id(item) in seen: raise ValueError('Recursive configuration aliases are unsupported')
|
||||
seen.add(id(item))
|
||||
try:
|
||||
if isinstance(item,list): return [walk(x,path+[str(i)],depth+1) for i,x in enumerate(item)]
|
||||
out={}
|
||||
for key,val in item.items():
|
||||
if not isinstance(key,str): key=str(key)
|
||||
here=path+[key]
|
||||
if _SECRET.search(key) or key.lower() in _COMMAND or (path and path[0].lower()=='mrpe' and key.lower() in ('config','entries')):
|
||||
out[key]='[REDACTED]'; redactions.append('.'.join(here)); continue
|
||||
out[key]=walk(val,here,depth+1)
|
||||
return out
|
||||
finally:seen.remove(id(item))
|
||||
if isinstance(item,str):
|
||||
# Do not expose URL userinfo, inline password assignment, PEM key bodies.
|
||||
if re.search(r'://[^/\s]+:[^/\s]+@|(?:password|passwd|token|secret)\s*[=:]|-----BEGIN .*PRIVATE KEY',item,re.I):
|
||||
redactions.append('.'.join(path)); return '[REDACTED]'
|
||||
# Multiline operational strings are represented by spaces, not terminal controls.
|
||||
return ' '.join(item.splitlines())
|
||||
if item is None or type(item) in (bool,int,float): return item
|
||||
return str(item)
|
||||
return dict(path=path,exists=bool(value.get('exists')),size_bytes=int(value.get('size_bytes',0)),
|
||||
last_write_time_utc=value.get('last_write_time_utc'),sections=walk(data,[]),
|
||||
redacted_paths=redactions,comment_preservation='not_in_structured_output')
|
||||
|
||||
|
||||
def checkmk_changes(copies, selected, plugin_update, unifi_result, opposite, mode, check=False):
|
||||
changed_checks=[r['item']['filename'] for r in copies.get('results',[]) if r.get('changed') and isinstance(r.get('item'),Mapping)]
|
||||
removed=[]
|
||||
if opposite.get('changed') and mode in ('os','network'):
|
||||
removed=['check_unifi-controller.sh' if mode=='os' else 'check_unifi-os.sh']
|
||||
changes=[dict(component='check',name=n,action='updated') for n in changed_checks]
|
||||
changes += [dict(component='check',name=n,action='removed') for n in removed]
|
||||
if plugin_update.get('changed'):changes.append(dict(component='section',name='plugins',action='updated'))
|
||||
if unifi_result.get('changed'):changes.append(dict(component='configuration',name='unifi.cfg',action='updated'))
|
||||
return dict(mode='check' if check else 'apply',changed=bool(changes),managed_sections=['plugins'] if plugin_update else [],
|
||||
changes=changes,deployed_checks=[s['filename'] for s in selected],changed_checks=changed_checks,
|
||||
removed_checks=removed if not check else [],unknown_files_policy='untouched_not_enumerated')
|
||||
|
||||
|
||||
def checkmk_state(observed, service_rows, change_report, package_changed=False, check=False):
|
||||
return dict(mode='check' if check else 'apply',installed=observed.get('installed'),version=observed.get('version'),
|
||||
version_source=observed.get('version_source','unavailable'),
|
||||
services=[dict(name=s['name'],state=s.get('state','unknown')) for s in service_rows],
|
||||
package_changed=bool(package_changed),configuration_updated=any(c['component'] in ('section','configuration') for c in change_report['changes']),
|
||||
checks_deployed=change_report['deployed_checks'],changed_checks=change_report['changed_checks'],removed_checks=change_report['removed_checks'])
|
||||
|
||||
|
||||
class FilterModule:
|
||||
def filters(self):
|
||||
return {'aim_epoch_iso_utc':epoch_iso_utc,'aim_report_capabilities':capabilities,'aim_report_disks':disks,'aim_report_services':services,
|
||||
'aim_report_patch_linux':patch_linux,'aim_report_patch_windows_runs':patch_windows_runs,
|
||||
'aim_report_patch_blocked':patch_blocked,
|
||||
'aim_windows_update_result_failed':windows_update_result_failed,
|
||||
'aim_windows_update_block_reason':windows_update_block_reason,
|
||||
'aim_report_cleanup':cleanup,'aim_report_checkmk_config':checkmk_config,
|
||||
'aim_report_checkmk_changes':checkmk_changes,'aim_report_checkmk_state':checkmk_state}
|
||||
@@ -1,74 +0,0 @@
|
||||
---
|
||||
- name: "Maintenance | Backup system logs"
|
||||
hosts: all
|
||||
gather_facts: true
|
||||
|
||||
vars:
|
||||
windows_event_log_backup_dir: 'C:\\Windows\\Temp\\EventLogBackup'
|
||||
windows_event_logs_to_backup:
|
||||
- Application
|
||||
- System
|
||||
- Security
|
||||
|
||||
linux_log_backup_dir: /var/backups/system-logs
|
||||
linux_journal_since: "-24h"
|
||||
|
||||
tasks:
|
||||
- name: "Windows | Ensure event log backup directory exists"
|
||||
when: ansible_facts['os_family'] == 'Windows'
|
||||
ansible.windows.win_file:
|
||||
path: "{{ windows_event_log_backup_dir }}"
|
||||
state: directory
|
||||
|
||||
- name: "Windows | Export event logs"
|
||||
when: ansible_facts['os_family'] == 'Windows'
|
||||
ansible.windows.win_command: >-
|
||||
wevtutil epl {{ item }} "{{ windows_event_log_backup_dir }}\\{{ item }}.evtx" /ow:true
|
||||
loop: "{{ windows_event_logs_to_backup }}"
|
||||
changed_when: true
|
||||
|
||||
- name: "Linux | Ensure system log backup directory exists"
|
||||
when: ansible_facts['os_family'] != 'Windows'
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ linux_log_backup_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
|
||||
- name: "Linux | Check whether systemd journal is available"
|
||||
when: ansible_facts['os_family'] != 'Windows'
|
||||
ansible.builtin.command:
|
||||
cmd: journalctl --version
|
||||
register: journalctl_available
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: "Linux | Export systemd journal"
|
||||
when:
|
||||
- ansible_facts['os_family'] != 'Windows'
|
||||
- journalctl_available.rc == 0
|
||||
become: true
|
||||
ansible.builtin.shell: >-
|
||||
journalctl --since {{ linux_journal_since | quote }} --no-pager
|
||||
> {{ (linux_log_backup_dir ~ '/journal.log') | quote }}
|
||||
args:
|
||||
executable: /bin/sh
|
||||
changed_when: true
|
||||
|
||||
- name: "Linux | Backup traditional system logs"
|
||||
when:
|
||||
- ansible_facts['os_family'] != 'Windows'
|
||||
- journalctl_available.rc != 0
|
||||
become: true
|
||||
ansible.builtin.shell: |
|
||||
set -e
|
||||
for file in /var/log/syslog /var/log/messages /var/log/auth.log /var/log/secure; do
|
||||
if [ -f "$file" ]; then
|
||||
cp -p "$file" "{{ linux_log_backup_dir }}/$(basename "$file")"
|
||||
fi
|
||||
done
|
||||
args:
|
||||
executable: /bin/sh
|
||||
changed_when: true
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
# PURPOSE: Export Windows event logs
|
||||
# DESCRIPTION: Export selected event channels to EVTX files on the target; existing event logs are not cleared.
|
||||
# TARGETS: windows
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# event_age_days [int]: 45
|
||||
# export_folder [text]: C:\Logs
|
||||
# event_log_channels [list]: Application, Security, System, Setup
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/maintenance_export_event_logs.yml --limit <host> --vault-id <customer>@prompt
|
||||
- name: Maintenance | Export Windows event logs
|
||||
hosts: windows
|
||||
gather_facts: false
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
roles:
|
||||
- role: maintenance_export_event_logs
|
||||
@@ -1,35 +1,70 @@
|
||||
---
|
||||
- name: "Maintenance | Patch operating system"
|
||||
hosts: all
|
||||
# PURPOSE: Patch operating systems
|
||||
# DESCRIPTION: Apply updates on Windows, Debian and RedHat-family systems; optionally reboot when required.
|
||||
# TARGETS: linux, windows
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# os_patching_reboot [bool]: true
|
||||
# os_patching_windows_categories [list]: SecurityUpdates, CriticalUpdates, UpdateRollups, DefinitionUpdates, Updates
|
||||
# os_patching_serial [serial]: 100%
|
||||
# os_patching_reboot_timeout [int]: 600
|
||||
# os_patching_reboot_delay_minutes [int]: 0
|
||||
# os_patching_reboot_message [text]: AIM maintenance: operating system patching requires a reboot.
|
||||
# os_patching_rescan_after_reboot [bool]: false (Windows only; continue with a newly discovered patch wave after reboot)
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: Updates production operating systems. Optional AIM-initiated reboots notify logged-in users and honor the configured delay.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/maintenance_patch_os.yml --limit <host> --vault-id <customer>@prompt
|
||||
- name: Maintenance | Patch Linux
|
||||
hosts: linux
|
||||
gather_facts: true
|
||||
|
||||
tasks:
|
||||
- name: "Debian | Update package cache and upgrade packages"
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
upgrade: dist
|
||||
|
||||
- name: "RedHat | Upgrade installed packages"
|
||||
when: ansible_facts['os_family'] == 'RedHat'
|
||||
ansible.builtin.dnf:
|
||||
name: '*'
|
||||
state: latest
|
||||
|
||||
- name: "Windows | Install available updates"
|
||||
when: ansible_facts['os_family'] == 'Windows'
|
||||
ansible.windows.win_updates:
|
||||
category_names:
|
||||
- CriticalUpdates
|
||||
- SecurityUpdates
|
||||
- UpdateRollups
|
||||
- Updates
|
||||
reboot: false
|
||||
register: windows_updates
|
||||
|
||||
- name: "Windows | Report reboot requirement"
|
||||
when:
|
||||
- ansible_facts['os_family'] == 'Windows'
|
||||
- windows_updates.reboot_required | default(false)
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg: "Windows updates were installed and a reboot is required. No reboot was performed."
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
become: true
|
||||
serial: '{{ os_patching_serial | default(''100%'') }}'
|
||||
roles:
|
||||
- role: maintenance_patch_os
|
||||
- name: Maintenance | Patch Windows
|
||||
hosts: windows
|
||||
gather_facts: true
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
serial: '{{ os_patching_serial | default(''100%'') }}'
|
||||
roles:
|
||||
- role: maintenance_patch_os
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
- name: "Maintenance | Reboot systems"
|
||||
hosts: all
|
||||
gather_facts: true
|
||||
|
||||
tasks:
|
||||
- name: "Windows | Reboot system"
|
||||
when: ansible_facts['os_family'] == 'Windows'
|
||||
ansible.windows.win_reboot:
|
||||
reboot_timeout: 1800
|
||||
|
||||
- name: "Linux | Reboot system"
|
||||
when: ansible_facts['os_family'] != 'Windows'
|
||||
ansible.builtin.reboot:
|
||||
reboot_timeout: 1800
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
# PURPOSE: Reboot hosts
|
||||
# DESCRIPTION: Reboot selected hosts in batches and wait for management connectivity.
|
||||
# TARGETS: linux, windows
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# maintenance_reboot_serial [serial]: 10
|
||||
# maintenance_reboot_timeout [int]: 1800
|
||||
# maintenance_reboot_message [text]: Reboot initiated by debsansible01.bfmiglabor.lan (Ansible)
|
||||
# maintenance_reboot_pre_delay [int]: 0
|
||||
# maintenance_reboot_post_delay [int]: 15
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: Every selected host will be rebooted. No reboot occurs before final confirmation.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/maintenance_reboot_hosts.yml --limit <host> --vault-id <customer>@prompt
|
||||
- name: Maintenance | Reboot Linux
|
||||
hosts: linux
|
||||
gather_facts: true
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
become: true
|
||||
serial: '{{ maintenance_reboot_serial | default(10) }}'
|
||||
roles:
|
||||
- role: maintenance_reboot_hosts
|
||||
- name: Maintenance | Reboot Windows
|
||||
hosts: windows
|
||||
gather_facts: false
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
serial: '{{ maintenance_reboot_serial | default(10) }}'
|
||||
roles:
|
||||
- role: maintenance_reboot_hosts
|
||||
@@ -1,82 +1,37 @@
|
||||
---
|
||||
- name: "Maintenance | Start stopped automatic services"
|
||||
hosts: all
|
||||
gather_facts: true
|
||||
|
||||
tasks:
|
||||
- name: "Windows | Start stopped automatic services"
|
||||
when: ansible_facts['os_family'] == 'Windows'
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$Ansible.Changed = $false
|
||||
$started = @()
|
||||
|
||||
Get-CimInstance Win32_Service |
|
||||
Where-Object {
|
||||
$_.StartMode -eq 'Auto' -and
|
||||
$_.State -ne 'Running'
|
||||
} |
|
||||
ForEach-Object {
|
||||
try {
|
||||
Start-Service -Name $_.Name -ErrorAction Stop
|
||||
$started += $_.Name
|
||||
$Ansible.Changed = $true
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not start service $($_.Name): $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
$Ansible.Result = @{
|
||||
started_services = $started
|
||||
}
|
||||
register: started_services_windows
|
||||
|
||||
- name: "Windows | Show started services"
|
||||
when: ansible_facts['os_family'] == 'Windows'
|
||||
# PURPOSE: Start stopped automatic services
|
||||
# DESCRIPTION: Start eligible stopped services, apply optional include/exclude lists and report partial failures.
|
||||
# TARGETS: windows
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# maintenance_service_include [list]: []
|
||||
# maintenance_service_exclude [list]: []
|
||||
# maintenance_service_fail_on_error [bool]: true
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/maintenance_start_stopped_services.yml --limit <host> --vault-id <customer>@prompt
|
||||
- name: Maintenance | Start automatic services
|
||||
hosts: windows
|
||||
gather_facts: false
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
var: started_services_windows.result.started_services
|
||||
|
||||
- name: "Linux | Collect service facts"
|
||||
when: ansible_facts['os_family'] != 'Windows'
|
||||
ansible.builtin.service_facts:
|
||||
|
||||
- name: "Linux | Start stopped enabled systemd services"
|
||||
when:
|
||||
- ansible_facts['os_family'] != 'Windows'
|
||||
- ansible_facts['service_mgr'] == 'systemd'
|
||||
- item.value.status | default('') == 'enabled'
|
||||
- item.value.state | default('') != 'running'
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item.key }}"
|
||||
state: started
|
||||
loop: "{{ ansible_facts.services | dict2items }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
register: started_services_linux
|
||||
failed_when: false
|
||||
|
||||
- name: "Linux | Show services that were started"
|
||||
when:
|
||||
- ansible_facts['os_family'] != 'Windows'
|
||||
- ansible_facts['service_mgr'] == 'systemd'
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{
|
||||
started_services_linux.results
|
||||
| default([])
|
||||
| selectattr('changed', 'defined')
|
||||
| selectattr('changed')
|
||||
| map(attribute='item.key')
|
||||
| list
|
||||
}}
|
||||
|
||||
- name: "Linux | Report unsupported service manager"
|
||||
when:
|
||||
- ansible_facts['os_family'] != 'Windows'
|
||||
- ansible_facts['service_mgr'] != 'systemd'
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
Automatic stopped-service recovery currently supports systemd hosts only.
|
||||
Detected service manager: {{ ansible_facts['service_mgr'] }}
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
roles:
|
||||
- role: maintenance_start_stopped_services
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
# PURPOSE: Apply bitformer pfSense baseline
|
||||
# DESCRIPTION: Apply the supplied pfSense baseline without changing its firewall/VPN policy.
|
||||
# TARGETS: pfsense
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: Contains the original any-source WAN management rule for ports 22/80/443. The original CA, VPN endpoint and client certificate reference are unchanged; verify them before execution. Requires separately approved pfsensible.core installation.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/pfsense_apply_baseline.yml --limit <host> --vault-id <customer>@prompt
|
||||
- name: Install pfSense sudo package
|
||||
hosts: pfsense
|
||||
tasks:
|
||||
- name: Apply supplied firewall policy
|
||||
ansible.builtin.import_role:
|
||||
name: pfsense_install_prerequisites
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
- name: Initial pfSense bitformer config
|
||||
hosts: pfsense
|
||||
become: true
|
||||
tasks:
|
||||
- name: Apply supplied firewall policy
|
||||
ansible.builtin.import_role:
|
||||
name: pfsense_apply_baseline
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
@@ -0,0 +1,77 @@
|
||||
# AIM public operation-data schema. Closed fields except explicitly redacted configuration sections.
|
||||
type: object
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- apply
|
||||
- check
|
||||
changed:
|
||||
type: boolean
|
||||
managed_sections:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
changes:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- section
|
||||
- check
|
||||
- configuration
|
||||
name:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
action:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- updated
|
||||
- removed
|
||||
required:
|
||||
- component
|
||||
- name
|
||||
- action
|
||||
additionalProperties: false
|
||||
maxItems: 20000
|
||||
deployed_checks:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
changed_checks:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
removed_checks:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
unknown_files_policy:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- untouched_not_enumerated
|
||||
required:
|
||||
- mode
|
||||
- changed
|
||||
- managed_sections
|
||||
- changes
|
||||
- deployed_checks
|
||||
- changed_checks
|
||||
- removed_checks
|
||||
- unknown_files_policy
|
||||
additionalProperties: false
|
||||
@@ -0,0 +1,74 @@
|
||||
# AIM public operation-data schema. Closed fields except explicitly redacted configuration sections.
|
||||
type: object
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- apply
|
||||
- check
|
||||
installed:
|
||||
type:
|
||||
- boolean
|
||||
- 'null'
|
||||
version:
|
||||
type:
|
||||
- string
|
||||
- 'null'
|
||||
maxLength: 1024
|
||||
version_source:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- registry
|
||||
- package_database
|
||||
- unavailable
|
||||
services:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
state:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
required:
|
||||
- name
|
||||
- state
|
||||
additionalProperties: false
|
||||
maxItems: 20000
|
||||
package_changed: &id001
|
||||
type: boolean
|
||||
configuration_updated: *id001
|
||||
checks_deployed:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
changed_checks:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
removed_checks:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
required:
|
||||
- mode
|
||||
- installed
|
||||
- version
|
||||
- version_source
|
||||
- services
|
||||
- package_changed
|
||||
- configuration_updated
|
||||
- checks_deployed
|
||||
- changed_checks
|
||||
- removed_checks
|
||||
additionalProperties: false
|
||||
@@ -0,0 +1,40 @@
|
||||
# AIM public operation-data schema. Closed fields except explicitly redacted configuration sections.
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
exists:
|
||||
type: boolean
|
||||
size_bytes:
|
||||
type: integer
|
||||
minimum: 0
|
||||
last_write_time_utc:
|
||||
type:
|
||||
- string
|
||||
- 'null'
|
||||
maxLength: 1024
|
||||
sections:
|
||||
type: object
|
||||
properties: {}
|
||||
additionalProperties: true
|
||||
redacted_paths:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
comment_preservation:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- not_in_structured_output
|
||||
required:
|
||||
- path
|
||||
- exists
|
||||
- size_bytes
|
||||
- last_write_time_utc
|
||||
- sections
|
||||
- redacted_paths
|
||||
- comment_preservation
|
||||
additionalProperties: false
|
||||
@@ -0,0 +1,30 @@
|
||||
# AIM public operation-data schema. Closed fields except explicitly redacted configuration sections.
|
||||
type: object
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- apply
|
||||
- check
|
||||
days:
|
||||
type: integer
|
||||
minimum: 0
|
||||
files:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
channels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
required:
|
||||
- mode
|
||||
- days
|
||||
- files
|
||||
- channels
|
||||
additionalProperties: false
|
||||
@@ -0,0 +1,66 @@
|
||||
# AIM public operation-data schema. Closed fields except explicitly redacted configuration sections.
|
||||
type: object
|
||||
properties:
|
||||
platform:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- windows
|
||||
- linux
|
||||
filesystems:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
mount:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
filesystem_type:
|
||||
type:
|
||||
- string
|
||||
- 'null'
|
||||
maxLength: 1024
|
||||
used_bytes:
|
||||
type:
|
||||
- integer
|
||||
- 'null'
|
||||
minimum: 0
|
||||
total_bytes:
|
||||
type:
|
||||
- integer
|
||||
- 'null'
|
||||
minimum: 0
|
||||
available_bytes:
|
||||
type:
|
||||
- integer
|
||||
- 'null'
|
||||
minimum: 0
|
||||
used_percent:
|
||||
type:
|
||||
- number
|
||||
- 'null'
|
||||
minimum: 0
|
||||
status:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- available
|
||||
- unavailable
|
||||
required:
|
||||
- name
|
||||
- mount
|
||||
- filesystem_type
|
||||
- used_bytes
|
||||
- total_bytes
|
||||
- available_bytes
|
||||
- used_percent
|
||||
- status
|
||||
additionalProperties: false
|
||||
maxItems: 20000
|
||||
required:
|
||||
- platform
|
||||
- filesystems
|
||||
additionalProperties: false
|
||||
@@ -0,0 +1,22 @@
|
||||
# AIM public operation-data schema. Closed fields except explicitly redacted configuration sections.
|
||||
type: object
|
||||
properties:
|
||||
is_dc: &id001
|
||||
type: boolean
|
||||
is_dhcp_server: *id001
|
||||
is_hyperv_host: *id001
|
||||
has_veeam_vbr: *id001
|
||||
has_veeam_vbo: *id001
|
||||
has_veeam_em: *id001
|
||||
is_unifi_controller: *id001
|
||||
is_unifi_os_server: *id001
|
||||
required:
|
||||
- is_dc
|
||||
- is_dhcp_server
|
||||
- is_hyperv_host
|
||||
- has_veeam_vbr
|
||||
- has_veeam_vbo
|
||||
- has_veeam_em
|
||||
- is_unifi_controller
|
||||
- is_unifi_os_server
|
||||
additionalProperties: false
|
||||
@@ -0,0 +1,43 @@
|
||||
# AIM public operation-data schema. Closed fields except explicitly redacted configuration sections.
|
||||
type: object
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- preview
|
||||
- apply
|
||||
- check
|
||||
directory:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
candidates:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
removed:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
unifi_configuration:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- retained
|
||||
- candidate
|
||||
- removed
|
||||
- unchanged
|
||||
complete:
|
||||
type: boolean
|
||||
required:
|
||||
- mode
|
||||
- directory
|
||||
- candidates
|
||||
- removed
|
||||
- unifi_configuration
|
||||
- complete
|
||||
additionalProperties: false
|
||||
@@ -0,0 +1,190 @@
|
||||
# AIM public operation-data schema. Closed fields; no raw package-manager/module output.
|
||||
type: object
|
||||
properties:
|
||||
platform:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- windows
|
||||
- debian
|
||||
- redhat
|
||||
mode:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- apply
|
||||
- check
|
||||
evidence:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- package_snapshots
|
||||
- windows_update_result
|
||||
- preflight_reboot_state
|
||||
complete:
|
||||
type: boolean
|
||||
updates:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
identifier:
|
||||
type: [string, 'null']
|
||||
maxLength: 1024
|
||||
architecture:
|
||||
type: [string, 'null']
|
||||
maxLength: 1024
|
||||
old_versions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 100
|
||||
new_versions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 100
|
||||
action:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum: [updated, installed, removed]
|
||||
kb:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 100
|
||||
required: [name, identifier, architecture, old_versions, new_versions, action, kb]
|
||||
additionalProperties: false
|
||||
maxItems: 20000
|
||||
updated_count:
|
||||
type: integer
|
||||
minimum: 0
|
||||
installed_count:
|
||||
type: integer
|
||||
minimum: 0
|
||||
removed_count:
|
||||
type: integer
|
||||
minimum: 0
|
||||
pending:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
identifier:
|
||||
type: [string, 'null']
|
||||
maxLength: 1024
|
||||
kb:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 100
|
||||
required: [name, identifier, kb]
|
||||
additionalProperties: false
|
||||
maxItems: 20000
|
||||
failed_updates:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
identifier:
|
||||
type: [string, 'null']
|
||||
maxLength: 1024
|
||||
native_code:
|
||||
type: [integer, 'null']
|
||||
minimum: 0
|
||||
native_code_hex:
|
||||
type: [string, 'null']
|
||||
maxLength: 32
|
||||
reason:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum: [update_failed, operation_in_progress, install_not_allowed, not_applicable, exclusive_install_conflict, self_update_in_progress, no_connection, timeout]
|
||||
message:
|
||||
type: string
|
||||
maxLength: 512
|
||||
required: [name, identifier, native_code, native_code_hex, reason, message]
|
||||
additionalProperties: false
|
||||
maxItems: 20000
|
||||
reboot_required:
|
||||
type: [boolean, 'null']
|
||||
description: Final observed/predicted pending reboot state after this run.
|
||||
reboot_required_before:
|
||||
type: boolean
|
||||
description: A pending reboot was detected before patching began.
|
||||
reboot_reasons_before:
|
||||
type: array
|
||||
description: Windows only. Native reboot sources reported by ansible.windows.win_reboot_info before patching.
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
source:
|
||||
type: string
|
||||
maxLength: 128
|
||||
description:
|
||||
type: string
|
||||
maxLength: 512
|
||||
required: [source, description]
|
||||
additionalProperties: false
|
||||
maxItems: 32
|
||||
reboot_required_after:
|
||||
type: [boolean, 'null']
|
||||
description: Final pending reboot state; false after an AIM-performed successful reboot.
|
||||
reboot_performed:
|
||||
type: boolean
|
||||
reboot_deferred:
|
||||
type: boolean
|
||||
description: A reboot remains required because automatic reboot was disabled.
|
||||
reboot_delay_minutes:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 1440
|
||||
blocked_reason:
|
||||
type: [string, 'null']
|
||||
maxLength: 128
|
||||
enum: [preexisting_reboot_required, update_failed, operation_in_progress, install_not_allowed, not_applicable, exclusive_install_conflict, self_update_in_progress, no_connection, timeout, cycle_limit_reached, null]
|
||||
rescan_after_reboot:
|
||||
type: boolean
|
||||
description: Windows only. True permits a newly discovered patch wave after an AIM-performed reboot.
|
||||
patch_cycles:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 12
|
||||
description: Windows discovery/install cycles entered by this run.
|
||||
continuation_required:
|
||||
type: boolean
|
||||
description: Another operator-approved patch run is recommended or required to continue patching.
|
||||
remaining_updates_known:
|
||||
type: boolean
|
||||
description: True only when the report contains an authoritative post-wave discovery in pending.
|
||||
required:
|
||||
- platform
|
||||
- mode
|
||||
- evidence
|
||||
- complete
|
||||
- updates
|
||||
- updated_count
|
||||
- installed_count
|
||||
- removed_count
|
||||
- pending
|
||||
- failed_updates
|
||||
- reboot_required
|
||||
- reboot_required_before
|
||||
- reboot_required_after
|
||||
- reboot_performed
|
||||
- reboot_deferred
|
||||
- reboot_delay_minutes
|
||||
- blocked_reason
|
||||
additionalProperties: false
|
||||
@@ -0,0 +1,113 @@
|
||||
# AIM public operation-data schema. Closed fields except explicitly redacted configuration sections.
|
||||
type: object
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- apply
|
||||
- check
|
||||
initially_stopped:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
eligible:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
attempted:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
excluded:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
newly_running:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
still_stopped:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
unobserved:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
maxItems: 20000
|
||||
failed_to_start:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
reason:
|
||||
type: string
|
||||
maxLength: 128
|
||||
enum:
|
||||
- dependency_failed
|
||||
- permission_denied
|
||||
- service_disabled
|
||||
- start_timeout
|
||||
- service_not_found
|
||||
- logon_failed
|
||||
- start_failed
|
||||
- not_running_after_start
|
||||
- state_unavailable
|
||||
message:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
native_code:
|
||||
type:
|
||||
- integer
|
||||
- 'null'
|
||||
minimum: 0
|
||||
required:
|
||||
- name
|
||||
- reason
|
||||
- message
|
||||
- native_code
|
||||
additionalProperties: false
|
||||
maxItems: 20000
|
||||
started_count:
|
||||
type: integer
|
||||
minimum: 0
|
||||
failed_count:
|
||||
type: integer
|
||||
minimum: 0
|
||||
before_count:
|
||||
type: integer
|
||||
minimum: 0
|
||||
after_observed:
|
||||
type: boolean
|
||||
required:
|
||||
- mode
|
||||
- initially_stopped
|
||||
- eligible
|
||||
- attempted
|
||||
- excluded
|
||||
- newly_running
|
||||
- still_stopped
|
||||
- unobserved
|
||||
- failed_to_start
|
||||
- started_count
|
||||
- failed_count
|
||||
- before_count
|
||||
- after_observed
|
||||
additionalProperties: false
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
# PURPOSE: Apply bitformer Sophos baseline
|
||||
# DESCRIPTION: Apply the supplied bitformer firewall baseline. Existing policy values and action order are preserved.
|
||||
# TARGETS: sophosxgs
|
||||
# INPUTS (omitted values inherit inventory / role defaults):
|
||||
# aim_debug [bool]: false
|
||||
# AUTH: existing inventory / Vault credentials; no embedded passwords.
|
||||
# CHANGES: Changes firewall management access, objects and rules, including rule removal and a final drop rule. Policy values have NOT been redesigned.
|
||||
# EXAMPLE: ansible-playbook -i inventories/<customer>/hosts.yml
|
||||
# playbooks/sophos_apply_baseline.yml --limit <host> --vault-id <customer>@prompt --ask-pass
|
||||
- name: Grundkonfiguration der Sophos-Firewall nach bitformer Standard
|
||||
hosts: sophosxgs
|
||||
gather_facts: false
|
||||
any_errors_fatal: false
|
||||
vars:
|
||||
network_hosts:
|
||||
- name: bf_spn_network
|
||||
network: 10.242.176.0
|
||||
subnetmask: 255.255.255.0
|
||||
- name: rfc_1918_a
|
||||
network: 10.0.0.0
|
||||
subnetmask: 255.0.0.0
|
||||
- name: rfc_1918_b
|
||||
network: 172.16.0.0
|
||||
subnetmask: 255.240.0.0
|
||||
- name: rfc_1918_c
|
||||
network: 192.168.0.0
|
||||
subnetmask: 255.255.0.0
|
||||
- name: rfc_5735
|
||||
network: 169.254.0.0
|
||||
subnetmask: 255.255.0.0
|
||||
firewall_rules_to_remove:
|
||||
- '[example] Traffic to Internal Zones'
|
||||
- '[example] Traffic to WAN'
|
||||
- '[example] Traffic to DMZ'
|
||||
wireless_networks_to_remove:
|
||||
- GuestAP
|
||||
- Sophos
|
||||
tasks:
|
||||
- name: Apply supplied firewall policy
|
||||
ansible.builtin.import_role:
|
||||
name: sophos_apply_baseline
|
||||
tasks_from: main
|
||||
pre_tasks:
|
||||
- name: AIM | Validate diagnostics option
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (aim_debug | default(false)) is boolean or (aim_debug | default(false) | string | lower) in ['true',
|
||||
'false']
|
||||
fail_msg: aim_debug must be a YAML/JSON boolean.
|
||||
quiet: true
|
||||
- name: AIM | Reject mixed platform membership
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (group_names | intersect(['linux', 'windows', 'sophosxgs', 'pfsense']) | length) <= 1
|
||||
fail_msg: This host belongs to incompatible platform groups. Use globally unique subgroups.
|
||||
quiet: true
|
||||
- name: AIM | Execution context
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
host: '{{ inventory_hostname }}'
|
||||
diagnostics: Enabled; secret values are never included by this task.
|
||||
when: aim_debug | default(false) | bool
|
||||
Reference in New Issue
Block a user