From bcc7ca0928acf5e996c3b724f2ec2b2c289371ef Mon Sep 17 00:00:00 2001 From: Jihan El Karz Date: Wed, 3 Jun 2026 18:33:15 +0200 Subject: [PATCH 1/5] feat(trend-micro) role and playbook --- CHANGELOG.md | 1 + COMPATIBILITY.md | 1 + playbooks/README.md | 7 + playbooks/all.yml | 1 + playbooks/trend_micro.yml | 23 ++ roles/trend_micro/README.md | 138 +++++++++++ roles/trend_micro/defaults/main.yml | 10 + roles/trend_micro/meta/argument_specs.yml | 108 +++++++++ roles/trend_micro/tasks/main.yml | 281 ++++++++++++++++++++++ 9 files changed, 570 insertions(+) create mode 100644 playbooks/trend_micro.yml create mode 100644 roles/trend_micro/README.md create mode 100644 roles/trend_micro/defaults/main.yml create mode 100644 roles/trend_micro/meta/argument_specs.yml create mode 100644 roles/trend_micro/tasks/main.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e63b911..115d9b58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* **role:trend_micro**: Add a role to install and activate the Trend Vision One Endpoint Security agent (Endpoint Sensor and Server & Workload Protection) on RHEL 9 and RHEL 10. * **role:mariadb_server**: Make `aria_pagecache_buffer_size`, `key_buffer_size` and `sort_buffer_size` configurable via the corresponding `mariadb_server__cnf_*` variables. * **plugin:platform_select**: New filter plugin for selecting a value from a platform-keyed dictionary by OS family / distribution / version. * **role:alternatives**: Support managing `subcommands` (slaves/followers) and the Red Hat-only `family` grouping. The role now also ensures the alternatives tooling is installed (`chkconfig` on RHEL 8, `alternatives` on RHEL 9/10; bundled with `dpkg` on Debian/Ubuntu), and can be included without variables as a no-op. diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 132f6645..b14a9191 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -158,6 +158,7 @@ Which Ansible role is proven to run on which OS? | telegraf | | | x | (x) | (x) | | | | | | timezone | (x) | (x) | x | x | x | (x) | (x) | (x) | Fedora 35 | | tools | | | x | x | x | | | | Fedora | +| trend_micro | | | x | x | x | | | | | | unattended_upgrades | (x) | (x) | | | | (x) | (x) | (x) | | | uptimerobot | | | | | | | | | controller-side, talks to UptimeRobot API | | vsftpd | | | x | (x) | (x) | | | | | diff --git a/playbooks/README.md b/playbooks/README.md index 1babb9b2..4d1d3e9a 100644 --- a/playbooks/README.md +++ b/playbooks/README.md @@ -1347,6 +1347,13 @@ Calls the following roles (in order): * [tools](https://github.com/Linuxfabrik/lfops/tree/main/roles/tools) +## trend_micro.yml + +Calls the following roles (in order): + +* [trend_micro](https://github.com/Linuxfabrik/lfops/tree/main/roles/trend_micro) + + ## unattended_upgrades.yml Calls the following roles (in order): diff --git a/playbooks/all.yml b/playbooks/all.yml index 5d66638c..3fa0b060 100644 --- a/playbooks/all.yml +++ b/playbooks/all.yml @@ -141,6 +141,7 @@ - import_playbook: 'telegraf.yml' - import_playbook: 'timezone.yml' - import_playbook: 'tools.yml' +- import_playbook: 'trend_micro.yml' - import_playbook: 'unattended_upgrades.yml' - import_playbook: 'vsftpd.yml' - import_playbook: 'yum_utils.yml' diff --git a/playbooks/trend_micro.yml b/playbooks/trend_micro.yml new file mode 100644 index 00000000..12e2177e --- /dev/null +++ b/playbooks/trend_micro.yml @@ -0,0 +1,23 @@ +- name: 'Playbook linuxfabrik.lfops.trend_micro' + hosts: + - 'lfops_trend_micro' + + pre_tasks: + - ansible.builtin.import_role: + name: 'shared' + tasks_from: 'log-start.yml' + tags: + - 'always' + + + roles: + + - role: 'linuxfabrik.lfops.trend_micro' + + + post_tasks: + - ansible.builtin.import_role: + name: 'shared' + tasks_from: 'log-end.yml' + tags: + - 'always' diff --git a/roles/trend_micro/README.md b/roles/trend_micro/README.md new file mode 100644 index 00000000..560b02aa --- /dev/null +++ b/roles/trend_micro/README.md @@ -0,0 +1,138 @@ +# Ansible Role linuxfabrik.lfops.trend_micro + +This role installs and activates the [Trend Vision One Endpoint Security](https://www.trendmicro.com/en_us/business/products/hybrid-cloud/vision-one-endpoint-security.html) agent (`v1es`) on Linux servers. It covers pre-flight checks, agent download and installation via the XBC API, agent registration, and Server & Workload Protection (SWP) activation. + + +*Available in the next LFOps release.* + + +## Background: Two Products, One Host + +Trend Vision One Endpoint Security combines two products on the same host, and this role configures both: + +* **Endpoint Sensor** (XDR/EDR) is the `tmxbc` / Endpoint Basecamp agent. It is downloaded and installed through the XBC API. It is configured via `customer_id`, the per-architecture `company_id` and `scenario_ids`, `xbc_fqdn`, and `xbc_env`. +* **Server & Workload Protection (SWP)** is the Deep Security agent (`ds_agent`). It ships inside the same full package and is activated against a Deep Security Manager (DSM). It is configured via `tenant_id`, `token`, `swp_dsm_fqdn`, and the optional `policy_id`, `group_id`, and `relay_group_id`. + +The **Server & Workload Protection (SWP) deployment script** generated in the Vision One console contains every value the role needs. It bundles both products in one full package: it lists two scenario IDs (one for the Endpoint Sensor, one for SWP) and carries the SWP activation values. The role installs that package, waits for the SWP agent to come up, and then activates it against the DSM. + +> **Use the SWP deployment script, not the Endpoint-Sensor-only one.** The Endpoint-Sensor-only script lists a single scenario ID and none of the SWP activation values (tenant ID, token, DSM FQDN), so the SWP agent (`ds_agent`) cannot be installed. + + +## Generating the Deployment Script + +1. In the Vision One console, make sure a **Server & Workload Protection** product instance exists. If not, create it under **Service Management → Create product instance → Server & Workload Protection**. +2. Navigate to **Endpoint Security → Endpoint Inventory → Agent Installer → Deployment Script**. +3. Select **Server & Workload Protection** as the endpoint group and **Linux** as the operating system. +4. The previewed script contains every value the role needs. + +The script is a `bash` installer. Map its values to the role variables as follows (the script has separate `x86_64` and `aarch64` blocks, so `company_id` and `scenario_ids` differ per architecture): + +| Role variable | Where it appears in the deployment script | +| ------------- | ----------------------------------------- | +| `trend_micro__customer_id` | `CUSTOMER_ID="..."` | +| `trend_micro__group_id` | `GROUP_ID=...` (optional, defaults to `0`) | +| `trend_micro__policy_id` | `POLICY_ID=...` (optional, defaults to `0`) | +| `trend_micro__relay_group_id` | `RELAY_GROUP_ID=...` (optional, defaults to `0`) | +| `trend_micro__swp_dsm_fqdn` | the host in `dsa_control -a dsm://:443/` | +| `trend_micro__swp_login.tenant_id` | `tenantID:...` in the `dsa_control` activation line | +| `trend_micro__swp_login.token` | `token:...` in the `dsa_control` activation line | +| `trend_micro__xbc_env` | `XBC_ENV="..."` (optional, defaults to `prod-eu1`) | +| `trend_micro__xbc_fqdn` | `XBC_FQDN="..."` | +| `trend_micro__xbc_installer__company_id` | `company_id` in the `HTTP_BODY` of the matching `archType` block | +| `trend_micro__xbc_installer__scenario_ids` | `scenario_ids` in the `HTTP_BODY` of the matching `archType` block (both IDs) | + + +## Relevant Information + +Learn about Trend Micro Concepts and Products: https://docs.linuxfabrik.ch/software/trend-micro.html +Agent Platform Compability: https://help.deepsecurity.trendmicro.com/20_0/on-premise/agent-compatibility.html + +## Mandatory Requirements + +* At least 2 GB of RAM. The Server & Workload Protection agent (`ds_agent`) enforces this minimum during its pre-check; on a host with less memory the pre-check fails and `ds_agent` is never installed. +* Running as `root` (use `become: true`). +* `/tmp` must be writable on the target host. + +Supported architectures: `x86_64`, `aarch64`. + + +## Tags + +| Tag | What it does | Reload / Restart | +| --- | ------------ | ---------------- | +| `trend_micro` | Runs all tasks | - | +| `trend_micro:install` | Installs `curl` and `tar` only | - | + + +## Mandatory Role Variables + +| Variable | Description | +| -------- | ----------- | +| `trend_micro__customer_id` | Customer ID used in the installer API request. Found in the Vision One deployment script preview. | +| `trend_micro__xbc_installer_x86_64_company_id` | Company ID for the x86\_64 installer API request. | +| `trend_micro__xbc_installer_x86_64_scenario_ids` | List of scenario IDs for the x86\_64 installer API request. Include all IDs from the deployment script (endpoint sensor and SWP).| +| `trend_micro__xbc_installer_aarch64_company_id` | Company ID for the aarch64 installer API request. | +| `trend_micro__xbc_installer_aarch64_scenario_ids` | List of scenario IDs for the aarch64 installer API request. Include all IDs from the deployment script (endpoint sensor and SWP).| +| `trend_micro__swp_dsm_fqdn` | Deep Security Manager FQDN used to activate Server & Workload Protection. | +| `trend_micro__swp_login` | Server & Workload Protection activation credentials. Dictionary with the keys `tenant_id` and `token`. Typically fed by `linuxfabrik.lfops.bitwarden_item`. | + +Example: +```yaml +# mandatory +trend_micro__customer_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + +trend_micro__xbc_installer_x86_64_company_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' +trend_micro__xbc_installer_x86_64_scenario_ids: + - '' + - '' + +trend_micro__xbc_installer_aarch64_company_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' +trend_micro__xbc_installer_aarch64_scenario_ids: + - '' + - '' + +trend_micro__swp_dsm_fqdn: 'agents.workload.de-1.cloudone.trendmicro.com' +trend_micro__swp_login: + tenant_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' +``` + + +## Optional Role Variables + +| Variable | Description | Default Value | +| -------- | ----------- | ------------- | +| `trend_micro__xbc_fqdn` | XBC backend FQDN. Use the FQDN matching your region. | `'api-eu1.xbc.trendmicro.com'` | +| `trend_micro__xbc_env` | XBC environment identifier. | `'prod-eu1'` | +| `trend_micro__policy_id` | Security policy to apply. `0` means no specific policy. | `0` | +| `trend_micro__group_id` | Agent group for organisation. | `0` | +| `trend_micro__relay_group_id` | Relay group for agent communication. | `0` | +| `trend_micro__proxy_addr` | Proxy hostname or IP. Leave empty for a direct connection. | `''` | +| `trend_micro__proxy_port` | Proxy port. | `''` | +| `trend_micro__proxy_login` | Proxy credentials. Dictionary with the keys `username` and `password`. Omit for a proxy that needs no authentication. | `{}` | +| `trend_micro__path_dsa` | Path to the `dsa_control` binary. | `'/opt/ds_agent/dsa_control'` | +| `trend_micro__path_identity_file` | Path polled to confirm agent registration. | `'/opt/TrendMicro/EndpointBasecamp/etc/.identity'` | + +Example: +```yaml +# optional +trend_micro__xbc_fqdn: 'api-us1.xbc.trendmicro.com' +trend_micro__policy_id: 42 +trend_micro__group_id: 10 +trend_micro__relay_group_id: 5 +trend_micro__proxy_addr: '192.0.2.30' +trend_micro__proxy_port: '8080' +trend_micro__proxy_login: + username: 'proxyuser' + password: 'linuxfabrik' +``` + + +## License + +[The Unlicense](https://unlicense.org/) + + +## Author Information + +[Linuxfabrik GmbH, Zurich](https://www.linuxfabrik.ch) diff --git a/roles/trend_micro/defaults/main.yml b/roles/trend_micro/defaults/main.yml new file mode 100644 index 00000000..01ec8398 --- /dev/null +++ b/roles/trend_micro/defaults/main.yml @@ -0,0 +1,10 @@ +trend_micro__group_id: 0 +trend_micro__path_dsa: '/opt/ds_agent/dsa_control' +trend_micro__path_identity_file: '/opt/TrendMicro/EndpointBasecamp/etc/.identity' +trend_micro__policy_id: 0 +trend_micro__proxy_addr: '' +trend_micro__proxy_login: {} +trend_micro__proxy_port: '' +trend_micro__relay_group_id: 0 +trend_micro__xbc_env: 'prod-eu1' +trend_micro__xbc_fqdn: 'api-eu1.xbc.trendmicro.com' diff --git a/roles/trend_micro/meta/argument_specs.yml b/roles/trend_micro/meta/argument_specs.yml new file mode 100644 index 00000000..a175d772 --- /dev/null +++ b/roles/trend_micro/meta/argument_specs.yml @@ -0,0 +1,108 @@ +argument_specs: + main: + options: + + trend_micro__customer_id: + type: 'str' + required: true + description: 'Customer ID used in the installer API request.' + + trend_micro__group_id: + type: 'int' + required: false + default: 0 + description: 'Agent group for organisation. 0 means no specific choice.' + + trend_micro__path_dsa: + type: 'str' + required: false + default: '/opt/ds_agent/dsa_control' + description: 'Path to the dsa_control binary.' + + trend_micro__path_identity_file: + type: 'str' + required: false + default: '/opt/TrendMicro/EndpointBasecamp/etc/.identity' + description: 'Path polled to confirm agent registration.' + + trend_micro__policy_id: + type: 'int' + required: false + default: 0 + description: 'Security policy to apply. 0 means no specific choice.' + + trend_micro__proxy_addr: + type: 'str' + required: false + default: '' + description: 'Proxy hostname or IP. Leave empty for a direct connection.' + + trend_micro__proxy_login: + type: 'dict' + required: false + default: {} + description: >- + Proxy credentials. May contain the keys `username` and `password`. + Typically fed by `linuxfabrik.lfops.bitwarden_item`, which returns the + full Bitwarden item; extra keys are ignored. Omit for a proxy that + needs no authentication. + + trend_micro__proxy_port: + type: 'str' + required: false + default: '' + description: 'Proxy port.' + + trend_micro__relay_group_id: + type: 'int' + required: false + default: 0 + description: 'Relay group for agent communication. 0 means no specific choice.' + + trend_micro__swp_dsm_fqdn: + type: 'str' + required: true + description: 'Deep Security Manager FQDN used to activate Server & Workload Protection.' + + trend_micro__swp_login: + type: 'dict' + required: true + description: >- + Server & Workload Protection activation credentials. Must contain the + keys `tenant_id` and `token`. Typically fed by + `linuxfabrik.lfops.bitwarden_item`, which returns the full Bitwarden + item; extra keys are ignored. + + trend_micro__xbc_env: + type: 'str' + required: false + default: 'prod-eu1' + description: 'XBC environment identifier.' + + trend_micro__xbc_fqdn: + type: 'str' + required: false + default: 'api-eu1.xbc.trendmicro.com' + description: 'XBC backend FQDN. Use the FQDN matching your region.' + + trend_micro__xbc_installer_aarch64_company_id: + type: 'str' + required: true + description: 'Company ID for the aarch64 installer API request.' + + trend_micro__xbc_installer_aarch64_scenario_ids: + type: 'list' + elements: 'str' + required: true + description: 'List of scenario IDs for the aarch64 installer API request.' + + trend_micro__xbc_installer_x86_64_company_id: + type: 'str' + required: true + description: 'Company ID for the x86_64 installer API request.' + + trend_micro__xbc_installer_x86_64_scenario_ids: + type: 'list' + elements: 'str' + required: true + description: 'List of scenario IDs for the x86_64 installer API request.' diff --git a/roles/trend_micro/tasks/main.yml b/roles/trend_micro/tasks/main.yml new file mode 100644 index 00000000..4f0e4ab4 --- /dev/null +++ b/roles/trend_micro/tasks/main.yml @@ -0,0 +1,281 @@ +- block: + + - name: 'stat /tmp' + ansible.builtin.stat: + path: '/tmp' + register: 'trend_micro__tmp_stat' + + - name: 'assert /tmp is writable' + ansible.builtin.assert: + that: + - 'trend_micro__tmp_stat.stat.writeable' + fail_msg: '/tmp is not writable. Please check the permissions of /tmp.' + + # ds_agent enforces this minimum during its own pre-check + - name: 'assert at least 2 GB of RAM (Server & Workload Protection requirement)' + ansible.builtin.assert: + that: + - 'ansible_facts.memtotal_mb | int >= 2048' + fail_msg: >- + Server & Workload Protection requires at least 2 GB of RAM; + this host has {{ ansible_facts.memtotal_mb }} MB. ds_agent will not install. + + tags: + - 'trend_micro' + + +- block: + + - name: 'package_facts' + ansible.builtin.package_facts: + check_mode: false + + - name: 'install curl' + ansible.builtin.package: + name: 'curl' + state: 'present' + + - name: 'install tar' + ansible.builtin.package: + name: 'tar' + state: 'present' + + tags: + - 'trend_micro:install' + + +- block: + + - name: 'Set proxy and connection config facts (proxy with username and password)' + ansible.builtin.set_fact: + trend_micro__proxy_env: + HTTP_PROXY: 'http://{{ trend_micro__proxy_login["username"] }}:{{ trend_micro__proxy_login["password"] }}@{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}/' + HTTPS_PROXY: 'http://{{ trend_micro__proxy_login["username"] }}:{{ trend_micro__proxy_login["password"] }}@{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}/' + trend_micro__proxy_config: '{{ (trend_micro__proxy_login["username"] + ":" + trend_micro__proxy_login["password"]) | b64encode }}@{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}' + trend_micro__connect_config: '{{ {"fps": [{"connections": [{"type": "USER_INPUT"}]}]} | to_json | b64encode }}' + when: + - 'trend_micro__proxy_addr | length > 0' + - 'trend_micro__proxy_login["username"] | d("") | length > 0' + - 'trend_micro__proxy_login["password"] | d("") | length > 0' + + - name: 'Set proxy and connection config facts (proxy with username only)' + ansible.builtin.set_fact: + trend_micro__proxy_env: + HTTP_PROXY: 'http://{{ trend_micro__proxy_login["username"] }}@{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}/' + HTTPS_PROXY: 'http://{{ trend_micro__proxy_login["username"] }}@{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}/' + trend_micro__proxy_config: '{{ (trend_micro__proxy_login["username"] + ":") | b64encode }}@{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}' + trend_micro__connect_config: '{{ {"fps": [{"connections": [{"type": "USER_INPUT"}]}]} | to_json | b64encode }}' + when: + - 'trend_micro__proxy_addr | length > 0' + - 'trend_micro__proxy_login["username"] | d("") | length > 0' + - 'trend_micro__proxy_login["password"] | d("") | length == 0' + + - name: 'Set proxy and connection config facts (proxy without credentials)' + ansible.builtin.set_fact: + trend_micro__proxy_env: + HTTP_PROXY: 'http://{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}/' + HTTPS_PROXY: 'http://{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}/' + trend_micro__proxy_config: '{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}' + trend_micro__connect_config: '{{ {"fps": [{"connections": [{"type": "USER_INPUT"}]}]} | to_json | b64encode }}' + when: + - 'trend_micro__proxy_addr | length > 0' + - 'trend_micro__proxy_login["username"] | d("") | length == 0' + + - name: 'Set connection config facts (direct connection)' + ansible.builtin.set_fact: + trend_micro__proxy_env: {} + trend_micro__proxy_config: '' + trend_micro__connect_config: '{{ {"fps": [{"connections": [{"type": "DIRECT_CONNECT"}]}]} | to_json | b64encode }}' + when: 'trend_micro__proxy_addr | length == 0' + + tags: + - 'trend_micro' + +# in original script, this part below is being done by a platform detection script https://api-eu1.xbc.trendmicro.com/apk/platform-detection-script +- block: + + - name: 'Set x86_64 installer facts' + ansible.builtin.set_fact: + trend_micro__installer_body: + company_id: '{{ trend_micro__xbc_installer_x86_64_company_id }}' + platform: 'linux64' + scenario_ids: '{{ trend_micro__xbc_installer_x86_64_scenario_ids }}' + trend_micro__xbc_agent_token: "{{ trend_micro__xbc_installer_x86_64_scenario_ids | join('|') }}" + when: "ansible_architecture == 'x86_64'" + + - name: 'Set aarch64 installer facts' + ansible.builtin.set_fact: + trend_micro__installer_body: + company_id: '{{ trend_micro__xbc_installer_aarch64_company_id }}' + platform: 'linuxaarch64' + scenario_ids: '{{ trend_micro__xbc_installer_aarch64_scenario_ids }}' + trend_micro__xbc_agent_token: "{{ trend_micro__xbc_installer_aarch64_scenario_ids | join('|') }}" + when: "ansible_architecture == 'aarch64'" + + - name: 'assert supported architecture' + ansible.builtin.assert: + that: "ansible_architecture in ['x86_64', 'aarch64']" + fail_msg: 'Architecture {{ ansible_architecture }} is not supported.' + + tags: + - 'trend_micro' + + +- block: + + - name: 'file state=absent /tmp/v1es_installer.tgz # force a fresh download' + ansible.builtin.file: + path: '/tmp/v1es_installer.tgz' + state: 'absent' + + - name: 'download installer to /tmp/v1es_installer.tgz' + ansible.builtin.uri: + url: 'https://{{ trend_micro__xbc_fqdn }}/apk/installer' + method: 'POST' + headers: + X-Customer-Id: '{{ trend_micro__customer_id }}' + body: '{{ trend_micro__installer_body }}' + body_format: 'json' + dest: '/tmp/v1es_installer.tgz' + follow_redirects: 'all' # the API answers with 303, then 200 + status_code: + - 200 + environment: '{{ trend_micro__proxy_env }}' + + tags: + - 'trend_micro' + + +- block: + + - name: 'file state=directory /tmp/v1es' + ansible.builtin.file: + path: '/tmp/v1es' + state: 'directory' + mode: 0o755 + + - name: 'unarchive /tmp/v1es_installer.tgz to /tmp/v1es' + ansible.builtin.unarchive: + src: '/tmp/v1es_installer.tgz' + dest: '/tmp/v1es' + remote_src: true + + - name: 'copy /tmp/v1es/.property' + ansible.builtin.copy: + content: '{{ {"xbc_env": trend_micro__xbc_env, "xbc_agent_token": trend_micro__xbc_agent_token, "full_package": true} | to_json }}' + dest: '/tmp/v1es/.property' + mode: 0o644 + + tags: + - 'trend_micro' + + +- block: + + # tmxbc installs the Endpoint Basecamp agent on a fresh install that Basecamp socket may not exist yet, so the command exits non-zero + - name: '/tmp/v1es/tmxbc install --proxiesWithCred ... --connection ... (proxy)' + ansible.builtin.command: + argv: + - '/tmp/v1es/tmxbc' + - 'install' + - '--proxiesWithCred' + - '{{ trend_micro__proxy_config }}' + - '--connection' + - '{{ trend_micro__connect_config }}' + environment: '{{ trend_micro__proxy_env }}' + register: 'trend_micro__install_result' + until: 'trend_micro__install_result.rc == 0' + retries: 6 + delay: 10 + when: "trend_micro__proxy_config | length > 0" + + - name: '/tmp/v1es/tmxbc install --connection ... (direct)' + ansible.builtin.command: + argv: + - '/tmp/v1es/tmxbc' + - 'install' + - '--connection' + - '{{ trend_micro__connect_config }}' + register: 'trend_micro__install_result' + until: 'trend_micro__install_result.rc == 0' + retries: 6 + delay: 10 + when: "trend_micro__proxy_config | length == 0" + + tags: + - 'trend_micro' + + +- block: + + - name: 'wait_for path={{ trend_micro__path_identity_file }} # agent registration' + ansible.builtin.wait_for: + path: '{{ trend_micro__path_identity_file }}' + timeout: 300 + + tags: + - 'trend_micro' + + +# the SWP agent (ds_agent) ships inside the XBC full package, so it is deployed by tmxbc; wait for its service to come up and initialize, then activate it +- block: + + - name: 'systemctl is-active ds_agent # wait for SWP to install and start' + ansible.builtin.command: + cmd: 'systemctl is-active ds_agent' + register: 'trend_micro__ds_agent_status' + until: 'trend_micro__ds_agent_status.rc == 0' + retries: 60 + delay: 10 + changed_when: false + check_mode: false + + - name: 'wait_for path=/var/opt/ds_agent/dsa_core/ds_agent.crt # SWP initialization' + ansible.builtin.wait_for: + path: '/var/opt/ds_agent/dsa_core/ds_agent.crt' + timeout: 300 + + tags: + - 'trend_micro' + + +# the full package installs ds_agent but does not self-activate it against the SWP DSM; activation must be done explicitly with the tenant credentials, mirroring the vendor deployment script which always runs `dsa_control -a` +- block: + + - name: 'pause seconds=15 # let ds_agent finish initializing before activation' + ansible.builtin.pause: + seconds: 15 + + - name: '{{ trend_micro__path_dsa }} -a dsm://{{ trend_micro__swp_dsm_fqdn }}:443/ ... # activate SWP' + ansible.builtin.command: + argv: + - '{{ trend_micro__path_dsa }}' + - '-a' + - 'dsm://{{ trend_micro__swp_dsm_fqdn }}:443/' + - 'tenantID:{{ trend_micro__swp_login["tenant_id"] }}' + - 'token:{{ trend_micro__swp_login["token"] }}' + - 'policyid:{{ trend_micro__policy_id }}' + - 'relaygroupid:{{ trend_micro__relay_group_id }}' + - 'groupid:{{ trend_micro__group_id }}' + register: 'trend_micro__activation_result' + # ds_agent needs a moment after install before it can talk to the DSM + until: >- + '200' in trend_micro__activation_result.stdout or + 'reset agent first' in trend_micro__activation_result.stdout + retries: 12 + delay: 10 + changed_when: "'200' in trend_micro__activation_result.stdout" + failed_when: false + + - name: 'assert SWP is activated' + ansible.builtin.assert: + that: + - >- + '200' in trend_micro__activation_result.stdout or + 'reset agent first' in trend_micro__activation_result.stdout + fail_msg: >- + Server & Workload Protection activation failed. Output: + {{ trend_micro__activation_result.stdout | default('n/a') }}. + + tags: + - 'trend_micro' From 3875b3e95700c63ced18f36d278cad1be4f5aa4c Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Thu, 18 Jun 2026 09:36:52 +0200 Subject: [PATCH 2/5] feat(roles/trend_micro): refine role, docs and proxy guidance - simplify tasks and argument_specs, drop unused defaults - import global-variables in the playbook - consistent assert task naming and list-form `that:` - document global HTTP proxy setup in the repo README --- CHANGELOG.md | 2 +- README.md | 14 + playbooks/trend_micro.yml | 6 + roles/trend_micro/README.md | 201 +++++++----- roles/trend_micro/defaults/main.yml | 5 - roles/trend_micro/meta/argument_specs.yml | 55 +--- roles/trend_micro/tasks/main.yml | 289 ++++++------------ .../templates/tmp/v1es/.property.j2 | 2 + 8 files changed, 253 insertions(+), 321 deletions(-) create mode 100644 roles/trend_micro/templates/tmp/v1es/.property.j2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a57b70f..a0d075f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -* **role:trend_micro**: Add a role to install and activate the Trend Vision One Endpoint Security agent (Endpoint Sensor and Server & Workload Protection) on RHEL 9 and RHEL 10. +* **role:trend_micro**: Add a role to install and activate the Trend Vision One Endpoint Security agent (Endpoint Sensor and Server & Workload Protection). * **role:matomo_import_logs**: New role that imports Apache access logs into Matomo on a schedule, one systemd timer per site, and ships the Matomo log-analytics import script (`import_logs.py`). The `token_auth` is provided via a per-site auth file instead of the command line (passing `--token-auth`, `--login` or `--password` is deprecated, since they are visible in the process list and now log a deprecation warning). The script also supports the Traefik access-log format and fixes a possible endless loop when reading a config file. * **role:glances**: Add RHEL 10 / Rocky 10 / Alma 10 support by installing glances into a Python venv via the `python_venv` role, since the package is not available in EPEL 10. RHEL 10 is now marked proven (`x`) in COMPATIBILITY. * **role:graylog_datanode**: Add `graylog_datanode__http_publish_uri` to set the REST API URI the DataNode advertises, needed when the bind address is not directly reachable (multiple interfaces, a NAT gateway, or a `0.0.0.0` bind address). diff --git a/README.md b/README.md index a4d6abad..99cb38b9 100644 --- a/README.md +++ b/README.md @@ -463,6 +463,20 @@ ansible-user ALL=(ALL) NOPASSWD: ALL ``` +**Q: How do I route a role's outbound traffic through an HTTP proxy?** + +Some roles download installers or contact external APIs directly from the managed node. LFOps does not expose per-role proxy variables. Instead, set the proxy globally on the target in `/etc/environment`; the variables are picked up by tasks running over SSH, including key-based logins: + +``` +HTTP_PROXY=http://192.0.2.30:8080/ +HTTPS_PROXY=http://192.0.2.30:8080/ +http_proxy=http://192.0.2.30:8080/ +https_proxy=http://192.0.2.30:8080/ +``` + +Set `NO_PROXY` / `no_proxy` (comma-separated hosts, domains or CIDRs) for destinations that must be reached directly. + + **Q: How do I find out which playbooks ran against a host?** All playbooks log every run to `/var/log/linuxfabrik-lfops.log` on the target host: diff --git a/playbooks/trend_micro.yml b/playbooks/trend_micro.yml index 12e2177e..5c204c2e 100644 --- a/playbooks/trend_micro.yml +++ b/playbooks/trend_micro.yml @@ -9,6 +9,12 @@ tags: - 'always' + - ansible.builtin.import_role: + name: 'shared' + tasks_from: 'global-variables.yml' + tags: + - 'always' + roles: diff --git a/roles/trend_micro/README.md b/roles/trend_micro/README.md index 560b02aa..d072068c 100644 --- a/roles/trend_micro/README.md +++ b/roles/trend_micro/README.md @@ -6,48 +6,34 @@ This role installs and activates the [Trend Vision One Endpoint Security](https: *Available in the next LFOps release.* -## Background: Two Products, One Host +## How the Role Behaves Trend Vision One Endpoint Security combines two products on the same host, and this role configures both: -* **Endpoint Sensor** (XDR/EDR) is the `tmxbc` / Endpoint Basecamp agent. It is downloaded and installed through the XBC API. It is configured via `customer_id`, the per-architecture `company_id` and `scenario_ids`, `xbc_fqdn`, and `xbc_env`. -* **Server & Workload Protection (SWP)** is the Deep Security agent (`ds_agent`). It ships inside the same full package and is activated against a Deep Security Manager (DSM). It is configured via `tenant_id`, `token`, `swp_dsm_fqdn`, and the optional `policy_id`, `group_id`, and `relay_group_id`. +* Endpoint Sensor (XDR/EDR) is the Endpoint Basecamp agent (`tmxbc`). It is downloaded and installed through the XBC API. +* Server & Workload Protection (SWP) is the Deep Security agent (`ds_agent`). It ships inside the same full package and is activated against a Deep Security Manager (DSM). -The **Server & Workload Protection (SWP) deployment script** generated in the Vision One console contains every value the role needs. It bundles both products in one full package: it lists two scenario IDs (one for the Endpoint Sensor, one for SWP) and carries the SWP activation values. The role installs that package, waits for the SWP agent to come up, and then activates it against the DSM. +The "Server & Workload Protection (SWP)" deployment script generated in the Vision One console contains every value the role needs. It bundles both products in one full package: it lists two scenario IDs (one for the Endpoint Sensor, one for SWP) and carries the SWP activation values. The role installs that package, waits for the SWP agent to come up, and then activates it against the DSM. -> **Use the SWP deployment script, not the Endpoint-Sensor-only one.** The Endpoint-Sensor-only script lists a single scenario ID and none of the SWP activation values (tenant ID, token, DSM FQDN), so the SWP agent (`ds_agent`) cannot be installed. +Further reading: -## Generating the Deployment Script +* Trend Micro concepts and products: https://docs.linuxfabrik.ch/software/trend-micro.html +* Agent platform compatibility: https://help.deepsecurity.trendmicro.com/20_0/on-premise/agent-compatibility.html -1. In the Vision One console, make sure a **Server & Workload Protection** product instance exists. If not, create it under **Service Management → Create product instance → Server & Workload Protection**. -2. Navigate to **Endpoint Security → Endpoint Inventory → Agent Installer → Deployment Script**. -3. Select **Server & Workload Protection** as the endpoint group and **Linux** as the operating system. -4. The previewed script contains every value the role needs. -The script is a `bash` installer. Map its values to the role variables as follows (the script has separate `x86_64` and `aarch64` blocks, so `company_id` and `scenario_ids` differ per architecture): +## Known Limitations -| Role variable | Where it appears in the deployment script | -| ------------- | ----------------------------------------- | -| `trend_micro__customer_id` | `CUSTOMER_ID="..."` | -| `trend_micro__group_id` | `GROUP_ID=...` (optional, defaults to `0`) | -| `trend_micro__policy_id` | `POLICY_ID=...` (optional, defaults to `0`) | -| `trend_micro__relay_group_id` | `RELAY_GROUP_ID=...` (optional, defaults to `0`) | -| `trend_micro__swp_dsm_fqdn` | the host in `dsa_control -a dsm://:443/` | -| `trend_micro__swp_login.tenant_id` | `tenantID:...` in the `dsa_control` activation line | -| `trend_micro__swp_login.token` | `token:...` in the `dsa_control` activation line | -| `trend_micro__xbc_env` | `XBC_ENV="..."` (optional, defaults to `prod-eu1`) | -| `trend_micro__xbc_fqdn` | `XBC_FQDN="..."` | -| `trend_micro__xbc_installer__company_id` | `company_id` in the `HTTP_BODY` of the matching `archType` block | -| `trend_micro__xbc_installer__scenario_ids` | `scenario_ids` in the `HTTP_BODY` of the matching `archType` block (both IDs) | - - -## Relevant Information +The role does not configure an HTTP/HTTPS proxy. On hosts that reach the Trend Micro backends only through a proxy, set the proxy globally on the target so both the installer download and `tmxbc` pick it up, for example in `/etc/environment`: +```bash +HTTP_PROXY=http://192.0.2.30:8080/ +HTTPS_PROXY=http://192.0.2.30:8080/ +http_proxy=http://192.0.2.30:8080/ +https_proxy=http://192.0.2.30:8080/ +``` -Learn about Trend Micro Concepts and Products: https://docs.linuxfabrik.ch/software/trend-micro.html -Agent Platform Compability: https://help.deepsecurity.trendmicro.com/20_0/on-premise/agent-compatibility.html -## Mandatory Requirements +## Requirements * At least 2 GB of RAM. The Server & Workload Protection agent (`ds_agent`) enforces this minimum during its pre-check; on a host with less memory the pre-check fails and `ds_agent` is never installed. * Running as `root` (use `become: true`). @@ -55,76 +41,145 @@ Agent Platform Compability: https://help.deepsecurity.trendmicro.com/20_0/on-pre Supported architectures: `x86_64`, `aarch64`. +Manual steps: + +* Generate the **Server & Workload Protection (SWP)** deployment script in the Vision One console and read the role variable values from it. This is mandatory: the script is the only source of the customer ID, company ID, scenario IDs and SWP activation values the role needs. + **Use the SWP deployment script, not the Endpoint-Sensor-only one.** The Endpoint-Sensor-only script lists a single scenario ID and none of the SWP activation values (tenant ID, token, DSM FQDN), so the SWP agent (`ds_agent`) cannot be installed. + + 1. In the Vision One console, make sure a "Server & Workload Protection" product instance exists. If not, create it under Service Management -> Create product instance -> Server & Workload Protection. + 2. Navigate to Endpoint Security -> Endpoint Inventory -> Agent Installer -> Deployment Script. + 3. Select "Server & Workload Protection" as the endpoint group and "Linux" as the operating system. + 4. The previewed script contains every value the role needs. + +The script is a `bash` installer. Map its values to the role variables as follows (the script has separate `x86_64` and `aarch64` blocks; `company_id` is identical in both, only `scenario_ids` differ per architecture): + +| Role variable | Where it appears in the deployment script | +| ------------- | ----------------------------------------- | +| `trend_micro__customer_id` | `CUSTOMER_ID="..."` | +| `trend_micro__group_id` | `GROUP_ID=...` (optional, defaults to `0`) | +| `trend_micro__policy_id` | `POLICY_ID=...` (optional, defaults to `0`) | +| `trend_micro__relay_group_id` | `RELAY_GROUP_ID=...` (optional, defaults to `0`) | +| `trend_micro__swp_dsm_fqdn` | the host in `dsa_control -a dsm://:443/` | +| `trend_micro__swp_login.tenant_id` | `tenantID:...` in the `dsa_control` activation line | +| `trend_micro__swp_login.token` | `token:...` in the `dsa_control` activation line | +| `trend_micro__xbc_env` | `XBC_ENV="..."` (optional, defaults to `prod-eu1`) | +| `trend_micro__xbc_fqdn` | `XBC_FQDN="..."` | +| `trend_micro__xbc_installer_company_id` | `company_id` in the `HTTP_BODY` (identical in both `archType` blocks) | +| `trend_micro__xbc_installer__scenario_ids` | `scenario_ids` in the `HTTP_BODY` of the matching `archType` block (both IDs) | + ## Tags -| Tag | What it does | Reload / Restart | -| --- | ------------ | ---------------- | -| `trend_micro` | Runs all tasks | - | -| `trend_micro:install` | Installs `curl` and `tar` only | - | +`trend_micro` + +* Runs the pre-flight checks (supported architecture, scenario IDs, writable `/tmp`, minimum RAM). +* Installs the required packages (`curl`, `tar`). +* Downloads the installer from the XBC API and installs the Endpoint Sensor agent (`tmxbc`). +* Waits for agent registration. +* Activates Server & Workload Protection (`ds_agent`) against the Deep Security Manager. +* Triggers: none. ## Mandatory Role Variables -| Variable | Description | -| -------- | ----------- | -| `trend_micro__customer_id` | Customer ID used in the installer API request. Found in the Vision One deployment script preview. | -| `trend_micro__xbc_installer_x86_64_company_id` | Company ID for the x86\_64 installer API request. | -| `trend_micro__xbc_installer_x86_64_scenario_ids` | List of scenario IDs for the x86\_64 installer API request. Include all IDs from the deployment script (endpoint sensor and SWP).| -| `trend_micro__xbc_installer_aarch64_company_id` | Company ID for the aarch64 installer API request. | -| `trend_micro__xbc_installer_aarch64_scenario_ids` | List of scenario IDs for the aarch64 installer API request. Include all IDs from the deployment script (endpoint sensor and SWP).| -| `trend_micro__swp_dsm_fqdn` | Deep Security Manager FQDN used to activate Server & Workload Protection. | -| `trend_micro__swp_login` | Server & Workload Protection activation credentials. Dictionary with the keys `tenant_id` and `token`. Typically fed by `linuxfabrik.lfops.bitwarden_item`. | +`trend_micro__customer_id` + +* Customer ID used in the installer API request. +* Type: String. + +`trend_micro__swp_dsm_fqdn` + +* Deep Security Manager FQDN used to activate Server & Workload Protection. +* Type: String. + +`trend_micro__swp_login` + +* Server & Workload Protection activation credentials. +* Type: Dictionary. +* Subkeys: + + * `tenant_id`: + + * Mandatory. Tenant ID used to activate Server & Workload Protection. + * Type: String. + + * `token`: + + * Mandatory. Activation token used to activate Server & Workload Protection. + * Type: String. + +`trend_micro__xbc_installer_aarch64_scenario_ids` + +* List of scenario IDs for the aarch64 installer API request. Include all IDs from the deployment script (endpoint sensor and SWP). Required on aarch64 hosts only. +* Type: List of strings. + +`trend_micro__xbc_installer_company_id` + +* Company ID for the installer API request. The same value for both architectures. +* Type: String. + +`trend_micro__xbc_installer_x86_64_scenario_ids` + +* List of scenario IDs for the x86_64 installer API request. Include all IDs from the deployment script (endpoint sensor and SWP). Required on x86_64 hosts only. +* Type: List of strings. Example: ```yaml # mandatory trend_micro__customer_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' - -trend_micro__xbc_installer_x86_64_company_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' -trend_micro__xbc_installer_x86_64_scenario_ids: - - '' - - '' - -trend_micro__xbc_installer_aarch64_company_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' -trend_micro__xbc_installer_aarch64_scenario_ids: - - '' - - '' - trend_micro__swp_dsm_fqdn: 'agents.workload.de-1.cloudone.trendmicro.com' trend_micro__swp_login: tenant_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' +trend_micro__xbc_installer_aarch64_scenario_ids: + - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' +trend_micro__xbc_installer_company_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' +trend_micro__xbc_installer_x86_64_scenario_ids: + - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' ``` ## Optional Role Variables -| Variable | Description | Default Value | -| -------- | ----------- | ------------- | -| `trend_micro__xbc_fqdn` | XBC backend FQDN. Use the FQDN matching your region. | `'api-eu1.xbc.trendmicro.com'` | -| `trend_micro__xbc_env` | XBC environment identifier. | `'prod-eu1'` | -| `trend_micro__policy_id` | Security policy to apply. `0` means no specific policy. | `0` | -| `trend_micro__group_id` | Agent group for organisation. | `0` | -| `trend_micro__relay_group_id` | Relay group for agent communication. | `0` | -| `trend_micro__proxy_addr` | Proxy hostname or IP. Leave empty for a direct connection. | `''` | -| `trend_micro__proxy_port` | Proxy port. | `''` | -| `trend_micro__proxy_login` | Proxy credentials. Dictionary with the keys `username` and `password`. Omit for a proxy that needs no authentication. | `{}` | -| `trend_micro__path_dsa` | Path to the `dsa_control` binary. | `'/opt/ds_agent/dsa_control'` | -| `trend_micro__path_identity_file` | Path polled to confirm agent registration. | `'/opt/TrendMicro/EndpointBasecamp/etc/.identity'` | +`trend_micro__group_id` + +* Agent group for organisation. +* Type: Number. +* Default: `0` + +`trend_micro__policy_id` + +* Security policy to apply. `0` means no specific policy. +* Type: Number. +* Default: `0` + +`trend_micro__relay_group_id` + +* Relay group for agent communication. +* Type: Number. +* Default: `0` + +`trend_micro__xbc_env` + +* XBC environment identifier. +* Type: String. +* Default: `'prod-eu1'` + +`trend_micro__xbc_fqdn` + +* XBC backend FQDN. Use the FQDN matching your region. +* Type: String. +* Default: `'api-eu1.xbc.trendmicro.com'` Example: ```yaml # optional -trend_micro__xbc_fqdn: 'api-us1.xbc.trendmicro.com' -trend_micro__policy_id: 42 trend_micro__group_id: 10 +trend_micro__policy_id: 42 trend_micro__relay_group_id: 5 -trend_micro__proxy_addr: '192.0.2.30' -trend_micro__proxy_port: '8080' -trend_micro__proxy_login: - username: 'proxyuser' - password: 'linuxfabrik' +trend_micro__xbc_fqdn: 'api-us1.xbc.trendmicro.com' ``` diff --git a/roles/trend_micro/defaults/main.yml b/roles/trend_micro/defaults/main.yml index 01ec8398..2bdd780e 100644 --- a/roles/trend_micro/defaults/main.yml +++ b/roles/trend_micro/defaults/main.yml @@ -1,10 +1,5 @@ trend_micro__group_id: 0 -trend_micro__path_dsa: '/opt/ds_agent/dsa_control' -trend_micro__path_identity_file: '/opt/TrendMicro/EndpointBasecamp/etc/.identity' trend_micro__policy_id: 0 -trend_micro__proxy_addr: '' -trend_micro__proxy_login: {} -trend_micro__proxy_port: '' trend_micro__relay_group_id: 0 trend_micro__xbc_env: 'prod-eu1' trend_micro__xbc_fqdn: 'api-eu1.xbc.trendmicro.com' diff --git a/roles/trend_micro/meta/argument_specs.yml b/roles/trend_micro/meta/argument_specs.yml index a175d772..06844817 100644 --- a/roles/trend_micro/meta/argument_specs.yml +++ b/roles/trend_micro/meta/argument_specs.yml @@ -13,46 +13,12 @@ argument_specs: default: 0 description: 'Agent group for organisation. 0 means no specific choice.' - trend_micro__path_dsa: - type: 'str' - required: false - default: '/opt/ds_agent/dsa_control' - description: 'Path to the dsa_control binary.' - - trend_micro__path_identity_file: - type: 'str' - required: false - default: '/opt/TrendMicro/EndpointBasecamp/etc/.identity' - description: 'Path polled to confirm agent registration.' - trend_micro__policy_id: type: 'int' required: false default: 0 description: 'Security policy to apply. 0 means no specific choice.' - trend_micro__proxy_addr: - type: 'str' - required: false - default: '' - description: 'Proxy hostname or IP. Leave empty for a direct connection.' - - trend_micro__proxy_login: - type: 'dict' - required: false - default: {} - description: >- - Proxy credentials. May contain the keys `username` and `password`. - Typically fed by `linuxfabrik.lfops.bitwarden_item`, which returns the - full Bitwarden item; extra keys are ignored. Omit for a proxy that - needs no authentication. - - trend_micro__proxy_port: - type: 'str' - required: false - default: '' - description: 'Proxy port.' - trend_micro__relay_group_id: type: 'int' required: false @@ -69,9 +35,7 @@ argument_specs: required: true description: >- Server & Workload Protection activation credentials. Must contain the - keys `tenant_id` and `token`. Typically fed by - `linuxfabrik.lfops.bitwarden_item`, which returns the full Bitwarden - item; extra keys are ignored. + keys `tenant_id` and `token`. trend_micro__xbc_env: type: 'str' @@ -85,24 +49,19 @@ argument_specs: default: 'api-eu1.xbc.trendmicro.com' description: 'XBC backend FQDN. Use the FQDN matching your region.' - trend_micro__xbc_installer_aarch64_company_id: - type: 'str' - required: true - description: 'Company ID for the aarch64 installer API request.' - trend_micro__xbc_installer_aarch64_scenario_ids: type: 'list' elements: 'str' - required: true - description: 'List of scenario IDs for the aarch64 installer API request.' + required: false + description: 'List of scenario IDs for the aarch64 installer API request. Required on aarch64 hosts only.' - trend_micro__xbc_installer_x86_64_company_id: + trend_micro__xbc_installer_company_id: type: 'str' required: true - description: 'Company ID for the x86_64 installer API request.' + description: 'Company ID for the installer API request. Same value for both architectures.' trend_micro__xbc_installer_x86_64_scenario_ids: type: 'list' elements: 'str' - required: true - description: 'List of scenario IDs for the x86_64 installer API request.' + required: false + description: 'List of scenario IDs for the x86_64 installer API request. Required on x86_64 hosts only.' diff --git a/roles/trend_micro/tasks/main.yml b/roles/trend_micro/tasks/main.yml index 4f0e4ab4..f837d08b 100644 --- a/roles/trend_micro/tasks/main.yml +++ b/roles/trend_micro/tasks/main.yml @@ -1,281 +1,182 @@ +# This role is adapted from the Server & Workload Protection (SWP) deployment script, +# a bash installer generated in the Trend Vision One console. Comments below that refer +# to "the original script" point to that installer. + - block: + - name: 'Assert supported architecture' + ansible.builtin.assert: + that: + - "ansible_facts['architecture'] in ['x86_64', 'aarch64']" + quiet: true + fail_msg: 'Architecture {{ ansible_facts["architecture"] }} is not supported.' + + - name: 'Assert x86_64 scenario IDs are set' + ansible.builtin.assert: + that: + - 'trend_micro__xbc_installer_x86_64_scenario_ids | d([]) | length > 0' + quiet: true + fail_msg: 'trend_micro__xbc_installer_x86_64_scenario_ids must be set on an x86_64 host.' + when: "ansible_facts['architecture'] == 'x86_64'" + + - name: 'Assert aarch64 scenario IDs are set' + ansible.builtin.assert: + that: + - 'trend_micro__xbc_installer_aarch64_scenario_ids | d([]) | length > 0' + quiet: true + fail_msg: 'trend_micro__xbc_installer_aarch64_scenario_ids must be set on an aarch64 host.' + when: "ansible_facts['architecture'] == 'aarch64'" + - name: 'stat /tmp' ansible.builtin.stat: path: '/tmp' - register: 'trend_micro__tmp_stat' + register: '__trend_micro__tmp_stat' - - name: 'assert /tmp is writable' + - name: 'Assert /tmp is writable' ansible.builtin.assert: that: - - 'trend_micro__tmp_stat.stat.writeable' + - '__trend_micro__tmp_stat["stat"]["writeable"]' + quiet: true fail_msg: '/tmp is not writable. Please check the permissions of /tmp.' # ds_agent enforces this minimum during its own pre-check - - name: 'assert at least 2 GB of RAM (Server & Workload Protection requirement)' + - name: 'Assert at least 2 GB of RAM (Server & Workload Protection requirement)' ansible.builtin.assert: that: - - 'ansible_facts.memtotal_mb | int >= 2048' + - 'ansible_facts["memtotal_mb"] | int >= 2048' + quiet: true fail_msg: >- Server & Workload Protection requires at least 2 GB of RAM; - this host has {{ ansible_facts.memtotal_mb }} MB. ds_agent will not install. - - tags: - - 'trend_micro' - - -- block: - - - name: 'package_facts' - ansible.builtin.package_facts: - check_mode: false - - - name: 'install curl' - ansible.builtin.package: - name: 'curl' - state: 'present' + this host has {{ ansible_facts["memtotal_mb"] }} MB. ds_agent will not install. - - name: 'install tar' + - name: 'Install required packages' ansible.builtin.package: - name: 'tar' + name: + - 'curl' + - 'tar' state: 'present' - tags: - - 'trend_micro:install' - - -- block: - - - name: 'Set proxy and connection config facts (proxy with username and password)' - ansible.builtin.set_fact: - trend_micro__proxy_env: - HTTP_PROXY: 'http://{{ trend_micro__proxy_login["username"] }}:{{ trend_micro__proxy_login["password"] }}@{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}/' - HTTPS_PROXY: 'http://{{ trend_micro__proxy_login["username"] }}:{{ trend_micro__proxy_login["password"] }}@{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}/' - trend_micro__proxy_config: '{{ (trend_micro__proxy_login["username"] + ":" + trend_micro__proxy_login["password"]) | b64encode }}@{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}' - trend_micro__connect_config: '{{ {"fps": [{"connections": [{"type": "USER_INPUT"}]}]} | to_json | b64encode }}' - when: - - 'trend_micro__proxy_addr | length > 0' - - 'trend_micro__proxy_login["username"] | d("") | length > 0' - - 'trend_micro__proxy_login["password"] | d("") | length > 0' - - - name: 'Set proxy and connection config facts (proxy with username only)' - ansible.builtin.set_fact: - trend_micro__proxy_env: - HTTP_PROXY: 'http://{{ trend_micro__proxy_login["username"] }}@{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}/' - HTTPS_PROXY: 'http://{{ trend_micro__proxy_login["username"] }}@{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}/' - trend_micro__proxy_config: '{{ (trend_micro__proxy_login["username"] + ":") | b64encode }}@{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}' - trend_micro__connect_config: '{{ {"fps": [{"connections": [{"type": "USER_INPUT"}]}]} | to_json | b64encode }}' - when: - - 'trend_micro__proxy_addr | length > 0' - - 'trend_micro__proxy_login["username"] | d("") | length > 0' - - 'trend_micro__proxy_login["password"] | d("") | length == 0' - - - name: 'Set proxy and connection config facts (proxy without credentials)' - ansible.builtin.set_fact: - trend_micro__proxy_env: - HTTP_PROXY: 'http://{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}/' - HTTPS_PROXY: 'http://{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}/' - trend_micro__proxy_config: '{{ trend_micro__proxy_addr }}:{{ trend_micro__proxy_port }}' - trend_micro__connect_config: '{{ {"fps": [{"connections": [{"type": "USER_INPUT"}]}]} | to_json | b64encode }}' - when: - - 'trend_micro__proxy_addr | length > 0' - - 'trend_micro__proxy_login["username"] | d("") | length == 0' - - - name: 'Set connection config facts (direct connection)' - ansible.builtin.set_fact: - trend_micro__proxy_env: {} - trend_micro__proxy_config: '' - trend_micro__connect_config: '{{ {"fps": [{"connections": [{"type": "DIRECT_CONNECT"}]}]} | to_json | b64encode }}' - when: 'trend_micro__proxy_addr | length == 0' - - tags: - - 'trend_micro' - -# in original script, this part below is being done by a platform detection script https://api-eu1.xbc.trendmicro.com/apk/platform-detection-script -- block: - + # in original script, this part below is being done by a platform detection script https://api-eu1.xbc.trendmicro.com/apk/platform-detection-script - name: 'Set x86_64 installer facts' ansible.builtin.set_fact: - trend_micro__installer_body: - company_id: '{{ trend_micro__xbc_installer_x86_64_company_id }}' + __trend_micro__installer_body: + company_id: '{{ trend_micro__xbc_installer_company_id }}' platform: 'linux64' scenario_ids: '{{ trend_micro__xbc_installer_x86_64_scenario_ids }}' - trend_micro__xbc_agent_token: "{{ trend_micro__xbc_installer_x86_64_scenario_ids | join('|') }}" - when: "ansible_architecture == 'x86_64'" + __trend_micro__xbc_agent_token: "{{ trend_micro__xbc_installer_x86_64_scenario_ids | join('|') }}" + when: "ansible_facts['architecture'] == 'x86_64'" - name: 'Set aarch64 installer facts' ansible.builtin.set_fact: - trend_micro__installer_body: - company_id: '{{ trend_micro__xbc_installer_aarch64_company_id }}' + __trend_micro__installer_body: + company_id: '{{ trend_micro__xbc_installer_company_id }}' platform: 'linuxaarch64' scenario_ids: '{{ trend_micro__xbc_installer_aarch64_scenario_ids }}' - trend_micro__xbc_agent_token: "{{ trend_micro__xbc_installer_aarch64_scenario_ids | join('|') }}" - when: "ansible_architecture == 'aarch64'" - - - name: 'assert supported architecture' - ansible.builtin.assert: - that: "ansible_architecture in ['x86_64', 'aarch64']" - fail_msg: 'Architecture {{ ansible_architecture }} is not supported.' - - tags: - - 'trend_micro' - + __trend_micro__xbc_agent_token: "{{ trend_micro__xbc_installer_aarch64_scenario_ids | join('|') }}" + when: "ansible_facts['architecture'] == 'aarch64'" -- block: - - - name: 'file state=absent /tmp/v1es_installer.tgz # force a fresh download' + - name: 'rm -f /tmp/v1es_installer.tgz # force a fresh download' ansible.builtin.file: path: '/tmp/v1es_installer.tgz' state: 'absent' - - name: 'download installer to /tmp/v1es_installer.tgz' + - name: 'curl --request POST --output /tmp/v1es_installer.tgz https://{{ trend_micro__xbc_fqdn }}/apk/installer' ansible.builtin.uri: url: 'https://{{ trend_micro__xbc_fqdn }}/apk/installer' method: 'POST' headers: X-Customer-Id: '{{ trend_micro__customer_id }}' - body: '{{ trend_micro__installer_body }}' + body: '{{ __trend_micro__installer_body }}' body_format: 'json' dest: '/tmp/v1es_installer.tgz' follow_redirects: 'all' # the API answers with 303, then 200 - status_code: - - 200 - environment: '{{ trend_micro__proxy_env }}' - - tags: - - 'trend_micro' + changed_when: false # transient download into /tmp, not a config change on the target - -- block: - - - name: 'file state=directory /tmp/v1es' + - name: 'mkdir -p /tmp/v1es' ansible.builtin.file: path: '/tmp/v1es' state: 'directory' mode: 0o755 - - name: 'unarchive /tmp/v1es_installer.tgz to /tmp/v1es' + - name: 'tar xzf /tmp/v1es_installer.tgz -C /tmp/v1es' ansible.builtin.unarchive: src: '/tmp/v1es_installer.tgz' dest: '/tmp/v1es' remote_src: true - - name: 'copy /tmp/v1es/.property' - ansible.builtin.copy: - content: '{{ {"xbc_env": trend_micro__xbc_env, "xbc_agent_token": trend_micro__xbc_agent_token, "full_package": true} | to_json }}' + - name: 'Deploy /tmp/v1es/.property' + ansible.builtin.template: + backup: true + src: 'tmp/v1es/.property.j2' dest: '/tmp/v1es/.property' + owner: 'root' + group: 'root' mode: 0o644 - tags: - - 'trend_micro' - - -- block: - # tmxbc installs the Endpoint Basecamp agent on a fresh install that Basecamp socket may not exist yet, so the command exits non-zero - - name: '/tmp/v1es/tmxbc install --proxiesWithCred ... --connection ... (proxy)' - ansible.builtin.command: - argv: - - '/tmp/v1es/tmxbc' - - 'install' - - '--proxiesWithCred' - - '{{ trend_micro__proxy_config }}' - - '--connection' - - '{{ trend_micro__connect_config }}' - environment: '{{ trend_micro__proxy_env }}' - register: 'trend_micro__install_result' - until: 'trend_micro__install_result.rc == 0' - retries: 6 - delay: 10 - when: "trend_micro__proxy_config | length > 0" - - - name: '/tmp/v1es/tmxbc install --connection ... (direct)' - ansible.builtin.command: - argv: - - '/tmp/v1es/tmxbc' - - 'install' - - '--connection' - - '{{ trend_micro__connect_config }}' - register: 'trend_micro__install_result' - until: 'trend_micro__install_result.rc == 0' + - name: '/tmp/v1es/tmxbc install --connection ...' + ansible.builtin.command: >- + /tmp/v1es/tmxbc install + --connection {{ {"fps": [{"connections": [{"type": "DIRECT_CONNECT"}]}]} | ansible.builtin.to_json | ansible.builtin.b64encode }} + register: '__trend_micro__install_result' + until: '__trend_micro__install_result["rc"] == 0' retries: 6 delay: 10 - when: "trend_micro__proxy_config | length == 0" - - tags: - - 'trend_micro' - -- block: - - - name: 'wait_for path={{ trend_micro__path_identity_file }} # agent registration' + - name: 'Wait for /opt/TrendMicro/EndpointBasecamp/etc/.identity # agent registration' ansible.builtin.wait_for: - path: '{{ trend_micro__path_identity_file }}' + path: '/opt/TrendMicro/EndpointBasecamp/etc/.identity' timeout: 300 - tags: - - 'trend_micro' - - -# the SWP agent (ds_agent) ships inside the XBC full package, so it is deployed by tmxbc; wait for its service to come up and initialize, then activate it -- block: - - - name: 'systemctl is-active ds_agent # wait for SWP to install and start' - ansible.builtin.command: - cmd: 'systemctl is-active ds_agent' - register: 'trend_micro__ds_agent_status' - until: 'trend_micro__ds_agent_status.rc == 0' - retries: 60 + # the SWP agent (ds_agent) ships inside the XBC full package, so it is deployed by tmxbc; wait for its service to come up and initialize, then activate it + - name: 'systemctl is-active --quiet ds_agent # wait for SWP to install and start' + ansible.builtin.command: 'systemctl is-active --quiet ds_agent' # noqa: command-instead-of-module + register: '__trend_micro__ds_agent_status' + until: '__trend_micro__ds_agent_status["rc"] == 0' + retries: 60 # sadly this really needs to be this large delay: 10 changed_when: false check_mode: false - - name: 'wait_for path=/var/opt/ds_agent/dsa_core/ds_agent.crt # SWP initialization' + - name: 'Wait for /var/opt/ds_agent/dsa_core/ds_agent.crt # SWP initialization' ansible.builtin.wait_for: path: '/var/opt/ds_agent/dsa_core/ds_agent.crt' timeout: 300 - tags: - - 'trend_micro' - - -# the full package installs ds_agent but does not self-activate it against the SWP DSM; activation must be done explicitly with the tenant credentials, mirroring the vendor deployment script which always runs `dsa_control -a` -- block: - - - name: 'pause seconds=15 # let ds_agent finish initializing before activation' + # the full package installs ds_agent but does not self-activate it against the SWP DSM; activation must be done explicitly with the tenant credentials, mirroring the vendor deployment script which always runs `dsa_control -a` + - name: 'sleep 15 # let ds_agent finish initializing before activation' ansible.builtin.pause: seconds: 15 - - name: '{{ trend_micro__path_dsa }} -a dsm://{{ trend_micro__swp_dsm_fqdn }}:443/ ... # activate SWP' - ansible.builtin.command: - argv: - - '{{ trend_micro__path_dsa }}' - - '-a' - - 'dsm://{{ trend_micro__swp_dsm_fqdn }}:443/' - - 'tenantID:{{ trend_micro__swp_login["tenant_id"] }}' - - 'token:{{ trend_micro__swp_login["token"] }}' - - 'policyid:{{ trend_micro__policy_id }}' - - 'relaygroupid:{{ trend_micro__relay_group_id }}' - - 'groupid:{{ trend_micro__group_id }}' - register: 'trend_micro__activation_result' + - name: '/opt/ds_agent/dsa_control -a dsm://{{ trend_micro__swp_dsm_fqdn }}:443/ ... # activate SWP' + ansible.builtin.command: >- + /opt/ds_agent/dsa_control + -a dsm://{{ trend_micro__swp_dsm_fqdn }}:443/ + tenantID:{{ trend_micro__swp_login["tenant_id"] }} + token:{{ trend_micro__swp_login["token"] }} + policyid:{{ trend_micro__policy_id }} + relaygroupid:{{ trend_micro__relay_group_id }} + groupid:{{ trend_micro__group_id }} + register: '__trend_micro__activation_result' # ds_agent needs a moment after install before it can talk to the DSM until: >- - '200' in trend_micro__activation_result.stdout or - 'reset agent first' in trend_micro__activation_result.stdout + '200' in __trend_micro__activation_result["stdout"] or + 'reset agent first' in __trend_micro__activation_result["stdout"] retries: 12 delay: 10 - changed_when: "'200' in trend_micro__activation_result.stdout" + changed_when: "'200' in __trend_micro__activation_result['stdout']" failed_when: false - - name: 'assert SWP is activated' + - name: 'Assert SWP is activated' ansible.builtin.assert: that: - >- - '200' in trend_micro__activation_result.stdout or - 'reset agent first' in trend_micro__activation_result.stdout + '200' in __trend_micro__activation_result["stdout"] or + 'reset agent first' in __trend_micro__activation_result["stdout"] + quiet: true fail_msg: >- Server & Workload Protection activation failed. Output: - {{ trend_micro__activation_result.stdout | default('n/a') }}. + {{ __trend_micro__activation_result["stdout"] | default('n/a') }}. tags: - 'trend_micro' diff --git a/roles/trend_micro/templates/tmp/v1es/.property.j2 b/roles/trend_micro/templates/tmp/v1es/.property.j2 new file mode 100644 index 00000000..6835631c --- /dev/null +++ b/roles/trend_micro/templates/tmp/v1es/.property.j2 @@ -0,0 +1,2 @@ +{#- no ansible_managed header: tmxbc parses this file as strict JSON, which has no comment syntax -#} +{{ {"xbc_env": trend_micro__xbc_env, "xbc_agent_token": __trend_micro__xbc_agent_token, "full_package": true} | ansible.builtin.to_json }} From 78fd16c11d9ac250459f73a6aa0a170f160eaa5c Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Thu, 18 Jun 2026 10:04:55 +0200 Subject: [PATCH 3/5] fix(roles/trend_micro): make the role idempotent Gate install and SWP activation on the agents' own registration artifacts (.identity and ds_agent.crt). Re-running against an installed host is now a no-op; a partial or removed install self-heals. Document the rerun behaviour in the README. --- roles/trend_micro/README.md | 1 + roles/trend_micro/tasks/main.yml | 273 +++++++++++++++++-------------- 2 files changed, 148 insertions(+), 126 deletions(-) diff --git a/roles/trend_micro/README.md b/roles/trend_micro/README.md index d072068c..dfb2bacb 100644 --- a/roles/trend_micro/README.md +++ b/roles/trend_micro/README.md @@ -15,6 +15,7 @@ Trend Vision One Endpoint Security combines two products on the same host, and t The "Server & Workload Protection (SWP)" deployment script generated in the Vision One console contains every value the role needs. It bundles both products in one full package: it lists two scenario IDs (one for the Endpoint Sensor, one for SWP) and carries the SWP activation values. The role installs that package, waits for the SWP agent to come up, and then activates it against the DSM. +The role decides whether to run the install and activation by checking two files that the agents create once they are registered: `/opt/TrendMicro/EndpointBasecamp/etc/.identity` (Endpoint Sensor) and `/var/opt/ds_agent/dsa_core/ds_agent.crt` (SWP). Once both exist the role is a no-op, so re-running it against an installed host changes nothing. If either file is missing (a partial install, or one of the agents was removed) the role downloads the package again and reinstalls and reactivates both agents. To force a clean reinstall, remove the agents so both files are gone before running the role. Further reading: diff --git a/roles/trend_micro/tasks/main.yml b/roles/trend_micro/tasks/main.yml index f837d08b..b6232ee1 100644 --- a/roles/trend_micro/tasks/main.yml +++ b/roles/trend_micro/tasks/main.yml @@ -49,134 +49,155 @@ Server & Workload Protection requires at least 2 GB of RAM; this host has {{ ansible_facts["memtotal_mb"] }} MB. ds_agent will not install. - - name: 'Install required packages' - ansible.builtin.package: - name: - - 'curl' - - 'tar' - state: 'present' - - # in original script, this part below is being done by a platform detection script https://api-eu1.xbc.trendmicro.com/apk/platform-detection-script - - name: 'Set x86_64 installer facts' - ansible.builtin.set_fact: - __trend_micro__installer_body: - company_id: '{{ trend_micro__xbc_installer_company_id }}' - platform: 'linux64' - scenario_ids: '{{ trend_micro__xbc_installer_x86_64_scenario_ids }}' - __trend_micro__xbc_agent_token: "{{ trend_micro__xbc_installer_x86_64_scenario_ids | join('|') }}" - when: "ansible_facts['architecture'] == 'x86_64'" - - - name: 'Set aarch64 installer facts' - ansible.builtin.set_fact: - __trend_micro__installer_body: - company_id: '{{ trend_micro__xbc_installer_company_id }}' - platform: 'linuxaarch64' - scenario_ids: '{{ trend_micro__xbc_installer_aarch64_scenario_ids }}' - __trend_micro__xbc_agent_token: "{{ trend_micro__xbc_installer_aarch64_scenario_ids | join('|') }}" - when: "ansible_facts['architecture'] == 'aarch64'" - - - name: 'rm -f /tmp/v1es_installer.tgz # force a fresh download' - ansible.builtin.file: - path: '/tmp/v1es_installer.tgz' - state: 'absent' - - - name: 'curl --request POST --output /tmp/v1es_installer.tgz https://{{ trend_micro__xbc_fqdn }}/apk/installer' - ansible.builtin.uri: - url: 'https://{{ trend_micro__xbc_fqdn }}/apk/installer' - method: 'POST' - headers: - X-Customer-Id: '{{ trend_micro__customer_id }}' - body: '{{ __trend_micro__installer_body }}' - body_format: 'json' - dest: '/tmp/v1es_installer.tgz' - follow_redirects: 'all' # the API answers with 303, then 200 - changed_when: false # transient download into /tmp, not a config change on the target - - - name: 'mkdir -p /tmp/v1es' - ansible.builtin.file: - path: '/tmp/v1es' - state: 'directory' - mode: 0o755 - - - name: 'tar xzf /tmp/v1es_installer.tgz -C /tmp/v1es' - ansible.builtin.unarchive: - src: '/tmp/v1es_installer.tgz' - dest: '/tmp/v1es' - remote_src: true - - - name: 'Deploy /tmp/v1es/.property' - ansible.builtin.template: - backup: true - src: 'tmp/v1es/.property.j2' - dest: '/tmp/v1es/.property' - owner: 'root' - group: 'root' - mode: 0o644 - - # tmxbc installs the Endpoint Basecamp agent on a fresh install that Basecamp socket may not exist yet, so the command exits non-zero - - name: '/tmp/v1es/tmxbc install --connection ...' - ansible.builtin.command: >- - /tmp/v1es/tmxbc install - --connection {{ {"fps": [{"connections": [{"type": "DIRECT_CONNECT"}]}]} | ansible.builtin.to_json | ansible.builtin.b64encode }} - register: '__trend_micro__install_result' - until: '__trend_micro__install_result["rc"] == 0' - retries: 6 - delay: 10 - - - name: 'Wait for /opt/TrendMicro/EndpointBasecamp/etc/.identity # agent registration' - ansible.builtin.wait_for: + # gate the install/activate pipeline on the two artifacts the agents create once they + # are registered: .identity for the Endpoint Sensor (XDR/EDR), ds_agent.crt for SWP. + # the pipeline runs when either is missing, so a partial or removed install self-heals, + # and is skipped (idempotent) once both are present. + - name: 'stat /opt/TrendMicro/EndpointBasecamp/etc/.identity # Endpoint Sensor (XDR/EDR) registration' + ansible.builtin.stat: path: '/opt/TrendMicro/EndpointBasecamp/etc/.identity' - timeout: 300 - - # the SWP agent (ds_agent) ships inside the XBC full package, so it is deployed by tmxbc; wait for its service to come up and initialize, then activate it - - name: 'systemctl is-active --quiet ds_agent # wait for SWP to install and start' - ansible.builtin.command: 'systemctl is-active --quiet ds_agent' # noqa: command-instead-of-module - register: '__trend_micro__ds_agent_status' - until: '__trend_micro__ds_agent_status["rc"] == 0' - retries: 60 # sadly this really needs to be this large - delay: 10 - changed_when: false - check_mode: false - - - name: 'Wait for /var/opt/ds_agent/dsa_core/ds_agent.crt # SWP initialization' - ansible.builtin.wait_for: + register: '__trend_micro__identity_stat' + + - name: 'stat /var/opt/ds_agent/dsa_core/ds_agent.crt # Server & Workload Protection (SWP) initialization' + ansible.builtin.stat: path: '/var/opt/ds_agent/dsa_core/ds_agent.crt' - timeout: 300 - - # the full package installs ds_agent but does not self-activate it against the SWP DSM; activation must be done explicitly with the tenant credentials, mirroring the vendor deployment script which always runs `dsa_control -a` - - name: 'sleep 15 # let ds_agent finish initializing before activation' - ansible.builtin.pause: - seconds: 15 - - - name: '/opt/ds_agent/dsa_control -a dsm://{{ trend_micro__swp_dsm_fqdn }}:443/ ... # activate SWP' - ansible.builtin.command: >- - /opt/ds_agent/dsa_control - -a dsm://{{ trend_micro__swp_dsm_fqdn }}:443/ - tenantID:{{ trend_micro__swp_login["tenant_id"] }} - token:{{ trend_micro__swp_login["token"] }} - policyid:{{ trend_micro__policy_id }} - relaygroupid:{{ trend_micro__relay_group_id }} - groupid:{{ trend_micro__group_id }} - register: '__trend_micro__activation_result' - # ds_agent needs a moment after install before it can talk to the DSM - until: >- - '200' in __trend_micro__activation_result["stdout"] or - 'reset agent first' in __trend_micro__activation_result["stdout"] - retries: 12 - delay: 10 - changed_when: "'200' in __trend_micro__activation_result['stdout']" - failed_when: false - - - name: 'Assert SWP is activated' - ansible.builtin.assert: - that: - - >- - '200' in __trend_micro__activation_result["stdout"] or - 'reset agent first' in __trend_micro__activation_result["stdout"] - quiet: true - fail_msg: >- - Server & Workload Protection activation failed. Output: - {{ __trend_micro__activation_result["stdout"] | default('n/a') }}. + register: '__trend_micro__ds_agent_crt_stat' + + + - block: + + - name: 'Install required packages' + ansible.builtin.package: + name: + - 'curl' + - 'tar' + state: 'present' + + # in original script, this part below is being done by a platform detection script https://api-eu1.xbc.trendmicro.com/apk/platform-detection-script + - name: 'Set x86_64 installer facts' + ansible.builtin.set_fact: + __trend_micro__installer_body: + company_id: '{{ trend_micro__xbc_installer_company_id }}' + platform: 'linux64' + scenario_ids: '{{ trend_micro__xbc_installer_x86_64_scenario_ids }}' + __trend_micro__xbc_agent_token: "{{ trend_micro__xbc_installer_x86_64_scenario_ids | join('|') }}" + when: "ansible_facts['architecture'] == 'x86_64'" + + - name: 'Set aarch64 installer facts' + ansible.builtin.set_fact: + __trend_micro__installer_body: + company_id: '{{ trend_micro__xbc_installer_company_id }}' + platform: 'linuxaarch64' + scenario_ids: '{{ trend_micro__xbc_installer_aarch64_scenario_ids }}' + __trend_micro__xbc_agent_token: "{{ trend_micro__xbc_installer_aarch64_scenario_ids | join('|') }}" + when: "ansible_facts['architecture'] == 'aarch64'" + + - name: 'rm -f /tmp/v1es_installer.tgz # force a fresh download' + ansible.builtin.file: + path: '/tmp/v1es_installer.tgz' + state: 'absent' + + - name: 'curl --request POST --output /tmp/v1es_installer.tgz https://{{ trend_micro__xbc_fqdn }}/apk/installer' + ansible.builtin.uri: + url: 'https://{{ trend_micro__xbc_fqdn }}/apk/installer' + method: 'POST' + headers: + X-Customer-Id: '{{ trend_micro__customer_id }}' + body: '{{ __trend_micro__installer_body }}' + body_format: 'json' + dest: '/tmp/v1es_installer.tgz' + follow_redirects: 'all' # the API answers with 303, then 200 + changed_when: false # transient download into /tmp, not a config change on the target + + - name: 'mkdir -p /tmp/v1es' + ansible.builtin.file: + path: '/tmp/v1es' + state: 'directory' + mode: 0o755 + + - name: 'tar xzf /tmp/v1es_installer.tgz -C /tmp/v1es' + ansible.builtin.unarchive: + src: '/tmp/v1es_installer.tgz' + dest: '/tmp/v1es' + remote_src: true + + - name: 'Deploy /tmp/v1es/.property' + ansible.builtin.template: + backup: true + src: 'tmp/v1es/.property.j2' + dest: '/tmp/v1es/.property' + owner: 'root' + group: 'root' + mode: 0o644 + + # tmxbc installs the Endpoint Basecamp agent on a fresh install that Basecamp socket may not exist yet, so the command exits non-zero + - name: '/tmp/v1es/tmxbc install --connection ...' + ansible.builtin.command: >- + /tmp/v1es/tmxbc install + --connection {{ {"fps": [{"connections": [{"type": "DIRECT_CONNECT"}]}]} | ansible.builtin.to_json | ansible.builtin.b64encode }} + register: '__trend_micro__install_result' + until: '__trend_micro__install_result["rc"] == 0' + retries: 6 + delay: 10 + changed_when: true # only runs on a fresh/incomplete install (block is gated), so it always installs + + - name: 'Wait for /opt/TrendMicro/EndpointBasecamp/etc/.identity # agent registration' + ansible.builtin.wait_for: + path: '/opt/TrendMicro/EndpointBasecamp/etc/.identity' + timeout: 300 + + # the SWP agent (ds_agent) ships inside the XBC full package, so it is deployed by tmxbc; wait for its service to come up and initialize, then activate it + - name: 'systemctl is-active --quiet ds_agent # wait for SWP to install and start' + ansible.builtin.command: 'systemctl is-active --quiet ds_agent' # noqa: command-instead-of-module + register: '__trend_micro__ds_agent_status' + until: '__trend_micro__ds_agent_status["rc"] == 0' + retries: 60 # sadly this really needs to be this large + delay: 10 + changed_when: false + check_mode: false + + - name: 'Wait for /var/opt/ds_agent/dsa_core/ds_agent.crt # SWP initialization' + ansible.builtin.wait_for: + path: '/var/opt/ds_agent/dsa_core/ds_agent.crt' + timeout: 300 + + # the full package installs ds_agent but does not self-activate it against the SWP DSM; activation must be done explicitly with the tenant credentials, mirroring the vendor deployment script which always runs `dsa_control -a` + - name: 'sleep 15 # let ds_agent finish initializing before activation' + ansible.builtin.pause: + seconds: 15 + + - name: '/opt/ds_agent/dsa_control -a dsm://{{ trend_micro__swp_dsm_fqdn }}:443/ ... # activate SWP' + ansible.builtin.command: >- + /opt/ds_agent/dsa_control + -a dsm://{{ trend_micro__swp_dsm_fqdn }}:443/ + tenantID:{{ trend_micro__swp_login["tenant_id"] }} + token:{{ trend_micro__swp_login["token"] }} + policyid:{{ trend_micro__policy_id }} + relaygroupid:{{ trend_micro__relay_group_id }} + groupid:{{ trend_micro__group_id }} + register: '__trend_micro__activation_result' + # ds_agent needs a moment after install before it can talk to the DSM + until: >- + '200' in __trend_micro__activation_result["stdout"] or + 'reset agent first' in __trend_micro__activation_result["stdout"] + retries: 12 + delay: 10 + changed_when: "'200' in __trend_micro__activation_result['stdout']" + failed_when: false + + - name: 'Assert SWP is activated' + ansible.builtin.assert: + that: + - >- + '200' in __trend_micro__activation_result["stdout"] or + 'reset agent first' in __trend_micro__activation_result["stdout"] + quiet: true + fail_msg: >- + Server & Workload Protection activation failed. Output: + {{ __trend_micro__activation_result["stdout"] | default('n/a') }}. + + when: + - 'not (__trend_micro__identity_stat["stat"]["exists"] and __trend_micro__ds_agent_crt_stat["stat"]["exists"])' tags: - 'trend_micro' From 09787d3a6854c0f84a344b6c9244f56eb79c8306 Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Thu, 18 Jun 2026 11:11:38 +0200 Subject: [PATCH 4/5] refactor(roles/trend_micro_v1es): rename role from trend_micro Name the role after the product (Vision One Endpoint Security, v1es) rather than the vendor, so other Trend Micro agents can get their own roles. Renames the role dir, playbook, host group, tag and all trend_micro__* variables to trend_micro_v1es__*. --- CHANGELOG.md | 2 +- COMPATIBILITY.md | 2 +- playbooks/README.md | 4 +- playbooks/all.yml | 2 +- .../{trend_micro.yml => trend_micro_v1es.yml} | 6 +- roles/trend_micro/defaults/main.yml | 5 -- .../templates/tmp/v1es/.property.j2 | 2 - .../README.md | 68 ++++++++-------- roles/trend_micro_v1es/defaults/main.yml | 5 ++ .../meta/argument_specs.yml | 22 ++--- .../tasks/main.yml | 80 +++++++++---------- .../templates/tmp/v1es/.property.j2 | 2 + 12 files changed, 100 insertions(+), 100 deletions(-) rename playbooks/{trend_micro.yml => trend_micro_v1es.yml} (76%) delete mode 100644 roles/trend_micro/defaults/main.yml delete mode 100644 roles/trend_micro/templates/tmp/v1es/.property.j2 rename roles/{trend_micro => trend_micro_v1es}/README.md (80%) create mode 100644 roles/trend_micro_v1es/defaults/main.yml rename roles/{trend_micro => trend_micro_v1es}/meta/argument_specs.yml (79%) rename roles/{trend_micro => trend_micro_v1es}/tasks/main.yml (68%) create mode 100644 roles/trend_micro_v1es/templates/tmp/v1es/.property.j2 diff --git a/CHANGELOG.md b/CHANGELOG.md index a0d075f2..91bb1c7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -* **role:trend_micro**: Add a role to install and activate the Trend Vision One Endpoint Security agent (Endpoint Sensor and Server & Workload Protection). +* **role:trend_micro_v1es**: Add a role to install and activate the Trend Vision One Endpoint Security agent (Endpoint Sensor and Server & Workload Protection). * **role:matomo_import_logs**: New role that imports Apache access logs into Matomo on a schedule, one systemd timer per site, and ships the Matomo log-analytics import script (`import_logs.py`). The `token_auth` is provided via a per-site auth file instead of the command line (passing `--token-auth`, `--login` or `--password` is deprecated, since they are visible in the process list and now log a deprecation warning). The script also supports the Traefik access-log format and fixes a possible endless loop when reading a config file. * **role:glances**: Add RHEL 10 / Rocky 10 / Alma 10 support by installing glances into a Python venv via the `python_venv` role, since the package is not available in EPEL 10. RHEL 10 is now marked proven (`x`) in COMPATIBILITY. * **role:graylog_datanode**: Add `graylog_datanode__http_publish_uri` to set the REST API URI the DataNode advertises, needed when the bind address is not directly reachable (multiple interfaces, a NAT gateway, or a `0.0.0.0` bind address). diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index c9283ef2..36bb3c96 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -162,7 +162,7 @@ Which Ansible role is proven to run on which OS? | timezone | (x) | (x) | x | x | x | (x) | (x) | (x) | Fedora 35 | | tmux | (x) | (x) | (x) | x | (x) | (x) | (x) | (x) | | | tools | | | x | x | x | | | | Fedora | -| trend_micro | | | x | x | x | | | | | +| trend_micro_v1es | | | x | x | x | | | | | | unattended_upgrades | (x) | (x) | | | | (x) | (x) | (x) | | | uptimerobot | | | | | | | | | controller-side, talks to UptimeRobot API | | vsftpd | | | x | (x) | (x) | | | | | diff --git a/playbooks/README.md b/playbooks/README.md index 17a0c904..fdba7736 100644 --- a/playbooks/README.md +++ b/playbooks/README.md @@ -1381,11 +1381,11 @@ Calls the following roles (in order): * [tools](https://github.com/Linuxfabrik/lfops/tree/main/roles/tools) -## trend_micro.yml +## trend_micro_v1es.yml Calls the following roles (in order): -* [trend_micro](https://github.com/Linuxfabrik/lfops/tree/main/roles/trend_micro) +* [trend_micro_v1es](https://github.com/Linuxfabrik/lfops/tree/main/roles/trend_micro_v1es) ## unattended_upgrades.yml diff --git a/playbooks/all.yml b/playbooks/all.yml index 82b509c4..27a13b7b 100644 --- a/playbooks/all.yml +++ b/playbooks/all.yml @@ -145,7 +145,7 @@ - import_playbook: 'timezone.yml' - import_playbook: 'tmux.yml' - import_playbook: 'tools.yml' -- import_playbook: 'trend_micro.yml' +- import_playbook: 'trend_micro_v1es.yml' - import_playbook: 'unattended_upgrades.yml' - import_playbook: 'vsftpd.yml' - import_playbook: 'yum_utils.yml' diff --git a/playbooks/trend_micro.yml b/playbooks/trend_micro_v1es.yml similarity index 76% rename from playbooks/trend_micro.yml rename to playbooks/trend_micro_v1es.yml index 5c204c2e..cb835118 100644 --- a/playbooks/trend_micro.yml +++ b/playbooks/trend_micro_v1es.yml @@ -1,6 +1,6 @@ -- name: 'Playbook linuxfabrik.lfops.trend_micro' +- name: 'Playbook linuxfabrik.lfops.trend_micro_v1es' hosts: - - 'lfops_trend_micro' + - 'lfops_trend_micro_v1es' pre_tasks: - ansible.builtin.import_role: @@ -18,7 +18,7 @@ roles: - - role: 'linuxfabrik.lfops.trend_micro' + - role: 'linuxfabrik.lfops.trend_micro_v1es' post_tasks: diff --git a/roles/trend_micro/defaults/main.yml b/roles/trend_micro/defaults/main.yml deleted file mode 100644 index 2bdd780e..00000000 --- a/roles/trend_micro/defaults/main.yml +++ /dev/null @@ -1,5 +0,0 @@ -trend_micro__group_id: 0 -trend_micro__policy_id: 0 -trend_micro__relay_group_id: 0 -trend_micro__xbc_env: 'prod-eu1' -trend_micro__xbc_fqdn: 'api-eu1.xbc.trendmicro.com' diff --git a/roles/trend_micro/templates/tmp/v1es/.property.j2 b/roles/trend_micro/templates/tmp/v1es/.property.j2 deleted file mode 100644 index 6835631c..00000000 --- a/roles/trend_micro/templates/tmp/v1es/.property.j2 +++ /dev/null @@ -1,2 +0,0 @@ -{#- no ansible_managed header: tmxbc parses this file as strict JSON, which has no comment syntax -#} -{{ {"xbc_env": trend_micro__xbc_env, "xbc_agent_token": __trend_micro__xbc_agent_token, "full_package": true} | ansible.builtin.to_json }} diff --git a/roles/trend_micro/README.md b/roles/trend_micro_v1es/README.md similarity index 80% rename from roles/trend_micro/README.md rename to roles/trend_micro_v1es/README.md index dfb2bacb..d1efabc4 100644 --- a/roles/trend_micro/README.md +++ b/roles/trend_micro_v1es/README.md @@ -1,4 +1,4 @@ -# Ansible Role linuxfabrik.lfops.trend_micro +# Ansible Role linuxfabrik.lfops.trend_micro_v1es This role installs and activates the [Trend Vision One Endpoint Security](https://www.trendmicro.com/en_us/business/products/hybrid-cloud/vision-one-endpoint-security.html) agent (`v1es`) on Linux servers. It covers pre-flight checks, agent download and installation via the XBC API, agent registration, and Server & Workload Protection (SWP) activation. @@ -56,22 +56,22 @@ The script is a `bash` installer. Map its values to the role variables as follow | Role variable | Where it appears in the deployment script | | ------------- | ----------------------------------------- | -| `trend_micro__customer_id` | `CUSTOMER_ID="..."` | -| `trend_micro__group_id` | `GROUP_ID=...` (optional, defaults to `0`) | -| `trend_micro__policy_id` | `POLICY_ID=...` (optional, defaults to `0`) | -| `trend_micro__relay_group_id` | `RELAY_GROUP_ID=...` (optional, defaults to `0`) | -| `trend_micro__swp_dsm_fqdn` | the host in `dsa_control -a dsm://:443/` | -| `trend_micro__swp_login.tenant_id` | `tenantID:...` in the `dsa_control` activation line | -| `trend_micro__swp_login.token` | `token:...` in the `dsa_control` activation line | -| `trend_micro__xbc_env` | `XBC_ENV="..."` (optional, defaults to `prod-eu1`) | -| `trend_micro__xbc_fqdn` | `XBC_FQDN="..."` | -| `trend_micro__xbc_installer_company_id` | `company_id` in the `HTTP_BODY` (identical in both `archType` blocks) | -| `trend_micro__xbc_installer__scenario_ids` | `scenario_ids` in the `HTTP_BODY` of the matching `archType` block (both IDs) | +| `trend_micro_v1es__customer_id` | `CUSTOMER_ID="..."` | +| `trend_micro_v1es__group_id` | `GROUP_ID=...` (optional, defaults to `0`) | +| `trend_micro_v1es__policy_id` | `POLICY_ID=...` (optional, defaults to `0`) | +| `trend_micro_v1es__relay_group_id` | `RELAY_GROUP_ID=...` (optional, defaults to `0`) | +| `trend_micro_v1es__swp_dsm_fqdn` | the host in `dsa_control -a dsm://:443/` | +| `trend_micro_v1es__swp_login.tenant_id` | `tenantID:...` in the `dsa_control` activation line | +| `trend_micro_v1es__swp_login.token` | `token:...` in the `dsa_control` activation line | +| `trend_micro_v1es__xbc_env` | `XBC_ENV="..."` (optional, defaults to `prod-eu1`) | +| `trend_micro_v1es__xbc_fqdn` | `XBC_FQDN="..."` | +| `trend_micro_v1es__xbc_installer_company_id` | `company_id` in the `HTTP_BODY` (identical in both `archType` blocks) | +| `trend_micro_v1es__xbc_installer__scenario_ids`| `scenario_ids` in the `HTTP_BODY` of the matching `archType` block (both IDs) | ## Tags -`trend_micro` +`trend_micro_v1es` * Runs the pre-flight checks (supported architecture, scenario IDs, writable `/tmp`, minimum RAM). * Installs the required packages (`curl`, `tar`). @@ -83,17 +83,17 @@ The script is a `bash` installer. Map its values to the role variables as follow ## Mandatory Role Variables -`trend_micro__customer_id` +`trend_micro_v1es__customer_id` * Customer ID used in the installer API request. * Type: String. -`trend_micro__swp_dsm_fqdn` +`trend_micro_v1es__swp_dsm_fqdn` * Deep Security Manager FQDN used to activate Server & Workload Protection. * Type: String. -`trend_micro__swp_login` +`trend_micro_v1es__swp_login` * Server & Workload Protection activation credentials. * Type: Dictionary. @@ -109,17 +109,17 @@ The script is a `bash` installer. Map its values to the role variables as follow * Mandatory. Activation token used to activate Server & Workload Protection. * Type: String. -`trend_micro__xbc_installer_aarch64_scenario_ids` +`trend_micro_v1es__xbc_installer_aarch64_scenario_ids` * List of scenario IDs for the aarch64 installer API request. Include all IDs from the deployment script (endpoint sensor and SWP). Required on aarch64 hosts only. * Type: List of strings. -`trend_micro__xbc_installer_company_id` +`trend_micro_v1es__xbc_installer_company_id` * Company ID for the installer API request. The same value for both architectures. * Type: String. -`trend_micro__xbc_installer_x86_64_scenario_ids` +`trend_micro_v1es__xbc_installer_x86_64_scenario_ids` * List of scenario IDs for the x86_64 installer API request. Include all IDs from the deployment script (endpoint sensor and SWP). Required on x86_64 hosts only. * Type: List of strings. @@ -127,16 +127,16 @@ The script is a `bash` installer. Map its values to the role variables as follow Example: ```yaml # mandatory -trend_micro__customer_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' -trend_micro__swp_dsm_fqdn: 'agents.workload.de-1.cloudone.trendmicro.com' -trend_micro__swp_login: +trend_micro_v1es__customer_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' +trend_micro_v1es__swp_dsm_fqdn: 'agents.workload.de-1.cloudone.trendmicro.com' +trend_micro_v1es__swp_login: tenant_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' -trend_micro__xbc_installer_aarch64_scenario_ids: +trend_micro_v1es__xbc_installer_aarch64_scenario_ids: - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' -trend_micro__xbc_installer_company_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' -trend_micro__xbc_installer_x86_64_scenario_ids: +trend_micro_v1es__xbc_installer_company_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' +trend_micro_v1es__xbc_installer_x86_64_scenario_ids: - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' ``` @@ -144,31 +144,31 @@ trend_micro__xbc_installer_x86_64_scenario_ids: ## Optional Role Variables -`trend_micro__group_id` +`trend_micro_v1es__group_id` * Agent group for organisation. * Type: Number. * Default: `0` -`trend_micro__policy_id` +`trend_micro_v1es__policy_id` * Security policy to apply. `0` means no specific policy. * Type: Number. * Default: `0` -`trend_micro__relay_group_id` +`trend_micro_v1es__relay_group_id` * Relay group for agent communication. * Type: Number. * Default: `0` -`trend_micro__xbc_env` +`trend_micro_v1es__xbc_env` * XBC environment identifier. * Type: String. * Default: `'prod-eu1'` -`trend_micro__xbc_fqdn` +`trend_micro_v1es__xbc_fqdn` * XBC backend FQDN. Use the FQDN matching your region. * Type: String. @@ -177,10 +177,10 @@ trend_micro__xbc_installer_x86_64_scenario_ids: Example: ```yaml # optional -trend_micro__group_id: 10 -trend_micro__policy_id: 42 -trend_micro__relay_group_id: 5 -trend_micro__xbc_fqdn: 'api-us1.xbc.trendmicro.com' +trend_micro_v1es__group_id: 10 +trend_micro_v1es__policy_id: 42 +trend_micro_v1es__relay_group_id: 5 +trend_micro_v1es__xbc_fqdn: 'api-us1.xbc.trendmicro.com' ``` diff --git a/roles/trend_micro_v1es/defaults/main.yml b/roles/trend_micro_v1es/defaults/main.yml new file mode 100644 index 00000000..c4b8cffe --- /dev/null +++ b/roles/trend_micro_v1es/defaults/main.yml @@ -0,0 +1,5 @@ +trend_micro_v1es__group_id: 0 +trend_micro_v1es__policy_id: 0 +trend_micro_v1es__relay_group_id: 0 +trend_micro_v1es__xbc_env: 'prod-eu1' +trend_micro_v1es__xbc_fqdn: 'api-eu1.xbc.trendmicro.com' diff --git a/roles/trend_micro/meta/argument_specs.yml b/roles/trend_micro_v1es/meta/argument_specs.yml similarity index 79% rename from roles/trend_micro/meta/argument_specs.yml rename to roles/trend_micro_v1es/meta/argument_specs.yml index 06844817..ef2a4611 100644 --- a/roles/trend_micro/meta/argument_specs.yml +++ b/roles/trend_micro_v1es/meta/argument_specs.yml @@ -2,65 +2,65 @@ argument_specs: main: options: - trend_micro__customer_id: + trend_micro_v1es__customer_id: type: 'str' required: true description: 'Customer ID used in the installer API request.' - trend_micro__group_id: + trend_micro_v1es__group_id: type: 'int' required: false default: 0 description: 'Agent group for organisation. 0 means no specific choice.' - trend_micro__policy_id: + trend_micro_v1es__policy_id: type: 'int' required: false default: 0 description: 'Security policy to apply. 0 means no specific choice.' - trend_micro__relay_group_id: + trend_micro_v1es__relay_group_id: type: 'int' required: false default: 0 description: 'Relay group for agent communication. 0 means no specific choice.' - trend_micro__swp_dsm_fqdn: + trend_micro_v1es__swp_dsm_fqdn: type: 'str' required: true description: 'Deep Security Manager FQDN used to activate Server & Workload Protection.' - trend_micro__swp_login: + trend_micro_v1es__swp_login: type: 'dict' required: true description: >- Server & Workload Protection activation credentials. Must contain the keys `tenant_id` and `token`. - trend_micro__xbc_env: + trend_micro_v1es__xbc_env: type: 'str' required: false default: 'prod-eu1' description: 'XBC environment identifier.' - trend_micro__xbc_fqdn: + trend_micro_v1es__xbc_fqdn: type: 'str' required: false default: 'api-eu1.xbc.trendmicro.com' description: 'XBC backend FQDN. Use the FQDN matching your region.' - trend_micro__xbc_installer_aarch64_scenario_ids: + trend_micro_v1es__xbc_installer_aarch64_scenario_ids: type: 'list' elements: 'str' required: false description: 'List of scenario IDs for the aarch64 installer API request. Required on aarch64 hosts only.' - trend_micro__xbc_installer_company_id: + trend_micro_v1es__xbc_installer_company_id: type: 'str' required: true description: 'Company ID for the installer API request. Same value for both architectures.' - trend_micro__xbc_installer_x86_64_scenario_ids: + trend_micro_v1es__xbc_installer_x86_64_scenario_ids: type: 'list' elements: 'str' required: false diff --git a/roles/trend_micro/tasks/main.yml b/roles/trend_micro_v1es/tasks/main.yml similarity index 68% rename from roles/trend_micro/tasks/main.yml rename to roles/trend_micro_v1es/tasks/main.yml index b6232ee1..131d6ef5 100644 --- a/roles/trend_micro/tasks/main.yml +++ b/roles/trend_micro_v1es/tasks/main.yml @@ -14,28 +14,28 @@ - name: 'Assert x86_64 scenario IDs are set' ansible.builtin.assert: that: - - 'trend_micro__xbc_installer_x86_64_scenario_ids | d([]) | length > 0' + - 'trend_micro_v1es__xbc_installer_x86_64_scenario_ids | d([]) | length > 0' quiet: true - fail_msg: 'trend_micro__xbc_installer_x86_64_scenario_ids must be set on an x86_64 host.' + fail_msg: 'trend_micro_v1es__xbc_installer_x86_64_scenario_ids must be set on an x86_64 host.' when: "ansible_facts['architecture'] == 'x86_64'" - name: 'Assert aarch64 scenario IDs are set' ansible.builtin.assert: that: - - 'trend_micro__xbc_installer_aarch64_scenario_ids | d([]) | length > 0' + - 'trend_micro_v1es__xbc_installer_aarch64_scenario_ids | d([]) | length > 0' quiet: true - fail_msg: 'trend_micro__xbc_installer_aarch64_scenario_ids must be set on an aarch64 host.' + fail_msg: 'trend_micro_v1es__xbc_installer_aarch64_scenario_ids must be set on an aarch64 host.' when: "ansible_facts['architecture'] == 'aarch64'" - name: 'stat /tmp' ansible.builtin.stat: path: '/tmp' - register: '__trend_micro__tmp_stat' + register: '__trend_micro_v1es__tmp_stat' - name: 'Assert /tmp is writable' ansible.builtin.assert: that: - - '__trend_micro__tmp_stat["stat"]["writeable"]' + - '__trend_micro_v1es__tmp_stat["stat"]["writeable"]' quiet: true fail_msg: '/tmp is not writable. Please check the permissions of /tmp.' @@ -56,12 +56,12 @@ - name: 'stat /opt/TrendMicro/EndpointBasecamp/etc/.identity # Endpoint Sensor (XDR/EDR) registration' ansible.builtin.stat: path: '/opt/TrendMicro/EndpointBasecamp/etc/.identity' - register: '__trend_micro__identity_stat' + register: '__trend_micro_v1es__identity_stat' - name: 'stat /var/opt/ds_agent/dsa_core/ds_agent.crt # Server & Workload Protection (SWP) initialization' ansible.builtin.stat: path: '/var/opt/ds_agent/dsa_core/ds_agent.crt' - register: '__trend_micro__ds_agent_crt_stat' + register: '__trend_micro_v1es__ds_agent_crt_stat' - block: @@ -76,20 +76,20 @@ # in original script, this part below is being done by a platform detection script https://api-eu1.xbc.trendmicro.com/apk/platform-detection-script - name: 'Set x86_64 installer facts' ansible.builtin.set_fact: - __trend_micro__installer_body: - company_id: '{{ trend_micro__xbc_installer_company_id }}' + __trend_micro_v1es__installer_body: + company_id: '{{ trend_micro_v1es__xbc_installer_company_id }}' platform: 'linux64' - scenario_ids: '{{ trend_micro__xbc_installer_x86_64_scenario_ids }}' - __trend_micro__xbc_agent_token: "{{ trend_micro__xbc_installer_x86_64_scenario_ids | join('|') }}" + scenario_ids: '{{ trend_micro_v1es__xbc_installer_x86_64_scenario_ids }}' + __trend_micro_v1es__xbc_agent_token: "{{ trend_micro_v1es__xbc_installer_x86_64_scenario_ids | join('|') }}" when: "ansible_facts['architecture'] == 'x86_64'" - name: 'Set aarch64 installer facts' ansible.builtin.set_fact: - __trend_micro__installer_body: - company_id: '{{ trend_micro__xbc_installer_company_id }}' + __trend_micro_v1es__installer_body: + company_id: '{{ trend_micro_v1es__xbc_installer_company_id }}' platform: 'linuxaarch64' - scenario_ids: '{{ trend_micro__xbc_installer_aarch64_scenario_ids }}' - __trend_micro__xbc_agent_token: "{{ trend_micro__xbc_installer_aarch64_scenario_ids | join('|') }}" + scenario_ids: '{{ trend_micro_v1es__xbc_installer_aarch64_scenario_ids }}' + __trend_micro_v1es__xbc_agent_token: "{{ trend_micro_v1es__xbc_installer_aarch64_scenario_ids | join('|') }}" when: "ansible_facts['architecture'] == 'aarch64'" - name: 'rm -f /tmp/v1es_installer.tgz # force a fresh download' @@ -97,13 +97,13 @@ path: '/tmp/v1es_installer.tgz' state: 'absent' - - name: 'curl --request POST --output /tmp/v1es_installer.tgz https://{{ trend_micro__xbc_fqdn }}/apk/installer' + - name: 'curl --request POST --output /tmp/v1es_installer.tgz https://{{ trend_micro_v1es__xbc_fqdn }}/apk/installer' ansible.builtin.uri: - url: 'https://{{ trend_micro__xbc_fqdn }}/apk/installer' + url: 'https://{{ trend_micro_v1es__xbc_fqdn }}/apk/installer' method: 'POST' headers: - X-Customer-Id: '{{ trend_micro__customer_id }}' - body: '{{ __trend_micro__installer_body }}' + X-Customer-Id: '{{ trend_micro_v1es__customer_id }}' + body: '{{ __trend_micro_v1es__installer_body }}' body_format: 'json' dest: '/tmp/v1es_installer.tgz' follow_redirects: 'all' # the API answers with 303, then 200 @@ -135,8 +135,8 @@ ansible.builtin.command: >- /tmp/v1es/tmxbc install --connection {{ {"fps": [{"connections": [{"type": "DIRECT_CONNECT"}]}]} | ansible.builtin.to_json | ansible.builtin.b64encode }} - register: '__trend_micro__install_result' - until: '__trend_micro__install_result["rc"] == 0' + register: '__trend_micro_v1es__install_result' + until: '__trend_micro_v1es__install_result["rc"] == 0' retries: 6 delay: 10 changed_when: true # only runs on a fresh/incomplete install (block is gated), so it always installs @@ -149,8 +149,8 @@ # the SWP agent (ds_agent) ships inside the XBC full package, so it is deployed by tmxbc; wait for its service to come up and initialize, then activate it - name: 'systemctl is-active --quiet ds_agent # wait for SWP to install and start' ansible.builtin.command: 'systemctl is-active --quiet ds_agent' # noqa: command-instead-of-module - register: '__trend_micro__ds_agent_status' - until: '__trend_micro__ds_agent_status["rc"] == 0' + register: '__trend_micro_v1es__ds_agent_status' + until: '__trend_micro_v1es__ds_agent_status["rc"] == 0' retries: 60 # sadly this really needs to be this large delay: 10 changed_when: false @@ -166,38 +166,38 @@ ansible.builtin.pause: seconds: 15 - - name: '/opt/ds_agent/dsa_control -a dsm://{{ trend_micro__swp_dsm_fqdn }}:443/ ... # activate SWP' + - name: '/opt/ds_agent/dsa_control -a dsm://{{ trend_micro_v1es__swp_dsm_fqdn }}:443/ ... # activate SWP' ansible.builtin.command: >- /opt/ds_agent/dsa_control - -a dsm://{{ trend_micro__swp_dsm_fqdn }}:443/ - tenantID:{{ trend_micro__swp_login["tenant_id"] }} - token:{{ trend_micro__swp_login["token"] }} - policyid:{{ trend_micro__policy_id }} - relaygroupid:{{ trend_micro__relay_group_id }} - groupid:{{ trend_micro__group_id }} - register: '__trend_micro__activation_result' + -a dsm://{{ trend_micro_v1es__swp_dsm_fqdn }}:443/ + tenantID:{{ trend_micro_v1es__swp_login["tenant_id"] }} + token:{{ trend_micro_v1es__swp_login["token"] }} + policyid:{{ trend_micro_v1es__policy_id }} + relaygroupid:{{ trend_micro_v1es__relay_group_id }} + groupid:{{ trend_micro_v1es__group_id }} + register: '__trend_micro_v1es__activation_result' # ds_agent needs a moment after install before it can talk to the DSM until: >- - '200' in __trend_micro__activation_result["stdout"] or - 'reset agent first' in __trend_micro__activation_result["stdout"] + '200' in __trend_micro_v1es__activation_result["stdout"] or + 'reset agent first' in __trend_micro_v1es__activation_result["stdout"] retries: 12 delay: 10 - changed_when: "'200' in __trend_micro__activation_result['stdout']" + changed_when: "'200' in __trend_micro_v1es__activation_result['stdout']" failed_when: false - name: 'Assert SWP is activated' ansible.builtin.assert: that: - >- - '200' in __trend_micro__activation_result["stdout"] or - 'reset agent first' in __trend_micro__activation_result["stdout"] + '200' in __trend_micro_v1es__activation_result["stdout"] or + 'reset agent first' in __trend_micro_v1es__activation_result["stdout"] quiet: true fail_msg: >- Server & Workload Protection activation failed. Output: - {{ __trend_micro__activation_result["stdout"] | default('n/a') }}. + {{ __trend_micro_v1es__activation_result["stdout"] | default('n/a') }}. when: - - 'not (__trend_micro__identity_stat["stat"]["exists"] and __trend_micro__ds_agent_crt_stat["stat"]["exists"])' + - 'not (__trend_micro_v1es__identity_stat["stat"]["exists"] and __trend_micro_v1es__ds_agent_crt_stat["stat"]["exists"])' tags: - - 'trend_micro' + - 'trend_micro_v1es' diff --git a/roles/trend_micro_v1es/templates/tmp/v1es/.property.j2 b/roles/trend_micro_v1es/templates/tmp/v1es/.property.j2 new file mode 100644 index 00000000..3bed4ed7 --- /dev/null +++ b/roles/trend_micro_v1es/templates/tmp/v1es/.property.j2 @@ -0,0 +1,2 @@ +{#- no ansible_managed header: tmxbc parses this file as strict JSON, which has no comment syntax -#} +{{ {"xbc_env": trend_micro_v1es__xbc_env, "xbc_agent_token": __trend_micro_v1es__xbc_agent_token, "full_package": true} | ansible.builtin.to_json }} From d9c1f8857d3e954c62b8e192217cd792d7c6f00c Mon Sep 17 00:00:00 2001 From: Jihan El Karz Date: Thu, 18 Jun 2026 15:59:25 +0200 Subject: [PATCH 5/5] fix(roles/trend_micro): no removal of v1es_installer.tgz was done to always have the latest installer, obsolete because it is downloaded into /tmp anyway --- roles/trend_micro_v1es/tasks/main.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/roles/trend_micro_v1es/tasks/main.yml b/roles/trend_micro_v1es/tasks/main.yml index 131d6ef5..a200b073 100644 --- a/roles/trend_micro_v1es/tasks/main.yml +++ b/roles/trend_micro_v1es/tasks/main.yml @@ -92,11 +92,6 @@ __trend_micro_v1es__xbc_agent_token: "{{ trend_micro_v1es__xbc_installer_aarch64_scenario_ids | join('|') }}" when: "ansible_facts['architecture'] == 'aarch64'" - - name: 'rm -f /tmp/v1es_installer.tgz # force a fresh download' - ansible.builtin.file: - path: '/tmp/v1es_installer.tgz' - state: 'absent' - - name: 'curl --request POST --output /tmp/v1es_installer.tgz https://{{ trend_micro_v1es__xbc_fqdn }}/apk/installer' ansible.builtin.uri: url: 'https://{{ trend_micro_v1es__xbc_fqdn }}/apk/installer'