From a1f6e315bb7438562a10e442e0fcba1342b228cd Mon Sep 17 00:00:00 2001 From: Adam Roberts Date: Fri, 19 May 2023 21:37:34 -0400 Subject: [PATCH 1/2] feat: migrate role to new vars schema Initial and incomplete commit of the switch to the new vars schema. So far all that is functional is the following: * Syscall driver type * Agent Version * Whether or not to install the probe build dependencies * Connection information (including proxy config) --- defaults/main.yml | 46 +++++-- filter_plugins/agent.py | 34 +++++ filter_plugins/dragent.py | 124 ++++++++++++++++++ .../install-almalinux-dependencies.yml | 2 +- .../centos/install-centos-dependencies.yml | 2 +- .../install-rockylinux-dependencies.yml | 2 +- tasks/main.yml | 15 ++- templates/agent_helpers.j2 | 48 ------- templates/dragent.yaml.j2 | 11 +- 9 files changed, 209 insertions(+), 75 deletions(-) create mode 100644 filter_plugins/agent.py create mode 100644 filter_plugins/dragent.py delete mode 100644 templates/agent_helpers.j2 diff --git a/defaults/main.yml b/defaults/main.yml index 8f1a3f9cc..bd9c86bf8 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -2,12 +2,40 @@ # defaults file for ansible-sysdig role_version: 0.0.1 -sysdig_agent_access_key: llsdkjfsdlfj -sysdig_api_key: 2398423948 -sysdig_region: us1 -sysdig_proxy: -sysdig_agent_install_build_dependencies: false -sysdig_agent_mode: platform -sysdig_agent_settings: "" -sysdig_agent_driver: "kmodule" -sysdig_agent_version: "12.12.1" +features: + monitoring: + app_checks: ~ + jmx: ~ + statsd: ~ + prometheus: ~ + security: + activity_audit: ~ + captures: ~ + drift_detection: ~ + falcobaseline: ~ + memdumper: ~ +configuration: + monitoring: standard + security: standard + connection: + access_key: ~ + region: ~ + custom_collector: + url: ~ + port: ~ + network_proxy: + host: ~ + port: ~ + username: ~ + password: ~ + ssl_enabled: ~ + ssl_verify_certificate: ~ + ca_certificate_path: ~ + agent: + driver: + type: kmodule + location: ~ + install_build_dependencies: false + override: ~ + version: 12.14.1 + diff --git a/filter_plugins/agent.py b/filter_plugins/agent.py new file mode 100644 index 000000000..2a304ad68 --- /dev/null +++ b/filter_plugins/agent.py @@ -0,0 +1,34 @@ +def to_agent_driver_type(data): + """ Return the desired Sysdig Agent driver type """ + try: + return data['agent']['driver']['type'] + except KeyError: + return "kmodule" + + +def to_agent_version(data): + """ Returns the agent version to install if provided, otherwise empty string + """ + try: + return data['agent']['version'] + except KeyError: + return "12.14.1" + + +def to_agent_install_probe_build_dependencies(data): + """ Return true or false depending on if the probe (ebpf|kmodule) build + dependencies should be installed + """ + try: + return data['agent']['driver']['install_build_dependencies'] + except KeyError: + return False + + +class FilterModule: + def filters(self): + return { + "toAgentDriverType": to_agent_driver_type, + "toAgentVersion": to_agent_version, + "toAgentInstallProbeBuildDependencies": to_agent_install_probe_build_dependencies + } diff --git a/filter_plugins/dragent.py b/filter_plugins/dragent.py new file mode 100644 index 000000000..a20e8c316 --- /dev/null +++ b/filter_plugins/dragent.py @@ -0,0 +1,124 @@ +from abc import ABCMeta, abstractmethod + + +class UserSettings(metaclass=ABCMeta): + def __init__(self, configuration: dict, features: dict): + self._configuration = configuration + self._features = features + + +class UserConnectionSettings(UserSettings): + @property + def access_key(self) -> str: + return self._configuration["access_key"] + + @property + def ca_certificate_path(self) -> str: + return self._configuration.get("network_proxy", {}).get("ca_certificate_path") + + @property + def collector_url(self) -> str: + return self._configuration.get("custom_collector", {}).get("url") + + @property + def collector_port(self) -> int: + return self._configuration.get("custom_collector", {}).get("port") + + @property + def proxy_defined(self) -> bool: + return "network_proxy" in self._configuration + + @property + def proxy_host(self) -> str: + return self._configuration["network_proxy"].get("url") + + @property + def proxy_port(self) -> int: + return self._configuration["network_proxy"].get("port") + + @property + def ssl(self) -> bool: + return self._configuration["network_proxy"].get("ssl_enabled") + + @property + def ssl_verify_certificate(self) -> bool: + return self._configuration["network_proxy"].get("ssl_verify_certificate") + + +######################################################## +# Above are User settings +# Below are Dragent config file items +######################################################## + + +class DragentSettings(metaclass=ABCMeta): + def __init__(self, config: dict): + """ + + :param config: All user vars + """ + pass + + @abstractmethod + def generate(self) -> dict: + """ Given the provided configuration, return a dict with the expected values set + + :return: dict + """ + pass + + +class DragentConnectionSettings(DragentSettings): + def __init__(self, config): + self.config = UserConnectionSettings(configuration=config["configuration"]["connection"], features={}) + super().__init__(config) + + def generate(self) -> dict: + ret = { + "collector": self.config.collector_url, + "collector_port": self.config.collector_port, + "customerid": self.config.access_key + } + if self.config.proxy_defined: + proxy_settings = {} + if self.config.proxy_host: + proxy_settings.update({'proxy_host': self.config.proxy_host}) + if self.config.proxy_host: + proxy_settings.update({'proxy_port': self.config.proxy_port}) + if self.config.ssl: + proxy_settings.update({'ssl': self.config.ssl}) + if self.config.ssl_verify_certificate: + proxy_settings.update({'ssl_verify_certificate': self.config.ssl_verify_certificate}) + if self.config.ca_certificate_path: + proxy_settings.update({'ca_certificate': self.config.ca_certificate_path}) + ret.update({'http_proxy': proxy_settings}) + return ret + + +class Dragent: + def __init__(self, config: dict): + """ + + :param config: + """ + self._config_types = [ + DragentConnectionSettings(config=config) + ] + + def generate(self) -> dict: + ret = {} + for config_type in self._config_types: + ret.update(config_type.generate()) + return ret + + +def to_dragent_configuration(data): + return Dragent(data).generate() + + +class FilterModule: + @staticmethod + def filters(): + return { + "toDragentConfiguration": to_dragent_configuration + } diff --git a/tasks/agent/dependencies/almalinux/install-almalinux-dependencies.yml b/tasks/agent/dependencies/almalinux/install-almalinux-dependencies.yml index 58b1d096f..ce9a0e208 100644 --- a/tasks/agent/dependencies/almalinux/install-almalinux-dependencies.yml +++ b/tasks/agent/dependencies/almalinux/install-almalinux-dependencies.yml @@ -11,7 +11,7 @@ when: ansible_distribution_major_version == "9" - name: (AlmaLinux) Install epel and dkms - when: sysdig_agent_driver == "kmodule" + when: sysdig_agent_driver_type == "kmodule" block: - name: Add epel GPG key ansible.builtin.rpm_key: diff --git a/tasks/agent/dependencies/centos/install-centos-dependencies.yml b/tasks/agent/dependencies/centos/install-centos-dependencies.yml index d38230cf3..5f8ed5b5c 100644 --- a/tasks/agent/dependencies/centos/install-centos-dependencies.yml +++ b/tasks/agent/dependencies/centos/install-centos-dependencies.yml @@ -31,4 +31,4 @@ ansible.builtin.yum: name: clang,llvm state: present - when: sysdig_agent_driver | lower == "ebpf" + when: sysdig_agent_driver_type | lower == "ebpf" diff --git a/tasks/agent/dependencies/rockylinux/install-rockylinux-dependencies.yml b/tasks/agent/dependencies/rockylinux/install-rockylinux-dependencies.yml index c49a832c7..4a76a593d 100644 --- a/tasks/agent/dependencies/rockylinux/install-rockylinux-dependencies.yml +++ b/tasks/agent/dependencies/rockylinux/install-rockylinux-dependencies.yml @@ -11,7 +11,7 @@ when: ansible_distribution_major_version == "9" - name: (RockyLinux) Install epel and dkms - when: sysdig_agent_driver == "kmodule" + when: sysdig_agent_driver_type == "kmodule" block: - name: Add epel GPG key ansible.builtin.rpm_key: diff --git a/tasks/main.yml b/tasks/main.yml index ee38cd083..5f7c1c62a 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,20 +1,25 @@ --- +- name: Set facts + ansible.builtin.set_fact: + sysdig_agent_version: "{{ configuration | toAgentVersion }}" + sysdig_agent_driver_type: "{{ configuration | toAgentDriverType | lower }}" + sysdig_agent_install_probe_build_dependencies: "{{ configuration | toAgentInstallProbeBuildDependencies | bool }}" + - name: Install Sysdig Agent - when: install_sysdig_agent block: - name: Validate Environment ansible.builtin.include_tasks: agent/validations/platforms.yml - name: Install Dependencies ansible.builtin.include_tasks: agent/dependencies/{{ ansible_distribution | lower }}/install-{{ ansible_distribution | lower }}-dependencies.yml - when: sysdig_agent_install_build_dependencies + when: sysdig_agent_install_probe_build_dependencies - name: Configure Sysdig Agent Repository ansible.builtin.include_tasks: "agent/configure-{{ 'rpm' if ansible_pkg_mgr in ['dnf', 'yum'] else 'deb' }}-repository.yml" - name: Install Sysdig Agent ansible.builtin.package: - name: "draios-agent{% if ansible_pkg_mgr == 'apt' %}={{ sysdig_agent_version }}{% else %}-{{ sysdig_agent_version }}{% endif %}" + name: "draios-agent{% if ansible_pkg_mgr == 'apt' %}={% else %}-{% endif %}{{ sysdig_agent_version }}" state: present - name: Create dragent.yaml file @@ -26,7 +31,7 @@ mode: 0644 - name: Enable eBPF - when: sysdig_agent_driver | lower == "ebpf" + when: sysdig_agent_driver_type == "ebpf" block: - name: (eBPF) Enable eBPF probe ansible.builtin.lineinfile: @@ -59,7 +64,7 @@ path: /etc/sysconfig/dragent regexp: SYSDIG_BPF_PROBE state: absent - when: sysdig_agent_driver == "kmodule" + when: sysdig_agent_driver_type == "kmodule" - name: Start dragent Service block: diff --git a/templates/agent_helpers.j2 b/templates/agent_helpers.j2 deleted file mode 100644 index d48c121c4..000000000 --- a/templates/agent_helpers.j2 +++ /dev/null @@ -1,48 +0,0 @@ -{%- macro mode_settings(mode) -%} -{%- if mode == 'secure' -%} -feature: - mode: secure -app_checks_enabled: false -jmx: - enabled: false -prometheus: - enabled: false -statsd: - enabled: false -{%- elif mode == 'monitor' -%} -feature: - mode: monitor -commandlines_capture: - enabled: false -drift_killer: - enabled: false -falcobaseline: - enabled: false -memdump: - enabled: false -network_topology: - enabled: false -secure_audit_streams: - enabled: false -{%- elif mode == 'secure_light' -%} -feature: - mode: secure_light -app_checks_enabled: false -drift_killer: - enabled: false -falcobaseline: - enabled: false -jmx: - enabled: false -memdump: - enabled: false -network_topology: - enabled: false -prometheus: - enabled: false -secure_audit_streams: - enabled: false -statsd: - enabled: false -{%- endif -%} -{% endmacro -%} diff --git a/templates/dragent.yaml.j2 b/templates/dragent.yaml.j2 index 4d5e2f449..a59cd8d50 100644 --- a/templates/dragent.yaml.j2 +++ b/templates/dragent.yaml.j2 @@ -1,10 +1 @@ -{% import 'agent_helpers.j2' as agent -%} -collector: {{ collector_url | default("https://collector.sysdigcloud.com") }} -customerid: {{ agent_access_key | default("CHANGE-ME") }} -{% if 'cointerface' not in sysdig_agent_settings -%} -cointerface: false -{% endif %} -{{ agent.mode_settings(sysdig_agent_mode | default('', true)) }} -{% if sysdig_agent_settings | default({}, true) | length > 0 %} -{{ sysdig_agent_settings | to_nice_yaml }} -{%- endif -%} +{{ vars | toDragentConfiguration | to_nice_yaml(indent=2, sort_keys=false) }} From cd3c7ad99a79b52963a3f8c88f8245285a43164e Mon Sep 17 00:00:00 2001 From: Adam Roberts Date: Mon, 22 May 2023 16:35:42 -0400 Subject: [PATCH 2/2] Add support for Secure and Monitor features --- filter_plugins/dragent.py | 160 ++++++++++++++++++++++++++++++++------ 1 file changed, 136 insertions(+), 24 deletions(-) diff --git a/filter_plugins/dragent.py b/filter_plugins/dragent.py index a20e8c316..6945e1304 100644 --- a/filter_plugins/dragent.py +++ b/filter_plugins/dragent.py @@ -7,17 +7,77 @@ def __init__(self, configuration: dict, features: dict): self._features = features +class UserPlanSettings(UserSettings): + def is_enabled(self) -> bool: + pass + + def type(self) -> str: + pass + + +class UserMonitorSettings(UserPlanSettings): + def is_enabled(self) -> bool: + return self.type() != "disabled" + + def type(self) -> str: + return self._configuration.get("monitoring", "standard").lower() + + @property + def app_checks(self) -> dict: + return self._features.get("app_checks", {}) + + @property + def jmx(self) -> dict: + return self._features.get("jmx", {}) + + @property + def prometheus(self) -> dict: + return self._features.get("prometheus", {}) + + @property + def statsd(self) -> dict: + return self._features.get("statsd", {}) + + +class UserSecureSettings(UserPlanSettings): + def is_enabled(self) -> bool: + return self.type() != "disabled" + + def type(self) -> str: + return self._configuration.get("security", "standard").lower() + + @property + def secure_audit_streams(self) -> dict: + return self._features.get("activity_audit", {}) + + @property + def commandlines_capture(self) -> dict: + return self._features.get("captures", {}) + + @property + def drift_detection(self) -> dict: + return self._features.get("drift_detection", {}) + + @property + def falcobaseline(self) -> dict: + return self._features.get("falcobaseline", {}) + + @property + def memdumper(self) -> dict: + return self._features.get("memdumper", {}) + + class UserConnectionSettings(UserSettings): @property - def access_key(self) -> str: + def customerid(self) -> str: return self._configuration["access_key"] @property - def ca_certificate_path(self) -> str: + def ca_certificate(self) -> str: return self._configuration.get("network_proxy", {}).get("ca_certificate_path") @property - def collector_url(self) -> str: + def collector(self) -> str: return self._configuration.get("custom_collector", {}).get("url") @property @@ -30,7 +90,7 @@ def proxy_defined(self) -> bool: @property def proxy_host(self) -> str: - return self._configuration["network_proxy"].get("url") + return self._configuration["network_proxy"].get("host") @property def proxy_port(self) -> int: @@ -45,6 +105,11 @@ def ssl_verify_certificate(self) -> bool: return self._configuration["network_proxy"].get("ssl_verify_certificate") +class UserExtraSettings(UserSettings): + @property + def override(self) -> dict: + return self._configuration.get("agent", {}).get("override", {}) + ######################################################## # Above are User settings # Below are Dragent config file items @@ -57,7 +122,10 @@ def __init__(self, config: dict): :param config: All user vars """ - pass + self.config = None + + def _get_config(self, keys): + return {k: getattr(self.config, k) for k in keys if getattr(self.config, k)} @abstractmethod def generate(self) -> dict: @@ -70,31 +138,72 @@ def generate(self) -> dict: class DragentConnectionSettings(DragentSettings): def __init__(self, config): - self.config = UserConnectionSettings(configuration=config["configuration"]["connection"], features={}) super().__init__(config) + self.config = UserConnectionSettings(configuration=config["configuration"]["connection"], features={}) def generate(self) -> dict: - ret = { - "collector": self.config.collector_url, - "collector_port": self.config.collector_port, - "customerid": self.config.access_key - } + ret = self._get_config(["collector", "collector_port", "customerid"]) + if self.config.proxy_defined: - proxy_settings = {} - if self.config.proxy_host: - proxy_settings.update({'proxy_host': self.config.proxy_host}) - if self.config.proxy_host: - proxy_settings.update({'proxy_port': self.config.proxy_port}) - if self.config.ssl: - proxy_settings.update({'ssl': self.config.ssl}) - if self.config.ssl_verify_certificate: - proxy_settings.update({'ssl_verify_certificate': self.config.ssl_verify_certificate}) - if self.config.ca_certificate_path: - proxy_settings.update({'ca_certificate': self.config.ca_certificate_path}) - ret.update({'http_proxy': proxy_settings}) + ret.update({'http_proxy': {k: getattr(self.config, k) for k in [ + "proxy_host", + "proxy_port", + "ssl", + "ssl_verify_certificate", + "ca_certificate" + ] if getattr(self.config, k)}}) return ret +class DragentMonitorSettings(DragentSettings): + def __init__(self, config): + super().__init__(config) + self.config = UserMonitorSettings(configuration=config["configuration"], + features=config["features"].get("monitoring", {})) + + def generate(self) -> dict: + if not self.config.is_enabled(): + ret = {"app_checks_enabled": False} + ret.update({feature: {"enabled": False} for feature in [ + "jmx", + "prometheus", + "statsd" + ]}) + return ret + return self._get_config(["app_checks", "jmx", "prometheus", "statsd"]) + + +class DragentSecureSettings(DragentSettings): + def __init__(self, config): + super().__init__(config) + self.config = UserSecureSettings(configuration=config["configuration"], + features=config["features"].get("security", {})) + + def generate(self) -> dict: + if not self.config.is_enabled(): + return {feature: {"enabled": False} for feature in [ + "commandlines_capture", + "drift_control", + "drift_killer", + "falcobaseline", + "memdump", + "secure_audit_streams" + ]} + return self._get_config(["commandlines_capture", "drift_detection", + "falcobaseline", "memdumper", "secure_audit_streams"]) + + +class DragentExtraSettings(DragentSettings): + def __init__(self, config): + super().__init__(config) + self.config = UserExtraSettings(configuration=config["configuration"], features={}) + + def generate(self) -> dict: + if self.config.override: + return self.config.override + return {} + + class Dragent: def __init__(self, config: dict): """ @@ -102,7 +211,10 @@ def __init__(self, config: dict): :param config: """ self._config_types = [ - DragentConnectionSettings(config=config) + DragentConnectionSettings(config=config), + DragentMonitorSettings(config=config), + DragentSecureSettings(config=config), + DragentExtraSettings(config=config) ] def generate(self) -> dict: