diff --git a/changelogs/fragments/remove-host.yml b/changelogs/fragments/remove-host.yml new file mode 100644 index 00000000..836797b5 --- /dev/null +++ b/changelogs/fragments/remove-host.yml @@ -0,0 +1,2 @@ +minor_changes: + - falcon_uninstall - Adds hide/remove host functionality (https://github.com/CrowdStrike/ansible_collection_falcon/pull/393) diff --git a/molecule/falcon_uninstall/converge.yml b/molecule/falcon_uninstall/converge.yml index a28dff2f..b3e4c1fe 100644 --- a/molecule/falcon_uninstall/converge.yml +++ b/molecule/falcon_uninstall/converge.yml @@ -2,5 +2,10 @@ - name: Converge hosts: all become: yes + vars: + falcon_client_id: "{{ lookup('env', 'FALCON_CLIENT_ID') }}" + falcon_client_secret: "{{ lookup('env', 'FALCON_CLIENT_SECRET') }}" roles: - role: crowdstrike.falcon.falcon_uninstall + vars: + falcon_remove_host: yes diff --git a/molecule/falcon_uninstall/prepare.yml b/molecule/falcon_uninstall/prepare.yml index 6addff9c..3ac32e85 100644 --- a/molecule/falcon_uninstall/prepare.yml +++ b/molecule/falcon_uninstall/prepare.yml @@ -2,6 +2,9 @@ - name: Prepare hosts: all become: yes + vars: + falcon_client_id: "{{ lookup('env', 'FALCON_CLIENT_ID') }}" + falcon_client_secret: "{{ lookup('env', 'FALCON_CLIENT_SECRET') }}" tasks: # Ubuntu specific - name: Install apt dependencies @@ -21,6 +24,7 @@ - name: Run falcon_install role ansible.builtin.include_role: name: crowdstrike.falcon.falcon_install - vars: - falcon_client_id: "{{ lookup('env', 'FALCON_CLIENT_ID') }}" - falcon_client_secret: "{{ lookup('env', 'FALCON_CLIENT_SECRET') }}" + + - name: Configure Falcon Sensor + ansible.builtin.include_role: + name: crowdstrike.falcon.falcon_configure diff --git a/molecule/win_falcon_uninstall/converge.yml b/molecule/win_falcon_uninstall/converge.yml index 673ab33f..02d301ba 100644 --- a/molecule/win_falcon_uninstall/converge.yml +++ b/molecule/win_falcon_uninstall/converge.yml @@ -1,5 +1,10 @@ --- - name: Converge hosts: all + vars: + falcon_client_id: "{{ lookup('env', 'FALCON_CLIENT_ID') }}" + falcon_client_secret: "{{ lookup('env', 'FALCON_CLIENT_SECRET') }}" roles: - role: crowdstrike.falcon.falcon_uninstall + vars: + falcon_remove_host: yes diff --git a/molecule/win_falcon_uninstall/verify.yml b/molecule/win_falcon_uninstall/verify.yml index 7281efdc..f1e92bf8 100644 --- a/molecule/win_falcon_uninstall/verify.yml +++ b/molecule/win_falcon_uninstall/verify.yml @@ -13,3 +13,23 @@ - name: Verify Falcon Sensor is not installed ansible.builtin.assert: that: "'[SC] EnumQueryServicesStatus:OpenService FAILED' in win_status.stdout" + + - name: CrowdStrike Falcon | Check for Windows Sensor directory (Windows) + ansible.windows.win_stat: + path: C:\Windows\System32\drivers\CrowdStrike + register: falcon_win_sensor_dir + + - name: CrowdStrike Falcon | Assert Windows Sensor directory is absent (Windows) + ansible.builtin.assert: + that: + - not falcon_win_sensor_dir.stat.exists + + - name: CrowdStrike Falcon | Check for Windows Sensor registry key (Windows) + ansible.windows.win_reg_stat: + path: HKLM:\System\Crowdstrike + register: falcon_win_sensor_reg + + - name: CrowdStrike Falcon | Assert Windows Sensor registry key is absent (Windows) + ansible.builtin.assert: + that: + - not falcon_win_sensor_reg.exists diff --git a/roles/falcon_uninstall/README.md b/roles/falcon_uninstall/README.md index 623e9586..ef246e34 100644 --- a/roles/falcon_uninstall/README.md +++ b/roles/falcon_uninstall/README.md @@ -13,12 +13,29 @@ Role Variables The following variables are currently supported: + * `falcon_api_enable_no_log` - Whether to enable or disable the logging of sensitive data being exposed in API calls. (bool, default: true) + * `falcon_cloud` - CrowdStrike API URL for downloading the Falcon sensor (string, default: `api.crowdstrike.com`) + * `falcon_cloud_autodiscover` - Auto-discover CrowdStrike API Cloud region (bool, default: true) + * `falcon_client_id` - CrowdStrike API OAUTH Client ID (string, default: null) + * `falcon_client_secret` - CrowdStrike API OAUTH Client Secret (string, default: null) + * `falcon_remove_host` - Whether to hide/remove the host from the CrowdStrike console (bool, default: false) * `falcon_windows_uninstall_args` - Additional Windows uninstall arguments (string, default: `/norestart`) * `falcon_windows_become_method` - The way to become a privileged user on Windows (string, default: `runas`) * `falcon_windows_become_user` - The privileged user to uninstall the sensor on Windows (string, default: `SYSTEM`) See [defaults/main.yml](defaults/main.yml) for more details on these variables. +Falcon API Permissions +---------------------- + +API clients are granted one or more API scopes. Scopes allow access to specific CrowdStrike APIs and describe the actions that an API client can perform. + +Ensure the following API scopes are enabled (***if applicable***) for this role: + +* When using API credentials `falcon_client_id` and `falcon_client_secret` + * To hide/remove the host from the CrowdStrike console: + * **Host** [write]**** + Dependencies ------------ diff --git a/roles/falcon_uninstall/defaults/main.yml b/roles/falcon_uninstall/defaults/main.yml index da742711..0750cc51 100644 --- a/roles/falcon_uninstall/defaults/main.yml +++ b/roles/falcon_uninstall/defaults/main.yml @@ -1,4 +1,39 @@ --- +# defaults for falcon_uninstall + +# Whether to enable or disable the logging of sensitive data being exposed in API calls. +# By default, this is enabled. +# +# Disabling this can expose your API credentials and authorization token. +# +falcon_api_enable_no_log: yes + +# CrowdStrike API URL for downloading the Falcon sensor. Possible values: +# us-1: api.crowdstrike.com +# us-2: api.us-2.crowdstrike.com +# eu-1: api.eu-1.crowdstrike.com +# us-gov-1: api.laggar.gcw.crowdstrike.com +# +falcon_cloud: "api.crowdstrike.com" + +# Auto-discover the CrowdStrike Cloud API Region. When disabled, +# 'falcon_cloud' should be changed to the appropriate cloud region. +# +falcon_cloud_autodiscover: true + +# 'Client ID' and 'Client Secret' for authentication against the CrowdStrike +# API. If unknown, get it from the CrowdStrike support portal: +# +# https://falcon.crowdstrike.com/support/api-clients-and-keys +# +falcon_client_id: +falcon_client_secret: + +# Whether to hide/remove the host from the CrowdStrike console. +# The default is false +# +falcon_remove_host: false + # Additional arugments to uninstall the sensor. # You can add your maintenance token here by adding MAINTENANCE_TOKEN= # diff --git a/roles/falcon_uninstall/tasks/hide_host.yml b/roles/falcon_uninstall/tasks/hide_host.yml new file mode 100644 index 00000000..c57ed7f2 --- /dev/null +++ b/roles/falcon_uninstall/tasks/hide_host.yml @@ -0,0 +1,32 @@ +--- +# This playbook will hide the host from the falcon console +- name: "CrowdStrike Falcon | Remove/Hide Host from Console (Linux)" + ansible.builtin.uri: + url: "https://{{ falcon_cloud }}/devices/entities/devices-actions/v2?action_name=hide_host" + method: POST + body_format: json + body: + ids: + - "{{ falcon_uninstall_remove_aid }}" + return_content: true + headers: + authorization: "Bearer {{ falcon_api_oauth2_token.json.access_token }}" + Content-Type: application/json + status_code: 202 + no_log: "{{ falcon_api_enable_no_log }}" + when: ansible_facts['os_family'] != "Windows" + +- name: "CrowdStrike Falcon | Remove/Hide Host from Console (Windows)" + ansible.windows.win_uri: + url: "https://{{ falcon_cloud }}/devices/entities/devices-actions/v2?action_name=hide_host" + method: POST + body: + ids: + - "{{ falcon_uninstall_remove_aid }}" + return_content: true + headers: + authorization: "Bearer {{ falcon_api_oauth2_token.json.access_token }}" + Content-Type: application/json + status_code: 202 + no_log: "{{ falcon_api_enable_no_log }}" + when: ansible_facts['os_family'] == "Windows" diff --git a/roles/falcon_uninstall/tasks/main.yml b/roles/falcon_uninstall/tasks/main.yml index 6e3b7a8b..a3e99530 100644 --- a/roles/falcon_uninstall/tasks/main.yml +++ b/roles/falcon_uninstall/tasks/main.yml @@ -1,5 +1,28 @@ --- # tasks file for falcon_uninstall +- name: Uninstall pretasks + ansible.builtin.include_tasks: preuninstall.yml + # noqa name[missing] + +- name: API block + when: + - falcon_client_id and falcon_client_secret + - falcon_sensor_installed + block: + - ansible.builtin.include_role: + name: falcon_install + tasks_from: "{{ 'win_auth.yml' if ansible_facts['os_family'] == 'Windows' else 'auth.yml' }}" + # noqa name[missing] + +- name: Remove/Hide Host pretasks + when: + - falcon_api_oauth2_token is defined + - falcon_remove_host + - falcon_sensor_installed + block: + - ansible.builtin.include_tasks: remove_host_pretasks.yml + # noqa name[missing] + - name: Linux Block when: - ansible_facts['os_family'] != "Windows" @@ -18,3 +41,12 @@ block: - ansible.builtin.include_tasks: win_uninstall.yml # noqa name[missing] + +# Hide host +- name: Hide host + when: + - falcon_client_id and falcon_client_secret + - falcon_uninstall_remove_aid is defined + block: + - ansible.builtin.include_tasks: hide_host.yml + # noqa name[missing] diff --git a/roles/falcon_uninstall/tasks/preuninstall.yml b/roles/falcon_uninstall/tasks/preuninstall.yml new file mode 100644 index 00000000..a6cf91bb --- /dev/null +++ b/roles/falcon_uninstall/tasks/preuninstall.yml @@ -0,0 +1,51 @@ +--- +# Linux Block +- name: "CrowdStrike Falcon | Linux Install State Block" + when: + - ansible_facts['system'] == 'Linux' + block: + - name: "CrowdStrike Falcon | Get List of Installed Packages (Linux)" + ansible.builtin.package_facts: + manager: auto + + - name: "CrowdStrike Falcon | Set Sensor Name (Linux)" + ansible.builtin.set_fact: + installed_sensor: falcon-sensor + + - name: "CrowdStrike Falcon | Check if Sensor is Installed (Linux)" + ansible.builtin.set_fact: + falcon_sensor_installed_linux: "{{ installed_sensor in ansible_facts.packages }}" + +# Windows block +- name: "CrowdStrike Falcon | Windows Install State Block" + when: + - ansible_facts['os_family'] == "Windows" + block: + - name: "CrowdStrike Falcon | Check status of Falcon Sensor (Windows)" + ansible.windows.win_command: sc.exe query csagent + failed_when: false + changed_when: false + register: win_status + + - name: "CrowdStrike Falcon | Check if Sensor is Installed (Windows)" + ansible.builtin.set_fact: + falcon_sensor_installed_windows: "{{ true if ('RUNNING' in win_status.stdout) else false }}" + +# macOS block +- name: "CrowdStrike Falcon | macOS Install State Block" + when: + - ansible_facts['distribution'] == "MacOSX" + block: + - name: CrowdStrike Falcon | Stat Falcon Sensor (macOS) + ansible.builtin.stat: + path: "/Applications/Falcon.app/Contents/Resources/falconctl" + register: falconctl_mac + + - name: CrowdStrike Falcon | Check if Sensor is Installed (macOS) + ansible.builtin.set_fact: + falcon_sensor_installed_mac: "{{ falconctl_mac.stat.exists }}" + +# Check if sensor is installed +- name: "CrowdStrike Falcon | Check if Sensor is Installed" + ansible.builtin.set_fact: + falcon_sensor_installed: "{{ falcon_sensor_installed_linux | default(falcon_sensor_installed_windows | default(falcon_sensor_installed_mac)) }}" diff --git a/roles/falcon_uninstall/tasks/remove_host_pretasks.yml b/roles/falcon_uninstall/tasks/remove_host_pretasks.yml new file mode 100644 index 00000000..d4b81364 --- /dev/null +++ b/roles/falcon_uninstall/tasks/remove_host_pretasks.yml @@ -0,0 +1,93 @@ +--- +### AID remove_host +# Linux block +- name: "CrowdStrike Falcon | Linux AID Block" + when: + - ansible_facts['system'] == 'Linux' + block: + - name: "CrowdStrike Falcon | Grab existing AID (Linux)" + crowdstrike.falcon.falconctl_info: + name: aid + register: falcon_uninstall_linux_aid_info + + - name: "CrowdStrike Falcon | Set AID (Linux)" + ansible.builtin.set_fact: + falcon_uninstall_linux_aid: "{{ falcon_uninstall_linux_aid_info.falconctl_info.aid }}" + + - name: "CrowdStrike Falcon | Assert AID found (Linux)" + ansible.builtin.assert: + that: + - falcon_uninstall_linux_aid | length > 0 + fail_msg: "AID was not found!" + success_msg: "Found AID." + +# Windows block +- name: "CrowdStrike Falcon | Windows AID Block" + when: + - ansible_facts['os_family'] == "Windows" + block: + - name: "CrowdStrike Falcon | Grab existing AID (Windows)" + ansible.windows.win_reg_stat: + path: "{{ item }}" + name: AG + register: falcon_uninstall_windows_aid_stat + loop: + - 'HKLM:\SYSTEM\CrowdStrike\{9b03c1d9-3138-44ed-9fae-d9f4c034b88d}\{16e0423f-7058-48c9-a204-725362b67639}\Default' + - 'HKLM:\SYSTEM\CurrentControlSet\Services\CSAgent\Sim' + + - name: "CrowdStrike Falcon | Set unique value (Windows)" + ansible.builtin.set_fact: + aid_win_value: "{{ falcon_uninstall_windows_aid_stat.results | selectattr('value', 'defined') | first }}" + + - name: "CrowdStrike Falcon | Convert Value to AID (Windows) " + ansible.windows.win_shell: | + $bytes = @( {{ aid_win_value.value | join(',') }} ) + $aid = [System.BitConverter]::ToString($bytes).ToLower() -replace '-', '' + Write-Host -NoNewline $aid + changed_when: false + register: falcon_uninstall_windows_aid_value + + - name: "CrowdStrike Falcon | Set AID (Windows)" + ansible.builtin.set_fact: + falcon_uninstall_windows_aid: "{{ falcon_uninstall_windows_aid_value.stdout }}" + + - name: "CrowdStrike Falcon | Assert AID found (Windows)" + ansible.builtin.assert: + that: + - falcon_uninstall_windows_aid | length > 0 + fail_msg: "AID was not found!" + success_msg: "Found AID." + +# Mac Block +- name: CrowdStrike Falcon | Stat Falcon Sensor (macOS) + ansible.builtin.stat: + path: "/Applications/Falcon.app/Contents/Resources/falconctl" + register: falconctl_mac + when: + - ansible_facts['distribution'] == "MacOSX" + +- name: "CrowdStrike Falcon | macOS AID Block" + when: + - ansible_facts['distribution'] == "MacOSX" + - falconctl_mac.stat.exists + block: + - name: CrowdStrike Falcon | Get AID Value from Stats (macOS) + ansible.builtin.command: | + /Applications/Falcon.app/Contents/Resources/falconctl stats agent_info | grep agentID + register: falcon_uninstall_mac_aid_stat + changed_when: false + + - name: "CrowdStrike Falcon | Set AID (macOS)" + ansible.builtin.set_fact: + falcon_uninstall_mac_aid: "{{ falcon_uninstall_mac_aid_stat.stdout | regex_replace('^.*: ', '') | lower | replace('-', '') }}" + + - name: "CrowdStrike Falcon | Assert AID found (macOS)" + ansible.builtin.assert: + that: + - falcon_uninstall_mac_aid | length > 0 + fail_msg: "AID was not found!" + success_msg: "Found AID." + +- name: "CrowdStrike Falcon | Set AID for Uninstall" + ansible.builtin.set_fact: + falcon_uninstall_remove_aid: "{{ falcon_uninstall_linux_aid | default(falcon_uninstall_windows_aid | default(falcon_uninstall_mac_aid)) }}" diff --git a/roles/falcon_uninstall/tasks/win_uninstall.yml b/roles/falcon_uninstall/tasks/win_uninstall.yml index c4b5f454..dcbf32ce 100644 --- a/roles/falcon_uninstall/tasks/win_uninstall.yml +++ b/roles/falcon_uninstall/tasks/win_uninstall.yml @@ -13,6 +13,13 @@ state: absent creates_service: csfalconservice arguments: '/uninstall /quiet {{ falcon_windows_uninstall_args }}' + register: falcon_win_sensor_uninstall when: - falcon_win_sensor_cache.files | length > 0 - ansible_facts['os_family'] == "Windows" + +- name: CrowdStrike Falcon | Wait for Falcon Sensor to be removed (Windows) + ansible.windows.win_wait_for: + path: C:\Windows\System32\drivers\CrowdStrike + state: absent + when: falcon_win_sensor_uninstall.changed # noqa: no-handler diff --git a/roles/falcon_uninstall/vars/main.yml b/roles/falcon_uninstall/vars/main.yml index 73692412..5ff3b376 100644 --- a/roles/falcon_uninstall/vars/main.yml +++ b/roles/falcon_uninstall/vars/main.yml @@ -1,2 +1,7 @@ --- # vars file for falcon_uninstall +falcon_cloud_urls: + us-1: "api.crowdstrike.com" + us-2: "api.us-2.crowdstrike.com" + eu-1: "api.eu-1.crowdstrike.com" + us-gov-1: "api.laggar.gcw.crowdstrike.com"