Add new role, 'debops.kmod'

parent 343bbefc
......@@ -615,6 +615,14 @@ stages:
JANE_DIFF_PATTERN: '.*/debops.kibana/.*'
JANE_LOG_PATTERN: '\[debops\.kibana\]'
'kmod role':
<<: *test_role_no_deps
variables:
JANE_TEST_PLAY: '${DEBOPS_PLAYBOOKS}/service/kmod.yml'
JANE_INVENTORY_GROUPS: 'debops_service_kmod'
JANE_DIFF_PATTERN: '.*/debops.kmod/.*'
JANE_LOG_PATTERN: '\[debops\.kmod\]'
# --- l --- [[[2
......
......@@ -31,6 +31,9 @@ Added
specify that particular packages should be held in their current state.
The role is included in the ``common.yml`` playbook.
- :ref:`debops.kmod`: manage kernel module configuration and module loading
at boot time. This role replaces the ``debops-contrib.kernel_module`` role.
Changed
~~~~~~~
......
---
- name: Manage kernel modules
hosts: [ 'debops_service_kmod' ]
become: True
environment: '{{ inventory__environment | d({})
| combine(inventory__group_environment | d({}))
| combine(inventory__host_environment | d({})) }}'
roles:
- role: debops.kmod
tags: [ 'role::kmod' ]
......@@ -2,6 +2,8 @@
- include: sysnews.yml
- include: kmod.yml
- include: sysfs.yml
- include: swapfile.yml
......
../service/kmod.yml
\ No newline at end of file
debops.kmod - Manage kernel modules using Ansible
Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2018 DebOps Project https://debops.org/
This Ansible role is part of DebOps.
DebOps is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3, as
published by the Free Software Foundation.
DebOps is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with DebOps. If not, see http://www.gnu.org/licenses/.
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# debops.kmod default variables
# =============================
# .. contents:: Sections
# :local:
# General configuration [[[
# -------------------------
# .. envvar:: kmod__enabled [[[
#
# Enable or disable support for kernel module management. The role will check
# if the :command:`/sbin/modprobe` is present on a host and will enable/disable
# itself automatically as needed. You can use this variable to override this
# mechanism.
kmod__enabled: '{{ True
if (kmod__register_modprobe.stat.exists|bool)
else False }}'
# ]]]
# ]]]
# Kernel module configuration [[[
# -------------------------------
# These variables define what kernel module configuration should be present on
# a host. See :ref:`kmod__ref_modules` for more details.
# .. envvar:: kmod__default_modules [[[
#
# List of default kernel module configuration entries defined by the role.
kmod__default_modules:
- name: 'blacklist-firewire-thunderbolt'
state: 'config'
comment: |
Protection against Firewire DMA attacks
https://security.stackexchange.com/a/49158/79474
https://github.com/lfit/itpol/blob/master/linux-workstation-security.md#blacklisting-modules
blacklist:
- 'firewire_sbp2'
- 'firewire_ohci'
- 'firewire_core'
- 'thunderbolt'
# ]]]
# .. envvar:: kmod__modules [[[
#
# List of kernel module configuration entries which should be present on all
# hosts in the Ansible inventory.
kmod__modules: []
# ]]]
# .. envvar:: kmod__group_modules [[[
#
# List of kernel module configuration entries which should be present on hosts
# in a specific Ansible inventory group.
kmod__group_modules: []
# ]]]
# .. envvar:: kmod__host_modules [[[
#
# List of kernel module configuration entries which should be present on
# specific hosts in the Ansible inventory.
kmod__host_modules: []
# ]]]
# .. envvar:: kmod__dependent_modules [[[
#
# List of kernel module configuration entries which are defined by other
# Ansible roles through role dependent variables.
kmod__dependent_modules: []
# ]]]
# .. envvar:: kmod__combined_modules [[[
#
# The variable that combines all lists of kernel module parameters and passes
# them to the role tasks.
kmod__combined_modules: '{{ kmod__default_modules
+ kmod__dependent_modules
+ kmod__modules
+ kmod__group_modules
+ kmod__host_modules }}'
# ]]]
# ]]]
# Kernel module loading at boot [[[
# ---------------------------------
# These variables control what kernel modules should be loaded at boot time.
# See :ref:`kmod__ref_load` for more details.
# .. envvar:: kmod__load [[[
#
# List of kernel modules which should be loaded at boot time on all hosts in the
# Ansible inventory.
kmod__load: []
# ]]]
# .. envvar:: kmod__group_load [[[
#
# List of kernel modules which should be loaded at boot time on hosts in
# a specific Ansible inventory group.
kmod__group_load: []
# ]]]
# .. envvar:: kmod__host_load [[[
#
# List of kernel modules which should be loaded at boot time on specific hosts
# in the Ansible inventory.
kmod__host_load: []
# ]]]
# .. envvar:: kmod__dependent_load [[[
#
# List of kernel modules to loat at boot time, defined by other Ansible roles
# through role dependent variables.
kmod__dependent_load: []
# ]]]
# .. envvar:: kmod__combined_load [[[
#
# Variable which combines all of the kernel module load lists and passes them
# to the role tasks.
kmod__combined_load: '{{ kmod__dependent_load
+ kmod__load
+ kmod__group_load
+ kmod__host_load }}'
# ]]]
# ]]]
---
dependencies:
- role: debops.ansible_plugins
galaxy_info:
company: 'DebOps'
author: 'Maciej Delmanowski'
description: 'Manage kernel modules'
license: 'GPL-3.0'
min_ansible_version: '2.4.0'
platforms:
- name: Ubuntu
versions:
- xenial
- bionic
- name: Debian
versions:
- jessie
- stretch
- buster
galaxy_tags:
- kernel
- linux
---
- name: Check if modprobe is available
stat:
path: '/sbin/modprobe'
register: kmod__register_modprobe
- name: Get list of loaded kernel modules
shell: lsmod | tail --lines=+2 | awk '{print $1}'
register: kmod__register_modules
check_mode: False
changed_when: False
when: kmod__enabled|bool
- name: Configure kernel modules
include_tasks: 'modprobe.yml'
loop_control:
loop_var: 'module'
with_items: '{{ kmod__combined_modules | parse_kv_items }}'
when: kmod__enabled|bool
- name: Remove module load configuration
file:
dest: '/etc/modules-load.d/{{ item.filename | d(item.name | replace("_","-") + ".conf") }}'
state: 'absent'
with_items: '{{ kmod__combined_load | parse_kv_items }}'
when: kmod__enabled|bool and item.name|d() and item.state|d('present') == 'absent'
- name: Configure module loading at boot
template:
src: 'etc/modules-load.d/module.conf.j2'
dest: '/etc/modules-load.d/{{ item.filename | d(item.name | replace("_","-") + ".conf") }}'
owner: 'root'
group: 'root'
mode: '0644'
with_items: '{{ kmod__combined_load | parse_kv_items }}'
when: kmod__enabled|bool and item.name|d() and item.state|d('present') not in [ 'absent', 'ignore' ]
- name: Make sure that Ansible local facts directory exists
file:
path: '/etc/ansible/facts.d'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
- name: Save kmod local facts
template:
src: 'etc/ansible/facts.d/kmod.fact.j2'
dest: '/etc/ansible/facts.d/kmod.fact'
owner: 'root'
group: 'root'
mode: '0755'
register: kmod__register_facts
- name: Update Ansible facts if they were modified
action: setup
when: kmod__register_facts is changed
---
- name: Remove module configuration
file:
dest: '/etc/modprobe.d/{{ module.filename | d(module.name | replace("_","-") + ".conf") }}'
state: 'absent'
register: kmod__register_module_config_delete
when: module.name|d() and module.state|d('present') == 'absent'
- name: Generate module configuration
template:
src: 'etc/modprobe.d/module.conf.j2'
dest: '/etc/modprobe.d/{{ module.filename | d(module.name | replace("_","-") + ".conf") }}'
owner: 'root'
group: 'root'
mode: '0644'
register: kmod__register_module_config_create
when: module.name|d() and module.state|d('present') != 'absent'
- name: Unload kernel module if configuration changed
modprobe:
name: '{{ module.name }}'
state: 'absent'
when: ((kmod__register_module_config_delete is changed or
kmod__register_module_config_create is changed) and
module.blacklist is not defined and
module.name in kmod__register_modules.stdout_lines and
module.state|d('present') not in [ 'config' ])
- name: Load kernel module if configuration changed
modprobe:
name: '{{ module.name }}'
state: 'present'
when: ((kmod__register_module_config_delete is changed or
kmod__register_module_config_create is changed) and
module.blacklist is not defined and
module.state|d('present') not in [ 'config', 'absent', 'blacklist' ])
#!/usr/bin/env python
# {{ ansible_managed }}
from __future__ import print_function
from json import loads, dumps
from sys import exit
import subprocess
import signal
import os
output = loads('''{{ {"configured": True,
"enabled": kmod__enabled|bool} | to_nice_json }}''')
kernel_modules = []
try:
with open(os.devnull, 'w') as devnull:
kernel_modules = subprocess.check_output(
["/sbin/lsmod | tail -n +2 | awk '{print $1}'"],
shell=True, stderr=devnull).strip().split('\n')
except subprocess.CalledProcessError:
pass
output['modules'] = filter(None, kernel_modules)
print(dumps(output, sort_keys=True, indent=4))
# {{ ansible_managed }}
{% macro print_value(value) %}
{% if value | bool and value is not iterable %}
{{ '{}'.format('1') }}
{% elif not value | bool and value is not iterable %}
{% if value is not none %}
{% if value | int or value | string == '0' %}
{{ '{}'.format(value) }}
{% else %}
{{ '{}'.format('0') }}
{% endif %}
{% endif %}
{% elif value is string %}
{{ '{}'.format(value) }}
{% elif value is number %}
{{ '{}'.format(value) }}
{% endif %}
{% endmacro %}
{% if module.comment|d() %}
{{ module.comment | regex_replace('\n$','') | comment(prefix='', postfix='') }}
{% endif %}
{% if module.alias|d() %}
{% for element in ([ module.alias ] if module.alias is string else module.alias) %}
{{ 'alias {} {}'.format(element, module.name) }}
{% endfor %}
{% endif %}
{% if module.aliases|d() %}
{% for element in ([ module.aliases ] if module.aliases is string else module.aliases) %}
{{ 'alias {} {}'.format(element, module.name) }}
{% endfor %}
{% endif %}
{% if module.blacklist|d() %}
{% for element in ([ module.blacklist ] if module.blacklist is string else module.blacklist) %}
{{ 'blacklist {}'.format(element) }}
{% endfor %}
{% endif %}
{% if module.state|d('present') == 'blacklist' %}
{{ 'blacklist {}'.format(module.name) }}
{% endif %}
{% if module.options|d() %}
{% for element in ([ module.options ] if module.options is string else module.options) %}
{% if element.name|d() and element.value|d() %}
{% if element.state|d('present') != 'absent' %}
{% if element.comment|d() %}
{{ element.comment | regex_replace('\n$','') | comment(prefix='', postfix='') -}}
{% endif %}
{{ 'options {} {}={}'.format(module.name, element.name, print_value(element.value)) -}}
{% endif %}
{% else %}
{% for key, value in element.items() %}
{{ 'options {} {}={}'.format(module.name, key, print_value(value)) -}}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{% if module.install|d() %}
{{ 'install {} {}'.format(module.name, module.install) }}
{% endif %}
{% if module.remove|d() %}
{{ 'remove {} {}'.format(module.name, module.softdep) }}
{% endif %}
{% if module.softdep|d() %}
{{ 'softdep {} {}'.format(module.name, module.softdep) }}
{% endif %}
{% if module.raw|d() %}
{{ module.raw }}
{% endif %}
# {{ ansible_managed }}
{% if item.comment|d() %}
{{ item.comment | regex_replace('\n$','') | comment(prefix='', postfix='') }}
{% endif %}
{% if item.modules|d() %}
{% for element in ([ item.modules ] if item.modules is string else item.modules) %}
{{ element }}
{% endfor %}
{% else %}
{{ item.name }}
{% endif %}
......@@ -149,6 +149,7 @@ Host provisioning
Kernel
------
- :ref:`debops.kmod`
- :ref:`debops.sysctl`
- :ref:`debops.sysfs`
- ``debops-contrib.kernel_module``
......
Default variable details
========================
Some of ``debops.kmod`` default variables have more extensive configuration
than simple strings or lists, here you can find documentation and examples for
them.
.. contents::
:local:
:depth: 1
.. _kmod__ref_modules:
kmod__modules
-------------
The ``kmod__*_modules`` variables define what kernel module options should be
defined on the remote hosts, in the :file:`/etc/modprobe.d/` directory. Each
variable is a YAML list, each list entry is a YAML dictionary with specific
parameters:
``name``
Required. Name of the kernel module to manage. This parameter is used as
a marker to merge configuration entries of the same modules together.
If the ``filename`` parameter is not specified, this parameter will be
included in the generated filename, saved as ``{{ name }}.conf``.
``filename``
Optional. Specify a custom filename for a given configuration snippet, the
``.conf`` extension needs to be included in the filename.
``state``
Optional. Specify the state of the kernel module and its configuration.
Multiple entries with the same ``name`` parameter are merged toghether in
order of appearance; the ``state`` parameter can affect how the entries are
merged.
Supported states:
============= =============================================================
Value Description
============= =============================================================
``present`` **Default if not specified.** Configuration will be present
and the role will try to unload and load the kernel module to
apply any changes. Kernel module unloading is not forced, if
the module cannot be unloaded, Ansible will error out.
------------- -------------------------------------------------------------
``absent`` The configuration of a given kernel module will be removed.
If the configuration state changes, the role will try to
unload the kernel module if it's loaded, in case of failure
Ansible will error out.
------------- -------------------------------------------------------------
``config`` Specified kernel module configuration is set in the
configuration file, but the role will not try to change the
module state in the kernel.
------------- -------------------------------------------------------------
``blacklist`` The role will write the kernel module configuration and will
try to unload the kernel module if it's currently loaded.
------------- -------------------------------------------------------------
``init`` Define initial configuration for a given kernel module, but
don't write it in the configuration files or change the state
of the kernel modules. Configuration defined with this state
can be enabled conditionally using another list entry.
------------- -------------------------------------------------------------
``append`` Merge given configuration entry with other entries of the
same name only if a given entry is supposed to be enabled
(has a state other than ``init``). This state can be used to
build configuration from multiple entries conditionally.
------------- -------------------------------------------------------------
``ignore`` Configuration entries with this state will not be evaluated
by the role and won't be merged with other entries with the
same ``name`` parameter.
============= =============================================================
``options``
Optional. Specify the configuration options of the kernel module. This is
a list of YAML dictionaries, each dictionary can have an option name as a key
and an option value as a value. Alternatively, iv ``name`` and ``value`` keys
are used, the dictionary can have the following parameters:
=============== ===========================================================
Key Value
=============== ===========================================================
``name`` Required. The option name.
--------------- -----------------------------------------------------------
``value`` Required. The option value.
--------------- -----------------------------------------------------------
``comment`` Optional. A custom comment added to a given option.
--------------- -----------------------------------------------------------
``state`` Optional. If not set or ``present``, the option is included
in the configuration file, if ``absent``, the option is not
included in the configuration file.
=============== ===========================================================
The ``options`` parameters in multiple configuration entries are merged
together, just like the main entries. This can be used to conditionally
enable or disable options as needed.
``comment``
Optional. String or YAML text block with a comment explaining the kernel
module configuration.
``alias`` or ``aliases``
Optional. String or a list of strings which specify aliases for a given
module. See :man:`modprobe.d(5)` for more details.
``blacklist``
Optional. String or a list of strings with kernel modules to blacklist. If
this parameter is specified, the role does not try to unload individual
modules; this can be useful to blacklist multiple modules at once
preemptively.
``install``
Optional. A shell command to execute by :command:`modprobe` command instead
of loading a given kernel module. See :man:`modprobe.d(5)` for more details.
``remove``
Optional. A shell command to execute by :program:`modprobe` command instead
of unloading a given kernel module. See :man:`modprobe.d(5)` for more
details.
``softdep``
Optional. Define soft dependencies between kernel modules which affect the
order of them being loaded into the kernel. See :man:`modprobe.d(5)` for more
details. How to write the definition, based on an example from the manpage:
.. code-block:: yaml
kmod__modules:
- name: 'c'
softdep: 'pre: a b post: d e'
``raw``
Optional. YAML text block which will be added at the end of the kernel module
configuration file. It can be used to provide configuration not covered by
other parameters.
Examples
~~~~~~~~
Disable PC Speaker support in the kernel:
.. code-block:: yaml
kmod__modules:
- name: 'pcspkr'
state: 'blacklist'
comment: 'Disable PC Speaker support'
On ThinkPad laptops, allow :command:`thinkfan` command to control the fan
speed:
.. code-block:: yaml
kmod__modules:
- name: 'thinkpad_acpi'
comment: 'Enable fan speed control for "thinkfan"'
options:
- fan_control: 1
.. _kmod__ref_load:
kmod__load
----------
The ``kmod__*_load`` list variables can be used to specify which kernel modules
shoud be loaded at boot time. The configuration is stored in the
:file:`/etc/modules-load.d/` directory. Each list entry is a YAML dictionary
with specific parameters:
``name``
Required. Name of the kernel module to manage. This parameter is used as
a marker to merge configuration entries of the same modules together.
If the ``filename`` parameter is not specified, this parameter will be
included in the generated filename, saved as ``{{ name }}.conf``.
``filename``
Optional. Specify a custom filename for a given configuration snippet, the
``.conf`` extension needs to be included in the filename.
``state``
Optional. Specify the state of the kernel module and its configuration.
Multiple entries with the same ``name`` parameter are merged toghether in
order of appearance; the ``state`` parameter can affect how the entries are
merged.
Supported states:
============= =============================================================
Value Description
============= =============================================================
``present`` **Default if not specified.** Configuration will be present.
------------- -------------------------------------------------------------
``absent`` The configuration of a given kernel module will be removed.
------------- -------------------------------------------------------------
``ignore`` Configuration entries with this state will not be evaluated
by the role and won't be merged with other entries with the
same ``name`` parameter.
============= =============================================================
``comment``
Optional. String or YAML text block with a comment explaining the kernel
module configuration.
``modules``
Optional. List of modules to load on boot time. If specified, the string used
in the ``name`` parameter is ignored.
Examples
~~~~~~~~
Load the ``pcspkr`` kernel module at boot time:
.. code-block:: yaml
kmod__load:
- name: 'pcspkr'
comment: 'Enable PC Speaker support'
Getting started
===============
.. contents::
:local:
Example inventory
-----------------
To manage kernel modules on a host, you need to add it to the
``[debops_service_kmod]`` Ansible inventory group:
.. code-block:: none
[debops_service_kmod]
hostname
Example playbook
----------------
If you are using this role without DebOps, here's an example Ansible playbook
that uses the ``debops.kmod`` role:
.. literalinclude:: ../../../../ansible/playbooks/service/kmod.yml
:language: yaml
Ansible tags
------------
You can use Ansible ``--tags`` or ``--skip-tags`` parameters to limit what
tasks are performed during Ansible run. This can be used after a host was first
configured to speed up playbook execution, when you are sure that most of the
configuration is already in the desired state.
Available role tags:
``role::kmod``
Main role tag, should be used in the playbook to execute all of the role
tasks as well as role dependencies.
Other resources
---------------
List of other useful resources related to the ``debops.kmod`` Ansible role:
- Manual pages: :man:`modprobe.d(5)`, :man:`modules-load.d(5)`
- `Linux Kernel Modules`__ page on Debian Wiki
- `Kernel Modules`__ page on Arch Wiki