mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-27 00:01:58 -06:00
Removed Ansible Auditing Logic
This commit is contained in:
@@ -1,204 +0,0 @@
|
||||
- hosts: all
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Collect summary via PowerShell (hostname, OS, uptime, last reboot)
|
||||
ansible.builtin.shell: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$hostname = $env:COMPUTERNAME
|
||||
$username = $env:USERNAME
|
||||
$domain = $env:USERDOMAIN
|
||||
$w = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
|
||||
$product = [string]$w.ProductName
|
||||
$display = [string]$w.DisplayVersion
|
||||
$build = [string]$w.CurrentBuildNumber
|
||||
$os = ($product + ' ' + $display).Trim()
|
||||
$boot = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
|
||||
$uptime = [int]((Get-Date) - $boot).TotalSeconds
|
||||
# Produce Last Reboot as UTC string to match UI expectations (YYYY-MM-DD HH:MM:SS)
|
||||
$bootUtc = (Get-Date $boot).ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss')
|
||||
$out = [pscustomobject]@{
|
||||
hostname=$hostname;
|
||||
operating_system=$os;
|
||||
username=$username;
|
||||
domain=$domain;
|
||||
uptime_sec=$uptime;
|
||||
last_reboot=$bootUtc
|
||||
}
|
||||
$out | ConvertTo-Json -Depth 4
|
||||
register: summary_raw
|
||||
changed_when: false
|
||||
|
||||
- name: Parse summary JSON
|
||||
ansible.builtin.set_fact:
|
||||
_summary: "{{ summary_raw.stdout | from_json | default({}, true) }}"
|
||||
|
||||
- name: Collect installed software
|
||||
ansible.builtin.shell: |
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
$paths = @('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
|
||||
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
|
||||
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*')
|
||||
$items = foreach ($p in $paths) {
|
||||
if (Test-Path $p) {
|
||||
Get-ItemProperty -Path $p | Where-Object { $_.DisplayName } | ForEach-Object {
|
||||
[pscustomobject]@{ name=[string]$_.DisplayName; version=[string]$_.DisplayVersion }
|
||||
}
|
||||
}
|
||||
}
|
||||
$items | Sort-Object name -Unique | ConvertTo-Json -Depth 4
|
||||
register: software_raw
|
||||
changed_when: false
|
||||
|
||||
- name: Parse software JSON
|
||||
ansible.builtin.set_fact:
|
||||
_software: "{{ software_raw.stdout | default('[]') | from_json | default([], true) }}"
|
||||
|
||||
- name: Collect memory modules
|
||||
ansible.builtin.shell: |
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
Get-CimInstance Win32_PhysicalMemory | ForEach-Object {
|
||||
[pscustomobject]@{ slot=$_.BankLabel; speed=[string]$_.Speed; serial=[string]$_.SerialNumber; capacity=[string]$_.Capacity }
|
||||
} | ConvertTo-Json -Depth 4
|
||||
register: memory_raw
|
||||
changed_when: false
|
||||
|
||||
- name: Parse memory JSON
|
||||
ansible.builtin.set_fact:
|
||||
_memory: "{{ memory_raw.stdout | default('[]') | from_json | default([], true) }}"
|
||||
|
||||
- name: Collect storage volumes
|
||||
ansible.builtin.shell: |
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
Get-CimInstance Win32_LogicalDisk | ForEach-Object {
|
||||
$total = [double]$_.Size; $free = [double]$_.FreeSpace; $used = $total - $free;
|
||||
$usage = if ($total -gt 0) { [math]::Round(($used / $total) * 100, 2) } else { 0 }
|
||||
$type = switch ($_.DriveType) { 2 {'Removable'} 3 {'Fixed Disk'} default {'Unknown'} }
|
||||
[pscustomobject]@{ drive=$_.DeviceID; disk_type=$type; usage=$usage; total=$total; free=$free; used=$used }
|
||||
} | ConvertTo-Json -Depth 4
|
||||
register: storage_raw
|
||||
changed_when: false
|
||||
|
||||
- name: Parse storage JSON
|
||||
ansible.builtin.set_fact:
|
||||
_storage: "{{ storage_raw.stdout | default('[]') | from_json | default([], true) }}"
|
||||
|
||||
- name: Collect network adapters
|
||||
ansible.builtin.shell: |
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
try {
|
||||
$ip = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction Stop | Where-Object { $_.IPAddress -and $_.IPAddress -notmatch '^169\.254\.' -and $_.IPAddress -ne '127.0.0.1' }
|
||||
$ad = Get-NetAdapter | ForEach-Object { $_ | Select-Object -Property Name, InterfaceAlias, MacAddress }
|
||||
$map = @{}; foreach($a in $ad){ $map[$a.InterfaceAlias] = $a.MacAddress }
|
||||
$tmp = @{}
|
||||
foreach($e in $ip){
|
||||
$alias = if ($e.InterfaceAlias) { $e.InterfaceAlias } else { 'unknown' }
|
||||
$item = $tmp[$alias]
|
||||
if (-not $item) { $item = [pscustomobject]@{ adapter=$alias; ips=@(); mac='' }; $tmp[$alias] = $item }
|
||||
$item.mac = $map[$alias]
|
||||
if ($e.IPAddress -and $item.ips -notcontains $e.IPAddress) { $item.ips += $e.IPAddress }
|
||||
}
|
||||
$out = $tmp.GetEnumerator() | ForEach-Object { $_.Value }
|
||||
} catch { $out = @() }
|
||||
$out | ConvertTo-Json -Depth 4
|
||||
register: network_raw
|
||||
changed_when: false
|
||||
|
||||
- name: Parse network JSON
|
||||
ansible.builtin.set_fact:
|
||||
_network: "{{ network_raw.stdout | default('[]') | from_json | default([], true) }}"
|
||||
|
||||
- name: Compose device details structure
|
||||
ansible.builtin.set_fact:
|
||||
device_details:
|
||||
summary: "{{ _summary }}"
|
||||
software: "{{ _software }}"
|
||||
memory: "{{ _memory }}"
|
||||
storage: "{{ _storage }}"
|
||||
network: "{{ _network }}"
|
||||
|
||||
- name: Derive internal IP from adapter list
|
||||
ansible.builtin.set_fact:
|
||||
device_details: "{{ device_details | combine({'summary': (device_details.summary | combine({'internal_ip': (_network | map(attribute='ips') | list | flatten | select('match','^(10\\.|172\\.(1[6-9]|2[0-9]|3[0-1])\\.|192\\.168\\.)') | list | first | default('')) })) }) }}"
|
||||
|
||||
- name: Detect device type (Server/Workstation/Virtual Machine)
|
||||
ansible.builtin.shell: |
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
function _getCim($cls){ try { return Get-CimInstance $cls -ErrorAction Stop } catch { try { return Get-WmiObject -Class $cls -ErrorAction Stop } catch { return $null } } }
|
||||
$os = _getCim 'Win32_OperatingSystem'
|
||||
$cs = _getCim 'Win32_ComputerSystem'
|
||||
$caption = ""; if ($os) { $caption = [string]$os.Caption }
|
||||
$model = ""; if ($cs) { $model = [string]$cs.Model }
|
||||
$manu = ""; if ($cs) { $manu = [string]$cs.Manufacturer }
|
||||
$virt = $false
|
||||
if ($model -match 'Virtual' -or $manu -match 'Microsoft Corporation' -and $model -match 'Virtual Machine' -or $manu -match 'VMware' -or $manu -match 'innotek' -or $manu -match 'VirtualBox' -or $manu -match 'QEMU' -or $manu -match 'Xen' -or $manu -match 'Parallels') { $virt = $true }
|
||||
if ($virt) { 'Virtual Machine' }
|
||||
elseif ($caption -match 'Server') { 'Server' }
|
||||
else { 'Workstation' }
|
||||
register: device_type_raw
|
||||
changed_when: false
|
||||
|
||||
- name: Attach device type to summary
|
||||
ansible.builtin.set_fact:
|
||||
device_details: "{{ device_details | combine({'summary': (device_details.summary | combine({'device_type': (device_type_raw.stdout | default('') | trim) })) }) }}"
|
||||
|
||||
- name: Collect external IP (best-effort)
|
||||
ansible.builtin.shell: |
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
$ip = ''
|
||||
try { $ip = (Invoke-RestMethod -Uri 'https://api.ipify.org?format=json' -TimeoutSec 3).ip } catch {}
|
||||
if (-not $ip) { try { $ip = (Invoke-WebRequest -Uri 'https://checkip.amazonaws.com' -TimeoutSec 3).Content.Trim() } catch {} }
|
||||
if (-not $ip) { try { $ip = (Invoke-WebRequest -Uri 'https://ifconfig.me/ip' -TimeoutSec 3).Content.Trim() } catch {} }
|
||||
$ip
|
||||
register: external_ip_raw
|
||||
changed_when: false
|
||||
|
||||
- name: Attach external IP to summary
|
||||
ansible.builtin.set_fact:
|
||||
device_details: "{{ device_details | combine({'summary': (device_details.summary | combine({'external_ip': (external_ip_raw.stdout | default('') | trim) })) }) }}"
|
||||
|
||||
- name: Collect last logged-on user from registry (SAM/UPN)
|
||||
ansible.builtin.shell: |
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
function Normalize-Sam([string]$s) {
|
||||
if ([string]::IsNullOrWhiteSpace($s)) { return '' }
|
||||
if ($s -match '\$$') { return '' } # exclude machine accounts
|
||||
if ($s -like 'DWM-*' -or $s -like 'UMFD-*') { return '' }
|
||||
if ($s -eq 'SYSTEM' -or $s -eq 'LOCAL SERVICE' -or $s -eq 'NETWORK SERVICE' -or $s -eq 'ANONYMOUS LOGON') { return '' }
|
||||
return $s
|
||||
}
|
||||
|
||||
$regPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI'
|
||||
$sam = ''
|
||||
$upn = ''
|
||||
try { $sam = (Get-ItemProperty -Path $regPath -Name 'LastLoggedOnSAMUser' -ErrorAction Stop).LastLoggedOnSAMUser } catch {}
|
||||
try { $upn = (Get-ItemProperty -Path $regPath -Name 'LastLoggedOnUser' -ErrorAction Stop).LastLoggedOnUser } catch {}
|
||||
|
||||
$user = Normalize-Sam $sam
|
||||
if (-not $user) {
|
||||
$user = Normalize-Sam $upn
|
||||
if ($user -and $user -like '*@*') {
|
||||
# Convert UPN to DOMAIN\user using machine domain (best effort)
|
||||
$domDns = (Get-WmiObject Win32_ComputerSystem).Domain
|
||||
$domShort = ''
|
||||
if ($domDns) { $domShort = ($domDns -split '\.')[0].ToUpper() }
|
||||
$parts = $user -split '@'
|
||||
if ($parts.Length -ge 1) {
|
||||
$u = $parts[0]
|
||||
if ($domShort) { $user = "$domShort\$u" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($user) { $user } else { 'No Users Logged In' }
|
||||
register: last_user_raw
|
||||
changed_when: false
|
||||
|
||||
- name: Attach last_user string to summary
|
||||
ansible.builtin.set_fact:
|
||||
device_details: "{{ device_details | combine({'summary': (device_details.summary | combine({'last_user': (last_user_raw.stdout | default('') | trim) })) }) }}"
|
||||
|
||||
- name: Write device details JSON
|
||||
ansible.builtin.copy:
|
||||
content: "{{ device_details | to_nice_json }}"
|
||||
dest: "{{ output_file }}"
|
||||
run_once: true
|
||||
@@ -134,69 +134,7 @@ def _project_root():
|
||||
return os.getcwd()
|
||||
|
||||
|
||||
def _run_ansible_audit(ctx) -> dict:
|
||||
try:
|
||||
exe_dir = os.path.dirname(sys.executable)
|
||||
candidate = os.path.join(exe_dir, 'ansible-playbook.exe' if IS_WINDOWS else 'ansible-playbook')
|
||||
ansible_playbook = candidate if os.path.isfile(candidate) else 'ansible-playbook'
|
||||
|
||||
base = os.path.join(_project_root(), 'Logs', 'Agent', 'ansible')
|
||||
os.makedirs(base, exist_ok=True)
|
||||
out_path = os.path.join(base, 'audit.json')
|
||||
|
||||
# Require an external playbook; look next to this role first, then source tree as fallback
|
||||
roles_dir = os.path.dirname(__file__)
|
||||
pb_candidates = [
|
||||
os.path.join(roles_dir, 'Device_Audit.yml'),
|
||||
os.path.join(_project_root(), 'Data', 'Agent', 'Roles', 'Device_Audit.yml'),
|
||||
]
|
||||
pb_path = next((p for p in pb_candidates if os.path.isfile(p)), None)
|
||||
if not pb_path:
|
||||
# Log helpful error and return empty
|
||||
try:
|
||||
with open(os.path.join(base, 'ansible.err.log'), 'w', encoding='utf-8', newline='\n') as ef:
|
||||
ef.write('Device_Audit.yml not found in roles directory.\n')
|
||||
ef.write('Searched:\n - ' + '\n - '.join(pb_candidates))
|
||||
except Exception:
|
||||
pass
|
||||
return {}
|
||||
|
||||
out_posix = Path(out_path).as_posix()
|
||||
py_interp = Path(sys.executable).as_posix()
|
||||
|
||||
env = os.environ.copy()
|
||||
env.setdefault('PYTHONIOENCODING', 'utf-8')
|
||||
env.setdefault('ANSIBLE_FORCE_COLOR', '0')
|
||||
|
||||
cmd = [
|
||||
ansible_playbook,
|
||||
'-i', 'localhost,',
|
||||
'-c', 'local',
|
||||
pb_path,
|
||||
'-e', f'ansible_python_interpreter={py_interp}',
|
||||
'-e', 'ansible_shell_type=powershell' if IS_WINDOWS else 'ansible_shell_type=sh',
|
||||
'-e', 'ansible_shell_executable=powershell.exe' if IS_WINDOWS else 'ansible_shell_executable=/bin/sh',
|
||||
'-e', f'output_file={out_posix}',
|
||||
]
|
||||
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=300, env=env)
|
||||
if proc.returncode != 0:
|
||||
try:
|
||||
with open(os.path.join(base, 'ansible.err.log'), 'w', encoding='utf-8', newline='\n') as ef:
|
||||
ef.write(proc.stdout or '')
|
||||
ef.write('\n--- STDERR ---\n')
|
||||
ef.write(proc.stderr or '')
|
||||
except Exception:
|
||||
pass
|
||||
return {}
|
||||
try:
|
||||
with open(out_path, 'r', encoding='utf-8') as jf:
|
||||
details = json.load(jf)
|
||||
return details if isinstance(details, dict) else {}
|
||||
except Exception:
|
||||
return {}
|
||||
except Exception:
|
||||
return {}
|
||||
# Removed Ansible-based audit path; Python collectors provide details directly.
|
||||
|
||||
|
||||
def _ps_json(cmd: str, timeout: int = 60):
|
||||
@@ -722,8 +660,7 @@ class Role:
|
||||
self.ctx = ctx
|
||||
self._ext_ip = None
|
||||
self._ext_ip_ts = 0
|
||||
self._ansible_cache = None
|
||||
self._ansible_ts = 0
|
||||
self._refresh_ts = 0
|
||||
self._last_details = None
|
||||
try:
|
||||
# Set OS string once
|
||||
@@ -756,12 +693,10 @@ class Role:
|
||||
refresh_sec = max(300, refresh_min * 60)
|
||||
|
||||
now = time.time()
|
||||
need_refresh = (not self._last_details) or ((now - self._ansible_ts) > refresh_sec)
|
||||
need_refresh = (not self._last_details) or ((now - self._refresh_ts) > refresh_sec)
|
||||
if need_refresh:
|
||||
details = _run_ansible_audit(self.ctx)
|
||||
if not details:
|
||||
# Fallback collector when Ansible is unavailable
|
||||
details = _build_details_fallback()
|
||||
# Always collect via built-in Python collectors
|
||||
details = _build_details_fallback()
|
||||
# Best-effort fill of missing/renamed fields so UI is happy
|
||||
try:
|
||||
details = self._normalize_details(details)
|
||||
@@ -769,7 +704,7 @@ class Role:
|
||||
pass
|
||||
if details:
|
||||
self._last_details = details
|
||||
self._ansible_ts = now
|
||||
self._refresh_ts = now
|
||||
|
||||
# Always post the latest available details (possibly cached)
|
||||
details_to_send = self._last_details or {'summary': collect_summary(self.ctx.config)}
|
||||
|
||||
Reference in New Issue
Block a user