From 3f1460d8351374324df22fac5baac10d94f9650a Mon Sep 17 00:00:00 2001 From: Paul Edwards Date: Tue, 1 Mar 2022 10:01:52 +0000 Subject: [PATCH] Added option for `ssh_port` This option will set the ssh port for VMs using a Public IP and set the necessary NSG rule to allow access. --- README.md | 3 ++- pyazhpc/arm.py | 8 +++++++- pyazhpc/azhpc.py | 19 ++++++++++++++----- pyazhpc/azinstall.py | 13 ++++++++----- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f279d4bf9..40cd0f403 100644 --- a/README.md +++ b/README.md @@ -68,11 +68,12 @@ The following properties are global : | **location** | The region where the resources are created | yes | | | **resource_group** | The resource group to put the resources | yes | | | **install_from** | The resource where the install script will be run | no | | +| **ssh_port** | The port to use for SSH | no | 22 | | **admin_user** | The admin user for all resources | yes | | | **proximity_placement_group_name** | The proximity group name to create | no | | | **global_tags** | Global tags to apply to all ARM resources | no | | -The `azhpc-build` command will generate an install script from the configuration file. This will be run from the `install_from` VM. The `install_from` VM must either have a public IP address or be accessible by hostname from where `azhpc-build` is run (i.e. run `azhpc-build` from a VM on the same vnet). +The `azhpc-build` command will generate an install script from the configuration file. This will be run from the `install_from` VM. The `install_from` VM must either have a public IP address or be accessible by hostname from where `azhpc-build` is run (i.e. run `azhpc-build` from a VM on the same vnet). The `ssh_port` refers to an _additional_ port number where the ssh daemon will listen on VMs with a public IP address. The NSG rules will only be applied to the `ssh_port` number although port 22 will be accessible from within the vnet. ### Network dictionary diff --git a/pyazhpc/arm.py b/pyazhpc/arm.py index 0ad0d171e..b1b64fe6c 100644 --- a/pyazhpc/arm.py +++ b/pyazhpc/arm.py @@ -461,6 +461,7 @@ def __helper_arm_add_zones(self, res, zones): def _add_vm(self, cfg, r, vnet_in_deployment): res = cfg["resources"][r] + rsshport = cfg.get("ssh_port", 22) rtype = res["type"] rsize = res["vm_type"] rimage = res["image"] @@ -504,6 +505,11 @@ def _add_vm(self, cfg, r, vnet_in_deployment): with open(adminuser+"_id_rsa.pub") as f: sshkey = f.read().strip() + if rsshport != 22: + if customdata: + log.error("Cannot specify custom data with a non-standard SSH port for VMs with a public IP.") + customdata = f"#!/bin/bash\nsed -i \"s/^#Port 22/Port 22\\nPort {rsshport}/\" /etc/ssh/sshd_config\nsemanage port -a -t ssh_port_t -p tcp {rsshport}\nsystemctl restart sshd\n" + if ravset and ravset not in self.avsets: arm_avset = { "name": ravset, @@ -590,7 +596,7 @@ def _add_vm(self, cfg, r, vnet_in_deployment): "properties": { "protocol": "Tcp", "sourcePortRange": "*", - "destinationPortRange": "22", + "destinationPortRange": str(rsshport), "sourceAddressPrefix": "*", "destinationAddressPrefix": "*", "access": "Allow", diff --git a/pyazhpc/azhpc.py b/pyazhpc/azhpc.py index d3ae15997..9432ae2d6 100644 --- a/pyazhpc/azhpc.py +++ b/pyazhpc/azhpc.py @@ -167,6 +167,8 @@ def do_scp(args): sshkey="{}_id_rsa".format(adminuser) # TODO: check ssh key exists + sshport = c.read_value("ssh_port", 22) + fqdn = c.get_install_from_destination() if not fqdn: log.error(f"Missing 'install_from' property") @@ -183,7 +185,7 @@ def do_scp(args): "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-i", sshkey, - "-o", f"ProxyCommand=ssh -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i {sshkey} -W %h:%p {adminuser}@{fqdn}" + "-o", f"ProxyCommand=ssh -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p {sshport} -i {sshkey} -W %h:%p {adminuser}@{fqdn}" ] + scp_args log.debug(" ".join([ f"'{a}'" for a in scp_cmd ])) os.execvp(scp_exe, scp_cmd) @@ -201,6 +203,8 @@ def do_connect(args): sshuser = adminuser else: sshuser = args.user + + sshport = c.read_value("ssh_port", 22) jumpbox = c.read_value("install_from") if not jumpbox: @@ -277,6 +281,7 @@ def do_connect(args): "ssh", "-t", "-q", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", + "-p", str(sshport), "-i", ssh_private_key, f"{sshuser}@{fqdn}" ] @@ -289,18 +294,19 @@ def do_connect(args): "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-i", ssh_private_key, - "-o", f"ProxyCommand=ssh -i {ssh_private_key} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -W %h:%p {sshuser}@{fqdn}", + "-o", f"ProxyCommand=ssh -p {sshport} -i {ssh_private_key} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -W %h:%p {sshuser}@{fqdn}", f"{sshuser}@{target}" ] log.debug(" ".join(ssh_args + cmdline)) os.execvp(ssh_exe, ssh_args + cmdline) -def _exec_command(fqdn, sshuser, sshkey, cmdline): +def _exec_command(fqdn, sshuser, sshport, sshkey, cmdline): ssh_exe = "ssh" ssh_args = [ ssh_exe, "-q", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", + "-p", str(sshport), "-i", sshkey, f"{sshuser}@{fqdn}" ] @@ -314,6 +320,7 @@ def do_status(args): adminuser = c.read_value("admin_user") ssh_private_key="{}_id_rsa".format(adminuser) + sshport = c.read_value("ssh_port", 22) fqdn = c.get_install_from_destination() if not fqdn: @@ -321,7 +328,7 @@ def do_status(args): sys.exit(1) tmpdir = "azhpc_install_" + os.path.basename(args.config_file).strip(".json") - _exec_command(fqdn, adminuser, ssh_private_key, f"pssh -h {tmpdir}/hostlists/linux -i -t 0 'printf \"%-20s%s\n\" \"$(hostname)\" \"$(uptime)\"' | grep -v SUCCESS") + _exec_command(fqdn, adminuser, sshport, ssh_private_key, f"pssh -h {tmpdir}/hostlists/linux -i -t 0 'printf \"%-20s%s\n\" \"$(hostname)\" \"$(uptime)\"' | grep -v SUCCESS") def do_run(args): @@ -338,6 +345,8 @@ def do_run(args): else: sshuser = args.user + sshport = c.read_value("ssh_port", 22) + jumpbox = c.read_value("install_from") if not jumpbox: log.error(f"Missing 'install_from' property") @@ -368,7 +377,7 @@ def do_run(args): hostlist = " ".join(hosts) cmd = " ".join(args.args) - _exec_command(fqdn, sshuser, ssh_private_key, f"pssh -H '{hostlist}' -i -t 0 '{cmd}'") + _exec_command(fqdn, sshuser, sshport, ssh_private_key, f"pssh -H '{hostlist}' -i -t 0 '{cmd}'") def _create_private_key(private_key_file, public_key_file): if not (os.path.exists(private_key_file) and os.path.exists(public_key_file)): diff --git a/pyazhpc/azinstall.py b/pyazhpc/azinstall.py index 70c14e63e..13e86ccb1 100644 --- a/pyazhpc/azinstall.py +++ b/pyazhpc/azinstall.py @@ -413,10 +413,10 @@ def generate_cc_clusters(config, tmpdir): f.write(json.dumps(cluster_params, indent=4)) __cyclecloud_create_cluster(cluster_template, cluster_name, cluster_json) -def __rsync(sshkey, src, dst): +def __rsync(sshkey, sshport, src, dst): cmd = [ "rsync", "-a", "--timeout=60", "-e", - f"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i {sshkey}", + f"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p {sshport} -i {sshkey}", src, dst ] res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -426,6 +426,7 @@ def __rsync(sshkey, src, dst): def run(cfg, tmpdir, adminuser, sshprivkey, sshpubkey, fqdn, startstep=0): jb = cfg.get("install_from") + sshport = cfg.get("ssh_port") install_steps = [{ "script": "install_node_setup.sh" }] + cfg.get("install", []) if jb: log.debug("wait for ssh on jumpbox") @@ -435,6 +436,7 @@ def run(cfg, tmpdir, adminuser, sshprivkey, sshpubkey, fqdn, startstep=0): "ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", + "-p", str(sshport), "-i", sshprivkey, f"{adminuser}@{fqdn}", "hostname" @@ -451,7 +453,7 @@ def run(cfg, tmpdir, adminuser, sshprivkey, sshpubkey, fqdn, startstep=0): time.sleep(10) log.debug("rsyncing install files") - __rsync(sshprivkey, tmpdir, f"{adminuser}@{fqdn}:.") + __rsync(sshprivkey, sshport, tmpdir, f"{adminuser}@{fqdn}:.") for idx, step in enumerate(install_steps): if idx == 0 and not jb: @@ -477,13 +479,14 @@ def run(cfg, tmpdir, adminuser, sshprivkey, sshpubkey, fqdn, startstep=0): "ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", + "-p", str(sshport), "-i", sshprivkey, f"{adminuser}@{fqdn}" ] + instcmd res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if res.returncode != 0: log.error("invalid returncode"+_make_subprocess_error_string(res)) - __rsync(sshprivkey, f"{adminuser}@{fqdn}:{tmpdir}/install/*.log", f"{tmpdir}/install/.") + __rsync(sshprivkey, sshport, f"{adminuser}@{fqdn}:{tmpdir}/install/*.log", f"{tmpdir}/install/.") sys.exit(1) else: log.warning("skipping step as no jumpbox (install_from) is set") @@ -502,5 +505,5 @@ def run(cfg, tmpdir, adminuser, sshprivkey, sshpubkey, fqdn, startstep=0): if jb: log.debug("rsyncing log files back") - __rsync(sshprivkey, f"{adminuser}@{fqdn}:{tmpdir}/install/*.log", f"{tmpdir}/install/.") + __rsync(sshprivkey, sshport, f"{adminuser}@{fqdn}:{tmpdir}/install/*.log", f"{tmpdir}/install/.")