Skip to content

Commit

Permalink
DRAFT: Use different users for remote nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
Aleksei Burlakov committed Aug 25, 2022
1 parent 6372acb commit a1b6ef8
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 66 deletions.
88 changes: 48 additions & 40 deletions crmsh/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ def _validate_nodes_option(self):
"""
Validate -N/--nodes option
"""
# FIXME! That's a workaround for the "join" case
if self.type == "join":
self.current_user = userdir.getuser()
if self.cluster_node.find('@') != -1:
Expand Down Expand Up @@ -758,12 +757,11 @@ def append_unique(fromfile, tofile, user, remote=None, from_local=False):
if not remote:
append fromfile to tofile, locally
"""
if not utils.check_file_content_included(
fromfile, tofile, _context.current_user, remote=remote, source_local=from_local):
if not utils.check_file_content_included(fromfile, tofile, user, remote, from_local):
if from_local and remote:
append_to_remote_file(fromfile, user, remote, tofile)
else:
append(fromfile, tofile, remote=remote)
append(fromfile, tofile, remote)


def rmfile(path, ignore_errors=False):
Expand Down Expand Up @@ -796,25 +794,31 @@ def init_ssh():
"""
Configure passwordless SSH.
"""

local_user = _context.current_user

utils.start_service("sshd.service", enable=True)
configure_ssh_key(_context.current_user)
configure_ssh_key(local_user)

# If not use -N/--nodes option
if not _context.node_list:
return

# FIXME! There are many remote users when -N
remote_user = _context.user_list[0]

print()
node_list = _context.node_list
# Swap public ssh key between remote node and local
for node in node_list:
swap_public_ssh_key(node, user=_context.current_user, add=True)
swap_public_ssh_key(local_user, node, remote_user, add=True)
if utils.service_is_active("pacemaker.service", node):
utils.fatal("Cluster is currently active on {} - can't run".format(node))
# Swap public ssh key between one remote node and other remote nodes
if len(node_list) > 1:
_, _, authorized_file = key_files("root").values()
for node in node_list:
public_key_file_remote = fetch_public_key_from_remote_node(node)
public_key_file_remote = fetch_public_key_from_remote_node(node, remote_user)
for other_node in node_list:
if other_node == node:
continue
Expand Down Expand Up @@ -868,10 +872,10 @@ def configure_ssh_key(user, remote=None):

cmd = ""
private_key, public_key, authorized_file = key_files(user).values()
if not utils.detect_file(private_key, user=user, remote=remote):
if not utils.detect_file(private_key, user, remote):
logger.info("SSH key for {} does not exist, hence generate it now".format(user))
cmd = "ssh-keygen -q -f {} -C 'Cluster Internal on {}' -N ''".format(private_key, remote if remote else utils.this_node())
elif not utils.detect_file(public_key, user=user, remote=remote):
elif not utils.detect_file(public_key, user, remote):
cmd = "ssh-keygen -y -f {} > {}".format(private_key, public_key)
if cmd:
#cmd = utils.add_su(cmd, user) # no more
Expand Down Expand Up @@ -904,7 +908,7 @@ def init_ssh_remote():
append(fn + ".pub", authorized_keys_file)


def copy_ssh_key(source_key, user, remote_node):
def copy_ssh_key(source_key, remote_node, remote_user):
"""
Copy ssh key from local to remote's authorized_keys
"""
Expand All @@ -913,18 +917,19 @@ def copy_ssh_key(source_key, user, remote_node):
As the hint, likely, `PasswordAuthentication` is 'no' in /etc/ssh/sshd_config.
Given in this case, users must setup passwordless ssh beforehand, or change it to 'yes' and manage passwords properly
"""
cmd = "ssh-copy-id -i {} {}@{}".format(source_key, user, remote_node)
cmd = "ssh-copy-id -i {} {}@{}".format(source_key, remote_user, remote_node)
try:
utils.get_stdout_or_raise_error(cmd)
except ValueError as err:
utils.fatal("{}\n{}".format(str(err), err_details_string))


def append_to_remote_file(fromfile, user, remote_node, tofile):
def append_to_remote_file(fromlocalfile, remote_user, remote_node, toremotefile):
"""
Append content of fromfile to tofile on remote_node
"""
cmd = "cat {} | ssh {} {}@{} 'cat >> {}'".format(fromfile, SSH_OPTION, user, remote_node, tofile)
cmd = "cat {} | ssh {} {}@{} 'cat >> {}'".format(
fromlocalfile, SSH_OPTION, remote_user, remote_node, toremotefile)
utils.get_stdout_or_raise_error(cmd)


Expand Down Expand Up @@ -1411,81 +1416,84 @@ def init():
init_network()


def join_ssh(user, seed_host):
def join_ssh(seed_host):
"""
SSH configuration for joining node.
"""
if not seed_host:
utils.fatal("No existing IP/hostname specified (use -c option)")

local_user = _context.current_user
remote_user = _context.user_list[0]
utils.start_service("sshd.service", enable=True)
configure_ssh_key(user)
swap_public_ssh_key(seed_host, user)
configure_ssh_key(local_user)
swap_public_ssh_key(local_user, seed_host, remote_user)

# This makes sure the seed host has its own SSH keys in its own
# authorized_keys file (again, to help with the case where the
# user has done manual initial setup without the assistance of
# ha-cluster-init).
rc, _, err = invoke("ssh {} {}@{} crm cluster init -i {} ssh_remote".format(SSH_OPTION, user, seed_host, _context.default_nic_list[0]))
rc, _, err = invoke("ssh {} {}@{} crm cluster init -i {} ssh_remote".format(
SSH_OPTION, remote_user, seed_host, _context.default_nic_list[0]))
if not rc:
utils.fatal("Can't invoke crm cluster init -i {} ssh_remote on {}: {}".format(_context.default_nic_list[0], seed_host, err))


