From a430a0c85fe7fdd9b542e4316a4b434c340178ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Sachse?= <Joerg.Sachse@slub-dresden.de>
Date: Fri, 5 Aug 2022 14:38:06 +0200
Subject: [PATCH] test: implement all necessary changes for using GitLab-CI,
 including Linter recommendations and idempotency changes

---
 .ansible-lint                                 | 45 +++++-----
 .config/molecule/config.yml                   |  2 +
 .gitignore                                    | 13 ++-
 .gitlab-ci.yml                                | 33 +++++++
 .yamllint                                     | 32 ++++---
 ansible.cfg                                   |  3 +-
 handlers/main.yml                             | 28 +++---
 meta/main.yml                                 | 46 +++++-----
 molecule/README.md                            | 85 -------------------
 molecule/default                              |  1 +
 molecule/default/converge.yml                 |  5 --
 molecule/default/molecule.yml                 | 36 --------
 molecule/default/prepare.yml                  | 39 ---------
 molecule/default/tests/conftest.py            | 20 -----
 molecule/default/tests/test_default.py        |  6 --
 molecule/resources/playbooks/Dockerfile       | 20 +++++
 .../playbooks}/INSTALL.rst                    |  6 +-
 molecule/resources/playbooks/README.md        |  3 +
 molecule/resources/playbooks/converge.yml     | 17 ++++
 molecule/resources/playbooks/prepare.yml      | 22 +++++
 molecule/resources/playbooks/verify.yml       | 10 +++
 molecule/virtualbox/molecule.yml              | 41 +++++++++
 requirements.yml => requirements.yml.example  |  0
 site.yml                                      |  1 -
 ...re-fail2ban.yml => configure_fail2ban.yml} | 24 +++---
 ...ons.yml => configure_home_permissions.yml} |  6 +-
 ...re-iptables.yml => configure_iptables.yml} | 33 +++----
 ...rs.yml => configure_kernel_parameters.yml} |  2 +-
 .../{configure-pam.yml => configure_pam.yml}  | 12 +--
 ...age.yml => configure_portable_storage.yml} |  6 +-
 ...{configure-root.yml => configure_root.yml} |  6 +-
 ...dening.yml => configure_ssh_hardening.yml} |  2 +-
 ...onfigure-umask.yml => configure_umask.yml} | 18 ++--
 tasks/install-debsecan.yml                    | 26 ------
 ...{install-auditd.yml => install_auditd.yml} |  6 +-
 ...{install-clamav.yml => install_clamav.yml} | 31 ++++---
 tasks/install_debsecan.yml                    | 25 ++++++
 ...tall-rkhunter.yml => install_rkhunter.yml} | 76 +++++++++++------
 tasks/main.yml                                | 37 ++++----
 39 files changed, 410 insertions(+), 414 deletions(-)
 create mode 100644 .config/molecule/config.yml
 create mode 100644 .gitlab-ci.yml
 delete mode 100644 molecule/README.md
 create mode 120000 molecule/default
 delete mode 100644 molecule/default/converge.yml
 delete mode 100644 molecule/default/molecule.yml
 delete mode 100644 molecule/default/prepare.yml
 delete mode 100644 molecule/default/tests/conftest.py
 delete mode 100644 molecule/default/tests/test_default.py
 create mode 100644 molecule/resources/playbooks/Dockerfile
 rename molecule/{default => resources/playbooks}/INSTALL.rst (84%)
 create mode 100644 molecule/resources/playbooks/README.md
 create mode 100644 molecule/resources/playbooks/converge.yml
 create mode 100644 molecule/resources/playbooks/prepare.yml
 create mode 100644 molecule/resources/playbooks/verify.yml
 create mode 100644 molecule/virtualbox/molecule.yml
 rename requirements.yml => requirements.yml.example (100%)
 rename tasks/{configure-fail2ban.yml => configure_fail2ban.yml} (89%)
 rename tasks/{configure-home-permissions.yml => configure_home_permissions.yml} (87%)
 rename tasks/{configure-iptables.yml => configure_iptables.yml} (90%)
 rename tasks/{configure-kernel-parameters.yml => configure_kernel_parameters.yml} (99%)
 rename tasks/{configure-pam.yml => configure_pam.yml} (93%)
 rename tasks/{configure-portable-storage.yml => configure_portable_storage.yml} (90%)
 rename tasks/{configure-root.yml => configure_root.yml} (64%)
 rename tasks/{configure-ssh-hardening.yml => configure_ssh_hardening.yml} (99%)
 rename tasks/{configure-umask.yml => configure_umask.yml} (85%)
 delete mode 100644 tasks/install-debsecan.yml
 rename tasks/{install-auditd.yml => install_auditd.yml} (89%)
 rename tasks/{install-clamav.yml => install_clamav.yml} (92%)
 create mode 100644 tasks/install_debsecan.yml
 rename tasks/{install-rkhunter.yml => install_rkhunter.yml} (69%)

diff --git a/.ansible-lint b/.ansible-lint
index 9c9323e..f18a647 100644
--- a/.ansible-lint
+++ b/.ansible-lint
@@ -6,7 +6,7 @@
 # and not relative to the CWD of execution. CLI arguments passed to the --exclude
 # option will be parsed relative to the CWD of execution.
 exclude_paths:
-  - .cache/ # implicit unless exclude_paths is defined in config
+  - .cache/    # implicit unless exclude_paths is defined in config
   - .git/
   - .githooks/
   - backups/
@@ -15,24 +15,19 @@ exclude_paths:
 # verbosity: 1
 
 # Mock modules or roles in order to pass ansible-playbook --syntax-check
-#mock_modules:
-#  - zuul_return
-#  # note the foo.bar is invalid as being neither a module or a collection
-#  - fake_namespace.fake_collection.fake_module
-#  - fake_namespace.fake_collection.fake_module.fake_submodule
-#mock_roles:
-#  - mocked_role
-#  - author.role_name # old standalone galaxy role
-#  - fake_namespace.fake_collection.fake_role # role within a collection
+# mock_modules:
+#   - zuul_return
+#   # note the foo.bar is invalid as being neither a module or a collection
+#   - fake_namespace.fake_collection.fake_module
+#   - fake_namespace.fake_collection.fake_module.fake_submodule
+# mock_roles:
+#   - mocked_role
+#   - author.role_name # old standalone galaxy role
+#   - fake_namespace.fake_collection.fake_role # role within a collection
 
 # Enable checking of loop variable prefixes in roles
 loop_var_prefix: "{role}_"
 
-# Enforce variable names to follow pattern below, in addition to Ansible own
-# requirements, like avoiding python identifiers. To disable add `var-naming`
-# to skip_list.
-var_naming_pattern: "^[a-z_][a-z0-9_]*$"
-
 use_default_rules: true
 # Load custom rules from this specific folder
 # rulesdir:
@@ -46,9 +41,9 @@ skip_list:
 # Any rule that has the 'opt-in' tag will not be loaded unless its 'id' is
 # mentioned in the enable_list:
 enable_list:
-  - empty-string-compare # opt-in
-  - no-log-password # opt-in
-  - no-same-owner # opt-in
+  - empty-string-compare    # opt-in
+  - no-log-password         # opt-in
+  - no-same-owner           # opt-in
   # add yaml here if you want to avoid ignoring yaml checks when yamllint
   # library is missing. Normally its absence just skips using that rule.
   - yaml
@@ -60,19 +55,19 @@ enable_list:
 warn_list:
   - skip_this_tag
   - git-latest
-  - experimental # experimental is included in the implicit list
+  - experimental    # experimental is included in the implicit list
   # - role-name
 
 # Offline mode disables installation of requirements.yml
 offline: false
 
 # Define required Ansible's variables to satisfy syntax check
-#extra_vars:
-#  foo: bar
-#  multiline_string_variable: |
-#    line1
-#    line2
-#  complex_variable: ":{;\t$()"
+# extra_vars:
+#   foo: bar
+#   multiline_string_variable: |
+#     line1
+#     line2
+#   complex_variable: ":{;\t$()"
 
 # Uncomment to enforce action validation with tasks, usually is not
 # needed as Ansible syntax check also covers it.
diff --git a/.config/molecule/config.yml b/.config/molecule/config.yml
new file mode 100644
index 0000000..ece7ff6
--- /dev/null
+++ b/.config/molecule/config.yml
@@ -0,0 +1,2 @@
+---
+prerun: false
diff --git a/.gitignore b/.gitignore
index 4ac23e9..ed78332 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,8 @@ Thumbs.db
 
 *.retry
 *.vault
+inventory.*
+inv.*
 
 # Vim #
 #######
@@ -73,7 +75,14 @@ tags
 .vagrant/
 *.box
 
-# Backups #
-###########
+# Temporary/Build/Backup #
+##########################
 
 backups/
+build/
+
+# CONFIDENTIAL #
+################
+
+ssh_host_*
+
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..df4e06f
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,33 @@
+---
+# A pipeline is composed of independent jobs that run scripts, grouped into stages.
+# Stages run in sequential order, but jobs within stages run in parallel.
+#
+# For more information, see: https://docs.gitlab.com/ee/ci/yaml/index.html#stages
+
+stages:          # List of stages for jobs, and their order of execution
+  - test
+
+default:
+  before_script:
+    - source /opt/molecule/bin/activate
+    - ansible --version
+    - molecule --version
+
+test-job:
+  stage: test
+  tags:
+    - "shell"
+  script:
+    # make sure that Ansible Vaults are present and can be decrypted
+    - echo "${VAULT_SERVER_HARDENING}" > ../lza_server_hardening.pass
+    - export ANSIBLE_VAULT_PASSWORD_FILE=../lza_server_hardening.pass
+    - rm -rf ../ansible_vaults/
+    - git clone https://gitlab+deploy-token-25:${VAULT_ACCESS_TOKEN}@git.slub-dresden.de/slub-referat-2-3/ansible_vaults.git ../ansible_vaults/; \
+    # run Molecule tests
+    - molecule syntax --scenario-name default
+    - molecule lint --scenario-name default
+    - molecule create --scenario-name default
+    - molecule converge --scenario-name default
+    - molecule idempotence --scenario-name default
+    # - molecule verify --scenario-name default
+    - molecule destroy --scenario-name default
diff --git a/.yamllint b/.yamllint
index 7c0f15c..8827676 100644
--- a/.yamllint
+++ b/.yamllint
@@ -1,7 +1,5 @@
 ---
-# based on documentation available at
-# https://yamllint.readthedocs.io/en/stable/rules.html
-
+# Based on ansible-lint config
 extends: default
 
 rules:
@@ -11,13 +9,25 @@ rules:
   brackets:
     max-spaces-inside: 1
     level: error
-  comments:
-    min-spaces-from-content: 4
+  colons:
+    max-spaces-after: -1
+    level: error
+  commas:
+    max-spaces-after: -1
+    level: error
+  comments: disable
   comments-indentation: disable
-  document-end: disable
-  document-start:
-    level: warning
-  octal-values:
-    forbid-explicit-octal: false
+  document-start: disable
+  empty-lines:
+    max: 3
+    level: error
+  hyphens:
+    level: error
+  indentation: disable
+  key-duplicates: enable
   line-length: disable
-  truthy: enable
+  new-line-at-end-of-file: disable
+  new-lines:
+    type: unix
+  trailing-spaces: disable
+  truthy: disable
diff --git a/ansible.cfg b/ansible.cfg
index c43f9b3..5c000f2 100644
--- a/ansible.cfg
+++ b/ansible.cfg
@@ -1,7 +1,8 @@
 [defaults]
 # If set, configures the path to the Vault password file as an alternative to
 # specifying --vault-password-file on the command line.
-vault_identity_list = ../lza_install_common.pass, ../lza_server_hardening.pass, ../slub_osquery.pass
+# vault_identity_list = ../lza_install_common.pass, ../lza_server_hardening.pass, ../slub_osquery.pass
+vault_identity_list = ../lza_server_hardening.pass
 
 # Path to default inventory file
 # Administrators can override this by using the "-i <inventoryfile>" CLI
diff --git a/handlers/main.yml b/handlers/main.yml
index ada4777..702d766 100644
--- a/handlers/main.yml
+++ b/handlers/main.yml
@@ -2,7 +2,7 @@
 - name: save iptables rules (Debian)
   block:
     - name: Ordner für iptables-Config erstellen
-      file:
+      ansible.builtin.file:
         path: "/etc/iptables"
         state: directory
         owner: "root"
@@ -10,19 +10,20 @@
         mode: 0755
       listen: "save iptables rules"
     - name: install netfilter-persistent to be able to save iptables rules
-      apt:
+      ansible.builtin.apt:
         name: netfilter-persistent
         state: present
       listen: "save iptables rules"
     - name: save iptables rules
-      command: 'netfilter-persistent save'
+      ansible.builtin.command: 'netfilter-persistent save'
       listen: "save iptables rules"
+      changed_when: false
   when: ansible_os_family == "Debian"
 
 - name: save iptables rules (RedHat)
   block:
     - name: make sure iptables config file exists
-      file:
+      ansible.builtin.file:
         path: "/etc/sysconfig/iptables"
         state: touch
         owner: "root"
@@ -30,33 +31,34 @@
         mode: 0600
       listen: "save iptables rules"
     - name: save rules
-      command: /usr/sbin/iptables-save        # noqa 303
+      ansible.builtin.command: /usr/sbin/iptables-save        # noqa 303
       listen: "save iptables rules"
+      changed_when: false
   when: ansible_os_family == "RedHat"
 
 - name: activate kernel parameter changes
-  command: sysctl -p
-  ignore_errors: true
+  ansible.builtin.command: sysctl -p
+  changed_when: false
 
 - name: restart fail2ban.service
-  service:
+  ansible.builtin.service:
     name: "fail2ban"
     state: restarted
 
 - name: restart sshd
-  service:
+  ansible.builtin.service:
     name: "sshd"
     state: restarted
 
 - name: restart auditd.service
-  service:
+  ansible.builtin.service:
     name: "auditd"
     state: restarted
   when: ansible_os_family == "Debian"
   listen: restart auditd.service
 
 - name: restart auditd.service
-  service:
+  ansible.builtin.service:
     name: "auditd"
     state: restarted
     use: "service"
@@ -64,13 +66,13 @@
   listen: restart auditd.service
 
 - name: restart clamav-daemon service
-  service:
+  ansible.builtin.service:
     name: "clamav-daemon"
     state: restarted
   when: ansible_os_family == "Debian"
 
 - name: restart clamd service
-  service:
+  ansible.builtin.service:
     name: "clamd@{{ ansible_hostname }}.service"
     state: restarted
   when: ansible_os_family == "RedHat"
diff --git a/meta/main.yml b/meta/main.yml
index a798dd1..cf10295 100644
--- a/meta/main.yml
+++ b/meta/main.yml
@@ -1,16 +1,24 @@
 ---
 galaxy_info:
-  author: Jörg Sachse
-  description: role to deploy a hardened install of Debian for use in the SLUBarchiv digital preservation repository
+  author: Jörg Sachse <Joerg.Sachse@slub-dresden.de>
   company: SLUB Dresden
