Skip to content

Commit

Permalink
Fix: hawk2 cannot call crm script as user hacluster (bsc#1228271) (#1533
Browse files Browse the repository at this point in the history
)

port from:

* #1522
  • Loading branch information
liangxin1300 authored Aug 29, 2024
2 parents 9e72303 + 79338f4 commit d528f3b
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 30 deletions.
4 changes: 2 additions & 2 deletions crmsh/completers.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ def primitives(args):


nodes = call(xmlutil.listnodes)
online_nodes = call(xmlutil.CrmMonXmlParser().get_node_list, "online")
standby_nodes = call(xmlutil.CrmMonXmlParser().get_node_list, "standby")
online_nodes = call(lambda x: xmlutil.CrmMonXmlParser().get_node_list(x), "online")
standby_nodes = call(lambda x: xmlutil.CrmMonXmlParser().get_node_list(x), "standby")

shadows = call(xmlutil.listshadows)

Expand Down
16 changes: 9 additions & 7 deletions crmsh/prun/prun.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ def pcopy_to_remote(
*,
timeout_seconds: int = -1,
concurrency: int = _DEFAULT_CONCURRENCY,
interceptor: PRunInterceptor = PRunInterceptor(),
) -> typing.Dict[str, typing.Optional[PRunError]]:
"""Copy file or directory from local to remote hosts concurrently."""
if src == dst:
Expand Down Expand Up @@ -176,13 +177,13 @@ def pcopy_to_remote(
tasks = [_build_copy_task("-S '{}'".format(ssh.name), script, host) for host in hosts]
runner = Runner(concurrency)
for task in tasks:
runner.add_task(task)
runner.add_task(interceptor.task(task))
runner.run(timeout_seconds)
finally:
if ssh is not None:
os.unlink(ssh.name)
ssh.close()
return {task.context['host']: _parse_copy_result(task) for task in tasks}
return {task.context['host']: _parse_copy_result(task, interceptor) for task in tasks}


def _build_copy_task(ssh: str, script: str, host: str):
Expand All @@ -202,13 +203,13 @@ def _build_copy_task(ssh: str, script: str, host: str):
)


def _parse_copy_result(task: Task) -> typing.Optional[PRunError]:
def _parse_copy_result(task: Task, interceptor: PRunInterceptor) -> typing.Optional[PRunError]:
if task.returncode == 0:
return None
elif task.returncode == 255:
return SSHError(task.context['ssh_user'], task.context['host'], Utils.decode_str(task.stdout))
return interceptor.exception(SSHError(task.context['ssh_user'], task.context['host'], Utils.decode_str(task.stdout)))
else:
return PRunError(task.context['ssh_user'], task.context['host'], Utils.decode_str(task.stdout))
return interceptor.exception(PRunError(task.context['ssh_user'], task.context['host'], Utils.decode_str(task.stdout)))


