From 312504199b4bbf831b0e730078bb549c38a74798 Mon Sep 17 00:00:00 2001 From: Donat Szabo Date: Tue, 6 Oct 2026 11:27:59 +0200 Subject: [PATCH 1/2] feat: Argument spec implementation for logging role --- defaults/main.yml | 4 +- meta/argument_specs.yml | 866 ++++++++++++++++++++++++ tasks/assert_role_vars.yml | 195 ++++++ tasks/main.yml | 3 + tasks/set_vars.yml | 6 + tests/tests_invalid_input.yml | 1166 +++++++++++++++++++++++++++++++++ 6 files changed, 2239 insertions(+), 1 deletion(-) create mode 100644 meta/argument_specs.yml create mode 100644 tasks/assert_role_vars.yml create mode 100644 tests/tests_invalid_input.yml diff --git a/defaults/main.yml b/defaults/main.yml index e4efb9d7..5ba52072 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -79,7 +79,9 @@ logging_pki_files: [] # logging_domain # # The default DNS domain used to accept remote incoming logs from remote hosts. -logging_domain: '{{ ansible_facts["domain"] if ansible_facts["domain"] else ansible_facts["hostname"] }}' +# When empty, the role uses ansible_facts["domain"] if set, otherwise +# ansible_facts["hostname"]. +logging_domain: "" # logging_elasticsearch_password # diff --git a/meta/argument_specs.yml b/meta/argument_specs.yml new file mode 100644 index 00000000..b63fb744 --- /dev/null +++ b/meta/argument_specs.yml @@ -0,0 +1,866 @@ +# SPDX-License-Identifier: MIT +--- +argument_specs: + main: + short_description: The logging role. + description: > + The logging role configures the system logging service. The default + provider is `rsyslog`. Inputs, outputs, and the flows that connect + them are specified with `logging_inputs`, `logging_outputs`, and + `logging_flows`. + options: + logging_provider: + type: str + default: rsyslog + choices: + - rsyslog + description: > + The logging provider used by the role. Currently only + `rsyslog` is supported. + logging_enabled: + type: bool + default: true + description: > + Whether the role deploys the specified logging configuration. + When `false`, the role does not configure the provider. + logging_outputs: + type: list + elements: dict + default: [] + description: > + List of logging output specifications. Each item describes a + destination such as local files, a remote syslog server, + Elasticsearch, or RELP. + options: + name: + type: str + description: > + Unique name of the output. Used in `logging_flows` + outputs lists and as part of the generated configuration + file name. An item without `name` is ignored. + type: + type: str + required: true + choices: + - elasticsearch + - files + - forwards + - remote_files + - relp + - custom + description: > + Type of the output. `elasticsearch` sends logs to + Elasticsearch. `files` writes logs to local files. + `forwards` sends logs to a remote syslog server. + `remote_files` stores remote host logs in local files. + `relp` sends logs over RELP. `custom` is deprecated; + use `logging_custom_config_files` instead. + state: + type: str + choices: + - present + - absent + description: > + Whether the output configuration should exist + (`present`) or be removed (`absent`). + queue: + type: dict + description: > + Rsyslog general queue parameters for this output, such as + `size`. See the rsyslog documentation for supported keys. + action: + type: dict + description: > + Rsyslog general action parameters for this output, such + as `writeallmarkmessages`. See the rsyslog documentation + for supported keys. + facility: + type: str + description: > + Syslog facility used in the selector filter, such as + `*` or `authpriv,auth`. + severity: + type: str + description: > + Syslog severity used in the selector filter, such as + `*` or `info`. + exclude: + type: list + elements: str + description: > + Selector exclusions such as `mail.none` or `authpriv.none`. + Ignored when property-based filter options are set. + property: + type: str + description: > + Property name for a property-based filter. When set, + selector options are ignored. + property_op: + type: str + description: > + Comparison operator for the property-based filter, such + as `contains`. Quote the value when using `!`. + property_value: + type: str + description: > + Value compared against `property` in a property-based + filter. + path: + type: path + description: > + Path of the local output file for `files` outputs. + mode: + type: raw + description: > + File creation mode for `files` outputs, mapped to the + rsyslog `FileCreateMode` parameter. Accepts a string + such as `0660` or an integer. + owner: + type: raw + description: > + File owner for `files` outputs. Accepts a user name + string or a numeric uid. + group: + type: raw + description: > + File group for `files` outputs. Accepts a group name + string or a numeric gid. + dir_mode: + type: raw + description: > + Directory creation mode for `files` outputs, mapped to + the rsyslog `DirCreateMode` parameter. Accepts a string + such as `0770` or an integer. + dir_owner: + type: raw + description: > + Directory owner for `files` outputs. Accepts a user name + string or a numeric uid. + dir_group: + type: raw + description: > + Directory group for `files` outputs. Accepts a group + name string or a numeric gid. + template: + type: str + description: > + Output template for `files` and `forwards` outputs. + Allowed values are `traditional`, `syslog`, and + `modern`, or the name of a template defined in + `logging_custom_templates`. + server_host: + type: raw + description: > + Elasticsearch host name or list of host names for + `elasticsearch` outputs. Accepts a string or list of + strings. + server_port: + type: int + description: > + Port number the Elasticsearch server listens on. + index_prefix: + type: str + description: > + Elasticsearch index prefix used to index the log. + input_type: + type: str + choices: + - ovirt + description: > + Input type associated with the Elasticsearch output. + Currently only `ovirt` is supported. + retryfailures: + type: bool + description: > + Whether to retry Elasticsearch output actions after a + failure. + searchIndex: + type: str + description: > + Elasticsearch `searchIndex` template name for + `elasticsearch` outputs. + dynSearchIndex: + type: bool + description: > + Whether an `elasticsearch` output uses a dynamic search + index. + searchType: + type: str + description: > + Elasticsearch `searchType` value for `elasticsearch` + outputs. + bulkmode: + type: bool + description: > + Whether an `elasticsearch` output sends documents in + bulk mode. + writeoperation: + type: str + description: > + Elasticsearch write operation for `elasticsearch` + outputs, such as `create`. + bulkid: + type: str + description: > + Template name used as the bulk document id for + `elasticsearch` outputs. + dynbulkid: + type: bool + description: > + Whether an `elasticsearch` output uses a dynamic bulk + document id. + allowUnsignedCerts: + type: bool + description: > + Whether an `elasticsearch` output allows unsigned TLS + certificates. + retryruleset: + type: str + description: > + Rsyslog ruleset called when an `elasticsearch` output + retries a failed action. + usehttps: + type: bool + description: > + Whether an `elasticsearch` output connects with HTTPS. + tls: + type: bool + description: > + Whether to encrypt the connection with TLS. When `true` + for RELP or Elasticsearch, key and certificate paths + must be provided. + use_cert: + type: bool + description: > + Deprecated alias for `tls` on `elasticsearch` outputs. + ca_cert: + type: path + description: > + Path of the CA certificate on the managed host. + cert: + type: path + description: > + Path of the client or server certificate on the managed + host. + private_key: + type: path + description: > + Path of the private key on the managed host. + ca_cert_src: + type: path + description: > + Path of the CA certificate on the control host. The file + is copied to the managed host. + cert_src: + type: path + description: > + Path of the certificate on the control host. The file is + copied to the managed host. + private_key_src: + type: path + description: > + Path of the private key on the control host. The file is + copied to the managed host. + uid: + type: str + description: > + User name for Elasticsearch HTTP basic authentication. + Used together with `logging_elasticsearch_password`. + target: + type: str + description: > + Remote host for `forwards` and `relp` outputs. + udp_port: + type: int + description: > + UDP port for `forwards` outputs. + tcp_port: + type: int + description: > + TCP port for `forwards` outputs. + pki_authmode: + type: str + description: > + TLS authentication mode. For `forwards` outputs, one of + `x509/name`, `x509/fingerprint`, or `anon`. For `relp` + outputs, one of `name` or `fingerprint`. + permitted_server: + type: str + description: > + Hostname, IP address, SHA1 fingerprint, or wildcard DNS + domain of the TLS peer this `forwards` client may + connect to, such as `*.example.com`. + port: + type: int + description: > + Port number for `relp` outputs. + permitted_servers: + type: list + elements: str + description: > + Hostnames, IP addresses, SHA1 fingerprints, or wildcard + DNS domains this `relp` client may connect to over TLS. + async_writing: + type: bool + description: > + Whether `remote_files` outputs write files asynchronously. + client_count: + type: int + description: > + Number of remote clients supported by this + `remote_files` output, used as the dynamic file cache + size. + io_buffer_size: + type: int + description: > + Buffer size in bytes used when writing `remote_files` + output data. + remote_log_path: + type: str + description: > + Full path used to store filtered `remote_files` logs, + and may include rsyslog template properties such as + `%FROMHOST%`. + remote_sub_path: + type: str + description: > + Path relative to `logging_system_log_dir` used to store + filtered `remote_files` logs. + comment: + type: str + description: > + Comment written above a `remote_files` output template + in the generated configuration. + custom_config_files: + type: list + elements: path + description: > + Deprecated list of extra configuration files for + `custom` outputs. Use `logging_custom_config_files` + instead. + logging_inputs: + type: list + elements: dict + default: [] + description: > + List of logging input specifications. Each item describes a + source such as the systemd journal, local files, oVirt, a + remote syslog sender, or RELP. + options: + name: + type: str + description: > + Unique name of the input. Used in `logging_flows` + inputs lists and as part of the generated configuration + file name. An item without `name` is ignored. + type: + type: str + required: true + choices: + - basics + - files + - ovirt + - remote + - relp + description: > + Type of the input. `basics` reads from the systemd + journal or a unix socket. `files` reads local files. + `ovirt` reads oVirt engine, VDSM, or collectd logs. + `remote` receives logs over the network. `relp` + receives logs over RELP. + state: + type: str + choices: + - present + - absent + description: > + Whether the input configuration should exist + (`present`) or be removed (`absent`). + kernel_message: + type: bool + description: > + Whether to load `imklog` for `basics` inputs. + use_imuxsock: + type: bool + description: > + Whether `basics` inputs use `imuxsock` instead of + `imjournal`. + ratelimit_burst: + type: int + description: > + Maximum number of messages allowed within + `ratelimit_interval` for `basics` inputs. + ratelimit_interval: + type: int + description: > + Interval in seconds used to evaluate `ratelimit_burst` + for `basics` inputs. `0` disables rate limiting. + journal_persist_state_interval: + type: int + description: > + Number of journal messages between persist operations + for `basics` inputs using `imjournal`. + input_log_path: + type: str + description: > + Full path of the file read by a `files` input. Wildcards + such as `*` are allowed. This field is required for + `files` inputs to be deployed. + facility: + type: str + description: > + Facility filter applied to `files` input messages. + severity: + type: str + description: > + Severity filter applied to `files` input messages. + startmsg_regex: + type: str + description: > + Regular expression that matches the start of a `files` + input message. + endmsg_regex: + type: str + description: > + Regular expression that matches the end of a `files` + input message. + reopen_on_truncate: + type: bool + description: > + Whether a `files` input reopens the file when it is + truncated. + subtype: + type: str + description: > + oVirt input subtype. `engine` and `vdsm` read log files. + `collectd` listens for collectd metrics. Any other value + is ignored and the input is not deployed. + ovirt_env_name: + type: str + description: > + oVirt environment name. + ovirt_env_uuid: + type: str + description: > + oVirt environment UUID. + ovirt_elasticsearch_index_prefix: + type: str + description: > + Elasticsearch index prefix for oVirt logs or metrics. + ovirt_engine_fqdn: + type: str + description: > + Fully qualified domain name of the oVirt engine. + ovirt_input_file: + type: path + description: > + Input log file for oVirt `engine` or `vdsm` subtypes. + ovirt_vds_cluster_name: + type: str + description: > + VDS cluster name for oVirt `engine` and `vdsm` inputs. + ovirt_collectd_port: + type: int + description: > + TCP port used to receive collectd metrics for the oVirt + `collectd` subtype. + port: + type: int + description: > + Port number a `relp` input listens on. + max_data_size: + type: int + description: > + Maximum message size in bytes for a `relp` input. When + unset, the input uses `logging_max_message_size`. + tls: + type: bool + description: > + Whether to encrypt the connection with TLS. + ca_cert: + type: path + description: > + Path of the CA certificate on the managed host. + cert: + type: path + description: > + Path of the server certificate on the managed host. + private_key: + type: path + description: > + Path of the private key on the managed host. + ca_cert_src: + type: path + description: > + Path of the CA certificate on the control host. The file + is copied to the managed host. + cert_src: + type: path + description: > + Path of the certificate on the control host. The file is + copied to the managed host. + private_key_src: + type: path + description: > + Path of the private key on the control host. The file is + copied to the managed host. + pki_authmode: + type: str + description: > + TLS authentication mode. For `relp` inputs, one of + `name` or `fingerprint`. For `remote` inputs, one of + `x509/name`, `x509/fingerprint`, or `anon`. + permitted_clients: + type: list + elements: str + description: > + Hostnames, IP addresses, SHA1 fingerprints, or wildcard + DNS domains allowed to send logs over TLS. + udp_ports: + type: list + elements: int + description: > + UDP port numbers a `remote` input listens on. When set, + `tcp_ports` in the same item is ignored. + tcp_ports: + type: list + elements: int + description: > + TCP port numbers a `remote` input listens on. + logging_flows: + type: list + elements: dict + default: [] + description: > + List of relationships between logging inputs and outputs. + Each flow sends messages from the named inputs to the named + outputs. + options: + name: + type: str + required: true + description: > + Unique name of the flow. + inputs: + type: list + elements: str + required: true + description: > + List of `logging_inputs` `name` values that start + processing for this flow. + outputs: + type: list + elements: str + required: true + description: > + List of `logging_outputs` `name` values that receive + messages for this flow. + logging_purge_confs: + type: bool + default: false + description: > + Whether to remove files in `rsyslog.d` that do not belong to + any RPM package, including files generated by a previous + run of this role. When `true` and no `logging_inputs` are + provided, the rsyslog package may be reinstalled to restore + the system default `/etc/rsyslog.conf`. + logging_system_log_dir: + type: path + default: /var/log + description: > + Directory where local log output files are stored. + logging_preserve_fqdn: + type: bool + default: false + description: > + Whether log entries use the fully qualified domain name + instead of the short hostname. + logging_udp_threads: + type: int + default: 1 + description: > + Number of input threads listening on UDP ports. + logging_tcp_threads: + type: int + default: 1 + description: > + Number of input threads listening on plain TCP ports. + logging_udp_system_time_requery: + type: int + default: 2 + description: > + Number of operating system calls between system time + queries for UDP input. Values above `10` are not + recommended. + logging_udp_batch_size: + type: int + default: 32 + description: > + Maximum number of UDP messages read per operating system + call. Values above `128` are not recommended. + logging_server_queue_type: + type: str + default: LinkedList + choices: + - LinkedList + - FixedArray + description: > + Type of the server queue. `LinkedList` uses a linked list + queue. `FixedArray` uses a fixed-size array queue. + logging_server_queue_size: + type: int + default: 50000 + description: > + Maximum number of messages in the server queue. + logging_server_threads: + type: int + default: >- + {{ logging_udp_threads | int + logging_tcp_threads | int }} + description: > + Number of server worker threads. + logging_mark: + type: bool + default: false + description: > + Whether rsyslog emits periodic mark messages via `immark`. + logging_mark_interval: + type: int + default: 3600 + description: > + Interval in seconds between `immark` mark messages when + `logging_mark` is `true`. + logging_max_message_size: + type: int + default: 8192 + description: > + Maximum supported message size in bytes for sending and + receiving. Must be greater than `1024`. + logging_pki_files: + type: list + elements: dict + default: [] + description: > + List of PKI file specifications used when TLS is configured. + Each item may provide control-host source paths, managed-host + destination paths, or both. + options: + ca_cert_src: + type: path + description: > + Path of the CA certificate on the control host. The file + is copied to the managed host. + cert_src: + type: path + description: > + Path of the certificate on the control host. The file is + copied to the managed host. + private_key_src: + type: path + description: > + Path of the private key on the control host. The file is + copied to the managed host. + ca_cert: + type: path + description: > + Path of the CA certificate on the managed host. Also used + in the rsyslog configuration. + cert: + type: path + description: > + Path of the certificate on the managed host. Also used in + the rsyslog configuration. + private_key: + type: path + description: > + Path of the private key on the managed host. Also used in + the rsyslog configuration. + logging_domain: + type: str + default: "" + description: > + Default DNS domain used to accept remote incoming logs when + permitted peer lists are not set. When empty, the role uses + `ansible_facts['domain']` if set, otherwise + `ansible_facts['hostname']`. + logging_elasticsearch_password: + type: str + default: "" + description: > + Password for Elasticsearch HTTP basic authentication. Shared + by all Elasticsearch outputs and used only when the output + also sets `uid`. + logging_files_template_format: + type: str + default: "" + description: > + Default template for `files` outputs. Allowed values are + `traditional`, `syslog`, and `modern`, or the name of a + template defined in `logging_custom_templates`. An empty + value selects `modern`. + logging_forwards_template_format: + type: str + default: "" + description: > + Default template for `forwards` outputs. Allowed values are + `traditional`, `syslog`, and `modern`, or the name of a + template defined in `logging_custom_templates`. An empty + value selects `modern`. + logging_manage_firewall: + type: bool + default: false + description: > + Whether to configure firewalld for ports found in the role + parameters using the firewall system role. Limited to adding + ports. + logging_manage_selinux: + type: bool + default: false + description: > + Whether to configure SELinux policy for ports found in the + role parameters using the selinux system role. Limited to + adding policy. + logging_custom_config_files: + type: list + elements: path + default: [] + description: > + List of extra configuration files copied into the provider + configuration directory, such as `/etc/rsyslog.d/`. + logging_certificates: + type: list + elements: dict + default: [] + description: > + Certificate request specifications passed to the + `certificate` system role as `certificate_requests`. Used + to generate a private key and certificate for logging TLS. + options: + name: + type: str + required: true + description: > + The name of the certificate. A full path can be used to + choose the directory where files will be stored. + ca: + type: str + required: true + description: > + The CA that will issue the certificate, such as + `self-sign` or `ipa`. + dns: + type: raw + description: > + A domain name or list of domain names to include in the + certificate Subject Alternative Name (SAN). + email: + type: raw + description: > + An email address or list of email addresses to include + in the certificate Subject Alternative Name (SAN). + ip: + type: raw + description: > + An IP address or list of IP addresses to include in the + certificate Subject Alternative Name (SAN). + auto_renew: + type: bool + description: > + Whether the certificate should be renewed automatically + before it expires. + owner: + type: str + description: > + The user name or user id for the certificate and key + files. + group: + type: str + description: > + The group name or group id for the certificate and key + files. + mode: + type: raw + description: > + The file system permissions for the certificate and key + files. Accepts a string such as `0644` or an integer. + key_size: + type: int + description: > + The key size in bits. + common_name: + type: str + description: > + The Common Name requested for the certificate subject. + country: + type: str + description: > + The country code requested for the certificate subject. + state: + type: str + description: > + The state requested for the certificate subject. + locality: + type: str + description: > + The locality requested for the certificate subject. + organization: + type: str + description: > + The organization requested for the certificate subject. + organizational_unit: + type: str + description: > + The organizational unit requested for the certificate + subject. + contact_email: + type: str + description: > + The contact email requested for the certificate subject. + key_usage: + type: list + elements: str + choices: + - digitalSignature + - nonRepudiation + - keyEncipherment + - dataEncipherment + - keyAgreement + - keyCertSign + - cRLSign + - encipherOnly + - decipherOnly + description: > + The allowed Key Usage extensions for the certificate. + extended_key_usage: + type: list + elements: str + description: > + The Extended Key Usage attributes for the certificate. + run_before: + type: str + description: > + A command to run before saving the certificate. + run_after: + type: str + description: > + A command to run after saving the certificate. + principal: + type: raw + description: > + A Kerberos principal or list of Kerberos principals. + provider: + type: str + description: > + The underlying method used to request and manage the + certificate. + issuer: + type: str + description: > + The issuer certificate nickname or template name. + logging_custom_templates: + type: list + elements: str + default: [] + description: > + List of rsyslog output template definitions. Each item is a + string in the legacy `$template` format or the RainerScript + `template()` format. diff --git a/tasks/assert_role_vars.yml b/tasks/assert_role_vars.yml new file mode 100644 index 00000000..494dc841 --- /dev/null +++ b/tasks/assert_role_vars.yml @@ -0,0 +1,195 @@ +# SPDX-License-Identifier: MIT +--- +- name: Assert logging_max_message_size is greater than 1024 + ansible.builtin.assert: + that: + - logging_max_message_size | int > 1024 + fail_msg: >- + logging_max_message_size must be greater than 1024, + got {{ logging_max_message_size }} + when: logging_max_message_size is defined + +- name: Assert server_host in logging_outputs is a string or list of strings + ansible.builtin.assert: + that: + - >- + item.server_host is string + or (item.server_host is sequence and item.server_host is not mapping + and item.server_host | reject('string') | list | length == 0) + fail_msg: >- + logging_outputs[{{ idx }}].server_host must be a string or list of + strings, got {{ item.server_host | type_debug }} + loop: "{{ logging_outputs }}" + loop_control: + index_var: idx + label: "{{ item.name | d('unnamed') }}" + when: item.server_host is defined + +- name: Assert owner in logging_outputs is a string or integer + ansible.builtin.assert: + that: + - >- + item.owner is string + or (item.owner | type_debug) == 'int' + fail_msg: >- + logging_outputs[{{ idx }}].owner must be a string or integer, + got {{ item.owner | type_debug }} + loop: "{{ logging_outputs }}" + loop_control: + index_var: idx + label: "{{ item.name | d('unnamed') }}" + when: item.owner is defined + +- name: Assert group in logging_outputs is a string or integer + ansible.builtin.assert: + that: + - >- + item.group is string + or (item.group | type_debug) == 'int' + fail_msg: >- + logging_outputs[{{ idx }}].group must be a string or integer, + got {{ item.group | type_debug }} + loop: "{{ logging_outputs }}" + loop_control: + index_var: idx + label: "{{ item.name | d('unnamed') }}" + when: item.group is defined + +- name: Assert mode in logging_outputs is a string or integer + ansible.builtin.assert: + that: + - >- + item.mode is string + or (item.mode | type_debug) == 'int' + fail_msg: >- + logging_outputs[{{ idx }}].mode must be a string or integer, + got {{ item.mode | type_debug }} + loop: "{{ logging_outputs }}" + loop_control: + index_var: idx + label: "{{ item.name | d('unnamed') }}" + when: item.mode is defined + +- name: Assert dir_owner in logging_outputs is a string or integer + ansible.builtin.assert: + that: + - >- + item.dir_owner is string + or (item.dir_owner | type_debug) == 'int' + fail_msg: >- + logging_outputs[{{ idx }}].dir_owner must be a string or integer, + got {{ item.dir_owner | type_debug }} + loop: "{{ logging_outputs }}" + loop_control: + index_var: idx + label: "{{ item.name | d('unnamed') }}" + when: item.dir_owner is defined + +- name: Assert dir_group in logging_outputs is a string or integer + ansible.builtin.assert: + that: + - >- + item.dir_group is string + or (item.dir_group | type_debug) == 'int' + fail_msg: >- + logging_outputs[{{ idx }}].dir_group must be a string or integer, + got {{ item.dir_group | type_debug }} + loop: "{{ logging_outputs }}" + loop_control: + index_var: idx + label: "{{ item.name | d('unnamed') }}" + when: item.dir_group is defined + +- name: Assert dir_mode in logging_outputs is a string or integer + ansible.builtin.assert: + that: + - >- + item.dir_mode is string + or (item.dir_mode | type_debug) == 'int' + fail_msg: >- + logging_outputs[{{ idx }}].dir_mode must be a string or integer, + got {{ item.dir_mode | type_debug }} + loop: "{{ logging_outputs }}" + loop_control: + index_var: idx + label: "{{ item.name | d('unnamed') }}" + when: item.dir_mode is defined + +- name: Assert dns in logging_certificates is a string or list of strings + ansible.builtin.assert: + that: + - >- + item.dns is string + or (item.dns is sequence and item.dns is not mapping + and item.dns | reject('string') | list | length == 0) + fail_msg: >- + logging_certificates[{{ idx }}].dns must be a string or list of + strings, got {{ item.dns | type_debug }} + loop: "{{ logging_certificates }}" + loop_control: + index_var: idx + label: "{{ item.name | d('unnamed') }}" + when: item.dns is defined + +- name: Assert email in logging_certificates is a string or list of strings + ansible.builtin.assert: + that: + - >- + item.email is string + or (item.email is sequence and item.email is not mapping + and item.email | reject('string') | list | length == 0) + fail_msg: >- + logging_certificates[{{ idx }}].email must be a string or list of + strings, got {{ item.email | type_debug }} + loop: "{{ logging_certificates }}" + loop_control: + index_var: idx + label: "{{ item.name | d('unnamed') }}" + when: item.email is defined + +- name: Assert ip in logging_certificates is a string or list of strings + ansible.builtin.assert: + that: + - >- + item.ip is string + or (item.ip is sequence and item.ip is not mapping + and item.ip | reject('string') | list | length == 0) + fail_msg: >- + logging_certificates[{{ idx }}].ip must be a string or list of + strings, got {{ item.ip | type_debug }} + loop: "{{ logging_certificates }}" + loop_control: + index_var: idx + label: "{{ item.name | d('unnamed') }}" + when: item.ip is defined + +- name: Assert principal in logging_certificates is a string or list of strings + ansible.builtin.assert: + that: + - >- + item.principal is string + or (item.principal is sequence and item.principal is not mapping + and item.principal | reject('string') | list | length == 0) + fail_msg: >- + logging_certificates[{{ idx }}].principal must be a string or list of + strings, got {{ item.principal | type_debug }} + loop: "{{ logging_certificates }}" + loop_control: + index_var: idx + label: "{{ item.name | d('unnamed') }}" + when: item.principal is defined + +- name: Assert mode in logging_certificates is a string or integer + ansible.builtin.assert: + that: + - >- + item.mode is string + or (item.mode | type_debug) == 'int' + fail_msg: >- + logging_certificates[{{ idx }}].mode must be a string or integer, + got {{ item.mode | type_debug }} + loop: "{{ logging_certificates }}" + loop_control: + index_var: idx + label: "{{ item.name | d('unnamed') }}" + when: item.mode is defined diff --git a/tasks/main.yml b/tasks/main.yml index ed36fe4b..75f8a601 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -2,6 +2,9 @@ - name: Set global variables include_tasks: set_vars.yml +- name: Validate role parameters + ansible.builtin.include_tasks: assert_role_vars.yml + - name: Set Rsyslog facts then include rsyslog role when: - logging_provider == 'rsyslog' diff --git a/tasks/set_vars.yml b/tasks/set_vars.yml index aaa1c0be..735d5ab0 100644 --- a/tasks/set_vars.yml +++ b/tasks/set_vars.yml @@ -6,6 +6,12 @@ when: __logging_required_facts | difference(ansible_facts.keys() | list) | length > 0 +- name: Set logging_domain from ansible facts + set_fact: + logging_domain: "{{ ansible_facts['domain'] | default(ansible_facts['hostname'] + | default(inventory_hostname), true) }}" + when: logging_domain | d('') | length == 0 + - name: Record role begin fingerprint sr_fingerprint: status: begin diff --git a/tests/tests_invalid_input.yml b/tests/tests_invalid_input.yml new file mode 100644 index 00000000..05d0dc48 --- /dev/null +++ b/tests/tests_invalid_input.yml @@ -0,0 +1,1166 @@ +# SPDX-License-Identifier: MIT +--- +- name: Verify invalid parameters are rejected + hosts: all + tasks: + - name: Run invalid input tests + block: + # ==================================================== + # Section 1: Verify role works with valid defaults + # ==================================================== + - name: Run role with valid defaults + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + + - name: Mark valid defaults succeeded + ansible.builtin.set_fact: + __valid_defaults_succeeded: true + + - name: Assert role completed with valid defaults + ansible.builtin.assert: + that: + - __valid_defaults_succeeded | default(false) + fail_msg: >- + Role should succeed with default parameters + + # ==================================================== + # Section 2: argument_specs validation (Ansible 2.11+) + # ==================================================== + - name: Run argument specs validation tests + when: ansible_version.full is version("2.11", ">=") + block: + # --- Test: logging_enabled wrong type --- + - name: Argument specs reject non-boolean logging_enabled + block: + - name: Run role with non-boolean logging_enabled + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_enabled: not_a_bool + rescue: + - name: Mark invalid logging_enabled type rejected + ansible.builtin.set_fact: + __invalid_input_enabled_type_failed: true + when: >- + 'logging_enabled' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_enabled type was rejected + ansible.builtin.assert: + that: + - __invalid_input_enabled_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_enabled values + that are not booleans + + # --- Test: logging_purge_confs wrong type --- + - name: Argument specs reject non-boolean logging_purge_confs + block: + - name: Run role with non-boolean logging_purge_confs + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_purge_confs: not_a_bool + rescue: + - name: Mark invalid logging_purge_confs type rejected + ansible.builtin.set_fact: + __invalid_input_purge_confs_type_failed: true + when: >- + 'logging_purge_confs' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_purge_confs type was rejected + ansible.builtin.assert: + that: + - __invalid_input_purge_confs_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_purge_confs values + that are not booleans + + # --- Test: logging_preserve_fqdn wrong type --- + - name: Argument specs reject non-boolean logging_preserve_fqdn + block: + - name: Run role with non-boolean logging_preserve_fqdn + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_preserve_fqdn: not_a_bool + rescue: + - name: Mark invalid logging_preserve_fqdn type rejected + ansible.builtin.set_fact: + __invalid_input_preserve_fqdn_type_failed: true + when: >- + 'logging_preserve_fqdn' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_preserve_fqdn type was rejected + ansible.builtin.assert: + that: + - __invalid_input_preserve_fqdn_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_preserve_fqdn values + that are not booleans + + # --- Test: logging_mark wrong type --- + - name: Argument specs reject non-boolean logging_mark + block: + - name: Run role with non-boolean logging_mark + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_mark: not_a_bool + rescue: + - name: Mark invalid logging_mark type rejected + ansible.builtin.set_fact: + __invalid_input_mark_type_failed: true + when: >- + 'logging_mark' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_mark type was rejected + ansible.builtin.assert: + that: + - __invalid_input_mark_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_mark values + that are not booleans + + # --- Test: logging_manage_firewall wrong type --- + - name: Argument specs reject non-boolean logging_manage_firewall + block: + - name: Run role with non-boolean logging_manage_firewall + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_manage_firewall: not_a_bool + rescue: + - name: Mark invalid logging_manage_firewall type rejected + ansible.builtin.set_fact: + __invalid_input_manage_firewall_type_failed: true + when: >- + 'logging_manage_firewall' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_manage_firewall type was rejected + ansible.builtin.assert: + that: + - __invalid_input_manage_firewall_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_manage_firewall + values that are not booleans + + # --- Test: logging_manage_selinux wrong type --- + - name: Argument specs reject non-boolean logging_manage_selinux + block: + - name: Run role with non-boolean logging_manage_selinux + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_manage_selinux: not_a_bool + rescue: + - name: Mark invalid logging_manage_selinux type rejected + ansible.builtin.set_fact: + __invalid_input_manage_selinux_type_failed: true + when: >- + 'logging_manage_selinux' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_manage_selinux type was rejected + ansible.builtin.assert: + that: + - __invalid_input_manage_selinux_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_manage_selinux + values that are not booleans + + # --- Test: logging_udp_threads wrong type --- + - name: Argument specs reject non-integer logging_udp_threads + block: + - name: Run role with non-integer logging_udp_threads + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_udp_threads: not_an_int + rescue: + - name: Mark invalid logging_udp_threads type rejected + ansible.builtin.set_fact: + __invalid_input_udp_threads_type_failed: true + when: >- + 'logging_udp_threads' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_udp_threads type was rejected + ansible.builtin.assert: + that: + - __invalid_input_udp_threads_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_udp_threads values + that are not integers + + # --- Test: logging_tcp_threads wrong type --- + - name: Argument specs reject non-integer logging_tcp_threads + block: + - name: Run role with non-integer logging_tcp_threads + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_tcp_threads: not_an_int + rescue: + - name: Mark invalid logging_tcp_threads type rejected + ansible.builtin.set_fact: + __invalid_input_tcp_threads_type_failed: true + when: >- + 'logging_tcp_threads' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_tcp_threads type was rejected + ansible.builtin.assert: + that: + - __invalid_input_tcp_threads_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_tcp_threads values + that are not integers + + # --- Test: logging_udp_system_time_requery wrong type --- + - name: Argument specs reject non-integer logging_udp_system_time_requery + block: + - name: Run role with non-integer logging_udp_system_time_requery + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_udp_system_time_requery: not_an_int + rescue: + - name: Mark invalid logging_udp_system_time_requery type rejected + ansible.builtin.set_fact: + __invalid_input_udp_requery_type_failed: true + when: >- + 'logging_udp_system_time_requery' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_udp_system_time_requery type was rejected + ansible.builtin.assert: + that: + - __invalid_input_udp_requery_type_failed | default(false) + fail_msg: >- + argument_specs should reject + logging_udp_system_time_requery values that are not + integers + + # --- Test: logging_udp_batch_size wrong type --- + - name: Argument specs reject non-integer logging_udp_batch_size + block: + - name: Run role with non-integer logging_udp_batch_size + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_udp_batch_size: not_an_int + rescue: + - name: Mark invalid logging_udp_batch_size type rejected + ansible.builtin.set_fact: + __invalid_input_udp_batch_size_type_failed: true + when: >- + 'logging_udp_batch_size' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_udp_batch_size type was rejected + ansible.builtin.assert: + that: + - __invalid_input_udp_batch_size_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_udp_batch_size + values that are not integers + + # --- Test: logging_server_queue_size wrong type --- + - name: Argument specs reject non-integer logging_server_queue_size + block: + - name: Run role with non-integer logging_server_queue_size + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_server_queue_size: not_an_int + rescue: + - name: Mark invalid logging_server_queue_size type rejected + ansible.builtin.set_fact: + __invalid_input_server_queue_size_type_failed: true + when: >- + 'logging_server_queue_size' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_server_queue_size type was rejected + ansible.builtin.assert: + that: + - __invalid_input_server_queue_size_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_server_queue_size + values that are not integers + + # --- Test: logging_server_threads wrong type --- + - name: Argument specs reject non-integer logging_server_threads + block: + - name: Run role with non-integer logging_server_threads + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_server_threads: not_an_int + rescue: + - name: Mark invalid logging_server_threads type rejected + ansible.builtin.set_fact: + __invalid_input_server_threads_type_failed: true + when: >- + 'logging_server_threads' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_server_threads type was rejected + ansible.builtin.assert: + that: + - __invalid_input_server_threads_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_server_threads + values that are not integers + + # --- Test: logging_mark_interval wrong type --- + - name: Argument specs reject non-integer logging_mark_interval + block: + - name: Run role with non-integer logging_mark_interval + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_mark_interval: not_an_int + rescue: + - name: Mark invalid logging_mark_interval type rejected + ansible.builtin.set_fact: + __invalid_input_mark_interval_type_failed: true + when: >- + 'logging_mark_interval' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_mark_interval type was rejected + ansible.builtin.assert: + that: + - __invalid_input_mark_interval_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_mark_interval + values that are not integers + + # --- Test: logging_max_message_size wrong type --- + - name: Argument specs reject non-integer logging_max_message_size + block: + - name: Run role with non-integer logging_max_message_size + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_max_message_size: not_an_int + rescue: + - name: Mark invalid logging_max_message_size type rejected + ansible.builtin.set_fact: + __invalid_input_max_message_size_type_failed: true + when: >- + 'logging_max_message_size' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_max_message_size type was rejected + ansible.builtin.assert: + that: + - __invalid_input_max_message_size_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_max_message_size + values that are not integers + + # --- Test: logging_inputs wrong type --- + - name: Argument specs reject non-list logging_inputs + block: + - name: Run role with non-list logging_inputs + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_inputs: not_a_list + rescue: + - name: Mark invalid logging_inputs type rejected + ansible.builtin.set_fact: + __invalid_input_inputs_type_failed: true + when: >- + 'logging_inputs' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_inputs type was rejected + ansible.builtin.assert: + that: + - __invalid_input_inputs_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_inputs values + that are not lists + + # --- Test: logging_outputs wrong type --- + - name: Argument specs reject non-list logging_outputs + block: + - name: Run role with non-list logging_outputs + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_outputs: not_a_list + rescue: + - name: Mark invalid logging_outputs type rejected + ansible.builtin.set_fact: + __invalid_input_outputs_type_failed: true + when: >- + 'logging_outputs' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_outputs type was rejected + ansible.builtin.assert: + that: + - __invalid_input_outputs_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_outputs values + that are not lists + + # --- Test: logging_flows wrong type --- + - name: Argument specs reject non-list logging_flows + block: + - name: Run role with non-list logging_flows + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_flows: not_a_list + rescue: + - name: Mark invalid logging_flows type rejected + ansible.builtin.set_fact: + __invalid_input_flows_type_failed: true + when: >- + 'logging_flows' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_flows type was rejected + ansible.builtin.assert: + that: + - __invalid_input_flows_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_flows values + that are not lists + + # --- Test: logging_provider invalid choice --- + - name: Argument specs reject invalid logging_provider choice + block: + - name: Run role with invalid logging_provider value + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_provider: invalid_choice + rescue: + - name: Mark invalid logging_provider choice rejected + ansible.builtin.set_fact: + __invalid_input_provider_choice_failed: true + when: >- + 'must be one of' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_provider choice was rejected + ansible.builtin.assert: + that: + - __invalid_input_provider_choice_failed | default(false) + fail_msg: >- + argument_specs should reject logging_provider value + 'invalid_choice' + + # --- Test: logging_server_queue_type invalid choice --- + - name: Argument specs reject invalid logging_server_queue_type choice + block: + - name: Run role with invalid logging_server_queue_type value + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_server_queue_type: invalid_choice + rescue: + - name: Mark invalid logging_server_queue_type choice rejected + ansible.builtin.set_fact: + __invalid_input_queue_type_choice_failed: true + when: >- + 'must be one of' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid logging_server_queue_type choice was rejected + ansible.builtin.assert: + that: + - __invalid_input_queue_type_choice_failed | default(false) + fail_msg: >- + argument_specs should reject logging_server_queue_type + value 'invalid_choice' + + # --- Test: required input type missing --- + - name: Argument specs reject missing required input type + block: + - name: Run role without required input type + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_inputs: + - name: basic_input + rescue: + - name: Mark missing input type rejected + ansible.builtin.set_fact: + __invalid_input_missing_input_type_failed: true + when: >- + 'missing required arguments' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert missing input type was rejected + ansible.builtin.assert: + that: + - __invalid_input_missing_input_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_inputs entries + missing the required type field + + # --- Test: required output type missing --- + - name: Argument specs reject missing required output type + block: + - name: Run role without required output type + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_outputs: + - name: files_output + rescue: + - name: Mark missing output type rejected + ansible.builtin.set_fact: + __invalid_input_missing_output_type_failed: true + when: >- + 'missing required arguments' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert missing output type was rejected + ansible.builtin.assert: + that: + - __invalid_input_missing_output_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_outputs entries + missing the required type field + + # --- Test: required flow name missing --- + - name: Argument specs reject missing required flow name + block: + - name: Run role without required flow name + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_flows: + - inputs: [basic_input] + outputs: [files_output] + rescue: + - name: Mark missing flow name rejected + ansible.builtin.set_fact: + __invalid_input_missing_flow_name_failed: true + when: >- + 'missing required arguments' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert missing flow name was rejected + ansible.builtin.assert: + that: + - __invalid_input_missing_flow_name_failed | default(false) + fail_msg: >- + argument_specs should reject logging_flows entries + missing the required name field + + # --- Test: required flow inputs missing --- + - name: Argument specs reject missing required flow inputs + block: + - name: Run role without required flow inputs + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_flows: + - name: flow0 + outputs: [files_output] + rescue: + - name: Mark missing flow inputs rejected + ansible.builtin.set_fact: + __invalid_input_missing_flow_inputs_failed: true + when: >- + 'missing required arguments' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert missing flow inputs was rejected + ansible.builtin.assert: + that: + - __invalid_input_missing_flow_inputs_failed | default(false) + fail_msg: >- + argument_specs should reject logging_flows entries + missing the required inputs field + + # --- Test: required flow outputs missing --- + - name: Argument specs reject missing required flow outputs + block: + - name: Run role without required flow outputs + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_flows: + - name: flow0 + inputs: [basic_input] + rescue: + - name: Mark missing flow outputs rejected + ansible.builtin.set_fact: + __invalid_input_missing_flow_outputs_failed: true + when: >- + 'missing required arguments' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert missing flow outputs was rejected + ansible.builtin.assert: + that: + - __invalid_input_missing_flow_outputs_failed | default(false) + fail_msg: >- + argument_specs should reject logging_flows entries + missing the required outputs field + + # --- Test: required certificate name missing --- + - name: Argument specs reject missing required certificate name + block: + - name: Run role without required certificate name + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_certificates: + - ca: self-sign + dns: localhost + rescue: + - name: Mark missing certificate name rejected + ansible.builtin.set_fact: + __invalid_input_missing_cert_name_failed: true + when: >- + 'missing required arguments' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert missing certificate name was rejected + ansible.builtin.assert: + that: + - __invalid_input_missing_cert_name_failed | default(false) + fail_msg: >- + argument_specs should reject logging_certificates + entries missing the required name field + + # --- Test: required certificate ca missing --- + - name: Argument specs reject missing required certificate ca + block: + - name: Run role without required certificate ca + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_certificates: + - name: logging_cert + dns: localhost + rescue: + - name: Mark missing certificate ca rejected + ansible.builtin.set_fact: + __invalid_input_missing_cert_ca_failed: true + when: >- + 'missing required arguments' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert missing certificate ca was rejected + ansible.builtin.assert: + that: + - __invalid_input_missing_cert_ca_failed | default(false) + fail_msg: >- + argument_specs should reject logging_certificates + entries missing the required ca field + + # --- Test: invalid input type choice --- + - name: Argument specs reject invalid logging_inputs type + block: + - name: Run role with invalid logging_inputs type + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_inputs: + - name: basic_input + type: invalid_choice + rescue: + - name: Mark invalid input type choice rejected + ansible.builtin.set_fact: + __invalid_input_input_type_choice_failed: true + when: >- + 'must be one of' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid input type choice was rejected + ansible.builtin.assert: + that: + - __invalid_input_input_type_choice_failed | default(false) + fail_msg: >- + argument_specs should reject logging_inputs type + 'invalid_choice' + + # --- Test: invalid output type choice --- + - name: Argument specs reject invalid logging_outputs type + block: + - name: Run role with invalid logging_outputs type + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_outputs: + - name: files_output + type: invalid_choice + rescue: + - name: Mark invalid output type choice rejected + ansible.builtin.set_fact: + __invalid_input_output_type_choice_failed: true + when: >- + 'must be one of' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid output type choice was rejected + ansible.builtin.assert: + that: + - __invalid_input_output_type_choice_failed | default(false) + fail_msg: >- + argument_specs should reject logging_outputs type + 'invalid_choice' + + # --- Test: invalid input state choice --- + - name: Argument specs reject invalid logging_inputs state + block: + - name: Run role with invalid logging_inputs state + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_inputs: + - name: basic_input + type: basics + state: invalid_choice + rescue: + - name: Mark invalid input state choice rejected + ansible.builtin.set_fact: + __invalid_input_input_state_choice_failed: true + when: >- + 'must be one of' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid input state choice was rejected + ansible.builtin.assert: + that: + - __invalid_input_input_state_choice_failed | default(false) + fail_msg: >- + argument_specs should reject logging_inputs state + 'invalid_choice' + + # --- Test: nested tls wrong type --- + - name: Argument specs reject non-boolean logging_inputs tls + block: + - name: Run role with non-boolean logging_inputs tls + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_inputs: + - name: remote_input + type: remote + tls: not_a_bool + rescue: + - name: Mark invalid input tls type rejected + ansible.builtin.set_fact: + __invalid_input_tls_type_failed: true + when: >- + 'tls' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid input tls type was rejected + ansible.builtin.assert: + that: + - __invalid_input_tls_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_inputs tls values + that are not booleans + + # --- Test: nested udp_ports wrong type --- + - name: Argument specs reject non-list logging_inputs udp_ports + block: + - name: Run role with non-list logging_inputs udp_ports + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_inputs: + - name: remote_input + type: remote + udp_ports: not_a_list + rescue: + - name: Mark invalid udp_ports type rejected + ansible.builtin.set_fact: + __invalid_input_udp_ports_type_failed: true + when: >- + 'udp_ports' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert invalid udp_ports type was rejected + ansible.builtin.assert: + that: + - __invalid_input_udp_ports_type_failed | default(false) + fail_msg: >- + argument_specs should reject logging_inputs udp_ports + values that are not lists + + # ==================================================== + # Section 3: assert_role_vars validation (all versions) + # ==================================================== + + # --- Test: logging_max_message_size out of range --- + - name: Assert rejects logging_max_message_size of 1024 + block: + - name: Run role with logging_max_message_size of 1024 + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_max_message_size: 1024 + rescue: + - name: Mark logging_max_message_size range rejected + ansible.builtin.set_fact: + __invalid_input_max_message_size_range_failed: true + when: >- + 'logging_max_message_size must be greater than 1024' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert logging_max_message_size of 1024 was rejected + ansible.builtin.assert: + that: + - >- + __invalid_input_max_message_size_range_failed + | default(false) + fail_msg: >- + assert_role_vars should reject logging_max_message_size + when given a value that is not greater than 1024 + + # --- Test: server_host as integer --- + - name: Assert rejects server_host as an integer + block: + - name: Run role with server_host as an integer + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_outputs: + - name: es_output + type: elasticsearch + server_host: 123 + rescue: + - name: Mark server_host integer rejected + ansible.builtin.set_fact: + __invalid_input_server_host_int_failed: true + when: >- + 'server_host must be a string or list of strings' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert server_host as an integer was rejected + ansible.builtin.assert: + that: + - __invalid_input_server_host_int_failed | default(false) + fail_msg: >- + assert_role_vars should reject logging_outputs server_host + when given an integer value + + # --- Test: owner as list --- + - name: Assert rejects owner as a list + block: + - name: Run role with owner as a list + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_outputs: + - name: files_output + type: files + owner: + - root + rescue: + - name: Mark owner list rejected + ansible.builtin.set_fact: + __invalid_input_owner_list_failed: true + when: >- + 'owner must be a string or integer' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert owner as a list was rejected + ansible.builtin.assert: + that: + - __invalid_input_owner_list_failed | default(false) + fail_msg: >- + assert_role_vars should reject logging_outputs owner when + given a list value + + # --- Test: group as dict --- + - name: Assert rejects group as a dict + block: + - name: Run role with group as a dict + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_outputs: + - name: files_output + type: files + group: + name: root + rescue: + - name: Mark group dict rejected + ansible.builtin.set_fact: + __invalid_input_group_dict_failed: true + when: >- + 'group must be a string or integer' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert group as a dict was rejected + ansible.builtin.assert: + that: + - __invalid_input_group_dict_failed | default(false) + fail_msg: >- + assert_role_vars should reject logging_outputs group when + given a dict value + + # --- Test: mode as list --- + - name: Assert rejects mode as a list + block: + - name: Run role with mode as a list + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_outputs: + - name: files_output + type: files + mode: + - "0644" + rescue: + - name: Mark mode list rejected + ansible.builtin.set_fact: + __invalid_input_mode_list_failed: true + when: >- + 'mode must be a string or integer' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert mode as a list was rejected + ansible.builtin.assert: + that: + - __invalid_input_mode_list_failed | default(false) + fail_msg: >- + assert_role_vars should reject logging_outputs mode when + given a list value + + # --- Test: dir_owner as list --- + - name: Assert rejects dir_owner as a list + block: + - name: Run role with dir_owner as a list + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_outputs: + - name: files_output + type: files + dir_owner: + - root + rescue: + - name: Mark dir_owner list rejected + ansible.builtin.set_fact: + __invalid_input_dir_owner_list_failed: true + when: >- + 'dir_owner must be a string or integer' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert dir_owner as a list was rejected + ansible.builtin.assert: + that: + - __invalid_input_dir_owner_list_failed | default(false) + fail_msg: >- + assert_role_vars should reject logging_outputs dir_owner + when given a list value + + # --- Test: dir_group as list --- + - name: Assert rejects dir_group as a list + block: + - name: Run role with dir_group as a list + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_outputs: + - name: files_output + type: files + dir_group: + - root + rescue: + - name: Mark dir_group list rejected + ansible.builtin.set_fact: + __invalid_input_dir_group_list_failed: true + when: >- + 'dir_group must be a string or integer' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert dir_group as a list was rejected + ansible.builtin.assert: + that: + - __invalid_input_dir_group_list_failed | default(false) + fail_msg: >- + assert_role_vars should reject logging_outputs dir_group + when given a list value + + # --- Test: dir_mode as list --- + - name: Assert rejects dir_mode as a list + block: + - name: Run role with dir_mode as a list + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_outputs: + - name: files_output + type: files + dir_mode: + - "0755" + rescue: + - name: Mark dir_mode list rejected + ansible.builtin.set_fact: + __invalid_input_dir_mode_list_failed: true + when: >- + 'dir_mode must be a string or integer' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert dir_mode as a list was rejected + ansible.builtin.assert: + that: + - __invalid_input_dir_mode_list_failed | default(false) + fail_msg: >- + assert_role_vars should reject logging_outputs dir_mode + when given a list value + + # --- Test: certificate dns as integer --- + - name: Assert rejects logging_certificates dns as an integer + block: + - name: Run role with logging_certificates dns as an integer + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_certificates: + - name: logging_cert + ca: self-sign + dns: 123 + rescue: + - name: Mark certificate dns integer rejected + ansible.builtin.set_fact: + __invalid_input_cert_dns_int_failed: true + when: >- + 'dns must be a string or list of strings' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert logging_certificates dns as an integer was rejected + ansible.builtin.assert: + that: + - __invalid_input_cert_dns_int_failed | default(false) + fail_msg: >- + assert_role_vars should reject logging_certificates dns + when given an integer value + + # --- Test: certificate email as integer --- + - name: Assert rejects logging_certificates email as an integer + block: + - name: Run role with logging_certificates email as an integer + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_certificates: + - name: logging_cert + ca: self-sign + email: 123 + rescue: + - name: Mark certificate email integer rejected + ansible.builtin.set_fact: + __invalid_input_cert_email_int_failed: true + when: >- + 'email must be a string or list of strings' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert logging_certificates email as an integer was rejected + ansible.builtin.assert: + that: + - __invalid_input_cert_email_int_failed | default(false) + fail_msg: >- + assert_role_vars should reject logging_certificates email + when given an integer value + + # --- Test: certificate ip as integer --- + - name: Assert rejects logging_certificates ip as an integer + block: + - name: Run role with logging_certificates ip as an integer + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_certificates: + - name: logging_cert + ca: self-sign + ip: 123 + rescue: + - name: Mark certificate ip integer rejected + ansible.builtin.set_fact: + __invalid_input_cert_ip_int_failed: true + when: >- + 'ip must be a string or list of strings' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert logging_certificates ip as an integer was rejected + ansible.builtin.assert: + that: + - __invalid_input_cert_ip_int_failed | default(false) + fail_msg: >- + assert_role_vars should reject logging_certificates ip + when given an integer value + + # --- Test: certificate principal as integer --- + - name: Assert rejects logging_certificates principal as an integer + block: + - name: Run role with logging_certificates principal as an integer + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_certificates: + - name: logging_cert + ca: self-sign + principal: 123 + rescue: + - name: Mark certificate principal integer rejected + ansible.builtin.set_fact: + __invalid_input_cert_principal_int_failed: true + when: >- + 'principal must be a string or list of strings' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert logging_certificates principal as an integer was rejected + ansible.builtin.assert: + that: + - __invalid_input_cert_principal_int_failed | default(false) + fail_msg: >- + assert_role_vars should reject logging_certificates + principal when given an integer value + + # --- Test: certificate mode as list --- + - name: Assert rejects logging_certificates mode as a list + block: + - name: Run role with logging_certificates mode as a list + ansible.builtin.include_tasks: tasks/run_role_with_clear_facts.yml + vars: + logging_certificates: + - name: logging_cert + ca: self-sign + mode: + - "0644" + rescue: + - name: Mark certificate mode list rejected + ansible.builtin.set_fact: + __invalid_input_cert_mode_list_failed: true + when: >- + 'mode must be a string or integer' in + (ansible_failed_result | default({}) | to_json) + + - name: Assert logging_certificates mode as a list was rejected + ansible.builtin.assert: + that: + - __invalid_input_cert_mode_list_failed | default(false) + fail_msg: >- + assert_role_vars should reject logging_certificates mode + when given a list value + + always: + - name: Clear test facts + ansible.builtin.set_fact: + __valid_defaults_succeeded: + __invalid_input_enabled_type_failed: + __invalid_input_purge_confs_type_failed: + __invalid_input_preserve_fqdn_type_failed: + __invalid_input_mark_type_failed: + __invalid_input_manage_firewall_type_failed: + __invalid_input_manage_selinux_type_failed: + __invalid_input_udp_threads_type_failed: + __invalid_input_tcp_threads_type_failed: + __invalid_input_udp_requery_type_failed: + __invalid_input_udp_batch_size_type_failed: + __invalid_input_server_queue_size_type_failed: + __invalid_input_server_threads_type_failed: + __invalid_input_mark_interval_type_failed: + __invalid_input_max_message_size_type_failed: + __invalid_input_inputs_type_failed: + __invalid_input_outputs_type_failed: + __invalid_input_flows_type_failed: + __invalid_input_provider_choice_failed: + __invalid_input_queue_type_choice_failed: + __invalid_input_missing_input_type_failed: + __invalid_input_missing_output_type_failed: + __invalid_input_missing_flow_name_failed: + __invalid_input_missing_flow_inputs_failed: + __invalid_input_missing_flow_outputs_failed: + __invalid_input_missing_cert_name_failed: + __invalid_input_missing_cert_ca_failed: + __invalid_input_input_type_choice_failed: + __invalid_input_output_type_choice_failed: + __invalid_input_input_state_choice_failed: + __invalid_input_tls_type_failed: + __invalid_input_udp_ports_type_failed: + __invalid_input_max_message_size_range_failed: + __invalid_input_server_host_int_failed: + __invalid_input_owner_list_failed: + __invalid_input_group_dict_failed: + __invalid_input_mode_list_failed: + __invalid_input_dir_owner_list_failed: + __invalid_input_dir_group_list_failed: + __invalid_input_dir_mode_list_failed: + __invalid_input_cert_dns_int_failed: + __invalid_input_cert_email_int_failed: + __invalid_input_cert_ip_int_failed: + __invalid_input_cert_principal_int_failed: + __invalid_input_cert_mode_list_failed: + tags: tests::cleanup From 4dfd76063ec9b52b037819b06f5c87a5520e5059 Mon Sep 17 00:00:00 2001 From: Donat Szabo Date: Wed, 7 Oct 2026 15:17:59 +0200 Subject: [PATCH 2/2] Made it so the role uses the internal variable __logging_domain instead of logging_domain --- roles/rsyslog/templates/input_relp.j2 | 2 +- roles/rsyslog/templates/input_remote_module.j2 | 2 +- roles/rsyslog/templates/output_forwards.j2 | 2 +- roles/rsyslog/templates/output_relp.j2 | 2 +- tasks/set_vars.yml | 7 +++---- tests/tests_relp.yml | 4 ++-- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/roles/rsyslog/templates/input_relp.j2 b/roles/rsyslog/templates/input_relp.j2 index f7071cff..2c39f2ec 100644 --- a/roles/rsyslog/templates/input_relp.j2 +++ b/roles/rsyslog/templates/input_relp.j2 @@ -35,7 +35,7 @@ input(name="{{ __rsyslog_input.name }}" {% if __rsyslog_input.permitted_clients is defined %} tls.permittedpeer=["{{ __rsyslog_input.permitted_clients | join('","') }}"] {% else %} - tls.permittedpeer=["{{ '*.' + logging_domain }}"] + tls.permittedpeer=["{{ '*.' + __logging_domain }}"] {% endif %} {% endif %} ) diff --git a/roles/rsyslog/templates/input_remote_module.j2 b/roles/rsyslog/templates/input_remote_module.j2 index 61f4713b..9b4ff848 100644 --- a/roles/rsyslog/templates/input_remote_module.j2 +++ b/roles/rsyslog/templates/input_remote_module.j2 @@ -22,7 +22,7 @@ module(load="imtcp" {% if __rsyslog_input.permitted_clients | d() %} PermittedPeer=["{{ __rsyslog_input.permitted_clients | join('","') }}"] {% else %} - PermittedPeer=["*.{{ logging_domain }}"] + PermittedPeer=["*.{{ __logging_domain }}"] {% endif %} {% endif %} ) diff --git a/roles/rsyslog/templates/output_forwards.j2 b/roles/rsyslog/templates/output_forwards.j2 index 78a39511..9b11b9ed 100644 --- a/roles/rsyslog/templates/output_forwards.j2 +++ b/roles/rsyslog/templates/output_forwards.j2 @@ -22,7 +22,7 @@ ruleset(name="{{ __rsyslog_output.name }}") { StreamDriver="{{ __rsyslog_tls_netstream_driver }}" StreamDriverMode="1" StreamDriverAuthMode="{{ __rsyslog_output.pki_authmode | d(__rsyslog_default_pki_authmode) }}" - StreamDriverPermittedPeers="{{ __rsyslog_output.permitted_server | d('*.' + logging_domain) }}" + StreamDriverPermittedPeers="{{ __rsyslog_output.permitted_server | d('*.' + __logging_domain) }}" {% endif %} {% if __forwards_port != '' %} Port="{{ __forwards_port }}" diff --git a/roles/rsyslog/templates/output_relp.j2 b/roles/rsyslog/templates/output_relp.j2 index 48eff80a..46bf5380 100644 --- a/roles/rsyslog/templates/output_relp.j2 +++ b/roles/rsyslog/templates/output_relp.j2 @@ -39,7 +39,7 @@ ruleset(name="{{ __rsyslog_output.name }}") { {% if __rsyslog_output.permitted_servers is defined %} tls.permittedpeer=["{{ __rsyslog_output.permitted_servers | join('","') }}"] {% else %} - tls.permittedpeer=["{{ '*.' + logging_domain }}"] + tls.permittedpeer=["{{ '*.' + __logging_domain }}"] {% endif %} {% if __rsyslog_output.template | d("") | length > 0 %} template="{{ __rsyslog_output.template }}" diff --git a/tasks/set_vars.yml b/tasks/set_vars.yml index 735d5ab0..430f1785 100644 --- a/tasks/set_vars.yml +++ b/tasks/set_vars.yml @@ -6,11 +6,10 @@ when: __logging_required_facts | difference(ansible_facts.keys() | list) | length > 0 -- name: Set logging_domain from ansible facts +- name: Set logging domain set_fact: - logging_domain: "{{ ansible_facts['domain'] | default(ansible_facts['hostname'] - | default(inventory_hostname), true) }}" - when: logging_domain | d('') | length == 0 + __logging_domain: "{{ logging_domain if logging_domain | d('') | length > 0 + else ansible_facts['domain'] | d(ansible_facts['hostname']) }}" - name: Record role begin fingerprint sr_fingerprint: diff --git a/tests/tests_relp.yml b/tests/tests_relp.yml index f4861637..19ee6409 100644 --- a/tests/tests_relp.yml +++ b/tests/tests_relp.yml @@ -171,7 +171,7 @@ shell: >- set -euo pipefail; grep - "tls.permittedpeer=\[\"\*.{{ logging_domain }}\"\]" "{{ __test_relp_server1 }}" | wc -l + "tls.permittedpeer=\[\"\*.{{ __logging_domain }}\"\]" "{{ __test_relp_server1 }}" | wc -l register: __result changed_when: false failed_when: __result.stdout != "1" @@ -335,7 +335,7 @@ shell: >- set -euo pipefail; grep - "tls.permittedpeer=\[\"\*.{{ logging_domain }}\"\]" "{{ __test_relp_client1 }}" | wc -l + "tls.permittedpeer=\[\"\*.{{ __logging_domain }}\"\]" "{{ __test_relp_client1 }}" | wc -l register: __result changed_when: false failed_when: __result.stdout != "1"