-  # If the issue tracker for your role is not on github, uncomment the next line and provide a value issue_tracker_url: http://example.com/issue/tracker Some suggested licenses: - BSD
-  # (default) - MIT - GPLv2 - GPLv3 - Apache - CC-BY
-  license: public domain
-  min_ansible_version: 2.4
-  # If this a Container Enabled role, provide the minimum Ansible Container version. min_ansible_container_version: Optionally specify the branch Galaxy will use when accessing the GitHub repo
-  # for this role. During role install, if no tags are available, Galaxy will use this branch. During import Galaxy will access files on this branch. If Travis integration is configured, only
-  # notifications for this branch will be accepted. Otherwise, in all cases, the repo's default branch (usually master) will be used. github_branch:
-  #
+  description: role to deploy a hardened install of Debian for use in the SLUBarchiv digital preservation repository
+  galaxy_tags: []
+    # List tags for your role here, one per line. A tag is a keyword that describes and categorizes the role. Users find roles by searching for tags. Be sure to remove the '[]' above, if you
+    # add tags to this list.
+    #
+    # NOTE: A tag is limited to a single word comprised of alphanumeric characters.
+    #       Maximum 20 tags per role.
+  # issue_tracker_url: "https://example.com/"
+    # If the issue tracker for your role is not on github, uncomment the next line and provide a value issue_tracker_url: http://example.com/issue/tracker
+  license: GPLv3
+    # Some suggested licenses: - BSD
+    # (default) - MIT - GPLv2 - GPLv3 - Apache - CC-BY
+  min_ansible_version: "2.5"
+    # If this a Container Enabled role, provide the minimum Ansible Container version. min_ansible_container_version: Optionally specify the branch Galaxy will use when accessing the GitHub repo
+    # for this role. During role install, if no tags are available, Galaxy will use this branch. During import Galaxy will access files on this branch. If Travis integration is configured, only
+    # notifications for this branch will be accepted. Otherwise, in all cases, the repo's default branch (usually master) will be used. github_branch:
+  namespace: "slub"
   # Provide a list of supported platforms, and for each platform a list of versions. If you don't wish to enumerate all versions for a particular platform, use 'all'. To view available
   # platforms and versions (or releases), visit: https://galaxy.ansible.com/api/v1/platforms/
   #
@@ -19,16 +27,10 @@ galaxy_info:
   platforms:
     - name: Debian
       versions:
-        - 9
-        - 10
-    - name: RedHat
+        - "buster"
+        - "bullseye"
+    - name: EL
       versions:
