Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .ansible-lint-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ roles/collabora/defaults/main.yml jinja[spacing]
roles/collect_rpmnew_rpmsave/defaults/main.yml jinja[spacing]
roles/coturn/defaults/main.yml jinja[spacing]
roles/crypto_policy/defaults/main.yml jinja[spacing]
roles/curaline/defaults/main.yml jinja[spacing]
roles/curaline/vars/main.yml jinja[spacing]
roles/dnf_makecache/defaults/main.yml jinja[spacing]
roles/dnf_versionlock/defaults/main.yml jinja[spacing]
roles/docker/defaults/main.yml jinja[spacing]
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* **role:curaline, playbook:curaline**: Add a role and playbook that install and configure the curaLINE Client of Swisscom, which exchanges files with the curaLINE Document Routing service.
* **role:gitlab_ce**: Behind a reverse proxy, GitLab can show and log the real client IP address instead of the address of the proxy, for example under "Current sign-in IP". Set `gitlab_ce__rb_nginx_real_ip_trusted_addresses` to the address of the proxy.
* **role:apache_tomcat**: With `lfops__remove_rpmnew_rpmsave`, the role also removes the `.rpmnew` and `.rpmsave` files next to the Tomcat configuration files it deploys.
* **role:apache_tomcat**: Add support for Tomcat 10.1 on RHEL 10.
Expand Down
1 change: 1 addition & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Which Ansible role is proven to run on which OS?
| core_dumps | x | x | x | x | x | x | x | x | |
| coturn | | | x | x | (x) | | | | |
| crypto_policy | | | x | x | x | | | | |
| curaline | x | x | x | x | x | x | x | x | x86_64 only |
| dnf_makecache | | | x | x | x | | | | |
| dnf_versionlock | | | x | x | (x) | | | | |
| docker | | | x | (x) | (x) | | | | |
Expand Down
2 changes: 2 additions & 0 deletions extensions/molecule/curaline/hnet/converge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- name: 'Converge curaline playbook'
ansible.builtin.import_playbook: 'linuxfabrik.lfops.curaline'
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
curaline__version: '6.2.1'

# the credentials are not valid: the client keeps running and reports the denied login in its
# health endpoint, which is all this scenario needs to see that it reads its configuration.
curaline__client_id: 'linuxfabrik'
curaline__client_secret: 'linuxfabrik'

# a non-default value, so verify.yml can see that the running client uses what was configured.
curaline__schedule_delay: 'PT15M'

curaline__connectors__group_var:
- connector-id: 40000
mode: 'test'
content-type: 'application/forumdatenaustausch+xml;charset=UTF-8'
source-folder: '/data/xml/outbox-test/XMLDatenforum'
target-folder: '/data/xml/inbox-test/XMLDatenforum'
source-archive-enabled: true
source-archive-folder: '/data/xml/archive-test'
# a connector that is set to absent must neither end up in the configuration nor get folders.
- connector-id: 40000
mode: 'production'
source-folder: '/data/xml/outbox-absent/XMLDatenforum'
target-folder: '/data/xml/inbox-absent/XMLDatenforum'
state: 'absent'

# take over the account and the data folders of the H-Net SecureClient that prepare.yml set up.
curaline__group: 'hnet'
curaline__user: 'hnet'
21 changes: 21 additions & 0 deletions extensions/molecule/curaline/hnet/inventory/hosts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# yamllint disable rule:empty-values

# curaline targets 'lfops_curaline' (see playbooks/curaline.yml: hosts).
#
# All platforms of COMPATIBILITY.md are covered: the client is a self-contained download
# with its own Java runtime, so the role differs between the families only in the packages
# and in SELinux, which the RHEL family has enforcing.
lfops_curaline:
children:
systems_under_test:

systems_under_test:
hosts:
debian12-vm:
debian13-vm:
rocky8-vm:
rocky9-vm:
rocky10-vm:
ubuntu2204-vm:
ubuntu2404-vm:
ubuntu2604-vm:
11 changes: 11 additions & 0 deletions extensions/molecule/curaline/hnet/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Molecule scenario marker for the 'hnet' sub-scenario, run with
# `molecule test --scenario-name curaline/hnet`.
#
# The prepare playbook is overridden so the host looks like one with an H-Net SecureClient
# installation before converge runs: the regular `hnet` account with its home in
# /home/hnet and the /data/xml tree owned by it, labelled `user_home_t` on the RHEL
# family. The scenario-local prepare.yml imports the shared vm-prepare.yml first, so the
# systems under test are still waited for and their facts still gathered.
provisioner:
playbooks:
prepare: '${MOLECULE_SCENARIO_DIRECTORY}/prepare.yml'
69 changes: 69 additions & 0 deletions extensions/molecule/curaline/hnet/prepare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Wait for the systems under test and gather their facts, as every other scenario does.
- name: 'Prepare VMs for Ansible'
ansible.builtin.import_playbook: '../../playbooks/vm-prepare.yml'


# Set the host up the way the Linuxfabrik installation guide of the H-Net SecureClient
# does, without the SecureClient itself: the role has to take over this account and these
# folders without changing them.
- name: 'Set up the account and the data folders of an H-Net SecureClient'
hosts: 'systems_under_test'

tasks:

- name: 'useradd --comment "User for H-Net Secure Client" hnet'
ansible.builtin.user:
name: 'hnet'
comment: 'User for H-Net Secure Client'
shell: '/bin/bash'
state: 'present'

- name: 'mkdir -p /data/xml/{inbox,inbox-test,outbox,outbox-test,archive,archive-test} /var/hnet'
ansible.builtin.file:
path: '{{ item }}'
state: 'directory'
owner: 'hnet'
group: 'hnet'
mode: 0o755
loop:
- '/data/xml'
- '/data/xml/archive'
- '/data/xml/archive-test'
- '/data/xml/inbox'
- '/data/xml/inbox-test'
- '/data/xml/outbox'
- '/data/xml/outbox-test'
- '/var/hnet'

# an existing connector folder with a mode the role would not set, so verify.yml can
# see that the role leaves it alone.
- name: 'mkdir -m 0770 /data/xml/outbox-test/XMLDatenforum'
ansible.builtin.file:
path: '/data/xml/outbox-test/XMLDatenforum'
state: 'directory'
owner: 'hnet'
group: 'hnet'
mode: 0o770

- name: 'Label /data/xml as user_home_t'
when:
- 'ansible_facts["os_family"] == "RedHat"'
block:

- name: 'dnf install policycoreutils-python-utils'
ansible.builtin.package:
name: 'policycoreutils-python-utils'
state: 'present'
retries: 3
delay: 10

- name: 'semanage fcontext --add --type user_home_t "/data/xml(/.*)?"'
community.general.sefcontext:
target: '/data/xml(/.*)?'
setype: 'user_home_t'
state: 'present'

- name: 'restorecon -Fvr /data/xml'
ansible.builtin.command: 'restorecon -Fvr /data/xml'
register: '__molecule__curaline_restorecon_result'
changed_when: '__molecule__curaline_restorecon_result["stdout"] | length > 0'
64 changes: 64 additions & 0 deletions extensions/molecule/curaline/hnet/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# The client has to work under the `hnet` account exactly as under its own one, so the
# checks of the install scenario run first.
- name: 'Verify curaline'
ansible.builtin.import_playbook: '../install/verify.yml'


# What the takeover must not change is the account and the folders of the H-Net
# SecureClient, and the client must not write into /home/hnet, which the SecureClient
# lives in.
- name: 'Verify that the H-Net SecureClient installation is untouched'
hosts: 'systems_under_test'
gather_facts: false
tasks:

- name: 'getent passwd hnet'
ansible.builtin.getent:
database: 'passwd'
key: 'hnet'

# getent_passwd values: password, UID, GID, comment, home, shell
- name: 'Assert that the hnet account keeps its comment, home and shell'
ansible.builtin.assert:
that:
- 'ansible_facts["getent_passwd"]["hnet"][3] == "User for H-Net Secure Client"'
- 'ansible_facts["getent_passwd"]["hnet"][4] == "/home/hnet"'
- 'ansible_facts["getent_passwd"]["hnet"][5] == "/bin/bash"'

- name: 'stat /data/xml/outbox-test/XMLDatenforum'
ansible.builtin.stat:
path: '/data/xml/outbox-test/XMLDatenforum'
register: '__molecule__curaline_outbox_result'

- name: 'Assert that the existing connector folder keeps its mode'
ansible.builtin.assert:
that:
- '__molecule__curaline_outbox_result["stat"]["mode"] == "0770"'
- '__molecule__curaline_outbox_result["stat"]["pw_name"] == "hnet"'

# the client gets its $HOME from the unit, so its `.cache` lands below
# /var/lib/swisscom-schweiz-ag/cdr-client instead.
- name: 'stat /home/hnet/.cache'
ansible.builtin.stat:
path: '/home/hnet/.cache'
register: '__molecule__curaline_home_cache_result'

- name: 'Assert that the client did not write into the home of hnet'
ansible.builtin.assert:
that: 'not __molecule__curaline_home_cache_result["stat"]["exists"]'

- name: 'Verify the SELinux context of the data folders'
when:
- 'inventory_hostname is match("rocky")'
block:

- name: 'stat --format=%C /data/xml/inbox-test/XMLDatenforum'
ansible.builtin.command: 'stat --format=%C /data/xml/inbox-test/XMLDatenforum'
changed_when: false
register: '__molecule__curaline_inbox_result'

# the role created this folder, the file context of the SecureClient setup has to
# apply to it as well.
- name: 'Assert that the folders the role created get the user_home_t type'
ansible.builtin.assert:
that: '":user_home_t:" in __molecule__curaline_inbox_result["stdout"]'
2 changes: 2 additions & 0 deletions extensions/molecule/curaline/install/converge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- name: 'Converge curaline playbook'
ansible.builtin.import_playbook: 'linuxfabrik.lfops.curaline'
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
curaline__version: '6.2.1'

# the credentials are not valid: the client keeps running and reports the denied login in its
# health endpoint, which is all this scenario needs to see that it reads its configuration.
curaline__client_id: 'linuxfabrik'
curaline__client_secret: 'linuxfabrik'

# a non-default value, so verify.yml can see that the running client uses what was configured.
curaline__schedule_delay: 'PT15M'

curaline__connectors__group_var:
- connector-id: 40000
mode: 'test'
content-type: 'application/forumdatenaustausch+xml;charset=UTF-8'
source-folder: '/data/xml/outbox-test/XMLDatenforum'
target-folder: '/data/xml/inbox-test/XMLDatenforum'
source-archive-enabled: true
source-archive-folder: '/data/xml/archive-test'
# a connector that is set to absent must neither end up in the configuration nor get folders.
- connector-id: 40000
mode: 'production'
source-folder: '/data/xml/outbox-absent/XMLDatenforum'
target-folder: '/data/xml/inbox-absent/XMLDatenforum'
state: 'absent'
21 changes: 21 additions & 0 deletions extensions/molecule/curaline/install/inventory/hosts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# yamllint disable rule:empty-values

# curaline targets 'lfops_curaline' (see playbooks/curaline.yml: hosts).
#
# All platforms of COMPATIBILITY.md are covered: the client is a self-contained download
# with its own Java runtime, so the role differs between the families only in the packages
# and in SELinux, which the RHEL family has enforcing.
lfops_curaline:
children:
systems_under_test:

systems_under_test:
hosts:
debian12-vm:
debian13-vm:
rocky8-vm:
rocky9-vm:
rocky10-vm:
ubuntu2204-vm:
ubuntu2404-vm:
ubuntu2604-vm:
7 changes: 7 additions & 0 deletions extensions/molecule/curaline/install/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Molecule scenario marker for the 'install' sub-scenario, run with
# `molecule test --scenario-name curaline/install`.
#
# The shared config.yml already provisions libvirt/KVM VMs, so this
# scenario needs no driver override: the role sets the SELinux file context that lets systemd
# execute the client from /usr/lib, which only a VM with an enforcing SELinux proves, and the
# Debian family needs systemd, which the Debian and Ubuntu container images do not ship.
112 changes: 112 additions & 0 deletions extensions/molecule/curaline/install/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# What only the running system can confirm is that the client starts from the unpacked
# download under the configured account, that SELinux lets systemd execute it, and that it reads the
# configuration the role wrote: the client is asked for its health and its version, and it
# reports the values it parsed in its log. The configuration files themselves are not checked.
- name: 'Verify curaline'
hosts: 'systems_under_test'
gather_facts: false
tasks:

- name: 'Gather the service facts'
ansible.builtin.service_facts: # yamllint disable-line rule:empty-values

- name: 'Assert that swisscom-schweiz-ag-cdr-client.service is running and enabled'
ansible.builtin.assert:
that:
- 'ansible_facts["services"]["swisscom-schweiz-ag-cdr-client.service"]["state"] == "running"'
- 'ansible_facts["services"]["swisscom-schweiz-ag-cdr-client.service"]["status"] == "enabled"'

- name: 'Wait for the management port of the client'
ansible.builtin.wait_for:
host: '127.0.0.1'
port: 8193
timeout: 60

# the overall status is UP even though the credentials of this scenario are rejected, so the
# components are asserted: `config` is OK only if the client parsed the file without
# complaint.
- name: 'curl http://127.0.0.1:8193/actuator/health'
ansible.builtin.uri:
url: 'http://127.0.0.1:8193/actuator/health'
return_content: true
register: '__molecule__curaline_health_result'

- name: 'Assert that the client accepted its configuration'
ansible.builtin.assert:
that:
- '__molecule__curaline_health_result["json"]["components"]["config"]["status"] == "OK"'
- '__molecule__curaline_health_result["json"]["components"]["fileSynchronization"]["status"] == "ENABLED"'

- name: 'curl http://127.0.0.1:8193/actuator/info'
ansible.builtin.uri:
url: 'http://127.0.0.1:8193/actuator/info'
return_content: true
register: '__molecule__curaline_info_result'

- name: 'Assert that the client is the installed version'
ansible.builtin.assert:
that: '__molecule__curaline_info_result["json"]["build"]["version"] == curaline__version'

- name: 'systemctl show swisscom-schweiz-ag-cdr-client.service --property=MainPID --value' # noqa command-instead-of-module (read-only property query)
ansible.builtin.command: 'systemctl show swisscom-schweiz-ag-cdr-client.service --property=MainPID --value'
changed_when: false
register: '__molecule__curaline_mainpid_result'

- name: 'stat /proc/{{ __molecule__curaline_mainpid_result["stdout"] }}'
ansible.builtin.stat:
path: '/proc/{{ __molecule__curaline_mainpid_result["stdout"] }}'
register: '__molecule__curaline_proc_result'

# the hnet scenario imports this playbook and sets curaline__user, the install scenario
# relies on the role default.
- name: 'Assert that the client runs as {{ curaline__user | d("swisscom-schweiz-ag-cdr-client") }}'
ansible.builtin.assert:
that: '__molecule__curaline_proc_result["stat"]["pw_name"] == curaline__user | d("swisscom-schweiz-ag-cdr-client")'

# the client logs the schedule delay and the folders it watches after it has parsed them.
- name: 'Wait for the client to log the polling of the source folder'
ansible.builtin.command: "grep --no-messages 'Polling source directory' /var/log/swisscom-schweiz-ag/cdr-client/cdr-client-service.log"
register: '__molecule__curaline_log_result'
changed_when: false
retries: 12
delay: 5
until: '__molecule__curaline_log_result["rc"] == 0'

- name: 'Assert that the client uses the configured schedule delay and source folder'
ansible.builtin.assert:
that:
- '"every ''15m'' for connector" in __molecule__curaline_log_result["stdout"]'
- '"/data/xml/outbox-test/XMLDatenforum" in __molecule__curaline_log_result["stdout"]'

- name: 'stat /data/xml/outbox-absent'
ansible.builtin.stat:
path: '/data/xml/outbox-absent'
register: '__molecule__curaline_absent_result'

- name: 'Assert that the absent connector got no folders'
ansible.builtin.assert:
that: 'not __molecule__curaline_absent_result["stat"]["exists"]'

# without the bin_t file context systemd cannot execute the client from /usr/lib, and the
# service would not run at all. a host that happens to run permissive would pass that
# assertion without proving anything, so the mode is asserted too.
- name: 'Verify the SELinux context of the client'
when:
- 'inventory_hostname is match("rocky")'
block:

- name: 'getenforce'
ansible.builtin.command: 'getenforce'
changed_when: false
register: '__molecule__curaline_getenforce_result'

- name: 'cat /proc/{{ __molecule__curaline_mainpid_result["stdout"] }}/attr/current'
ansible.builtin.command: 'cat /proc/{{ __molecule__curaline_mainpid_result["stdout"] }}/attr/current'
changed_when: false
register: '__molecule__curaline_context_result'

- name: 'Assert that SELinux is enforcing and the client runs in unconfined_service_t'
ansible.builtin.assert:
that:
- '__molecule__curaline_getenforce_result["stdout"] == "Enforcing"'
- '"unconfined_service_t" in __molecule__curaline_context_result["stdout"]'
Loading
Loading