diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index 5a4a509..0998917 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -19,7 +19,6 @@ jobs: strategy: matrix: ansible: - - stable-2.13 - stable-2.14 - stable-2.15 - stable-2.16 @@ -28,13 +27,11 @@ jobs: - '3.10' - '3.11' exclude: - - ansible: stable-2.13 - python: '3.11' - ansible: stable-2.16 python: '3.9' include: - - ansible: stable-2.13 - python: '3.8' + - ansible: stable-2.16 + python: '3.12' runs-on: ubuntu-latest steps: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3da7c9b..8d32a7d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,25 @@ Sva.Sentinelone Release Notes .. contents:: Topics +v1.1.0 +====== + +Release Summary +--------------- + +This is the release v1.1.0 of the ``sva.sentinelone`` collection. It introduces new modules and roles. +Modules: sentinelone_download_agent +Roles: install_agent + +New Modules +----------- + +- sva.sentinelone.sentinelone_download_agent - Download SentinelOne agent from Management Console + +New Roles +--------- + +- sva.sentinelone.install_agent - A role to download and install SentinelAgent on Windows and Linux hosts v1.0.3 ====== diff --git a/README.md b/README.md index 08a751f..e4ad20e 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,15 @@ It provides several modules which helps to configure and manage SentinelOne Mana - [sentinelone_path_exclusions](https://svalabs.github.io/sva.sentinelone/branch/main/collections/sva/sentinelone/sentinelone_path_exclusions_module.html) - [sentinelone_policies](https://svalabs.github.io/sva.sentinelone/branch/main/collections/sva/sentinelone/sentinelone_policies_module.html) +- **Roles:** + - [install_agent](roles/install_agent/README.md) + ## Requirements ### Ansible -- ansible >= 6 **or** ansible-core >= 2.13 +- ansible >= 7 **or** ansible-core >= 2.14 (Lower versions may work but they have not been tested) ### Python -- Python >= 3.6 (deepdiff requirement) +- Python >= 3.9 (Ansible control node requirement) ### External This collection needs the following Python modules: @@ -34,13 +37,11 @@ This collection needs the following Python modules: ## Tested with Ansible and the following Python versions Tested Ansible versions: -- 2.13 - 2.14 - 2.15 - 2.16 Tested Python versions: -- 3.8 - 3.9 - 3.10 - 3.11 @@ -77,7 +78,9 @@ See [Ansible Using collections](https://docs.ansible.com/ansible/devel/user_guid The module documentation can be found [here](https://svalabs.github.io/sva.sentinelone/branch/main/collections/index_module.html). ## Changelog -**v1.0.3**: Increased request timeout and implemented error handling for requests that timed out. +**v1.1.0**: Added new sentinelone_download_agent module and install_agent role + +**v1.0.3**: Increased request timeout and implemented error handling for requests that timed out **v1.0.2**: Added detailed error message to module output if an API call fails @@ -92,4 +95,4 @@ Detailed Changelog can be found at [CHANGELOG](CHANGELOG.rst) - [ ] Unit tests needs to be written ## Licensing -The SVA SentinelOne collection is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text. +The SVA SentinelOne collection is licensed under the GNU General Public License v3.0+. See [LICENSE](LICENSE) for the full license text. diff --git a/changelogs/.plugin-cache.yaml b/changelogs/.plugin-cache.yaml index 1b02b50..1bda5ca 100644 --- a/changelogs/.plugin-cache.yaml +++ b/changelogs/.plugin-cache.yaml @@ -14,6 +14,11 @@ plugins: name: sentinelone_config_overrides namespace: '' version_added: 1.0.0 + sentinelone_download_agent: + description: Download SentinelOne agent from Management Console + name: sentinelone_download_agent + namespace: '' + version_added: 1.1.0 sentinelone_filters: description: Manage SentinelOne Filters name: sentinelone_filters @@ -48,4 +53,4 @@ plugins: shell: {} strategy: {} vars: {} -version: 1.0.3 +version: 1.1.0 diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 388a18a..a0ff7e7 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -53,3 +53,26 @@ releases: fragments: - v1.0.3.yml release_date: '2023-03-13' + 1.1.0: + changes: + release_summary: 'This is the release v1.1.0 of the ``sva.sentinelone`` collection. + It introduces new modules and roles. + + Modules: sentinelone_download_agent + + Roles: install_agent + + ' + fragments: + - v1.1.0.yml + modules: + - description: Download SentinelOne agent from Management Console + name: sentinelone_download_agent + namespace: '' + objects: + role: + - description: A role to download and install SentinelAgent on Windows and Linux + hosts + name: install_agent + namespace: null + release_date: '2024-03-14' diff --git a/galaxy.yml b/galaxy.yml index 3329e39..faddaca 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,3 +1,4 @@ +--- ### REQUIRED # The namespace of the collection. This can be a company/brand/organization or product namespace under which all # content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with @@ -8,14 +9,14 @@ namespace: "sva" name: "sentinelone" # The version of the collection. Must be compatible with semantic versioning -version: "1.0.3" +version: "1.1.0" # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: "README.md" # A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) # @nicks:irc/im.site#channel' -authors: +authors: - Marco Wester ### OPTIONAL but strongly recommended @@ -24,14 +25,17 @@ description: "Collection for Sentinelone Modules" # Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only # accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' -license: "Apache-2.0" +license: + - "GPL-3.0-or-later" # The path to the license file for the collection. This path is relative to the root of the collection. This key is # mutually exclusive with 'license' -license_file: "" +license_file: "LICENSE" # A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character # requirements as 'namespace' and 'name' -tags: +tags: + - application + - security - sentinelone - sentinelone_config_overrides - sentinelone_filters @@ -40,6 +44,10 @@ tags: - sentinelone_policies - sentinelone_sites - sentinelone_upgrade_policies + - install_agent + +dependencies: + ansible.windows: "*" # The URL of the originating SCM repository repository: "https://github.com/svalabs/sva.sentinelone" @@ -57,6 +65,6 @@ issues: "https://github.com/svalabs/sva.sentinelone/issues" # artifact. A pattern is matched from the relative path of the file or directory of the collection directory. This # uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry', # and '.git' are always filtered -build_ignore: +build_ignore: - .github/** - .gitignore diff --git a/meta/execution-environment.yml b/meta/execution-environment.yml index 3154362..e5f6c02 100644 --- a/meta/execution-environment.yml +++ b/meta/execution-environment.yml @@ -1,5 +1,5 @@ --- -version: 1 - +version: 3 dependencies: python: requirements.txt + galaxy: requirements.yml diff --git a/plugins/modules/sentinelone_config_overrides.py b/plugins/modules/sentinelone_config_overrides.py index 522664d..a5a1bd9 100644 --- a/plugins/modules/sentinelone_config_overrides.py +++ b/plugins/modules/sentinelone_config_overrides.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright: (c) 2023, Marco Wester +# Copyright: (c) 2024, Marco Wester # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type @@ -108,7 +108,7 @@ EXAMPLES = r''' --- - name: Create/Update config_override for all agents on site - sentinelone_config_overrides: + sva.sentinelone.sentinelone_config_overrides: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -116,7 +116,7 @@ os_type: "windows" config_override: { powershellProtection: true } - name: Create/Update config_override for all agents on group - sentinelone_config_overrides: + sva.sentinelone.sentinelone_config_overrides: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -126,7 +126,7 @@ config_override: powershellProtection: true - name: Create/Update config_override for specific agent version on group - sentinelone_config_overrides: + sva.sentinelone.sentinelone_config_overrides: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -137,7 +137,7 @@ config_override: powershellProtection: true - name: Delete config_override for all agents on group - sentinelone_config_overrides: + sva.sentinelone.sentinelone_config_overrides: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -145,7 +145,7 @@ group: "testgroup" os_type: "windows" - name: Delete config_override for specific agent version on site - sentinelone_config_overrides: + sva.sentinelone.sentinelone_config_overrides: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -507,7 +507,7 @@ def run_module(): result = dict( changed=False, - original_message=str(diffs), + original_message=diffs, message=basic_message ) diff --git a/plugins/modules/sentinelone_download_agent.py b/plugins/modules/sentinelone_download_agent.py index dd3d809..615771e 100644 --- a/plugins/modules/sentinelone_download_agent.py +++ b/plugins/modules/sentinelone_download_agent.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright: (c) 2023, Marco Wester +# Copyright: (c) 2024, Marco Wester # Erik Schindler # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) @@ -45,7 +45,7 @@ - "Version of the agent to be downloaded." - "B(latest) (default) - download latest GA (stable) release for the specified parameters" - "B(latest_ea) - same as latest, but also includes EA packages" - - "B() - download an explicit version of the agent" + - "B(custom) - custom_version is required when agent_versioin is custom" type: str default: latest required: false @@ -82,21 +82,13 @@ description: - "Architecture of the packet which should be downloaded" - "Windows: Only B(32_bit) and B(64_bit) are allowed" - - "Linux: Of not set 64 bit agent will be downloaded. If set to B(aarch64) the ARM agent will be downloaded" + - "Linux: If not set 64 bit agent will be downloaded. If set to B(aarch64) the ARM agent will be downloaded" type: str required: false choices: - 32_bit - 64_bit - aarch64 - signed_packages: - description: - - "Linux only. Will be ignored if B(os_type) is 'Windows'" - - "B(true): Only search and download signed agent packages" - - "B(false): Only search an download unsigned agent packages" - type: bool - required: false - default: false download_dir: description: - "Set the path where the agent should be downloaded." @@ -118,7 +110,7 @@ EXAMPLES = r''' --- - name: Download latest agent for linux - sentinelone_download_agent: + sva.sentinelone.sentinelone_download_agent: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" os_type: "Linux" @@ -126,26 +118,16 @@ download_path: "/tmp" architecture: "64_bit" - name: Download latest agent for linux and include EA packages - sentinelone_download_agent: - console_url: "https://XXXXX.sentinelone.net" - token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" - os_type: "Linux" - packet_format: "rpm" - download_path: "/tmp" - architecture: "64_bit" - agent_version: "latest_ea" -- name: Download latest signed agent for linux and include EA packages - sentinelone_download_agent: + sva.sentinelone.sentinelone_download_agent: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" os_type: "Linux" packet_format: "rpm" download_path: "/tmp" architecture: "64_bit" - signed_packages: true agent_version: "latest_ea" - name: Download specific agent version - sentinelone_download_agent: + sva.sentinelone.sentinelone_download_agent: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" os_type: "Windows" @@ -154,7 +136,7 @@ agent_version: "custom" custom_version: "23.2.3.358" - name: Get info about specified package - sentinelone_download_agent: + sva.sentinelone.sentinelone_download_agent: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" state: "info" @@ -209,7 +191,6 @@ def __init__(self, module: AnsibleModule): self.os_type = module.params["os_type"] self.packet_format = module.params["packet_format"] self.architecture = module.params["architecture"] - self.signed_packages = module.params["signed_packages"] self.download_dir = module.params["download_dir"] # Do sanity checks @@ -240,7 +221,7 @@ def check_sanity(os_type: str, packet_format: str, architecture: str, module: An module.fail_json(msg="Error: 'packet_format' needs to be 'deb' or 'rpm' if os_type is 'Linux'") def get_package_obj(self, agent_version: str, custom_version: str, os_type: str, packet_format: str, - architecture: str, signed_packages: bool, module: AnsibleModule): + architecture: str, module: AnsibleModule): """ Queries the API to get the info about the agent package which maches the parameters @@ -254,8 +235,6 @@ def get_package_obj(self, agent_version: str, custom_version: str, os_type: str, :type packet_format: str :param architecture: The OS architecture :type architecture: str - :param signed_packages: Wether or not the package should be signed - :type signed_packages: bool :param module: Ansible module for error handling :type module: AnsibleModule :return: Returns the found agent object @@ -284,8 +263,6 @@ def get_package_obj(self, agent_version: str, custom_version: str, os_type: str, # provide the information elementary. 'osArches' parameter applies only for windows if architecture == 'aarch64': query_params['query'] = 'SentinelAgent-aarch64' - elif signed_packages: - query_params['query'] = 'Signed-SentinelAgent_linux' else: query_params['query'] = 'SentinelAgent_linux' else: @@ -316,7 +293,6 @@ def run_module(): os_type=dict(type='str', required=True, choices=['Linux', 'Windows']), packet_format=dict(type='str', required=True, choices=['rpm', 'deb', 'msi', 'exe']), architecture=dict(type='str', required=False, choices=['32_bit', '64_bit', 'aarch64']), - signed_packages=dict(type='bool', required=False, default='false'), download_dir=dict(type='str', required=False, default='./') ) @@ -340,11 +316,10 @@ def run_module(): os_type = download_agent_obj.os_type packet_format = download_agent_obj.packet_format architecture = download_agent_obj.architecture - signed_packages = download_agent_obj.signed_packages # Get package object from API with given parameters package_obj = download_agent_obj.get_package_obj(agent_version, custom_version, os_type, packet_format, - architecture, signed_packages, module) + architecture, module) changed = False if state == 'present': @@ -356,7 +331,6 @@ def run_module(): if path.exists(filepath): basic_message = f"File {filename} already exists in {download_dir} - nothing to do." - original_message = basic_message else: # Ensure download_dir exists and is a directory dest_is_dir = path.isdir(download_dir) @@ -379,7 +353,7 @@ def run_module(): changed = True basic_message = f"Downloaded file {filename} to {download_dir}" - original_message = {'download_dir': download_dir, 'filename': filename, 'full_path': filepath} + original_message = {'download_dir': download_dir, 'filename': filename, 'full_path': filepath} else: # If state=info original_message = package_obj @@ -387,7 +361,7 @@ def run_module(): result = dict( changed=changed, - original_message=str(original_message), + original_message=original_message, message=basic_message ) diff --git a/plugins/modules/sentinelone_filters.py b/plugins/modules/sentinelone_filters.py index e36718c..7cfd593 100644 --- a/plugins/modules/sentinelone_filters.py +++ b/plugins/modules/sentinelone_filters.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright: (c) 2023, Marco Wester +# Copyright: (c) 2024, Marco Wester # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type @@ -62,7 +62,7 @@ EXAMPLES = r''' --- - name: Create filter - sentinelone_filters: + sva.sentinelone.sentinelone_filters: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -73,7 +73,7 @@ osTypes: - windows - name: Update filter - sentinelone_filters: + sva.sentinelone.sentinelone_filters: state: "present" console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" @@ -86,7 +86,7 @@ osTypes: - windows - name: Delete filter - sentinelone_filters: + sva.sentinelone.sentinelone_filters: state: "absent" console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" @@ -292,7 +292,7 @@ def run_module(): result = dict( changed=False, - original_message=str(diffs), + original_message=diffs, message=basic_message ) diff --git a/plugins/modules/sentinelone_groups.py b/plugins/modules/sentinelone_groups.py index 4687f67..8a0176b 100644 --- a/plugins/modules/sentinelone_groups.py +++ b/plugins/modules/sentinelone_groups.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright: (c) 2023, Marco Wester +# Copyright: (c) 2024, Marco Wester # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type @@ -65,7 +65,7 @@ EXAMPLES = r''' --- - name: Create single static group - sentinelone_groups: + sva.sentinelone.sentinelone_groups: state: "present" console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" @@ -73,7 +73,7 @@ name: "MyGroup" - name: Create single dynamic group - sentinelone_groups: + sva.sentinelone.sentinelone_groups: state: "present" console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" @@ -82,7 +82,7 @@ filter_name: "MyFilter" - name: Create multiple static groups - sentinelone_groups: + sva.sentinelone.sentinelone_groups: state: "present" console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" @@ -93,7 +93,7 @@ - "MyGroup3" - name: Delete single static/dynamic group - sentinelone_groups: + sva.sentinelone.sentinelone_groups: state: "absent" console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" @@ -101,7 +101,7 @@ name: "MyGroup" - name: Delete multiple static/dynamic groups - sentinelone_groups: + sva.sentinelone.sentinelone_groups: state: "absent" console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" @@ -370,7 +370,7 @@ def run_module(): result = dict( changed=False, - original_message=str(diffs), + original_message=diffs, message=basic_message ) diff --git a/plugins/modules/sentinelone_path_exclusions.py b/plugins/modules/sentinelone_path_exclusions.py index e7fe7cb..6c1f829 100644 --- a/plugins/modules/sentinelone_path_exclusions.py +++ b/plugins/modules/sentinelone_path_exclusions.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright: (c) 2023, Marco Wester , Lasse Wackers +# Copyright: (c) 2024, Marco Wester , Lasse Wackers # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type @@ -110,7 +110,7 @@ EXAMPLES = r''' --- - name: Create exclusion in site scope - sentinelone_path_exclusions: + sva.sentinelone.sentinelone_path_exclusions: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -118,7 +118,7 @@ mode: "performance_focus" os_type: "windows" - name: Create exclusion in single group - sentinelone_path_exclusions: + sva.sentinelone.sentinelone_path_exclusions: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -127,7 +127,7 @@ mode: "interoperability_extended" os_type: "windows" - name: Create exclusion in multiple groups - sentinelone_path_exclusions: + sva.sentinelone.sentinelone_path_exclusions: state: "present" console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" @@ -139,7 +139,7 @@ mode: "performance_focus_extended" os_type: "windows" - name: Create exclusion in multiple groups and disable automatic upload to Binary Vault - sentinelone_path_exclusions: + sva.sentinelone.sentinelone_path_exclusions: state: "present" console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" @@ -153,14 +153,14 @@ os_type: "windows" ef_binary_vault: true - name: Delete exclusion in site scope - sentinelone_path_exclusions: + sva.sentinelone.sentinelone_path_exclusions: state: "absent" console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "msd" os_path: "C:\\Test1234\\" - name: Delete exclusion in group scope - sentinelone_path_exclusions: + sva.sentinelone.sentinelone_path_exclusions: state: "absent" console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" @@ -543,7 +543,7 @@ def run_module(): result = dict( changed=False, - original_message=str(diffs), + original_message=diffs, message=basic_message ) diff --git a/plugins/modules/sentinelone_policies.py b/plugins/modules/sentinelone_policies.py index aa3b719..2ab8f1e 100644 --- a/plugins/modules/sentinelone_policies.py +++ b/plugins/modules/sentinelone_policies.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright: (c) 2023, Marco Wester +# Copyright: (c) 2024, Marco Wester # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type @@ -65,7 +65,7 @@ EXAMPLES = r''' --- - name: Set custom policy on multiple groups - sentinelone_policies: + sva.sentinelone.sentinelone_policies: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -77,7 +77,7 @@ agentUi: agentUiOn: false - name: Set custom policy on site - sentinelone_policies: + sva.sentinelone.sentinelone_policies: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -86,7 +86,7 @@ agentUi: agentUiOn: false - name: Revert to group default policy inherited from site - sentinelone_policies: + sva.sentinelone.sentinelone_policies: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -95,7 +95,7 @@ - group1 - group2 - name: Revert to site default policy inherited from account - sentinelone_policies: + sva.sentinelone.sentinelone_policies: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -330,7 +330,7 @@ def run_module(): result = dict( changed=False, - original_message=str(diffs), + original_message=diffs, message=basic_message ) diff --git a/plugins/modules/sentinelone_sites.py b/plugins/modules/sentinelone_sites.py index 48610cb..b0f39d8 100644 --- a/plugins/modules/sentinelone_sites.py +++ b/plugins/modules/sentinelone_sites.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright: (c) 2023, Marco Wester +# Copyright: (c) 2024, Marco Wester # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type @@ -94,7 +94,7 @@ EXAMPLES = r''' --- - name: Create / update site - sentinelone_sites: + sva.sentinelone.sentinelone_sites: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" name: "test" @@ -102,7 +102,7 @@ expiration_date: "2022-06-01T12:00+01:00" description: "Testsite" - name: Delete site - sentinelone_sites: + sva.sentinelone.sentinelone_sites: state: "absent" console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" @@ -373,7 +373,7 @@ def run_module(): result = dict( changed=False, - original_message=str(diffs), + original_message=diffs, message=basic_message ) diff --git a/plugins/modules/sentinelone_upgrade_policies.py b/plugins/modules/sentinelone_upgrade_policies.py index 8e9ff5d..b3953cf 100644 --- a/plugins/modules/sentinelone_upgrade_policies.py +++ b/plugins/modules/sentinelone_upgrade_policies.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright: (c) 2023, Marco Wester +# Copyright: (c) 2024, Marco Wester # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type @@ -86,7 +86,7 @@ EXAMPLES = r''' - name: Set custom 'Maximum Concurrent Downloads' on multiple groups - sentinelone_upgrade_policies: + sva.sentinelone.sentinelone_upgrade_policies: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -95,14 +95,14 @@ - group2 max_concurrent_downloads: 1000 - name: Enable inheritance for 'Maximum Concurrent Downloads' and for 'Maintenance Windows Settings' on site scope - sentinelone_upgrade_policies: + sva.sentinelone.sentinelone_upgrade_policies: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" inherit_max_concurrent_downloads: yes inherit_maintenance_windows: yes - name: Set custom 'Maintenance Windows' for monday and tuesday on single group - sentinelone_upgrade_policies: + sva.sentinelone.sentinelone_upgrade_policies: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -120,7 +120,7 @@ - from: "3:00 pm" to: "7:00 pm" - name: Set custom 'Maintenance Windows' for monday on single group and use specific timezone - sentinelone_upgrade_policies: + sva.sentinelone.sentinelone_upgrade_policies: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -134,7 +134,7 @@ to: "11:00 pm" timezone: "+02:00" - name: Set custom 'Maintenance Windows' for whole wednesday on site scope - sentinelone_upgrade_policies: + sva.sentinelone.sentinelone_upgrade_policies: console_url: "https://XXXXX.sentinelone.net" token: "XXXXXXXXXXXXXXXXXXXXXXXXXXX" site_name: "test" @@ -536,7 +536,7 @@ def run_module(): result = dict( changed=False, - original_message=str(diffs), + original_message=diffs, message=basic_message ) diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..6cdc4cf --- /dev/null +++ b/requirements.yml @@ -0,0 +1,3 @@ +--- +collections: + - name: ansible.windows \ No newline at end of file diff --git a/roles/install_agent/README.md b/roles/install_agent/README.md new file mode 100644 index 0000000..a72d9b8 --- /dev/null +++ b/roles/install_agent/README.md @@ -0,0 +1,143 @@ +install_agent +========= + +This Ansible role is designed to install the SentinelOne agent package and register the new endpoint in the SentinelOne Management Console. + +Supported Operating Systems: +------------ +- Red Hat Enterprise Linux (RHEL) + - 8 + - 9 +- SUSE Linux Enterprise Server (SLES) + - 12 + - 15 +- Debian + - 10 + - 11 + - 12 +- Ubuntu + - 20.04 + - 22.04 + - 24.04 + +- Windows + - Server 2016 + - Server 2019 + - Server 2022 + - Desktop 10 + - Desktop 11 + +Requirements +------------ +### API Token +An API key is required to use this role. It is considered best practice to create a specific 'API user' role for this purpose. + +The API user requires the following permissions: +- Read site info +- Read group info (if the scope is set to group) +- Download agent packages +- Read the site or group registration token +- Read agent information + +### GPG Key (Linux only) +You need to provide the gpg key to validate the package signatures correctly. You obtain the download link from the Sentinelone Help page: "**How to Install on a Linux Endpoint with Yum**". + +Place the key on the host executing the Playbook and adjust the `gpg_key` variable accordingly. + + +Role Variables +-------------- + +### Mandatory Variables + +| Variable | Example | Description | +| --- | --- | --- | +| `console_url` | https://my-console.sentinelone.net | The URL of the SentinelOne Management Console | +| `api_token` | XXXXXXXXXXXXXXXXXX | The API token for the API user for authentication | +| `site` | prod | The site to which the new hosts should be assigned | +| `gpg_key` | /tmp/sentinel_one.gpg | Only required on **Linux** agents. Path to the gpg key which will be installed and used for package signature verification | + +### Optional Variables + +| Variable | Default | Choices | Description | +| --- | --- | --- | --- | +| `group` | | | An optional group which is part of the site. If set, the agent will be assigned to this group instead of the 'Default Group'. | +| `agent_version` | latest | latest, latest_ea, custom | Controls which agent should be installed. latest installs the latest general availability version. If custom is set, `custom_version` is mandatory | +| `custom_version` | | | Install a specific version of the SentinelOne agent. Must be used in combination with `agent_version` set to 'custom' | +| `hide_sensitive` | true | true, false | Hides sensitive information like API keys in module output. Only set to false for debugging purposes | +| `lx_force_new_token` | false | true, false | Linux only: Set the management token on the linux agent even if it is already registered. | +| `win_use_exe` | false | true, false | Windows only: By default, the .msi package is used for installation. If you prefer to use the .exe file, enable this setting | +| `win_allow_reboot` | true | true, false | Windows only: After the removal of a Windows Feature (here Windows Defender) and after the agent installation, a reboot is required. The role is set to reboot at the end of the installation by default. Disable this setting if you wish to skip the reboot. | + +### Variables from `vars.yml` + +**Note:** These variables are for documentation only. Do not override these unless you fully understand their functionality. + +| Variable | Description | +| --- | --- | +| `pkg_format` | Determines the package format (like .exe, .msi, .deb, .rpm) based on the Ansible facts | +| `pkg_arch` | Sets the agent package architecture based on the Ansible facts | +| `custom_os_family` | Identifies the underlying operating system (Linux or Windows) | +| `api_url` | Sets the API base URL | +| `agent_installed` | Determines if the agent is already installed | + +Dependencies +------------ + +If this role is used for Windows hosts, the `ansible.windows` collection needs to be installed. + +Example Playbook +---------------- + +This is an example how to use this role in your Playbooks: + +--- + - name: Sentinelone Agent Deployment + hosts: all + gather_facts: true + tasks: + - name: "Install agent on Linux" + ansible.builtin.include_role: + name: sva.sentinelone.install_agent + vars: + console_url: "https://your-instance.sentinelone.net" + api_token: "YOUR_S1_API_TOKEN" + site: "ansible-test" + group: "linux" # optional + gpg_key: "/tmp/sentinel_one.gpg" + when: ansible_facts.ansible_system is defined and ansible_facts.ansible_system == "Linux" + + - name: "Install specific agent version on Linux" + ansible.builtin.include_role: + name: sva.sentinelone.install_agent + vars: + console_url: "https://your-instance.sentinelone.net" + api_token: "YOUR_S1_API_TOKEN" + site: "ansible-test" + gpg_key: "/tmp/sentinel_one.gpg" + agent_version: 'custom' + custom_version: '23.4.2.14' + when: ansible_facts.ansible_system is defined and ansible_facts.ansible_system == "Linux" + + - name: "Install agent on Windows" + ansible.builtin.include_role: + name: sva.sentinelone.install_agent + vars: + console_url: "https://your-instance.sentinelone.net" + api_token: "YOUR_S1_API_TOKEN" + site: "ansible-test" + group: "windows" # optional + win_use_exe: true # optional + win_allow_reboot: false # optional + when: os_family == "Windows" + + +License +------- + +This SVA SentinelOne install_agent role is licensed under the GNU General Public License v3.0+. You can view the complete license text [here](../../LICENSE). + +Author Information +------------------ + + - Marco Wester (@mwester117) diff --git a/roles/install_agent/defaults/main.yml b/roles/install_agent/defaults/main.yml new file mode 100644 index 0000000..3c20a45 --- /dev/null +++ b/roles/install_agent/defaults/main.yml @@ -0,0 +1,8 @@ +--- +# defaults file for install_agent +win_use_exe: false +win_allow_reboot: true +agent_version: 'latest' +hide_sensitive: true +lx_force_new_token: false +# custom_version, console_url, api_token, site, group diff --git a/roles/install_agent/handlers/main.yml b/roles/install_agent/handlers/main.yml new file mode 100644 index 0000000..92aff58 --- /dev/null +++ b/roles/install_agent/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for install_agent diff --git a/roles/install_agent/meta/ argument_specs.yml b/roles/install_agent/meta/ argument_specs.yml new file mode 100644 index 0000000..f3a0511 --- /dev/null +++ b/roles/install_agent/meta/ argument_specs.yml @@ -0,0 +1,101 @@ +--- +argument_specs: + # roles/install_agent/tasks/main.yml entry point + main: + short_description: "Entrypoint for install_agent role" + description: + - "This is the main entrypoint for the C(install_agent) role." + - "The entrypoint contains all os independent tasks and prepares the environment + for the os specific tasks." + author: + - Marco Wester + options: + win_use_exe: + type: "bool" + required: false + default: false + description: + - "Windows only: Controls the Windows agent package format." + - "B(false:) Downloads .msi installer file" + - "B(true:) Downloads .exe installer file" + options: + - true + - false + + win_allow_reboot: + type: "bool" + required: false + default: true + description: + - "Windows only: Endpoint needs to be rebooted to SentinelOne work properly." + - "Set to B(false) to disable automatic reboot" + options: + - true + - false + + agent_version: + type: "str" + required: false + default: "latest" + description: + - "The agent version string. B(default) is to install 'latest' version." + - "B(latest:) Install latest GA version." + - "B(latest_ea:) Install latest EA version." + - "B(custom): Install custom agent version. If set to custom argument 'custom_version' is required." + choices: + - "latest" + - "latest_ea" + - "custom" + + hide_sensitive: + type: "bool" + required: false + default: true + description: + - "Hide sensitive information like API keys in module output." + - "Only change to false for debugging purposes." + options: + - true + - false + + lx_force_new_token: + type: "bool" + required: false + default: false + description: "Linux only: Set the management token on the linux agent even if it is already registered." + options: + - true + - false + + custom_version: + type: "str" + required: false + description: + - "Required when 'agent_version' is set to B(custom)." + - "Explicit version of the agent to be installed." + + console_url: + type: "str" + required: true + description: "The SentinelOne management console URL. E.g. https://my-s1-console.net." + + api_token: + type: "str" + required: true + description: "The API token to authenticate at the management console." + + site: + type: "str" + required: true + description: "The name of the site where the new endpoint should join." + + group: + type: "str" + required: false + description: "Optional: The name of the group where the new endpoint should join." + + gpg_key: + type: "str" + required: false + description: + - "The path to the gpg key to verify the agent package integrity. Required if installing a rpm agent package >= 23.3.2.12" diff --git a/roles/install_agent/meta/main.yml b/roles/install_agent/meta/main.yml new file mode 100644 index 0000000..3620c9f --- /dev/null +++ b/roles/install_agent/meta/main.yml @@ -0,0 +1,62 @@ +--- +galaxy_info: + author: Marco Wester + description: This role installs the SentinelOne agent package on Linux or Windows endpoints + company: Systemvertrieb Alexander GmbH (SVA) + + # If the issue tracker for your role is not on github, uncomment the + # next line and provide a value + issue_tracker_url: "https://github.com/svalabs/sva.sentinelone/issues" + + # Choose a valid license ID from https://spdx.org - some suggested licenses: + # - BSD-3-Clause (default) + # - MIT + # - GPL-2.0-or-later + # - GPL-3.0-only + # - Apache-2.0 + # - CC-BY-4.0 + license: GPL-3.0-or-later + + min_ansible_version: "2.10" + + # If this a Container Enabled role, provide the minimum Ansible Container version. + # min_ansible_container_version: + + # + # Provide a list of supported platforms, and for each platform a list of versions. + # If you don't wish to enumerate all versions for a particular platform, use 'all'. + # To view available platforms and versions (or releases), visit: + # https://galaxy.ansible.com/api/v1/platforms/ + # + platforms: + - name: Ubuntu + - name: Fedora + - name: Debian + - name: Rocky + - name: SLES + - name: EL + - name: Windows + + galaxy_tags: + - sentinelone + - agent + - install + - installagent + - linux + - windows + - rhel + - el + - sles + - suse + - debian + - ubuntu + # List tags for your role here, one per line. A tag is a keyword that describes + # and categorizes the role. Users find roles by searching for tags. Be sure to + # remove the '[]' above, if you add tags to this list. + # + # NOTE: A tag is limited to a single word comprised of alphanumeric characters. + # Maximum 20 tags per role. + +dependencies: [] + # List your role dependencies here, one per line. Be sure to remove the '[]' above, + # if you add dependencies to this list. diff --git a/roles/install_agent/tasks/Linux.yml b/roles/install_agent/tasks/Linux.yml new file mode 100644 index 0000000..89a1537 --- /dev/null +++ b/roles/install_agent/tasks/Linux.yml @@ -0,0 +1,110 @@ +--- +- name: "Linux: Include Linux.yml vars" + ansible.builtin.include_vars: + file: Linux.yml + +- name: "Linux Block: Skip if sentinelagent is already installed." + when: not agent_installed + block: + - name: Get dmesg output + ansible.builtin.command: dmesg + changed_when: false + become: true + register: dmesg_output + + - name: Assert that no function tracing issues occured + ansible.builtin.assert: + that: + - "'FUNCTION TRACING IS CORRUPTED' not in dmesg_output.stdout" + fail_msg: 'System instability detected, function tracing seems corrupted' + + - name: "Linux: Copy agent package to remote server" + ansible.builtin.copy: + src: "{{ return_download_agent.original_message.full_path }}" + dest: "{{ remote_pkg_path }}" + mode: "0644" + + - name: "Block: RPM based systems" + when: pkg_format == "rpm" + block: + - name: "Linux: Check if package is signed" + ansible.builtin.include_tasks: signed.yml + + - name: "Linux: Copy gpg key to remote server" + ansible.builtin.copy: + src: "{{ gpg_key }}" + dest: "{{ remote_gpg_key_path }}" + mode: "0644" + when: signed_package + + - name: "Linux: Import GPG key for rpm" + ansible.builtin.rpm_key: + key: "{{ remote_gpg_key_path }}" + when: signed_package + + - name: "Linux: Install unsigned .rpm agent package via rpm" + ansible.builtin.command: + cmd: "rpm -i --nodigest {{ remote_pkg_path }}" + creates: "/opt/sentinelone/bin/sentinelctl" + when: not signed_package + + - name: "Linux: Install signed .rpm agent package {{ remote_pkg_path }}" + ansible.builtin.package: + name: "{{ remote_pkg_path }}" + when: signed_package + + - name: "Block: DEB based systems" + when: pkg_format == "deb" + block: + - name: "Install gpg" + ansible.builtin.apt: + name: gpg + update_cache: true + + - name: "Linux: Copy gpg key to remote server" + ansible.builtin.copy: + src: "{{ gpg_key }}" + dest: "{{ remote_gpg_key_path }}" + mode: "0644" + + - name: "Linux: Import GPG key for apt" + ansible.builtin.apt_key: + file: "{{ remote_gpg_key_path }}" + + - name: "Linux: Install deb agent package {{ remote_pkg_path }}" + ansible.builtin.apt: + deb: "{{ remote_pkg_path }}" + +- name: "Linux: Check if agent is already registered" + ansible.builtin.shell: + # \\s needed because yaml interprets \s as escape sequence + cmd: "set -o pipefail && /opt/sentinelone/bin/sentinelctl management status | grep -E '^Connectivity\\s+(On|Off)$' | awk '{ print $2 }'" + executable: "/bin/bash" + register: agent_status + failed_when: agent_status.stdout is not regex("On|Off") + changed_when: agent_status.stdout is not regex("On|Off") + +- name: "Linux: Register agent" + ansible.builtin.command: + cmd: '/opt/sentinelone/bin/sentinelctl management token set {{ reg_token_obj.json.data.token }}' + register: token_set_output + no_log: "{{ hide_sensitive }}" + failed_when: '"Registration token successfully set" not in token_set_output.stdout' + when: agent_status.stdout == 'Off' or lx_force_new_token + +- name: "Linux: Start and enable service 'sentinelone' if neccessary" + ansible.builtin.service: + name: sentinelone + state: started + enabled: true + +- name: "Linux: Remove agent install package from target machine" + ansible.builtin.file: + path: "{{ remote_pkg_path }}" + state: absent + when: not agent_installed + +- name: "Linux: Remove gpg key from target machine" + ansible.builtin.file: + path: "{{ remote_gpg_key_path }}" + state: absent diff --git a/roles/install_agent/tasks/Windows.yml b/roles/install_agent/tasks/Windows.yml new file mode 100644 index 0000000..935e781 --- /dev/null +++ b/roles/install_agent/tasks/Windows.yml @@ -0,0 +1,52 @@ +--- +- name: "Windows: Include Windows.yml vars" + ansible.builtin.include_vars: + file: Windows.yml + +- name: "Windows: Remove windows defender feature on Windows Server OS" + ansible.windows.win_feature: + name: Windows-Defender + state: absent + register: win_feature_remove + when: "'Windows Server' in ansible_os_name" + +- name: "Block: Windows: Install SentinelOne agent" + when: not agent_installed + block: + - name: "Windows: Copy agent package to remote server" + ansible.windows.win_copy: + src: "{{ return_download_agent.original_message.full_path }}" + dest: "{{ remote_pkg_path }}" + mode: "0644" + + - name: "Windows: Install agent package {{ return_download_agent.original_message.filename }}" + ansible.windows.win_package: + path: "{{ remote_pkg_path }}" + creates_service: "SentinelAgent" + arguments: "{{ exe_parameters if pkg_format == 'exe' else msi_parameters }}" + register: installation_result + no_log: "{{ hide_sensitive }}" + when: pkg_format == "msi" or pkg_format == "exe" + + - name: "Windows: Wait for 15 seconds" + ansible.builtin.pause: + seconds: 15 + +- name: "Windows: Remove agent package from target machine" + ansible.windows.win_file: + path: "{{ remote_pkg_path }}" + state: absent + +- name: "Windows: Get service information" + ansible.windows.win_service_info: + name: SentinelAgent + register: sentinelagent_postinstall_service + +- name: "Windows: Fail when agent not started and/or not in auto start mode" + ansible.builtin.fail: "Sentinelone Agent is not started and/or not configured for automatic start. Service: {{ sentinelagent_postinstall_service }}" + when: sentinelagent_postinstall_service.services[0].state != "started" or sentinelagent_postinstall_service.services[0].start_mode != "auto" + +- name: "Windos: Reboot after agent installation or Windows Feature removal" + ansible.windows.win_reboot: + post_reboot_delay: 60 + when: (installation_result.changed | default(false) or win_feature_remove.reboot_required) and win_allow_reboot diff --git a/roles/install_agent/tasks/main.yml b/roles/install_agent/tasks/main.yml new file mode 100644 index 0000000..6f6cd91 --- /dev/null +++ b/roles/install_agent/tasks/main.yml @@ -0,0 +1,141 @@ +--- +# tasks file for install_agent +- name: "Gather facts" + ansible.builtin.setup: + when: ansible_facts.distribution is not defined + +- name: "Preflight check: Fail when OS is unknown" + ansible.builtin.fail: + msg: "The package management system on this system is unknown. This can happen if the role runs on an unsupported os." + when: pkg_format == 'unknown' + +- name: "Linux: Install python bindings for package managers" + ansible.builtin.package: + name: "{{ 'python3-rpm' if ansible_facts.os_family == 'Suse' else 'python-apt-common' }}" + when: ansible_facts.os_family == 'Suse' or ansible_facts.os_family == 'Debian' + +- name: "Linux: Gather package facts" + ansible.builtin.package_facts: + when: ansible_facts.packages is not defined and custom_os_family == 'Linux' + +- name: "Windows: Check if SentinelOne is already installed" + ansible.windows.win_service_info: + name: SentinelAgent + register: sentinelagent_service + when: custom_os_family == 'Windows' + +- name: "Set fact: agent_installed" + ansible.builtin.set_fact: + agent_installed: "{{ true if ansible_facts.packages.sentinelagent is defined or ansible_facts.packages.SentinelAgent is defined + or sentinelagent_service.exists | default(false) else false }}" + +- name: "Download {{ pkg_format }} agent {{ agent_version }} to localhost" + sentinelone_download_agent: + console_url: "{{ console_url }}" + token: "{{ api_token }}" + site: "{{ site }}" + os_type: "{{ custom_os_family }}" + packet_format: "{{ pkg_format }}" + architecture: "{{ pkg_arch }}" + agent_version: "{{ agent_version }}" + custom_version: "{{ custom_version | default(omit) }}" + register: return_download_agent + delegate_to: localhost + throttle: 1 + when: not agent_installed + +- name: "Block: Get registration token from API" + run_once: true + block: + - name: "Get siteid" + ansible.builtin.uri: + url: "{{ api_url }}sites?name={{ site }}&state=active" + method: GET + return_content: true + headers: + Accept: application/json + Authorization: "APIToken {{ api_token }}" + validate_certs: true + status_code: 200 + register: siteobj + delegate_to: localhost + no_log: "{{ hide_sensitive }}" + until: ((siteobj.json.data.sites | length) > 0) and (siteobj.status == 200) + retries: 3 + delay: 20 + + - name: "Extract siteid" + ansible.builtin.set_fact: + siteid: "{{ siteobj.json.data.sites[0].id }}" + + - name: "Get group id" + ansible.builtin.uri: + url: "{{ api_url }}groups?name={{ group }}&siteIds={{ siteid }}" + method: GET + return_content: true + headers: + Accept: application/json + Authorization: "APIToken {{ api_token }}" + validate_certs: true + status_code: 200 + register: groupobj + delegate_to: localhost + no_log: "{{ hide_sensitive }}" + until: ((groupobj.json.data | length) > 0) and (groupobj.status == 200) + retries: 3 + delay: 20 + when: group is defined + + - name: "Extract groupid" + ansible.builtin.set_fact: + groupid: "{{ groupobj.json.data[0].id }}" + when: group is defined + + - name: "Set endpoint URI to get the correct registration token" + ansible.builtin.set_fact: + reg_token_uri: "{{ \"groups/{{ groupid }}/token\" if group is defined else \"sites/{{ siteid }}/token\" }}" + + - name: "Get registration token" + ansible.builtin.uri: + url: "{{ api_url }}{{ reg_token_uri }}" + method: GET + return_content: true + headers: + Accept: application/json + Authorization: "APIToken {{ api_token }}" + validate_certs: true + status_code: 200 + register: reg_token_obj + delegate_to: localhost + no_log: "{{ hide_sensitive }}" + until: reg_token_obj.status == 200 + retries: 3 + delay: 20 + +- name: "Include tasks for: {{ custom_os_family }}" + ansible.builtin.include_tasks: "{{ custom_os_family }}.yml" + +- name: "Remove agent install package from localhost" + ansible.builtin.file: + path: "{{ return_download_agent.original_message.full_path }}" + state: absent + delegate_to: localhost + when: not agent_installed + + +- name: "Fail if new client does not appear in management console" + ansible.builtin.uri: + url: "{{ api_url }}agents?siteIds={{ siteid }}&computerName={{ ansible_hostname }}&isActive=true" + method: GET + return_content: true + headers: + Accept: application/json + Authorization: "APIToken {{ api_token }}" + validate_certs: true + status_code: 200 + register: registrationstatus + delegate_to: localhost + no_log: "{{ hide_sensitive }}" + until: ((registrationstatus.json.data | length) > 0) and (registrationstatus.status == 200) + retries: 3 + delay: 20 diff --git a/roles/install_agent/tasks/signed.yml b/roles/install_agent/tasks/signed.yml new file mode 100644 index 0000000..269739f --- /dev/null +++ b/roles/install_agent/tasks/signed.yml @@ -0,0 +1,12 @@ +--- +- name: Gather RPM package version + ansible.builtin.command: + cmd: "rpm -qp --queryformat '%{VERSION}' {{ remote_pkg_path }}" + register: sentinelone_client_rpm_version + changed_when: false + +- name: Set nodigest flag, if required + ansible.builtin.set_fact: + signed_package: false + when: + - "sentinelone_client_rpm_version.stdout is version('23.3.2.12', '<')" diff --git a/roles/install_agent/tests/inventory b/roles/install_agent/tests/inventory new file mode 100644 index 0000000..878877b --- /dev/null +++ b/roles/install_agent/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/roles/install_agent/tests/test.yml b/roles/install_agent/tests/test.yml new file mode 100644 index 0000000..932b878 --- /dev/null +++ b/roles/install_agent/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - install_agent diff --git a/roles/install_agent/vars/Linux.yml b/roles/install_agent/vars/Linux.yml new file mode 100644 index 0000000..fdd6c5a --- /dev/null +++ b/roles/install_agent/vars/Linux.yml @@ -0,0 +1,4 @@ +--- +remote_pkg_path: "/tmp/{{ return_download_agent.original_message.filename }}" +remote_gpg_key_path: "/tmp/sentinel_one.gpg" +signed_package: true diff --git a/roles/install_agent/vars/Windows.yml b/roles/install_agent/vars/Windows.yml new file mode 100644 index 0000000..e9bd31c --- /dev/null +++ b/roles/install_agent/vars/Windows.yml @@ -0,0 +1,4 @@ +--- +remote_pkg_path: "C:\\Windows\\Temp\\{{ return_download_agent.original_message.filename }}" +exe_parameters: "-t {{ reg_token_obj.json.data.token }} -q" +msi_parameters: "SITE_TOKEN={{ reg_token_obj.json.data.token }} /QUIET" diff --git a/roles/install_agent/vars/main.yml b/roles/install_agent/vars/main.yml new file mode 100644 index 0000000..d650dfd --- /dev/null +++ b/roles/install_agent/vars/main.yml @@ -0,0 +1,13 @@ +--- +# vars file for install_agent +pkg_format: "{% if ansible_facts.distribution in ['RedHat', 'CentOS', 'Fedora', 'SLES', 'Suse'] %}rpm{% + elif ansible_facts.distribution in ['Debian', 'Ubuntu'] %}deb{% + elif ansible_facts.os_family == 'Windows' and not win_use_exe %}msi{% + elif ansible_facts.os_family == 'Windows' and win_use_exe %}exe{% + else %}unknown{% endif %}" +pkg_arch: "{% if ansible_facts.architecture | regex_search('x86_64|64-bit') %}64_bit{% + elif ansible_facts.architecture | regex_search('i386|32-bit') %}32_bit{% + elif ansible_facts.architecture == 'aarch64' %}aarch64{% + else %}unknown{% endif %}" +custom_os_family: "{{ 'Windows' if pkg_format | regex_search('msi|exe') else 'Linux' }}" +api_url: "{{ console_url }}/web/api/v2.1/" diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt deleted file mode 120000 index f749b40..0000000 --- a/tests/sanity/ignore-2.13.txt +++ /dev/null @@ -1 +0,0 @@ -ignore.txt \ No newline at end of file