def pfetch_from_remote(
Expand All @@ -217,6 +218,7 @@ def pfetch_from_remote(
recursive=False,
*,
concurrency: int = _DEFAULT_CONCURRENCY,
interceptor: PRunInterceptor = PRunInterceptor(),
) -> typing.Dict[str, typing.Union[str, PRunError]]:
"""Copy files from remote hosts to local concurrently.
Expand All @@ -237,7 +239,7 @@ def pfetch_from_remote(
tasks = [_build_fetch_task("-S '{}'".format(ssh.name), host, src, dst, flags) for host in hosts]
runner = Runner(concurrency)
for task in tasks:
runner.add_task(task)
runner.add_task(interceptor.task(task))
runner.run()
finally:
if ssh is not None:
Expand All @@ -246,7 +248,7 @@ def pfetch_from_remote(
basename = os.path.basename(src)
return {
host: v if v is not None else f"{dst}/{host}/{basename}"
for host, v in ((task.context['host'], _parse_copy_result(task)) for task in tasks)
for host, v in ((task.context['host'], _parse_copy_result(task, interceptor)) for task in tasks)
}


Expand Down
47 changes: 44 additions & 3 deletions crmsh/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
from glob import glob
from lxml import etree

from .prun.runner import Task

try:
import json
except ImportError:
import simplejson as json

from . import config
from . import config, constants
from . import handles
from . import options
from . import userdir
Expand Down Expand Up @@ -1075,10 +1077,30 @@ def _check_control_persist():
return "Bad configuration option" not in err


class _PRunAsCurrentUserInterceptor(prun.PRunInterceptor):
_user = userdir.getuser()

def task(self, task: Task) -> Task:
if self._user != 'hacluster':
return task
elif any('ssh' in arg for arg in task.args):
task.args = ['ssh']
task.args .extend(constants.SSH_OPTION_ARGS)
task.args .extend([task.context['host'], '/bin/bash'])
else:
task.args = ['/bin/bash']
task.context['ssh_user'] = self._user
return task


def _parallax_call(printer, hosts, cmd, timeout_seconds):
"parallax.call with debug logging"
printer.debug("parallax.call(%s, %s)" % (repr(hosts), cmd))
return prun.prun({host: cmd for host, _, _ in hosts}, timeout_seconds=timeout_seconds)
return prun.prun(
{host: cmd for host, _, _ in hosts},
interceptor=_PRunAsCurrentUserInterceptor(),
timeout_seconds=timeout_seconds,
)


def _resolve_script(name):
Expand All @@ -1092,10 +1114,29 @@ def _resolve_script(name):
return None


class _PRunCopyAsCurrentUserInterceptor(prun.PRunInterceptor):
_user = userdir.getuser()

def task(self, task: Task) -> Task:
if self._user != 'hacluster':
return task
task.args = ['sftp']
task.args.extend(constants.SSH_OPTION_ARGS)
task.args.extend(['-o', 'BatchMode=yes', '-b', '-', task.context['host']])
task.context['remote_user'] = userdir.getuser()
return task


def _parallax_copy(printer, hosts, src, dst, timeout_seconds):
"parallax.copy with debug logging"
printer.debug("parallax.copy(%s, %s, %s)" % (repr(hosts), src, dst))
return prun.pcopy_to_remote(src, [x[0] for x in hosts], dst, recursive=True, timeout_seconds=timeout_seconds)
return prun.pcopy_to_remote(
src,
[x[0] for x in hosts],
dst,
recursive=True, timeout_seconds=timeout_seconds,
interceptor=_PRunCopyAsCurrentUserInterceptor(),
)


def _tempname(prefix):
Expand Down
4 changes: 1 addition & 3 deletions crmsh/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2995,9 +2995,7 @@ def check_user_access(level_name):
Check current user's privilege and give hints to user
"""
current_user = userdir.getuser()
if current_user == "root":
return
if level_name != "cluster" and in_haclient():
if current_user == "root" or in_haclient():
return

if not has_sudo_access():
Expand Down
17 changes: 2 additions & 15 deletions test/features/user_access.feature
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,12 @@ Feature: Functional test for user access

Scenario: User in haclient group
Given Cluster service is "stopped" on "hanode1"
When Run "useradd -m -s /bin/bash -N -g 90 xin1" on "hanode1"
When Run "useradd -m -s /bin/bash -N -g haclient xin1" on "hanode1"
When Run "echo 'export PATH=$PATH:/usr/sbin/' >> ~xin1/.bashrc" on "hanode1"
When Try "su - xin1 -c 'crm cluster init -y'"
Then Except multiple lines
"""
ERROR: Please run this command starting with "sudo".
Currently, this command needs to use sudo to escalate itself as root.
Please consider to add "xin1" as sudoer. For example:
sudo bash -c 'echo "xin1 ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/xin1'
"""
When Run "echo "xin1 ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/xin1" on "hanode1"
When Try "su - xin1 -c 'crm cluster init -y'"
Then Except multiple lines
"""
ERROR: Please run this command starting with "sudo"
"""
When Run "su - xin1 -c 'sudo crm cluster init -y'" on "hanode1"
Then Cluster service is "started" on "hanode1"

And Run "su - hacluster -c 'crm script run health'" OK on "hanode1"
When Run "su - xin1 -c 'crm node standby hanode1'" on "hanode1"
Then Node "hanode1" is standby

Expand Down

0 comments on commit d528f3b

Please sign in to comment.