-        - 6
-        - 7
-  galaxy_tags: []
-  # List tags for your role here, one per line. A tag is a keyword that describes and categorizes the role. Users find roles by searching for tags. Be sure to remove the '[]' above, if you
-  # add tags to this list.
-  #
-  # NOTE: A tag is limited to a single word comprised of alphanumeric characters.
-  #       Maximum 20 tags per role.
-# dependencies: []
+        - "7"
+        - "8"
+dependencies: []
diff --git a/molecule/README.md b/molecule/README.md
deleted file mode 100644
index 33a7eb5..0000000
--- a/molecule/README.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# Testing with Molecule
-
-## Prerequisites
-
-In order to be able to use the tests, you need to have some software packages installed. You may need sudo privileges for some of these operations.
-
-        ### install VirtualBox
-        # do NOT use distribution packages
-        # process documented at https://www.virtualbox.org/wiki/Linux_Downloads
-        #
-        # add repository URL
-        sudo echo "deb [arch=amd64] https://download.virtualbox.org/virtualbox/debian stretch contrib" > /etc/apt/sources.d/virtualbox.list
-        # add GPG key
-        wget -q https://www.virtualbox.org/download/oracle_vbox.asc -O- | sudo apt-key add -
-        # update sources
-        sudo apt update
-        # install VirtualBox
-        sudo apt-get install virtualbox-6.1
-
-        ### install Vagrant
-        # do NOT use distribution packages
-        # 
-        # download Debian package from Hashicorp
-        wget https://releases.hashicorp.com/vagrant/2.2.9/vagrant_2.2.9_x86_64.deb
-        # install package
-        sudo dpkg -i vagrant_2.2.9_x86_64.deb
-
-        ### install Molecule et. al.
-        # prepare directories
-        mkdir ~/python-envs/ && cd ~/python-env/
-        # create Python Virtual Environment with Python3 interpreter (Python2 is deprecated!)
-        virtualenv -p python3 molecule-env
-        # enter the Virtual Environment in your current shell (other shells will be unaffected)
-        source molecule-env/bin/activate
-        # install packages
-        pip3 install molecule ansible testinfra ansible-lint molecule-vagrant molecule-docker
-
-        # leave the Virtual Environment only when you're done
-        deactivate
-
-You can find suitable documentation at the respective vendors' websites.
-* [Vagrant Installation Guide](https://www.vagrantup.com/docs/installation/)
-* [VirtualBox Installation Guide](https://www.virtualbox.org/wiki/Downloads)
-* [Molecule Installation Guide](https://molecule.readthedocs.io/en/stable/installation.html)
-
-## Initialising a new Molecule scenario
-
-If you have already created a role without the Molecule test framework or you want to add test scenarios, you can use:
-	molecule init scenario --scenario-name <my_scenario> --driver [azure|delegated|docker|ec2|gce|linode|lxc|lxd|openstack|vagrant] --verifier-name [ansible|testinfra]
-
-If you need any help with the options, please use:
-	molecule init role --help
-
-## Running Tests
-
-Molecule helps with creating a test infrastructure, running tests against it and removing the test infrastructure afterwards.
-
-Various test environments are separated into so-called "scenarios" that can be based on different OSses, drivers, verifiers or might just differ in a minor detail. 
-
-In the simplest configuration, the `molecule/` directory only contains one `default/` directory that contains the default scenario. This scenario is run if no other scenario is chosen using the `-s` CLI option.
-
-This is the basic usage of Molecule:
-	# create test infrastructure
-	cd <role_directory>
-	molecule create
-	# run playbooks against test infrastructure
-	molecule converge
-	# run Testinfra tests
-	molecule verify
-	# remove test infrastructure
-	molecule destroy
-
-	# run all steps at once:
-	molecule test
-
-It has proven helpful to use Vagrant to create a snapshot of the VM after the creation phase has completed.
-	# First, get UUID of the VM
-	vagrant global-status
-	# Then, create the snapshot
-	vagrant snapshot save <uuid> <snapshot_name>
-	# To restore the snapshot, use
-	vagrant snapshot restore <uuid> <snapshot_name>
-	# And to remove the snapshot, use
-	vagrant snapshot delete <uuid> <snapshot_name>
-
diff --git a/molecule/default b/molecule/default
new file mode 120000
index 0000000..3841ab1
--- /dev/null
+++ b/molecule/default
@@ -0,0 +1 @@
+./virtualbox
\ No newline at end of file
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml
deleted file mode 100644
index 489d40a..0000000
--- a/molecule/default/converge.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-- name: Converge
-  hosts: all
-  roles:
-    - {role: "ansible_lza_server_hardening", become: true}
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
deleted file mode 100644
index f435b3f..0000000
--- a/molecule/default/molecule.yml
+++ /dev/null
@@ -1,36 +0,0 @@
----
-dependency:
-  name: galaxy
-driver:
-  name: vagrant
-  provider:
-    name: virtualbox
-lint: |
-  set -e
-  yamllint .
-  ansible-lint -x formatting
-  flake8 --ignore=E501
-platforms:
-  - name: molecule-server-hardening-debian
-    box: debian/buster64
-    memory: 512
-    cpus: 1
-provisioner:
-  name: ansible
-  log: true
-  config_options:
-    defaults:
-      vault_identity_list: "@$HOME/.ansible/roles/molecule_prepare.pass, @$HOME/.ansible/roles/lza_install_common.pass, @$HOME/.ansible/roles/lza_server_hardening.pass"
-  lint: |
-    set -e
-    ansible-lint
-  vvv: false
-verifier:
-  name: testinfra
-  env:
-    PYTHONWARNINGS: "ignore:.*U.*mode is deprecated:DeprecationWarning"
-  lint: |
-    set -e
-    flake8
-  options:
-    v: 1
diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml
deleted file mode 100644
index ee22e99..0000000
--- a/molecule/default/prepare.yml
+++ /dev/null
@@ -1,39 +0,0 @@
----
-- name: Prepare
-  hosts: all
-  gather_facts: true
-  pre_tasks:
-    - name: include vars
-      include_vars: "../../../ansible_vaults/molecule_prepare/{{ item }}"
-      loop:
-        - "prepare.vault"
-    - name: Install python for Ansible
-      raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
-      become: true
-      changed_when: false
-    - name: create users (as deployed in production)
-      user:
-        name: "{{ item.name }}"
-        uid: "{{ item.uid }}"
-        create_home: "yes"
-        shell: "/bin/bash"
-      loop: "{{ vault_molecule_users | flatten(levels=1) }}"
-      become: true
-    - name: add nonfree repos
-      apt_repository:
-        repo: "deb http://ftp2.de.debian.org/debian/ buster main non-free contrib"
-        state: present
-        update-cache: "yes"
-      become: true
-    - name: Install required packages
-      apt:
-        name: [
-          'aptitude',
-          'gpg',
-          'less',
-          'libuser'
-        ]
-        state: present
-      become: true
-  roles:
-    - {role: ansible_lza_install_common, become: true}
diff --git a/molecule/default/tests/conftest.py b/molecule/default/tests/conftest.py
deleted file mode 100644
index ba0f1e8..0000000
--- a/molecule/default/tests/conftest.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""PyTest Fixtures."""
-from __future__ import absolute_import
-import os
-import pytest
-
-
-def pytest_runtest_setup(item):
-    """Run tests only when under molecule with testinfra installed."""
-    try:
-        import testinfra
-    except ImportError:
-        pytest.skip("Test requires testinfra", allow_module_level=True)
-    if "MOLECULE_INVENTORY_FILE" in os.environ:
-        pytest.testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
-            os.environ["MOLECULE_INVENTORY_FILE"]
-        ).get_hosts("all")
-    else:
-        pytest.skip(
-            "Test should run only from inside molecule.", allow_module_level=True
-        )
diff --git a/molecule/default/tests/test_default.py b/molecule/default/tests/test_default.py
deleted file mode 100644
index 39da80a..0000000
--- a/molecule/default/tests/test_default.py
+++ /dev/null
@@ -1,6 +0,0 @@
-def test_hosts_file(host):
-    f = host.file('/etc/hosts')
-
-    assert f.exists
-    assert f.user == 'root'
-    assert f.group == 'root'
diff --git a/molecule/resources/playbooks/Dockerfile b/molecule/resources/playbooks/Dockerfile
new file mode 100644
index 0000000..da596e7
--- /dev/null
+++ b/molecule/resources/playbooks/Dockerfile
@@ -0,0 +1,20 @@
+FROM debian:stable-slim
+
+RUN adduser lza;
+
+### configure SLUB Debian Repository
+RUN apt-get update; \
+    apt-get install -y --no-install-recommends gnupg wget git python3 ansible sudo; \
+    wget -O - http://sdvdebianrepo.slub-dresden.de/deb-repository/pub.gpg.key | apt-key add - ; \
+    echo "deb http://sdvdebianrepo.slub-dresden.de/deb-repository bullseye main" > /etc/apt/sources.list.d/slub.list; \
+    apt-get update;
+    #apt-get -y --no-install-recommends install python3-pip python3-virtualenv;
+
+RUN echo "lza     ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/lza-user
+
+#RUN mkdir /opt/venv/ && cd /opt/venv/; \
+#    virtualenv -p python3 molecule; \
+#    . /opt/venv/molecule/bin/activate; \
+#    pip3 install ansible molecule molecule-docker;
+
+USER lza
diff --git a/molecule/default/INSTALL.rst b/molecule/resources/playbooks/INSTALL.rst
similarity index 84%
rename from molecule/default/INSTALL.rst
rename to molecule/resources/playbooks/INSTALL.rst
index 4f44b67..0c4bf5c 100644
--- a/molecule/default/INSTALL.rst
+++ b/molecule/resources/playbooks/INSTALL.rst
@@ -1,6 +1,6 @@
-*******
+*********************************
 Vagrant driver installation guide
-*******
+*********************************
 
 Requirements
 ============
@@ -20,4 +20,4 @@ widely recommended `'--user' flag`_ when invoking ``pip``.
 
 .. code-block:: bash
 
-    $ pip install 'molecule[vagrant]'
+    $ pip install 'molecule_vagrant'
diff --git a/molecule/resources/playbooks/README.md b/molecule/resources/playbooks/README.md
new file mode 100644
index 0000000..0c91883
--- /dev/null
+++ b/molecule/resources/playbooks/README.md
@@ -0,0 +1,3 @@
+This drectory contains shared playbooks and a shared Dockerfile.
+
+Visit https://molecule.readthedocs.io/en/latest/examples.html#sharing-across-scenarios for details on sharing playbooks, tests etc. across multiple scenarios.
diff --git a/molecule/resources/playbooks/converge.yml b/molecule/resources/playbooks/converge.yml
new file mode 100644
index 0000000..4a49614
--- /dev/null
+++ b/molecule/resources/playbooks/converge.yml
@@ -0,0 +1,17 @@
+---
+- name: Converge
+  hosts: all
+  pre_tasks:
+    - name: update apt cache
+      ansible.builtin.apt:
+        update_cache: true
+        upgrade: dist
+      become: true
+      when: ansible_os_family == "Debian"
+    - name: update yum cache
+      ansible.builtin.yum:
+        update_cache: true
+      become: true
+      when: ansible_os_family == "RedHat"
+  roles:
+    - {role: "ansible_lza_server_hardening", become: true}
diff --git a/molecule/resources/playbooks/prepare.yml b/molecule/resources/playbooks/prepare.yml
new file mode 100644
index 0000000..a20ecff
--- /dev/null
+++ b/molecule/resources/playbooks/prepare.yml
@@ -0,0 +1,22 @@
+---
+- name: Prepare
+  hosts: "*"
+  tasks:
+    - name: install GPG
+      ansible.builtin.apt:
+        name: "gnupg"
+        state: latest
+        update_cache: true
+      become: true
+    - name: add GPG key for SLUB Debian repository
+      ansible.builtin.apt_key:
+        url: "https://sdvdebianrepo.slub-dresden.de/deb-repository/pub.gpg.key"
+        state: present
+      become: true
+    - name: add repo URL to sources.list
+      ansible.builtin.apt_repository:
+        repo: "deb https://sdvdebianrepo.slub-dresden.de/deb-repository bullseye main"
+        state: present
+        update_cache: true
+        mode: "0644"
+      become: true
diff --git a/molecule/resources/playbooks/verify.yml b/molecule/resources/playbooks/verify.yml
new file mode 100644
index 0000000..e707420
--- /dev/null
+++ b/molecule/resources/playbooks/verify.yml
@@ -0,0 +1,10 @@
+---
+# This is an example playbook to execute Ansible tests.
+
+- name: Verify
+  hosts: all
+  gather_facts: false
+  tasks:
+  - name: Example assertion
+    ansible.builtin.assert:
+      that: true
diff --git a/molecule/virtualbox/molecule.yml b/molecule/virtualbox/molecule.yml
new file mode 100644
index 0000000..6f084c7
--- /dev/null
+++ b/molecule/virtualbox/molecule.yml
@@ -0,0 +1,41 @@
+---
+dependency:
+  name: galaxy
+  enabled: false
+driver:
+  name: vagrant
+lint: |
+  set -e
+  yamllint .
+  ansible-lint -x no-loop-var-prefix,command-instead-of-module,package-latest
+platforms:
+  # Check out the documentation at
+  # https://github.com/ansible-community/molecule-vagrant#documentation
+  # for more platform parameters.
+  - name: vm-runner
+    box: debian/bullseye64
+    memory: 1024
+    # List of raw Vagrant `config` options.
+    # provider_raw_config_args:
+    #   - "customize [ 'modifyvm', :id, '--natdnshostresolver1', 'on' ]"
+    # Dictionary of `config` options.
+    config_options:
+      ssh.keep_alive: yes
+      ssh.remote_user: "'lza'"
+provisioner:
+  name: ansible
+  log: true
+  config_options:
+    defaults:
+      # https://stackoverflow.com/questions/57435811/ansible-molecule-pass-multiple-vault-ids
+      #vault_identity_list: "@$HOME/.ansible/roles/lza_install_common.pass, @$HOME/.ansible/roles/passfile_1.pass"
+      vault_identity_list: "../lza_server_hardening.pass"
+  vvv: false
+  playbooks:
+    # create: ../resources/playbooks/create.yml
+    # destroy: ../resources/playbooks/destroy.yml
+    converge: ../resources/playbooks/converge.yml
+    # prepare: ../resources/playbooks/prepare.yml
+    verify: ../resources/playbooks/verify.yml
+verifier:
+  name: ansible
diff --git a/requirements.yml b/requirements.yml.example
similarity index 100%
rename from requirements.yml
rename to requirements.yml.example
diff --git a/site.yml b/site.yml
index 9dd4661..5cac957 100644
--- a/site.yml
+++ b/site.yml
@@ -24,5 +24,4 @@
   strategy: linear
 
   roles:
-    - { role: ansible_lza_install_common, become: true }
     - { role: ansible_lza_server_hardening, become: true }
diff --git a/tasks/configure-fail2ban.yml b/tasks/configure_fail2ban.yml
similarity index 89%
rename from tasks/configure-fail2ban.yml
rename to tasks/configure_fail2ban.yml
index 7208bc8..8836af2 100644
--- a/tasks/configure-fail2ban.yml
+++ b/tasks/configure_fail2ban.yml
@@ -1,17 +1,17 @@
 ---
 ### Fail2Ban einrichten ###
 - name: fail2ban IDS installieren
-  package:
+  ansible.builtin.package:
     name: "fail2ban"
     state: present
   tags: [apt, yum]
 
 # neue Konfiguration einspielen
 - name: Konfiguration fuer fail2ban einspielen (1/4)
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/fail2ban/jail.local"
     backup: "no"
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     mode: 0644
@@ -80,10 +80,10 @@
   notify: restart fail2ban.service
 
 - name: Konfiguration fuer fail2ban einspielen (2/4)
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/fail2ban/filter.d/f2b-loop.conf"
     backup: "no"
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     mode: "0644"
@@ -100,22 +100,23 @@
   notify: restart fail2ban.service
 
 - name: see if fail2ban.local exists
-  stat:
+  ansible.builtin.stat:
     path: "/etc/fail2ban/fail2ban.local"
   register: old_fail2ban_local
 
 - name: fail2ban.local bereinigen
-  file:
+  ansible.builtin.file:
     path: "/etc/fail2ban/fail2ban.local"
     state: absent
   when: old_fail2ban_local.stat.exists
+  changed_when: false    # we cannot make this task idempotent in any other way, because we previously deleted the file to get a clean state
   notify: restart fail2ban.service
 
 - name: Konfiguration fuer fail2ban einspielen (4/4)
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/fail2ban/fail2ban.local"
     backup: "no"
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     mode: "0644"
@@ -131,11 +132,12 @@
       pidfile = /var/run/fail2ban/fail2ban.pid
       dbfile = /var/lib/fail2ban/fail2ban.sqlite3
       dbpurgeage = 86400
+  changed_when: false    # we cannot make this task idempotent in any other way, because we previously deleted the file to get a clean state
   notify: restart fail2ban.service
 
 # Ordner für Check_MK-Plugin anlegen
 - name: Ordner für Check_MK-Plugin anlegen
-  file:
+  ansible.builtin.file:
     path: "/usr/lib/check_mk_agent/plugins"
     state: directory
     owner: "root"
@@ -144,7 +146,7 @@
 
 # Plugin bereitstellen
 - name: Check_MK-Plugin installieren (fail2ban-Zustand)
-  copy:
+  ansible.builtin.copy:
     src: "usr/lib/check_mk_agent/plugins/check_fail2ban_status.sh"
     dest: "/usr/lib/check_mk_agent/plugins/check_fail2ban_status.sh"
     owner: "root"
diff --git a/tasks/configure-home-permissions.yml b/tasks/configure_home_permissions.yml
similarity index 87%
rename from tasks/configure-home-permissions.yml
rename to tasks/configure_home_permissions.yml
index 3624d1a..0343af6 100644
--- a/tasks/configure-home-permissions.yml
+++ b/tasks/configure_home_permissions.yml
@@ -2,16 +2,16 @@
 - name: $HOME-Verzeichnisse von Usern mit gleichen Gruppen schützen
   block:
     - name: $HOME-Verzeichnisse sammeln
-      find:
+      ansible.builtin.find:
         file_type: directory
         paths: "/home/"
         excludes: 'import'
       register: ls_out
     - name: striktere Berechtigungen für Homeverzeichnisse setzen
-      file:
+      ansible.builtin.file:
         path: "{{ item.path }}/"
         mode: "0700"
-      with_items:
+      loop:
         # - "{{ ls_out.files | difference(['import','zih']) }}"
         - "{{ ls_out.files }}"
       when: item.path not in "import"
diff --git a/tasks/configure-iptables.yml b/tasks/configure_iptables.yml
similarity index 90%
rename from tasks/configure-iptables.yml
rename to tasks/configure_iptables.yml
index 75a32a8..00b634e 100644
--- a/tasks/configure-iptables.yml
+++ b/tasks/configure_iptables.yml
@@ -1,6 +1,6 @@
 ---
 - name: stop and disable firewalld on RHEL systems (until we have a decent config)
-  systemd:
+  ansible.builtin.systemd:
     name: "{{ item.service }}"
     state: "{{ item.state }}"
     enabled: "{{ item.enabled }}"
@@ -11,7 +11,7 @@
   when: ansible_os_family == "RedHat"
 
 # - name: clean IPtables rules (1)
-#   iptables:
+#   ansible.builtin.iptables:
 #     chain: "INPUT"
 #     ip_version: "{{ item }}"
 #     policy: "ACCEPT"
@@ -20,19 +20,17 @@
 #     - "ipv6"
 #   notify:
 #     - save iptables rules
-#   tags: [molecule-notest]
 #
 # - name: clean IPtables rules (2)
-#   iptables:
+#   ansible.builtin.iptables:
 #     chain: "INPUT"
 #     flush: "true"
 #   notify:
 #     - save iptables rules
-#   tags: [molecule-notest]
 
 
 - name: Allow related and established IPv4 connections
-  iptables:
+  ansible.builtin.iptables:
     chain: "INPUT"
     ctstate: "ESTABLISHED,RELATED"
     jump: "ACCEPT"
@@ -42,7 +40,7 @@
     - save iptables rules
 
 # - name: Allow related and established IPv4 connections
-#   iptables:
+#   ansible.builtin.iptables:
 #     chain: "OUTPUT"
 #     comment: 'allow related and established connections'
 #     ctstate: "ESTABLISHED,RELATED"
@@ -52,7 +50,7 @@
 #     - save iptables rules
 
 - name: Allow all loop back traffic
-  iptables:
+  ansible.builtin.iptables:
     action: "insert"
     chain: "INPUT"
     comment: 'allow all loop back traffic'
@@ -62,7 +60,7 @@
     - save iptables rules
 
 # - name: Allow all loop back traffic
-#   iptables:
+#   ansible.builtin.iptables:
 #     action: "insert"
 #     chain: "OUTPUT"
 #     comment: 'allow all loop back traffic'
@@ -73,7 +71,7 @@
 
 # Set default policy for INPUT chain
 - name: iptables-Policy für INPUT-Chain setzen
-  iptables:
+  ansible.builtin.iptables:
     chain: "INPUT"
     ip_version: "{{ item }}"
     policy: "DROP"
@@ -82,11 +80,10 @@
     - "ipv6"
   notify:
     - save iptables rules
-  tags: [molecule-notest]
 
 ## Set default policy for OUTPUT chain
 # - name: iptables-Policy für OUTPUT-Chain setzen
-#   iptables:
+#   ansible.builtin.iptables:
 #     chain: "OUTPUT"
 #     ip_version: "{{ item }}"
 #     policy: "DROP"
@@ -95,11 +92,10 @@
 #     - "ipv6"
 #   notify:
 #     - save iptables rules
-#   tags: [molecule-notest]
 
 # Configure specific rules - Chain INPUT
 - name: iptables-Regeln (IPv4) setzen - Chain INPUT
-  iptables:
+  ansible.builtin.iptables:
     action: "insert"
     chain: "INPUT"
     comment: "{{ item.comment | default(omit) }}"
@@ -116,17 +112,16 @@
     source_port: "{{ item.src_port | default(omit) }}"
     state: "{{ item.state }}"
     table: "filter"
-  loop: "{{ vault_iptables_input|flatten(levels=1) }}"
+  loop: "{{ vault_iptables_input | flatten(levels=1) }}"
   notify:
     - save iptables rules
-  tags: [molecule-notest]
 # http://shouldiblockicmp.com
 
 # TODO: Outgoing iptables Regeln erstellen, und bloß keine vergessen!!!
 
 ## Configure specific rules - Chain OUTPUT
 #  - name: iptables-Regeln (IPv4) setzen - Chain OUTPUT
-#    iptables:
+#    ansible.builtin.iptables:
 #      action: "insert"
 #      chain: OUTPUT
 #      comment: "{{ item.comment }}"
@@ -146,11 +141,10 @@
 #    loop: "{{ vault_iptables_output|flatten(levels=1) }}"
 #    notify:
 #      - save iptables rules
-#    tags: [molecule-notest]
 
 # THESE NEED TO BE THE LAST RULES IN IPTABLES' RULE LIST!!!
 - name: iptables-Regeln (IPv4) setzen - REJECT
-  iptables:
+  ansible.builtin.iptables:
     action: "append"
     chain: "{{ item.chain }}"
     jump: "REJECT"
@@ -163,4 +157,3 @@
     - chain: "FORWARD"
   notify:
     - save iptables rules
-  tags: [molecule-notest]
diff --git a/tasks/configure-kernel-parameters.yml b/tasks/configure_kernel_parameters.yml
similarity index 99%
rename from tasks/configure-kernel-parameters.yml
rename to tasks/configure_kernel_parameters.yml
index daf4440..8dae33c 100644
--- a/tasks/configure-kernel-parameters.yml
+++ b/tasks/configure_kernel_parameters.yml
@@ -3,7 +3,7 @@
 - name: set custom kernel parameters
   block:
     - name: write kernel parameter changes
-      blockinfile:
+      ansible.builtin.blockinfile:
         insertafter: EOF
         marker: '### {mark} ANSIBLE MANAGED BLOCK - SERVER HARDENING'
         path: "/etc/sysctl.conf"
diff --git a/tasks/configure-pam.yml b/tasks/configure_pam.yml
similarity index 93%
rename from tasks/configure-pam.yml
rename to tasks/configure_pam.yml
index b2ffa7a..de65f78 100644
--- a/tasks/configure-pam.yml
+++ b/tasks/configure_pam.yml
@@ -2,9 +2,9 @@
 # Documentation: http://www.linux-pam.org/Linux-PAM-html/sag-module-reference.html "The Linux-PAM System Administrators' Guide - Chapter 6. A reference guide for available modules"
 
 - name: configure 'su' usage restrictions through PAM
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/pam.d/su"
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     insertafter: EOF
@@ -18,9 +18,9 @@
         account    requisite  pam_time.so
 
 - name: configure times for certain system actions
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/security/time.conf"
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     insertafter: EOF
@@ -42,9 +42,9 @@
         login;tty*;operator;!Al2300-0500
 
 - name: configure login actions
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/pam.d/login"
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     insertafter: EOF
diff --git a/tasks/configure-portable-storage.yml b/tasks/configure_portable_storage.yml
similarity index 90%
rename from tasks/configure-portable-storage.yml
rename to tasks/configure_portable_storage.yml
index df8abdf..d4d9558 100644
--- a/tasks/configure-portable-storage.yml
+++ b/tasks/configure_portable_storage.yml
@@ -1,15 +1,15 @@
 ---
 - name: disable USB storage support
-  lineinfile:
+  ansible.builtin.lineinfile:
     state: present
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     mode: "0700"
     insertafter: EOF
     path: "/etc/modprobe.d/{{ item.path }}"
     line: "{{ item.line }}"
-  with_items:
+  loop:
     - path: "disable-usb-storage.conf"
       line: "blacklist usb-storage"
       # Alternative: "fake install" (https://linuxtechlab.com/disable-usb-storage-linux/)
diff --git a/tasks/configure-root.yml b/tasks/configure_root.yml
similarity index 64%
rename from tasks/configure-root.yml
rename to tasks/configure_root.yml
index 09c75f5..f501d77 100644
--- a/tasks/configure-root.yml
+++ b/tasks/configure_root.yml
@@ -1,9 +1,9 @@
 ---
 - name: prevent root login
-  user:
+  ansible.builtin.user:
     name: "root"
     shell: "/bin/bash"
     comment: "root user disabled"
-    # local: "yes"
+    # local: true
     local: false
-    password_lock: "yes"
+    password_lock: true
diff --git a/tasks/configure-ssh-hardening.yml b/tasks/configure_ssh_hardening.yml
similarity index 99%
rename from tasks/configure-ssh-hardening.yml
rename to tasks/configure_ssh_hardening.yml
index e7e235e..cbc8cdc 100644
--- a/tasks/configure-ssh-hardening.yml
+++ b/tasks/configure_ssh_hardening.yml
@@ -2,7 +2,7 @@
 - name: Konfiguration für OpenSSH einspielen - gehärtete Config
   ansible.builtin.blockinfile:
     path: "/etc/ssh/sshd_config"
-    backup: "yes"
+    backup: true
     insertbefore: "### BEGIN ANSIBLE MANAGED BLOCK - SFTP SERVER"
     marker: "### {mark} ANSIBLE MANAGED BLOCK - HARDENED SSH SERVER"
     validate: /usr/sbin/sshd -T -f %s
diff --git a/tasks/configure-umask.yml b/tasks/configure_umask.yml
similarity index 85%
rename from tasks/configure-umask.yml
rename to tasks/configure_umask.yml
index f5a5233..66f7b1c 100644
--- a/tasks/configure-umask.yml
+++ b/tasks/configure_umask.yml
@@ -1,8 +1,8 @@
 ---
 - name: set more secure umask (block everything for 'other' users)
-  blockinfile:
+  ansible.builtin.blockinfile:
     state: present
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     mode: "0644"
@@ -15,15 +15,15 @@
 - name: libpam-umask installieren (Debian)
   block:
     - name: Paket installieren
-      apt:
+      ansible.builtin.apt:
         name: "libpam-umask"
         state: present
       tags: [apt]
 
     - name: Standard-umask mit PAM anpassen
-      blockinfile:
+      ansible.builtin.blockinfile:
         path: "/etc/pam.d/common-session"
-        create: "yes"
+        create: true
         owner: "root"
         group: "root"
         insertafter: EOF
@@ -37,14 +37,14 @@
   tags: [apt]
 
 - name: set default login umask
-  lineinfile:
+  ansible.builtin.lineinfile:
     path: "/etc/login.defs"
     regex: "{{ umask[ansible_os_family] }}"
     line: "UMASK           026"
   vars:
-    - umask:
-        Debian: "UMASK		022"
-        RedHat: "UMASK           077"
+    umask:
+      Debian: "UMASK           022"
+      RedHat: "UMASK           077"
 
 # umask could also be set in:
 # - /etc/profile.d/umask (Setting umask in profile.d sets it for all users who
diff --git a/tasks/install-debsecan.yml b/tasks/install-debsecan.yml
deleted file mode 100644
index 8ae3ea1..0000000
--- a/tasks/install-debsecan.yml
+++ /dev/null
@@ -1,26 +0,0 @@
----
-- block:
-    - name: include vars debsecan
-      include_vars: debsecan_preseed.yml
-
-    - name: install debsecan package
-      apt:
-        name: "debsecan"
-        state: present
-
-    - name: reconfigure debsecan package (dpkg-reconfigure)
-      debconf:
-        name: "debsecan"
-        question: "{{ item.question }}"
-        value: "{{ item.value }}"
-        vtype: "{{ item.vtype }}"
-      loop: "{{ debsecan_dpkg | flatten(levels=1) }}"
-
-    - name: reconfigure debsecan package (/etc/default/debsecan)
-      template:
-        src: "debsecan.j2"
-        dest: "/etc/default/debsecan"
-        owner: "root"
-        group: "root"
-        mode: "0644"
-        force: "yes"
diff --git a/tasks/install-auditd.yml b/tasks/install_auditd.yml
similarity index 89%
rename from tasks/install-auditd.yml
rename to tasks/install_auditd.yml
index 62ecb17..ce245c1 100644
--- a/tasks/install-auditd.yml
+++ b/tasks/install_auditd.yml
@@ -2,21 +2,21 @@
 # install & configure auditd
 
 - name: install auditd package (Debian)
-  apt:
+  ansible.builtin.apt:
     name: "auditd"
     state: present
   when: ansible_os_family == "Debian"
   tags: [apt]
 
 - name: install auditd package (RedHat)
-  yum:
+  ansible.builtin.yum:
     name: "audit"
     state: present
   when: ansible_os_family == "RedHat"
   tags: [yum]
 
 - name: configure auditd rules
-  copy:
+  ansible.builtin.copy:
     src: "etc/audit/rules.d/audit.rules"
     dest: "/etc/audit/rules.d/audit.rules"
     mode: "0640"
diff --git a/tasks/install-clamav.yml b/tasks/install_clamav.yml
similarity index 92%
rename from tasks/install-clamav.yml
rename to tasks/install_clamav.yml
index 1ea310d..dfa9548 100644
--- a/tasks/install-clamav.yml
+++ b/tasks/install_clamav.yml
@@ -2,20 +2,20 @@
 # based on https://www.golinuxcloud.com/steps-install-configure-clamav-antivirus-centos-linux/
 
 - name: include vars clamav
-  include_vars: "clamav.yml"
+  ansible.builtin.include_vars: "clamav.yml"
   tags: [apt]
 
 
 
 - name: install clamav packages (Debian)
-  apt:
+  ansible.builtin.apt:
     name: "clamav-daemon"
     state: present
   when: ansible_os_family == "Debian"
   tags: [apt]
 
 - name: install clamav packages (RedHat)
-  yum:
+  ansible.builtin.yum:
     name: [
       'clamav-server',
       'clamav',
@@ -34,9 +34,10 @@
 
 
 - name: create ClamAV log directory
-  file:
+  ansible.builtin.file:
     path: "/var/log/clamav/"
     state: directory
+    mode: "0755"
     owner: "{{ 'clamav' if ansible_os_family == 'Debian' else 'clamupdate' }}"
     group: "adm"
 
@@ -44,12 +45,12 @@
 
 # clamav-freshclam.service und clamav-daemon.service laufen nach der Installation sofort los
 - name: configure freshclam
-  blockinfile:
+  ansible.builtin.blockinfile:
     name: "{{ clamav_cfg_path }}/freshclam.conf"
     mode: "0444"
     owner: "{{ 'clamav' if ansible_os_family == 'Debian' else 'clamupdate' }}"
     group: "adm"
-    create: "yes"
+    create: true
     block: |
       # Automatically created by the clamav-freshclam postinst
       # Comments will get lost when you reconfigure the clamav-freshclam package
@@ -92,13 +93,14 @@
 #  when: ansible_os_family == "RedHat"
 
 - name: install Freshclam timer
-  copy:
+  ansible.builtin.copy:
     src: "etc/systemd/system/clamav-freshclam.timer"
     dest: "/etc/systemd/system/clamav-freshclam.timer"
+    mode: "0644"
   when: ansible_os_family == "RedHat"
 
 - name: start and enable Freshclam timer
-  systemd:
+  ansible.builtin.systemd:
     service: "clamav-freshclam.timer"
     enabled: true
     state: started
@@ -107,7 +109,7 @@
 
 
 - name: configure ClamD
-  blockinfile:
+  ansible.builtin.blockinfile:
     name: "{{ clamav_cfg_path }}/{{ 'clamd' if ansible_os_family == 'Debian' else ansible_hostname }}.conf"
     mode: "0444"
     owner: "{{ 'clamav' if ansible_os_family == 'Debian' else 'clamscan' }}"
@@ -137,7 +139,7 @@
   notify: restart clamd service
 
 - name: configure ClamD exclude paths
-  blockinfile:
+  ansible.builtin.blockinfile:
     name: "{{ clamav_cfg_path }}/{{ 'clamd' if ansible_os_family == 'Debian' else ansible_hostname }}.conf"
     mode: "0444"
     owner: "{{ 'clamav' if ansible_os_family == 'Debian' else 'clamscan' }}"
@@ -154,12 +156,12 @@
     - restart clamd service
 
 - name: configure ClamD to refresh rkhunter after DB updates
-  blockinfile:
+  ansible.builtin.blockinfile:
     name: "/usr/local/bin/refresh_rkhunter.sh"
     mode: "0755"
     owner: "{{ 'clamav' if ansible_os_family == 'Debian' else 'clamupdate' }}"
     group: "adm"
-    create: "yes"
+    create: true
     insertafter: EOF
     block: |
       #!/usr/bin/env bash
@@ -170,14 +172,15 @@
       fi
 
 - name: copy systemd service
-  copy:
+  ansible.builtin.copy:
    src: "/usr/lib/systemd/system/clamd@.service"
    dest: "/etc/systemd/system/"
+   mode: "0644"
    remote_src: true
   when: ansible_os_family == "RedHat"
 
 - name: enable ClamD systemd service
-  systemd:
+  ansible.builtin.systemd:
     service: "clamd@{{ ansible_hostname }}.service"
     enabled: true
     state: "started"
diff --git a/tasks/install_debsecan.yml b/tasks/install_debsecan.yml
new file mode 100644
index 0000000..52ff558
--- /dev/null
+++ b/tasks/install_debsecan.yml
@@ -0,0 +1,25 @@
+---
+- name: include vars debsecan
+  ansible.builtin.include_vars: debsecan_preseed.yml
+
+- name: install debsecan package
+  ansible.builtin.apt:
+    name: "debsecan"
+    state: present
+
+- name: reconfigure debsecan package (dpkg-reconfigure)
+  ansible.builtin.debconf:
+    name: "debsecan"
+    question: "{{ item.question }}"
+    value: "{{ item.value }}"
+    vtype: "{{ item.vtype }}"
+  loop: "{{ debsecan_dpkg | flatten(levels=1) }}"
+
+- name: reconfigure debsecan package (/etc/default/debsecan)
+  ansible.builtin.template:
+    src: "debsecan.j2"
+    dest: "/etc/default/debsecan"
+    owner: "root"
+    group: "root"
+    mode: "0644"
+    force: true
diff --git a/tasks/install-rkhunter.yml b/tasks/install_rkhunter.yml
similarity index 69%
rename from tasks/install-rkhunter.yml
rename to tasks/install_rkhunter.yml
index 410c1c7..4a2dd99 100644
--- a/tasks/install-rkhunter.yml
+++ b/tasks/install_rkhunter.yml
@@ -2,33 +2,40 @@
 # install rkhunter 1.4.4 due to CVE-2017-7480 (https://www.cvedetails.com/cve/CVE-2017-7480/)
 # version restriction can be removed once a higher version becomes available
 - name: install rkhunter
-  package:
+  ansible.builtin.package:
     name: "rkhunter"
     state: present
   tags: [apt]
 
+- name: create rkhunter log dir
+  ansible.builtin.file:
+    path: "/var/log/rkhunter/"
+    state: directory
+    owner: "root"
+    group: "root"
+    mode: "0750"
+
+- name: check if rkhunter logfile exists
+  ansible.builtin.stat:
+    path: "/var/log/rkhunter/rkhunter.log"
+  register: rkhunter_log_exists
+
 - name: create /var/log/rkhunter/rkhunter.log if it doesn't exist, so logrotate doesn't fail
-  file:
-    path: "{{ item.path }}"
-    state: "{{ item.state }}"
+  ansible.builtin.file:
+    path: "/var/log/rkhunter/rkhunter.log"
+    state: touch    # "touch" is never idempotent, so we have to rely on a precheck to see if we need to run this task
     owner: "root"
     group: "root"
-    mode: "{{ item.mode }}"
-  loop:
-    - path: "/var/log/rkhunter/"
-      mode: "0750"
-      state: directory
-    - path: "/var/log/rkhunter/rkhunter.log"
-      mode: "0640"
-      state: touch
+    mode: "0640"
+  when: not rkhunter_log_exists.stat.exists
 
 - name: configure /etc/default/rkhunter
-  lineinfile:
+  ansible.builtin.lineinfile:
     path: "/etc/default/rkhunter"
     regexp: "{{ item.regexp }}"
     line: "{{ item.line }}"
     # validate: rkhunter --config-check --configfile %s
-  with_items:
+  loop:
     # - regexp: '^# APT_AUTOGEN="false"'
     #   line: 'APT_AUTOGEN="yes"'
     - regexp: '^APT_AUTOGEN="false"'
@@ -38,7 +45,7 @@
   when: ansible_distribution == "Debian"
 
 - name: create rkhunter config directory
-  file:
+  ansible.builtin.file:
     path: "/etc/rkhunter.d"
     state: "directory"
     owner: "root"
@@ -46,9 +53,10 @@
     mode: 0755
 
 - name: configure /etc/rkhunter.d/rkhunter.local.conf
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/rkhunter.d/rkhunter.local.conf"
-    create: "yes"
+    mode: "0644"
+    create: true
     block: |
       # Default: UPDATE_MIRRORS=0
       UPDATE_MIRRORS=1
@@ -103,20 +111,34 @@
       WEB_CMD=""
     # validate: rkhunter --config-check --configfile %s
 
+- name: find rkhunter systemd units so we don't have to hardcode their names in the loops
+  ansible.builtin.find:
+    path: "/etc/systemd/user/"
+    pattern: "rkhunter.*"
+  register: rkhunter_units
+
+- name: check if rkhunter Systemd units are already disabled
+  ansible.builtin.command: "systemctl is-enabled rkhunter.{{ item.path | basename }}"
+  loop: "{{ rkhunter_units.files }}"
+  register: rkhunter_disabled
+  changed_when: false
+  failed_when:
+    - rkhunter_disabled.stdout != "enabled"
+    - rkhunter_disabled.stdout != "disabled"
+    - '"No such file or directory" not in rkhunter_disabled.stderr"'
+
 - name: stop & disable RKhunter service unit & timer
-  systemd:
-    name: "rkhunter.{{ item }}"
+  ansible.builtin.systemd:
+    name: "{{ item.item.path }}"
     enabled: "false"
     state: stopped
-  loop:
-    - "service"
-    - "timer"
-  when: ansible_distribution == "Debian"
-  tags: [molecule-notest]
-  ignore_errors: "yes"
+  loop: "{{ rkhunter_disabled.results }}"
+  when:
+    - ansible_distribution == "Debian"
+    - item.stdout != "disabled"
 
 - name: remove Rkhunter service unit & timer
-  file:
+  ansible.builtin.file:
     path: "/etc/systemd/user/rkhunter.{{ item }}"
     state: absent
   loop:
@@ -126,7 +148,7 @@
 
 # Unitfiles neu einlesen (implizit mit enable), Services sofort starten & automatisch bei jedem Booten starten
 # - name: Service automatisch bei jedem Booten starten - rkhunter.service
-#   command: systemctl enable "{{ item }}"
+#   ansible.builtin.command: systemctl enable "{{ item }}"
 #   loop:
 #     - "/etc/systemd/user/rkhunter.service"
 #     - "/etc/systemd/user/rkhunter.timer"
diff --git a/tasks/main.yml b/tasks/main.yml
index ada0022..52ab1ea 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -1,6 +1,6 @@
 ---
 - name: include Ansible Vaults
-  include_vars: "{{ role_path }}/../ansible_vaults/{{ role_name }}/{{ item }}"
+  ansible.builtin.include_vars: "{{ role_path }}/../ansible_vaults/{{ role_name }}/{{ item }}"
   loop:
     - "fail2ban.vault"
     - "iptables.vault"
@@ -13,56 +13,56 @@
 
 # install hardened server configuration
 - name: configure root
-  import_tasks: configure-root.yml
+  ansible.builtin.import_tasks: "configure_root.yml"
   tags: [users]
 
 - name: configure kernel parameters
-  import_tasks: ./configure-kernel-parameters.yml
+  ansible.builtin.import_tasks: "configure_kernel_parameters.yml"
   tags: [kernel]
 
 - name: configure portable storage drivers
-  import_tasks: ./configure-portable-storage.yml
+  ansible.builtin.import_tasks: "configure_portable_storage.yml"
   tags: [usb]
 
 - name: configute SSH hardening
-  import_tasks: ./configure-ssh-hardening.yml
+  ansible.builtin.import_tasks: "configure_ssh_hardening.yml"
   tags: [ssh]
 
 - name: configure fail2ban
-  import_tasks: ./configure-fail2ban.yml
+  ansible.builtin.import_tasks: "configure_fail2ban.yml"
   tags: [fail2ban, ssh]
 
 - name: configure $HOME permissions
-  import_tasks: ./configure-home-permissions.yml
+  ansible.builtin.import_tasks: "configure_home_permissions.yml"
   tags: [users]
 
 - name: configure PAM
-  import_tasks: ./configure-pam.yml
+  ansible.builtin.import_tasks: "configure_pam.yml"
   tags: [pam]
 
 - name: install iptables filter rules
-  import_tasks: ./configure-iptables.yml
+  ansible.builtin.import_tasks: "configure_iptables.yml"
   tags: [iptables]
 
 - name: include rkhunter install task
-  import_tasks: ./install-rkhunter.yml
+  ansible.builtin.import_tasks: "install_rkhunter.yml"
   tags: [rkhunter]
 
 - name: include ClamAV install task
-  import_tasks: ./install-clamav.yml
+  ansible.builtin.import_tasks: "install_clamav.yml"
   tags: [clamav]
 
 - name: include auditd install task
-  import_tasks: ./install-auditd.yml
+  ansible.builtin.import_tasks: "install_auditd.yml"
   tags: [auditd]
 
 - name: install debsecan
-  import_tasks: ./install-debsecan.yml
+  ansible.builtin.import_tasks: "install_debsecan.yml"
   when: ansible_os_family == "Debian"
   tags: [debsecan]
 
 - name: install security-related packages (debian-goodies, debsums, libpam-cracklib, libpam-tmpdir)
-  apt:
+  ansible.builtin.apt:
     name: [
       'debian-goodies',
       'debsums',
@@ -74,12 +74,13 @@
   tags: [apt]
 
 - name: autoclean & autoremove
-  apt:
-    autoclean: "yes"
-    autoremove: "yes"
+  ansible.builtin.apt:
+    autoclean: true
+    autoremove: true
   when: ansible_os_family == "Debian"
   tags: [apt]
 
 - name: Flush handlers am Ende der Rolle
-  meta: flush_handlers
+  ansible.builtin.meta: flush_handlers
+  changed_when: false
   tags: [always]
-- 
GitLab