Skip to content

Commit

Permalink
add systemd run0 as a become method (#8306)
Browse files Browse the repository at this point in the history
* add systemd run0 as a become method

Signed-off-by: Thomas Sjögren <[email protected]>

* add fragment

Signed-off-by: Thomas Sjögren <[email protected]>

* remove space after hyphen

Signed-off-by: Thomas Sjögren <[email protected]>

* replace ansible with collection version

Signed-off-by: Thomas Sjögren <[email protected]>

* update version_added and remove changelog fragment

Signed-off-by: Thomas Sjögren <[email protected]>

* update formating

Signed-off-by: Thomas Sjögren <[email protected]>

* add types

Signed-off-by: Thomas Sjögren <[email protected]>

* slim super()

Signed-off-by: Thomas Sjögren <[email protected]>

* imports must appear below docs

Signed-off-by: Thomas Sjögren <[email protected]>

* add initial unit test

Signed-off-by: Thomas Sjögren <[email protected]>

* update unit tests

Signed-off-by: Thomas Sjögren <[email protected]>

---------

Signed-off-by: Thomas Sjögren <[email protected]>
  • Loading branch information
konstruktoid authored May 11, 2024
1 parent 3b7f13c commit d347bf5
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/BOTMETA.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ files:
maintainers: $team_ansible_core
$becomes/pmrun.py:
maintainers: $team_ansible_core
$becomes/run0.py:
maintainers: konstruktoid
$becomes/sesu.py:
maintainers: nekonyuu
$becomes/sudosu.py:
Expand Down
128 changes: 128 additions & 0 deletions plugins/become/run0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# -*- 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 = """
name: run0
short_description: Systemd's run0
description:
- This become plugins allows your remote/login user to execute commands as another user via the C(run0) utility.
author:
- Thomas Sjögren (@konstruktoid)
version_added: '9.0.0'
options:
become_user:
description: User you 'become' to execute the task.
default: root
ini:
- section: privilege_escalation
key: become_user
- section: run0_become_plugin
key: user
vars:
- name: ansible_become_user
- name: ansible_run0_user
env:
- name: ANSIBLE_BECOME_USER
- name: ANSIBLE_RUN0_USER
type: string
become_exe:
description: The C(run0) executable.
default: run0
ini:
- section: privilege_escalation
key: become_exe
- section: run0_become_plugin
key: executable
vars:
- name: ansible_become_exe
- name: ansible_run0_exe
env:
- name: ANSIBLE_BECOME_EXE
- name: ANSIBLE_RUN0_EXE
type: string
become_flags:
description: Options to pass to run0.
default: ''
ini:
- section: privilege_escalation
key: become_flags
- section: run0_become_plugin
key: flags
vars:
- name: ansible_become_flags
- name: ansible_run0_flags
env:
- name: ANSIBLE_BECOME_FLAGS
- name: ANSIBLE_RUN0_FLAGS
type: string
notes:
- This plugin will only work when a polkit rule is in place.
"""

EXAMPLES = r"""
# An example polkit rule that allows the user 'ansible' in the 'wheel' group
# to execute commands using run0 without authentication.
/etc/polkit-1/rules.d/60-run0-fast-user-auth.rules: |
polkit.addRule(function(action, subject) {
if(action.id == "org.freedesktop.systemd1.manage-units" &&
subject.isInGroup("wheel")
subject.user == "ansible") {
return polkit.Result.YES;
}
});
"""

from re import compile as re_compile

from ansible.plugins.become import BecomeBase
from ansible.module_utils._text import to_bytes

ansi_color_codes = re_compile(to_bytes(r"\x1B\[[0-9;]+m"))


class BecomeModule(BecomeBase):

name = "community.general.run0"

prompt = "Password: "
fail = ("==== AUTHENTICATION FAILED ====",)
success = ("==== AUTHENTICATION COMPLETE ====",)
require_tty = (
True # see https://github.com/ansible-collections/community.general/issues/6932
)

@staticmethod
def remove_ansi_codes(line):
return ansi_color_codes.sub(b"", line)

def build_become_command(self, cmd, shell):
super().build_become_command(cmd, shell)

if not cmd:
return cmd

become = self.get_option("become_exe")
flags = self.get_option("become_flags")
user = self.get_option("become_user")

return (
f"{become} --user={user} {flags} {self._build_success_command(cmd, shell)}"
)

def check_success(self, b_output):
b_output = self.remove_ansi_codes(b_output)
return super().check_success(b_output)

def check_incorrect_password(self, b_output):
b_output = self.remove_ansi_codes(b_output)
return super().check_incorrect_password(b_output)

def check_missing_password(self, b_output):
b_output = self.remove_ansi_codes(b_output)
return super().check_missing_password(b_output)
64 changes: 64 additions & 0 deletions tests/unit/plugins/become/test_run0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 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

import re

from ansible import context

from .helper import call_become_plugin


def test_run0_basic(mocker, parser, reset_cli_args):
options = parser.parse_args([])
context._init_global_context(options)

default_cmd = "/bin/foo"
default_exe = "/bin/sh"
run0_exe = "run0"

success = "BECOME-SUCCESS-.+?"

task = {
"become_method": "community.general.run0",
}
var_options = {}
cmd = call_become_plugin(task, var_options, cmd=default_cmd, executable=default_exe)
assert (
re.match(
f"{run0_exe} --user=root {default_exe} -c 'echo {success}; {default_cmd}'",
cmd,
)
is not None
)


def test_run0_flags(mocker, parser, reset_cli_args):
options = parser.parse_args([])
context._init_global_context(options)

default_cmd = "/bin/foo"
default_exe = "/bin/sh"
run0_exe = "run0"
run0_flags = "--nice=15"

success = "BECOME-SUCCESS-.+?"

task = {
"become_method": "community.general.run0",
"become_flags": run0_flags,
}
var_options = {}
cmd = call_become_plugin(task, var_options, cmd=default_cmd, executable=default_exe)
assert (
re.match(
f"{run0_exe} --user=root --nice=15 {default_exe} -c 'echo {success}; {default_cmd}'",
cmd,
)
is not None
)

0 comments on commit d347bf5

Please sign in to comment.