add support for systemd creds encrypt/decrypt (#9383)

* add support for systemd creds encrypt/decrypt

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* add __metaclass__

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* Python 2.7 issues

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* update version_added and ci test aliases

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* switch to container

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* run tests in docker as well

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* move tasks into tasks/

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* no need to call echo

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* lint and add become:

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* dont append a newline

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* don't clean newlines

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* only use module name

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* clean

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* change msg to value

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* add return values

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* update attributes and description

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* Update plugins/modules/systemd_creds_decrypt.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* set newline default

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* Update plugins/modules/systemd_creds_encrypt.py

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>

* Update plugins/modules/systemd_creds_encrypt.py

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>

* Update plugins/modules/systemd_creds_encrypt.py

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>

* update required and spelling

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

* use single backslash

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>

---------

Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
pull/9431/head
Thomas Sjögren 2024-12-29 21:15:57 +01:00 committed by GitHub
parent d887930e49
commit 482a90e8b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 445 additions and 0 deletions

4
.github/BOTMETA.yml vendored
View File

@ -1341,6 +1341,10 @@ files:
maintainers: precurse
$modules/sysrc.py:
maintainers: dlundgren
$modules/systemd_creds_decrypt.py:
maintainers: konstruktoid
$modules/systemd_creds_encrypt.py:
maintainers: konstruktoid
$modules/sysupgrade.py:
maintainers: precurse
$modules/taiga_issue.py:

View File

@ -0,0 +1,157 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2024, Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: systemd_creds_decrypt
short_description: C(systemd)'s C(systemd-creds decrypt) plugin
description:
- This module decrypts input using C(systemd)'s C(systemd-creds decrypt).
author:
- Thomas Sjögren (@konstruktoid)
version_added: '10.2.0'
extends_documentation_fragment:
- community.general.attributes
attributes:
check_mode:
support: full
details:
- This action does not modify state.
diff_mode:
support: N/A
details:
- This action does not modify state.
options:
name:
description:
- The credential name to validate the embedded credential name.
type: str
required: false
newline:
description:
- Whether to add a trailing newline character to the end of the output,
if not present.
type: bool
required: false
default: false
secret:
description:
- The secret to decrypt.
type: str
required: true
timestamp:
description:
- The timestamp to use to validate the V(not-after) timestamp that
was used during encryption.
- Takes a timestamp specification in the format described in
V(systemd.time(7\)).
type: str
required: false
transcode:
description:
- Whether to transcode the output before returning it.
type: str
choices: [ base64, unbase64, hex, unhex ]
required: false
user:
description:
- A user name or numeric UID when decrypting from a specific user context.
- If set to the special string V(self) it sets the user to the user
of the calling process.
- Requires C(systemd) 256 or later.
type: str
required: false
notes:
- C(systemd-creds) requires C(systemd) 250 or later.
"""
EXAMPLES = """
- name: Decrypt secret
community.general.systemd_creds_decrypt:
name: db
secret: "WhQZht+JQJax1aZemmGLxmAAAA..."
register: decrypted_secret
- name: Print the decrypted secret
ansible.builtin.debug:
msg: "{{ decrypted_secret }}"
"""
RETURN = r"""
value:
description:
- The decrypted secret.
- Note that Ansible only supports returning UTF-8 encoded strings.
If the decrypted secret is binary data, or a string encoded in another
way, use O(transcode=base64) or O(transcode=hex) to circument this
restriction. You then need to decode the data when using it, for
example using the P(ansible.builtin.b64decode#filter) filter.
type: str
returned: always
sample: "access_token"
"""
from ansible.module_utils.basic import AnsibleModule
def main():
"""Decrypt secret using systemd-creds."""
module = AnsibleModule(
argument_spec=dict(
name=dict(type="str", required=False),
newline=dict(type="bool", required=False, default=False),
secret=dict(type="str", required=True, no_log=True),
timestamp=dict(type="str", required=False),
transcode=dict(
type="str",
choices=["base64", "unbase64", "hex", "unhex"],
required=False,
),
user=dict(type="str", required=False),
),
supports_check_mode=True,
)
cmd = module.get_bin_path("systemd-creds", required=True)
name = module.params["name"]
newline = module.params["newline"]
secret = module.params["secret"]
timestamp = module.params["timestamp"]
transcode = module.params["transcode"]
user = module.params["user"]
decrypt_cmd = [cmd, "decrypt"]
if name:
decrypt_cmd.append("--name=" + name)
else:
decrypt_cmd.append("--name=")
decrypt_cmd.append("--newline=" + ("yes" if newline else "no"))
if timestamp:
decrypt_cmd.append("--timestamp=" + timestamp)
if transcode:
decrypt_cmd.append("--transcode=" + transcode)
if user:
decrypt_cmd.append("--uid=" + user)
decrypt_cmd.extend(["-", "-"])
rc, stdout, stderr = module.run_command(decrypt_cmd, data=secret, binary_data=True)
module.exit_json(
changed=False,
value=stdout,
rc=rc,
stderr=stderr,
)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,149 @@
#!/usr/bin/python
# Copyright (c) 2024, Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: systemd_creds_encrypt
short_description: C(systemd)'s C(systemd-creds encrypt) plugin
description:
- This module encrypts input using C(systemd)'s C(systemd-creds encrypt).
author:
- Thomas Sjögren (@konstruktoid)
version_added: '10.2.0'
extends_documentation_fragment:
- community.general.attributes
attributes:
check_mode:
support: full
details:
- This action does not modify state.
diff_mode:
support: N/A
details:
- This action does not modify state.
options:
name:
description:
- The credential name to embed in the encrypted credential data.
type: str
required: false
not_after:
description:
- The time when the credential shall not be used anymore.
- Takes a timestamp specification in the format described in
V(systemd.time(7\)).
type: str
required: false
pretty:
description:
- Pretty print the output so that it may be pasted directly into a
unit file.
type: bool
required: false
default: false
secret:
description:
- The secret to encrypt.
type: str
required: true
timestamp:
description:
- The timestamp to embed into the encrypted credential.
- Takes a timestamp specification in the format described in
V(systemd.time(7\)).
type: str
required: false
user:
description:
- A user name or numeric UID to encrypt the credential for.
- If set to the special string V(self) it sets the user to the user
of the calling process.
- Requires C(systemd) 256 or later.
type: str
required: false
notes:
- C(systemd-creds) requires C(systemd) 250 or later.
"""
EXAMPLES = """
- name: Encrypt secret
become: true
community.general.systemd_creds_encrypt:
name: db
not_after: +48hr
secret: access_token
register: encrypted_secret
- name: Print the encrypted secret
ansible.builtin.debug:
msg: "{{ encrypted_secret }}"
"""
RETURN = r"""
value:
description: The Base64 encoded encrypted secret.
type: str
returned: always
sample: "WhQZht+JQJax1aZemmGLxmAAAA..."
"""
from ansible.module_utils.basic import AnsibleModule
def main():
"""Encrypt secret using systemd-creds."""
module = AnsibleModule(
argument_spec=dict(
name=dict(type="str", required=False),
not_after=dict(type="str", required=False),
pretty=dict(type="bool", default=False),
secret=dict(type="str", required=True, no_log=True),
timestamp=dict(type="str", required=False),
user=dict(type="str", required=False),
),
supports_check_mode=True,
)
cmd = module.get_bin_path("systemd-creds", required=True)
name = module.params["name"]
not_after = module.params["not_after"]
pretty = module.params["pretty"]
secret = module.params["secret"]
timestamp = module.params["timestamp"]
user = module.params["user"]
encrypt_cmd = [cmd, "encrypt"]
if name:
encrypt_cmd.append("--name=" + name)
else:
encrypt_cmd.append("--name=")
if not_after:
encrypt_cmd.append("--not-after=" + not_after)
if pretty:
encrypt_cmd.append("--pretty")
if timestamp:
encrypt_cmd.append("--timestamp=" + timestamp)
if user:
encrypt_cmd.append("--uid=" + user)
encrypt_cmd.extend(["-", "-"])
rc, stdout, stderr = module.run_command(encrypt_cmd, data=secret, binary_data=True)
module.exit_json(
changed=False,
value=stdout,
rc=rc,
stderr=stderr,
)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,11 @@
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
needs/root
azp/posix/1
skip/aix
skip/freebsd
skip/osx
skip/macos

View File

@ -0,0 +1,58 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
- name: Test systemd_creds_decrypt
when:
- ansible_systemd.version is defined
- ansible_systemd.version | int >= 250
block:
- name: Encrypt secret
become: true
systemd_creds_encrypt:
name: api
not_after: +48hr
secret: access_token
register: encrypted_api_secret
- name: Print the encrypted secret
ansible.builtin.debug:
msg: "{{ encrypted_api_secret }}"
- name: Decrypt secret
community.general.systemd_creds_decrypt:
name: api
newline: false
secret: "{{ encrypted_api_secret.value }}"
register: decrypted_secret
- name: Print the decrypted secret
ansible.builtin.debug:
msg: "{{ decrypted_secret }}"
- name: Assert that the decrypted secret is the same as the original secret
ansible.builtin.assert:
that:
- decrypted_secret.value == 'access_token'
fail_msg: "Decrypted secret is not the same as the original secret"
success_msg: "Decrypted secret is the same as the original secret"
- name: Decrypt secret into hex
community.general.systemd_creds_decrypt:
name: api
newline: false
secret: "{{ encrypted_api_secret.value }}"
transcode: hex
register: decrypted_secret_hex
- name: Print the trancoded decrypted secret
ansible.builtin.debug:
msg: "{{ decrypted_secret_hex }}"
- name: Assert that the decrypted secret is the same as the original secret
ansible.builtin.assert:
that:
- decrypted_secret_hex.value == '6163636573735f746f6b656e'
fail_msg: "Decrypted secret is not the same as the original secret"
success_msg: "Decrypted secret is the same as the original secret"

View File

@ -0,0 +1,11 @@
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
needs/root
azp/posix/1
skip/aix
skip/freebsd
skip/osx
skip/macos

View File

@ -0,0 +1,55 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
- name: Test systemd_creds_encrypt
when:
- ansible_systemd.version is defined
- ansible_systemd.version | int >= 250
block:
- name: Encrypt secret
become: true
systemd_creds_encrypt:
name: db
not_after: +48hr
secret: access_token
register: encrypted_secret
- name: Assert encrypted secret output is base64 encoded
ansible.builtin.assert:
that:
- encrypted_secret.value | b64decode
fail_msg: "Encrypted secret is not base64 encoded"
success_msg: "Encrypted secret is base64 encoded"
- name: Print the encrypted secret
ansible.builtin.debug:
msg: "{{ encrypted_secret }}"
- name: Assert that SetCredentialEncrypted message is not in the output
ansible.builtin.assert:
that:
- '"SetCredentialEncrypted" not in encrypted_secret.value'
fail_msg: "SetCredentialEncrypted is in the output"
success_msg: "SetCredentialEncrypted is not in the output"
- name: Encrypt secret
become: true
community.general.systemd_creds_encrypt:
name: web
not_after: +5y
pretty: true
secret: token
register: pretty_encrypted_secret
- name: Pretty print the encrypted secret
ansible.builtin.debug:
msg: "{{ pretty_encrypted_secret }}"
- name: Assert that SetCredentialEncrypted message is in the output
ansible.builtin.assert:
that:
- '"SetCredentialEncrypted=web: " in pretty_encrypted_secret.value'
fail_msg: "SetCredentialEncrypted is not in the output"
success_msg: "SetCredentialEncrypted is in the output"