def swap_public_ssh_key(remote_node, user=userdir.getuser(), add=False):
def swap_public_ssh_key(local_user, remote_node, remote_user, add=False):
"""
Swap public ssh key between remote_node and local
"""
if user != "root" and not _context.with_other_user:
if local_user != "root" and not _context.with_other_user:
return

_, public_key, authorized_file = key_files(user).values()
_, local_public_key, local_authorized_file = key_files(local_user).values()
# Detect whether need password to login to remote_node
if utils.check_ssh_passwd_need(remote_node, user):
if utils.check_ssh_passwd_need(remote_node, remote_user):
# If no passwordless configured, paste /root/.ssh/id_rsa.pub to remote_node's /root/.ssh/authorized_keys
logger.info("Configuring SSH passwordless with {}@{}".format(user, remote_node))
logger.info("Configuring SSH passwordless with {}@{}".format(remote_user, remote_node))
# After this, login to remote_node is passwordless
if user == "root":
copy_ssh_key(public_key, user, remote_node)
else:
append_to_remote_file(public_key, user, remote_node, authorized_file)
copy_ssh_key(local_public_key, remote_node, remote_user)

if add:
configure_ssh_key(user, remote_node)
configure_ssh_key(remote_user, remote_node) #FIXME! Is it really a remote_user? not local?

try:
# Fetch public key file from remote_node
public_key_file_remote = fetch_public_key_from_remote_node(remote_node, user)
remote_public_key = fetch_public_key_from_remote_node(remote_node, remote_user)
except ValueError as err:
logger.warning(err)
return
# Append public key file from remote_node to local's /root/.ssh/authorized_keys
# After this, login from remote_node is passwordless
# Should do this step even passwordless is True, to make sure we got two-way passwordless
append_unique(public_key_file_remote, authorized_file, user)
append_unique(remote_public_key, local_authorized_file, local_user)


def fetch_public_key_from_remote_node(node, user="root"):
def fetch_public_key_from_remote_node(remote_node, remote_user):
"""
Fetch public key file from remote node
Return a temp file contains public key
Return None if no key exist
"""

#FIXME! This function is all mess. Refactor it.

# For dsa, might need to add PubkeyAcceptedKeyTypes=+ssh-dss to config file, see
# https://superuser.com/questions/1016989/ssh-dsa-keys-no-longer-work-for-password-less-authentication
home_dir = userdir.gethomedir(user)
#home_dir = userdir.gethomedir(remote_user) #FIXME! WRONG! it's a homedir of a local user
home_dir = '/home/' + remote_user # FIXME! HARDCODE.
for key in ("id_rsa", "id_ecdsa", "id_ed25519", "id_dsa"):
public_key_file = "{}/.ssh/{}.pub".format(home_dir, key)
cmd = "ssh {} {}@{} 'test -f {}'".format(SSH_OPTION, user, node, public_key_file)
cmd = "ssh {} {}@{} 'test -f {}'".format(SSH_OPTION, remote_user, remote_node, public_key_file)
if not invokerc(cmd):
continue
_, temp_public_key_file = tmpfiles.create()
cmd = "scp {} {}@{}:{} {}".format(SSH_OPTION, user, node, public_key_file, temp_public_key_file)
cmd = "scp {} {}@{}:{} {}".format(SSH_OPTION, remote_user, remote_node, public_key_file, temp_public_key_file)
rc, _, err = invoke(cmd)
if not rc:
utils.fatal("Failed to run \"{}\": {}".format(cmd, err))
return temp_public_key_file
raise ValueError("No ssh key exist on {}".format(node))
raise ValueError("No ssh key exist on {}".format(remote_node))


def join_csync2(seed_host):
Expand Down Expand Up @@ -1694,7 +1702,7 @@ def setup_passwordless_with_other_nodes(init_node):
# Swap ssh public key between join node and other cluster nodes
for node in cluster_nodes_list:
#FIXME! It should take the user_list from the context
swap_public_ssh_key(node, _context.current_user)
swap_public_ssh_key(_context.current_user, node, _context.current_user)


def sync_files_to_disk():
Expand Down Expand Up @@ -1745,7 +1753,7 @@ def update_nodeid(nodeid, node=None):
# mountpoints for clustered filesystems. Unfortunately we don't have
# that yet, so the following crawling horror takes a punt on the seed
# node being up, then asks it for a list of mountpoints...
remote_user = _context.current_user # TODO! Let them be different across nodes
remote_user = _context.user_list[0] # TODO! Let them be different across nodes
if _context.cluster_node:
_rc, outp, _ = utils.get_stdout_stderr("ssh {} {}@{} 'cibadmin -Q --xpath \"//primitive\"'".format(
SSH_OPTION, remote_user, seed_host))
Expand Down Expand Up @@ -2122,13 +2130,13 @@ def bootstrap_join(context):

utils.ping_node(cluster_node)

#FIXME! There should be not only current user,
# but also the one specified in -c user@node
join_ssh(_context.current_user, cluster_node)
join_ssh(cluster_node)

remote_user = _context.user_list[0] #FIXME! There are several users

n = 0
while n < REJOIN_COUNT:
if utils.service_is_active("pacemaker.service", cluster_node):
if utils.service_is_active("pacemaker.service", remote_user, cluster_node):
break
n += 1
logger.warning("Cluster is inactive on %s. Retry in %d seconds", cluster_node, REJOIN_INTERVAL)
Expand Down
Loading

0 comments on commit a1b6ef8

Please sign in to comment.