Skip to content

Commit

Permalink
WIP on uki python module.
Browse files Browse the repository at this point in the history
Current challenge is accessing parameters, since the example code in the
documentation is not passing pyright. This might be a problem with
pyright itself, but I'll do more work next week.
  • Loading branch information
Christopher Palmer-Richez committed Aug 15, 2024
1 parent a1f7e7c commit 1874ef4
Showing 1 changed file with 182 additions and 0 deletions.
182 changes: 182 additions & 0 deletions plugins/modules/uki.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#!/usr/bin/python

# Copyright: (c) 2024, Christopher Palmer-Richez (tofu.ansible@chorky.net)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = r'''
---
module: uki
short_description: Configure unified kernel images
version_added: "1.0.0"
description: Configure kernel-install to make unified kernel images.
options:
signing_key:
description: |
The path to a RSA private key to use for UKI signing.
Specifying this option enables UKI signing for secure boot.
The `signing_cert` option must be provided as well.
type: path
signing_cert:
description: |
The path to the x509 certificate used for UKI singing.
Specifying this option enables UKI signing for secure boot.
The `signing_key` option must be provided as well.
type: path
initrd_generator:
description: |
The tool to use to make the UKI's initramfs section.
You must use a tool supported by kernel-install. If an unknown or
unsupported value is provided, the UKI build will fail.
type: str
default: dracut
# Specify this value according to your collection
# in format of namespace.collection.doc_fragment_name
# extends_documentation_fragment:
# - my_namespace.my_collection.my_doc_fragment_name
author:
- Christopher Palmer-Richez @crichez
'''

EXAMPLES = r'''
# Build and boot from a UKI without secure boot support
- name: Use UKIs
crichez.secureboot.uki:
# Build and boot from a signed UKI using a custom MOK
- name: Use signed UKIs
crichez.secureboot.uki:
signing_key: /etc/kernel/MOK.priv
signing_cert: /etc/kernel/MOK.pem
# Build and boot from a signed UKI using a custom initrd generator
- name: Use signed UKIs with mkinitcpio
crichez.secureboot.uki:
signing_key: /etc/kernel/DB.priv
signing_cert: /etc/kernel/DB.pem
initrd_generator: mkinitcpio
'''

RETURN = r'''
uki_path:
description: The path to the generated UKI.
type: path
returned: success
sample: /boot/efi/EFI/Linux/6f51ea06-4933-4666-937e-f83391673562-6.9.9-f40-x86_64.efi
kernel_install_output:
description: The output of kernel-install
type: str
returned: always
sample: ''
'''

from ansible.module_utils.basic import AnsibleModule
import re

def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
signing_key=dict(type='path', required=False),
signing_cert=dict(type='path', required=False),
initrd_generator=dict(type='str', required=False, default='dracut')
)

# seed the result dict in the object
# we primarily care about changed and state
# changed is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
uki_path='',
kernel_install_output=''
)

# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=False
)



# if the user is working with this module in only check mode we do not
# want to make any changes to the environment, just return the current
# state with no modifications
if module.check_mode:
module.exit_json(**result)

# manipulate or modify the state as needed (this is going to be the
# part where your module will do what it needs to do)
change_list = dict(
install_conf_path=[],
ukify_conf_path=[])

# Set the install.conf file to the desired state
install_conf_path = '/etc/kernel/install.conf'
install_conf = None
try:
with open(install_conf_path, mode='r', encoding='utf-8') as f:
install_conf = f.read()
except FileNotFoundError:
install_conf = ''

# Set install.conf layout
layout_re = re.compile(r'^layout=([a-z]*)$')
layout_match = layout_re.search(install_conf)
if layout_match:
layout = layout_match.group(1)
if layout != 'uki':
install_conf = layout_re.sub('layout=uki', install_conf)
change = f'changed layout from {layout} to uki'
change_list[install_conf_path].append(change)
else:
layout_line = 'layout=uki\n'
if install_conf != '' and install_conf[-1] != '\n':
layout_line = '\n'.join(layout_line)
install_conf = install_conf.join(layout_line)
change = 'change layout from default to uki'
change_list[install_conf_path].append(change)

# Set install.conf initrd_generator
initrd_re = re.compile(r'^initrd_generator=([a-z]*)$')
initrd_match = initrd_re.search(install_conf)
if initrd_match:
initrd_generator = initrd_match.group(1)
new_initrd_gen = module.params['initrd_generator']
if initrd_generator != new_initrd_gen:
replacement = f"initrd_generator={new_initrd_gen}"
install_conf = initrd_re.sub(replacement, install_conf)

# use whatever logic you need to determine whether or not this module
# made any modifications to your target

# during the execution of the module, if there is an exception or a
# conditional state that effectively causes a failure, run
# AnsibleModule.fail_json() to pass in the message and the result
if module.params['name'] == 'fail me':
module.fail_json(msg='You requested this to fail', **result)

# in the event of a successful module execution, you will want to
# simple AnsibleModule.exit_json(), passing the key/value results
module.exit_json(**result)


def main():
run_module()


if __name__ == '__main__':
main()

0 comments on commit 1874ef4

Please sign in to comment.