CVE-2021-3020 (Python, published 2022-08-26T00:15Z)
Description: value: An issue was discovered in ClusterLabs Hawk (aka HA Web Konsole) through 2.3.0-15. It ships the binary hawk_invoke (built from tools/hawk_invoke.c), intended to be used as a setuid program. This allows the hacluster user to invoke certain commands as root (with an attempt to limit this to safe combinations). This user is able to execute an interactive "shell" that isn't limited to the commands specified in hawk_invoke, allowing escalation to root.
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-269 \u2014 Improper Privilege Management
The product does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor.
Repository: https://github.com/ClusterLabs/crmsh
Files changed:
crmsh/bootstrap.py
crmsh/utils.py
Commit message: Fix: bootstrap: setup authorized ssh access for hacluster(CVE-2020-35459, bsc#1179999; CVE-2021-3020, bsc#1180571)


Vulnerable code:
RSA_PRIVATE_KEY = "/root/.ssh/id_rsa"
RSA_PUBLIC_KEY = "/root/.ssh/id_rsa.pub"
AUTHORIZED_KEYS_FILE = "/root/.ssh/authorized_keys"
    configure_local_ssh_key()
def configure_local_ssh_key():
    If /root/.ssh/id_rsa not exist, generate a new one
    Add /root/.ssh/id_rsa.pub to /root/.ssh/authorized_keys anyway, make sure itself authorized
    if not os.path.exists(RSA_PRIVATE_KEY):
        status("Generating SSH key")
        invoke("ssh-keygen -q -f {} -C 'Cluster Internal on {}' -N ''".format(RSA_PRIVATE_KEY, utils.this_node()))
    if not os.path.exists(AUTHORIZED_KEYS_FILE):
        open(AUTHORIZED_KEYS_FILE, 'w').close()
    append_unique(RSA_PUBLIC_KEY, AUTHORIZED_KEYS_FILE)
    configure_local_ssh_key()
    swap_public_ssh_key(seed_host)
def swap_public_ssh_key(remote_node):
    if utils.check_ssh_passwd_need(remote_node):
        status("Configuring SSH passwordless with root@{}".format(remote_node))
        append_to_remote_file(RSA_PUBLIC_KEY, remote_node, AUTHORIZED_KEYS_FILE)
        public_key_file_remote = fetch_public_key_from_remote_node(remote_node)
    append_unique(public_key_file_remote, AUTHORIZED_KEYS_FILE)
def fetch_public_key_from_remote_node(node):
        public_key_file = "/root/.ssh/{}.pub".format(key)
        swap_public_ssh_key(node)
    if corosync_active:
def check_ssh_passwd_need(host):

Fixed code:
from . import userdir
USER_LIST = ["root", "hacluster"]
        self.with_other_user = True
    for user in USER_LIST:
        configure_local_ssh_key(user)
def key_files(user):
    """
    Find home directory for user and return key files with abspath
    """
    keyfile_dict = {}
    home_dir = userdir.gethomedir(user)
    keyfile_dict['private'] = "{}/.ssh/id_rsa".format(home_dir)
    keyfile_dict['public'] = "{}/.ssh/id_rsa.pub".format(home_dir)
    keyfile_dict['authorized'] = "{}/.ssh/authorized_keys".format(home_dir)
    return keyfile_dict


def is_nologin(user):
    """
    Check if user's shell is /sbin/nologin
    """
    with open("/etc/passwd") as f:
        return re.search("{}:.*:/sbin/nologin".format(user), f.read())


def change_user_shell(user):
    """
    To change user's login shell
    """
    if user != "root" and is_nologin(user):
        if not _context.yes_to_all:
            status("""
User {} will be changed the login shell as /bin/bash, and
be setted up authorized ssh access among cluster nodes""".format(user))
            if not confirm("Continue?"):
                _context.with_other_user = False
                return
        invoke("usermod -s /bin/bash {}".format(user))


def configure_local_ssh_key(user="root"):
    If <home_dir>/.ssh/id_rsa not exist, generate a new one
    Add <home_dir>/.ssh/id_rsa.pub to <home_dir>/.ssh/authorized_keys anyway, make sure itself authorized
    change_user_shell(user)

    private_key, public_key, authorized_file = key_files(user).values()
    if not os.path.exists(private_key):
        status("Generating SSH key for {}".format(user))
        cmd = "ssh-keygen -q -f {} -C 'Cluster Internal on {}' -N ''".format(private_key, utils.this_node())
        cmd = utils.add_su(cmd, user)
        rc, _, err = invoke(cmd)
        if not rc:
            error("Failed to generate ssh key for {}: {}".format(user, err))

    if not os.path.exists(authorized_file):
        open(authorized_file, 'w').close()
    append_unique(public_key, authorized_file)
    for user in USER_LIST:
        configure_local_ssh_key(user)
        swap_public_ssh_key(seed_host, user)
def swap_public_ssh_key(remote_node, user="root"):
    if user != "root" and not _context.with_other_user:
        return

    _, public_key, authorized_file = key_files(user).values()
    if utils.check_ssh_passwd_need(remote_node, user):
        status("Configuring SSH passwordless with {}@{}".format(user, remote_node))
        append_to_remote_file(public_key, remote_node, authorized_file)
        public_key_file_remote = fetch_public_key_from_remote_node(remote_node, user)
    append_unique(public_key_file_remote, authorized_file)
def fetch_public_key_from_remote_node(node, user="root"):
    home_dir = userdir.gethomedir(user)
        public_key_file = "{}/.ssh/{}.pub".format(home_dir, key)
        for user in USER_LIST:
            swap_public_ssh_key(node, user)
    if corosync_active and _context.stage != "ssh":
def add_su(cmd, user):
    """
    Wrapped cmd with su -c "<cmd>" <user>
    """
    if user == "root":
        return cmd
    return "su -c \"{}\" {}".format(cmd, user)


def check_ssh_passwd_need(host, user="root"):
    ssh_cmd = add_su(ssh_cmd, user)