diff --git a/kconfigs/Kconfig.ansible_cfg b/kconfigs/Kconfig.ansible_cfg index c04532e81..e3fd02f18 100644 --- a/kconfigs/Kconfig.ansible_cfg +++ b/kconfigs/Kconfig.ansible_cfg @@ -316,6 +316,47 @@ config ANSIBLE_CFG_INVENTORY endif # ANSIBLE_CFG_INVENTORY_CUSTOM +config ANSIBLE_CFG_SSH_PORT_SET_BY_CLI + bool + default $(shell, scripts/check-cli-set-var.sh ANSIBLE_CFG_SSH_PORT) + +config ANSIBLE_CFG_SSH_PORT_CUSTOM + bool "Enable a custom Ansible SSH port setting" + default n + help + When this setting is enabled, specify the SSH port for + Ansible to use when connecting to target nodes. + + When this setting is disabled, kdevops uses the default + SSH port (22), which can be overridden with + "ANSIBLE_CFG_SSH_PORT=NN" on the "make" command line. + + This is useful when your target hosts use a non-standard + SSH port for security or network configuration reasons. + +if ANSIBLE_CFG_SSH_PORT_CUSTOM + +config ANSIBLE_CFG_SSH_PORT + int "Ansible SSH port" + output yaml + help + Set the SSH port for Ansible to use when connecting to target + nodes. The default port is 22. + + https://docs.ansible.com/ansible/latest/collections/ansible/builtin/ssh_connection.html#parameter-remote_port + +endif # ANSIBLE_CFG_SSH_PORT_CUSTOM + +if !ANSIBLE_CFG_SSH_PORT_CUSTOM + +config ANSIBLE_CFG_SSH_PORT + int + output yaml + default 22 if !ANSIBLE_CFG_SSH_PORT_SET_BY_CLI + default $(shell, ./scripts/append-makefile-vars-int.sh $(ANSIBLE_CFG_SSH_PORT)) if ANSIBLE_CFG_SSH_PORT_SET_BY_CLI + +endif # !ANSIBLE_CFG_SSH_PORT_CUSTOM + if DISTRO_OPENSUSE config ANSIBLE_CFG_RECONNECTION_RETRIES diff --git a/playbooks/nixos.yml b/playbooks/nixos.yml index bdc9b1e85..262855202 100644 --- a/playbooks/nixos.yml +++ b/playbooks/nixos.yml @@ -430,7 +430,7 @@ python3 {{ playbook_dir }}/../scripts/update_ssh_config_nixos.py update \ {{ item }} \ {{ nixos_vm_ips[item] }} \ - 22 \ + {{ ansible_cfg_ssh_port }} \ kdevops \ {{ nixos_ssh_config_file | default(ansible_env.HOME + '/.ssh/config') }} \ {{ ssh_key_path_for_config.stdout | trim }} \ diff --git a/playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 b/playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 index f3f6c723f..deb1a559d 100644 --- a/playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 +++ b/playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 @@ -47,7 +47,10 @@ playbook_on_stats_msg_color = bright green [callback_profile_tasks] summary_only = true {% endif %} +[ssh_connection] +remote_port = {{ ansible_cfg_ssh_port }} {% if ansible_facts['distribution'] == 'openSUSE' %} + [connection] retries = {{ ansible_cfg_reconnection_retries }} {% endif %} diff --git a/playbooks/roles/base_image/templates/virt-builder.j2 b/playbooks/roles/base_image/templates/virt-builder.j2 index 277c94141..608dc31f7 100644 --- a/playbooks/roles/base_image/templates/virt-builder.j2 +++ b/playbooks/roles/base_image/templates/virt-builder.j2 @@ -12,7 +12,7 @@ mkdir {{ target_dir }} copy-in {{ guestfs_distro_source_and_dest_file }}:{{ target_dir }} {% endif %} -install sudo,qemu-guest-agent,python3,bash +install sudo,qemu-guest-agent,python3,bash,policycoreutils-python-utils # get rid of any rescue initramfs images, and prevent new ones from being generated uninstall dracut-config-rescue diff --git a/playbooks/roles/gen_tfvars/templates/aws/terraform.tfvars.j2 b/playbooks/roles/gen_tfvars/templates/aws/terraform.tfvars.j2 index 4b20667f0..fc9c94441 100644 --- a/playbooks/roles/gen_tfvars/templates/aws/terraform.tfvars.j2 +++ b/playbooks/roles/gen_tfvars/templates/aws/terraform.tfvars.j2 @@ -18,6 +18,7 @@ aws_ebs_volume_throughput = {{ terraform_aws_ebs_volume_throughput }} ssh_config_pubkey_file = "{{ kdevops_terraform_ssh_config_pubkey_file }}" ssh_config_user = "{{ kdevops_terraform_ssh_config_user }}" ssh_config = "{{ sshconfig }}" +ssh_config_port = {{ ansible_cfg_ssh_port }} ssh_config_update = "{{ kdevops_terraform_ssh_config_update | lower }}" ssh_config_use_strict_settings = "{{ kdevops_terraform_ssh_config_update_strict | lower }}" diff --git a/playbooks/roles/gen_tfvars/templates/azure/terraform.tfvars.j2 b/playbooks/roles/gen_tfvars/templates/azure/terraform.tfvars.j2 index 7ce0f6170..9c3ac0a0f 100644 --- a/playbooks/roles/gen_tfvars/templates/azure/terraform.tfvars.j2 +++ b/playbooks/roles/gen_tfvars/templates/azure/terraform.tfvars.j2 @@ -13,6 +13,7 @@ azure_managed_disks_tier = "{{ terraform_azure_managed_disks_tier }}" ssh_config_pubkey_file = "{{ kdevops_terraform_ssh_config_pubkey_file }}" ssh_config_user = "{{ kdevops_terraform_ssh_config_user }}" ssh_config = "{{ sshconfig }}" +ssh_config_port = {{ ansible_cfg_ssh_port }} ssh_config_update = "{{ kdevops_terraform_ssh_config_update | lower }}" ssh_config_use_strict_settings = "{{ kdevops_terraform_ssh_config_update_strict | lower }}" diff --git a/playbooks/roles/gen_tfvars/templates/gce/terraform.tfvars.j2 b/playbooks/roles/gen_tfvars/templates/gce/terraform.tfvars.j2 index c6093aeff..950e12b78 100644 --- a/playbooks/roles/gen_tfvars/templates/gce/terraform.tfvars.j2 +++ b/playbooks/roles/gen_tfvars/templates/gce/terraform.tfvars.j2 @@ -20,6 +20,7 @@ gce_disk_throughput = {{ terraform_gce_disk_throughput }} ssh_config_pubkey_file = "{{ kdevops_terraform_ssh_config_pubkey_file }}" ssh_config_user = "{{ kdevops_terraform_ssh_config_user }}" ssh_config = "{{ sshconfig }}" +ssh_config_port = {{ ansible_cfg_ssh_port }} ssh_config_update = "{{ kdevops_terraform_ssh_config_update | lower }}" ssh_config_use_strict_settings = "{{ kdevops_terraform_ssh_config_update_strict | lower }}" diff --git a/playbooks/roles/gen_tfvars/templates/lambdalabs/terraform.tfvars.j2 b/playbooks/roles/gen_tfvars/templates/lambdalabs/terraform.tfvars.j2 index 4fd8cad63..a4ba26fc1 100644 --- a/playbooks/roles/gen_tfvars/templates/lambdalabs/terraform.tfvars.j2 +++ b/playbooks/roles/gen_tfvars/templates/lambdalabs/terraform.tfvars.j2 @@ -7,6 +7,7 @@ ssh_config_pubkey_file = "{{ kdevops_terraform_ssh_config_pubkey_file }}" ssh_config_privkey_file = "{{ kdevops_terraform_ssh_config_privkey_file }}" ssh_config_user = "{{ kdevops_terraform_ssh_config_user }}" ssh_config = "{{ sshconfig }}" +ssh_config_port = {{ ansible_cfg_ssh_port }} # Use unique SSH config file per directory to avoid conflicts ssh_config_name = "{{ kdevops_ssh_config_prefix }}{{ topdir_path_sha256sum[:8] }}" diff --git a/playbooks/roles/gen_tfvars/templates/oci/terraform.tfvars.j2 b/playbooks/roles/gen_tfvars/templates/oci/terraform.tfvars.j2 index 0839bfacf..5f3ceed19 100644 --- a/playbooks/roles/gen_tfvars/templates/oci/terraform.tfvars.j2 +++ b/playbooks/roles/gen_tfvars/templates/oci/terraform.tfvars.j2 @@ -25,6 +25,7 @@ oci_sparse_volume_device_file_name = "{{ terraform_oci_sparse_volume_device_file ssh_config_pubkey_file = "{{ kdevops_terraform_ssh_config_pubkey_file }}" ssh_config_user = "{{ kdevops_terraform_ssh_config_user }}" ssh_config = "{{ sshconfig }}" +ssh_config_port = {{ ansible_cfg_ssh_port }} ssh_config_update = "{{ kdevops_terraform_ssh_config_update | lower }}" ssh_config_use_strict_settings = "{{ kdevops_terraform_ssh_config_update_strict | lower }}" diff --git a/playbooks/roles/gen_tfvars/templates/openstack/terraform.tfvars.j2 b/playbooks/roles/gen_tfvars/templates/openstack/terraform.tfvars.j2 index 3df0e3a4d..a50468072 100644 --- a/playbooks/roles/gen_tfvars/templates/openstack/terraform.tfvars.j2 +++ b/playbooks/roles/gen_tfvars/templates/openstack/terraform.tfvars.j2 @@ -7,6 +7,7 @@ ssh_pubkey_name = "{{ terraform_openstack_ssh_pubkey_name }}" ssh_config_pubkey_file = "{{ kdevops_terraform_ssh_config_pubkey_file }}" ssh_config_user = "{{ kdevops_terraform_ssh_config_user }}" ssh_config = "{{ sshconfig }}" +ssh_config_port = {{ ansible_cfg_ssh_port }} ssh_config_update = "{{ kdevops_terraform_ssh_config_update | lower }}" ssh_config_use_strict_settings = "{{ kdevops_terraform_ssh_config_update_strict | lower }}" diff --git a/playbooks/roles/guestfs/tasks/bringup/main.yml b/playbooks/roles/guestfs/tasks/bringup/main.yml index e5fcbb2e1..81bac7ceb 100644 --- a/playbooks/roles/guestfs/tasks/bringup/main.yml +++ b/playbooks/roles/guestfs/tasks/bringup/main.yml @@ -61,35 +61,39 @@ register: host_timezone delegate_to: localhost + - name: Build virt-sysprep command arguments for each target node + ansible.builtin.set_fact: + virt_sysprep_args: >- + {{ + [ + "virt-sysprep", + "-a", root_image, + "--hostname", inventory_hostname, + "--ssh-inject", "kdevops:file:" + ssh_key + ".pub", + "--timezone", host_timezone.stdout + ] + ( + [ + "--run-command", "sed -i '/^#*Port /d' /etc/ssh/sshd_config", + "--append-line", "/etc/ssh/sshd_config:Port " + (ansible_cfg_ssh_port | string), + "--firstboot-command", "semanage port -a -t ssh_port_t -p tcp " + (ansible_cfg_ssh_port | string) + " 2>/dev/null || semanage port -m -t ssh_port_t -p tcp " + (ansible_cfg_ssh_port | string) + "; systemctl restart sshd", + "--firstboot-command", "if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-enabled firewalld >/dev/null 2>&1; then firewall-cmd --permanent --add-port=" + (ansible_cfg_ssh_port | string) + "/tcp && firewall-cmd --reload; fi", + "--firstboot-command", "if command -v ufw >/dev/null 2>&1 && systemctl is-active ufw >/dev/null 2>&1; then ufw allow " + (ansible_cfg_ssh_port | string) + "/tcp; fi" + ] + if ansible_cfg_ssh_port | int != 22 else [] + ) + }} + - name: Build the root image for each target node (as root) become: true become_method: ansible.builtin.sudo ansible.builtin.command: - argv: - - "virt-sysprep" - - "-a" - - "{{ root_image }}" - - "--hostname" - - "{{ inventory_hostname }}" - - "--ssh-inject" - - "kdevops:file:{{ ssh_key }}.pub" - - "--timezone" - - "{{ host_timezone.stdout }}" + argv: "{{ virt_sysprep_args }}" when: - libvirt_uri_system|bool - name: Build the root image for each target node (non-root) ansible.builtin.command: - argv: - - "virt-sysprep" - - "-a" - - "{{ root_image }}" - - "--hostname" - - "{{ inventory_hostname }}" - - "--ssh-inject" - - "kdevops:file:{{ ssh_key }}.pub" - - "--timezone" - - "{{ host_timezone.stdout }}" + argv: "{{ virt_sysprep_args }}" when: - not libvirt_uri_system|bool diff --git a/playbooks/roles/terraform/templates/ssh_config.j2 b/playbooks/roles/terraform/templates/ssh_config.j2 index 5e8adf025..ba62a2209 100644 --- a/playbooks/roles/terraform/templates/ssh_config.j2 +++ b/playbooks/roles/terraform/templates/ssh_config.j2 @@ -1,7 +1,7 @@ Host {{ item.key }} {{ item.value }} HostName {{ item.value }} User {{ kdevops_terraform_ssh_config_user }} - Port 22 + Port {{ ansible_cfg_ssh_port }} IdentityFile {{ kdevops_terraform_ssh_config_privkey_file }} {% if ssh_config_kexalgorithms %} KexAlgorithms {{ ssh_config_kexalgorithms }} diff --git a/scripts/update_ssh_config_guestfs.py b/scripts/update_ssh_config_guestfs.py index 143ff4fc2..40f1ccad0 100755 --- a/scripts/update_ssh_config_guestfs.py +++ b/scripts/update_ssh_config_guestfs.py @@ -21,7 +21,7 @@ ssh_template = """Host {name} {addr} HostName {addr} User kdevops - Port 22 + Port {port} IdentityFile {sshkey} UserKnownHostsFile /dev/null StrictHostKeyChecking no @@ -97,6 +97,7 @@ def main(): context = { "name": name, "addr": addr, + "port": extra_vars.get("ansible_cfg_ssh_port", 22), "sshkey": f"{extra_vars['guestfs_path']}/{name}/ssh/id_ed25519", } sshconf.write(ssh_template.format(**context)) diff --git a/scripts/update_ssh_config_lambdalabs.py b/scripts/update_ssh_config_lambdalabs.py index 5b9ab0aa8..265f85c23 100755 --- a/scripts/update_ssh_config_lambdalabs.py +++ b/scripts/update_ssh_config_lambdalabs.py @@ -11,7 +11,7 @@ def update_ssh_config( - action, hostname, ip_address, username, config_file, ssh_key, provider_name + action, hostname, ip_address, username, config_file, ssh_key, provider_name, port=22 ): """ Update SSH configuration file with Lambda Labs instance details. @@ -24,6 +24,7 @@ def update_ssh_config( config_file: SSH config file path ssh_key: Path to SSH private key provider_name: Provider name for comments + port: SSH port number (default: 22) """ config_file = os.path.expanduser(config_file) ssh_key = os.path.expanduser(ssh_key) @@ -33,7 +34,7 @@ def update_ssh_config( Host {hostname} {ip_address} \tHostName {ip_address} \tUser {username} -\tPort 22 +\tPort {port} \tIdentityFile {ssh_key} \tUserKnownHostsFile /dev/null \tStrictHostKeyChecking no @@ -90,7 +91,7 @@ def main(): """Main entry point.""" if len(sys.argv) < 7: print( - f"Usage: {sys.argv[0]} [provider_name]" + f"Usage: {sys.argv[0]} [provider_name] [port]" ) print(" action: 'update' or 'remove'") print(" hostname: Instance hostname") @@ -99,6 +100,7 @@ def main(): print(" config_file: SSH config file path") print(" ssh_key: Path to SSH private key") print(" provider_name: Optional provider name (default: 'Lambda Labs')") + print(" port: Optional SSH port (default: 22)") sys.exit(1) action = sys.argv[1] @@ -108,9 +110,17 @@ def main(): config_file = sys.argv[5] ssh_key = sys.argv[6] provider_name = sys.argv[7] if len(sys.argv) > 7 else "Lambda Labs" + port = int(sys.argv[8]) if len(sys.argv) > 8 else 22 update_ssh_config( - action, hostname, ip_address, username, config_file, ssh_key, provider_name + action, + hostname, + ip_address, + username, + config_file, + ssh_key, + provider_name, + port, ) diff --git a/terraform/aws/main.tf b/terraform/aws/main.tf index ac6c6846f..0de2e5371 100644 --- a/terraform/aws/main.tf +++ b/terraform/aws/main.tf @@ -39,8 +39,8 @@ resource "aws_security_group" "kdevops_sec_group" { cidr_blocks = [ "0.0.0.0/0", ] - from_port = 22 - to_port = 22 + from_port = var.ssh_config_port + to_port = var.ssh_config_port protocol = "tcp" } @@ -76,12 +76,13 @@ resource "aws_key_pair" "kdevops_keypair" { data "template_file" "script_user_data" { count = local.kdevops_num_boxes - template = file("templates/script.sh") + template = file("../scripts/cloud-init.sh") vars = { user_data_log_dir = var.user_data_log_dir user_data_enabled = var.user_data_enabled ssh_config_user = var.ssh_config_user + ssh_config_port = var.ssh_config_port new_hostname = element(var.kdevops_nodes, count.index), } } diff --git a/terraform/aws/templates/script.sh b/terraform/aws/templates/script.sh deleted file mode 100755 index 926afe99f..000000000 --- a/terraform/aws/templates/script.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash -# oscheck kdevops cloud-init script. -# -# This script accepts the following variables set and passed: -# -# user_data_log_dir -# user_data_enabled -# -# new_hostname -# -# Note: terraform passed variables must be in the form: dollar{variable}, -# where dollar is $ and provides an implicit restriction on bash variables to -# not use this same form for bash variables *not* coming from terraform. This -# restriction applies even to bash comments such as this one. Terraform -# processes these variables prior to giving the file to the host. We cannot -# use this form of variables for user_data scripts then. Or another way to -# say it, terraform implicates restrictions on user_data bash scripts to -# only one way to use bash variables because of how it processes its own -# variables. -# -# Note 2: "let" returns non-zero if the argument passed is 0, use "let" to -# increment variables with care in your bash scripts if using "set -e" - -ADMIN_LOG="${user_data_log_dir}/admin.txt" -USERDATA_ENABLED="${user_data_enabled}" - -NEW_HOSTNAME="${new_hostname}" - -set -e - -run_cmd_admin() -{ - if $@ ; then - DATE=$(date) - echo "$DATE --- $@" >> $ADMIN_LOG - return 0 - else - DATE=$(date) - echo "$DATE --- Return value: $? --- Command failed: $@ --- " >> $ADMIN_LOG - return 1 - fi -} - -mkdir -p ${user_data_log_dir} - -if [ "$USERDATA_ENABLED" != "yes" ]; then - run_cmd_admin echo "cloud-init: kdevops script user data processing disabled" - exit 0 -fi - -run_cmd_admin echo "cloud-init: kdevops script user data processing enabled" -run_cmd_admin echo "Nothing to do..." - -# Add more functionality below if you see fit. Be sure to use a variable -# to allow to easily enable / disable each mechanism. diff --git a/terraform/azure/main.tf b/terraform/azure/main.tf index 8dcead78b..eb609933f 100644 --- a/terraform/azure/main.tf +++ b/terraform/azure/main.tf @@ -43,7 +43,7 @@ resource "azurerm_network_security_group" "kdevops_sg" { access = "Allow" protocol = "Tcp" source_port_range = "*" - destination_port_range = "22" + destination_port_range = tostring(var.ssh_config_port) source_address_prefix = "*" destination_address_prefix = "*" } @@ -89,6 +89,13 @@ resource "azurerm_linux_virtual_machine" "kdevops_vm" { size = var.azure_vmsize admin_username = var.ssh_config_user disable_password_authentication = true + custom_data = base64encode(templatefile("${path.module}/../scripts/cloud-init.sh", { + user_data_log_dir = "/var/log/kdevops" + user_data_enabled = "yes" + ssh_config_user = var.ssh_config_user + ssh_config_port = var.ssh_config_port + new_hostname = element(var.kdevops_nodes, count.index) + })) os_disk { # Note: yes using the names like the ones below is better however it also diff --git a/terraform/gce/main.tf b/terraform/gce/main.tf index 816f43098..254ecb6a6 100644 --- a/terraform/gce/main.tf +++ b/terraform/gce/main.tf @@ -3,6 +3,19 @@ data "google_compute_image" "kdevops_image" { family = var.gce_image_family } +resource "google_compute_firewall" "kdevops_ssh" { + name = "kdevops-allow-ssh" + network = "default" + + allow { + protocol = "tcp" + ports = [tostring(var.ssh_config_port)] + } + + source_ranges = ["0.0.0.0/0"] + target_tags = ["kdevops-ssh"] +} + resource "google_compute_instance" "kdevops_instance" { count = local.kdevops_num_boxes name = element(var.kdevops_nodes, count.index) @@ -33,7 +46,15 @@ resource "google_compute_instance" "kdevops_instance" { ssh-keys = format("%s:%s", var.ssh_config_user, file(var.ssh_config_pubkey_file)) } - metadata_startup_script = "echo hi > /test.txt" + metadata_startup_script = templatefile("${path.module}/../scripts/cloud-init.sh", { + user_data_log_dir = "/var/log/kdevops" + user_data_enabled = "yes" + ssh_config_user = var.ssh_config_user + ssh_config_port = var.ssh_config_port + new_hostname = element(var.kdevops_nodes, count.index) + }) + + tags = ["kdevops-ssh"] } module "kdevops_compute_disks" { diff --git a/terraform/lambdalabs/main.tf b/terraform/lambdalabs/main.tf index a78866c7c..1d736f0c5 100644 --- a/terraform/lambdalabs/main.tf +++ b/terraform/lambdalabs/main.tf @@ -88,7 +88,7 @@ resource "null_resource" "ansible_update_ssh_config_hosts" { for_each = var.ssh_config_update ? toset(var.kdevops_nodes) : [] provisioner "local-exec" { - command = "python3 ${path.module}/../../scripts/update_ssh_config_lambdalabs.py update ${each.key} ${lambdalabs_instance.kdevops[each.key].ip} ${local.ssh_user} ${var.ssh_config_name} ${var.ssh_config_privkey_file} 'Lambda Labs'" + command = "python3 ${path.module}/../../scripts/update_ssh_config_lambdalabs.py update ${each.key} ${lambdalabs_instance.kdevops[each.key].ip} ${local.ssh_user} ${var.ssh_config_name} ${var.ssh_config_privkey_file} 'Lambda Labs' ${var.ssh_config_port}" } triggers = { @@ -113,6 +113,43 @@ resource "null_resource" "remove_ssh_config" { } } +# Configure SSH port if not using default port 22 +resource "null_resource" "configure_ssh_port" { + for_each = var.ssh_config_port != 22 ? toset(var.kdevops_nodes) : [] + + connection { + type = "ssh" + host = lambdalabs_instance.kdevops[each.key].ip + user = local.ssh_user + port = 22 + private_key = file(pathexpand(var.ssh_config_privkey_file)) + } + + provisioner "remote-exec" { + inline = [ + "echo 'Waiting for system to be ready...'", + "sudo cloud-init status --wait || true", + "echo 'Configuring SSH to listen on port ${var.ssh_config_port}'", + "sudo sed -i '/^[#[:space:]]*Port/d' /etc/ssh/sshd_config", + "echo 'Port ${var.ssh_config_port}' | sudo tee -a /etc/ssh/sshd_config", + "if [ -d /etc/selinux ] && sudo sestatus 2>/dev/null | grep -q 'SELinux status.*enabled'; then if ! command -v semanage >/dev/null 2>&1; then sudo yum install -y policycoreutils-python-utils 2>&1 || sudo dnf install -y policycoreutils-python-utils 2>&1 || true; fi; if command -v semanage >/dev/null 2>&1; then sudo semanage port -a -t ssh_port_t -p tcp ${var.ssh_config_port} 2>&1 || sudo semanage port -m -t ssh_port_t -p tcp ${var.ssh_config_port} 2>&1 || true; fi; fi", + "if command -v firewall-cmd >/dev/null 2>&1 && sudo systemctl is-enabled firewalld >/dev/null 2>&1; then sudo firewall-cmd --permanent --add-port=${var.ssh_config_port}/tcp && sudo firewall-cmd --reload; fi", + "if command -v ufw >/dev/null 2>&1 && sudo systemctl is-active ufw >/dev/null 2>&1; then sudo ufw allow ${var.ssh_config_port}/tcp; fi", + "sudo systemctl restart sshd", + "echo 'SSH port configuration completed'" + ] + } + + depends_on = [ + lambdalabs_instance.kdevops, + null_resource.ansible_update_ssh_config_hosts + ] + + triggers = { + instance_id = lambdalabs_instance.kdevops[each.key].id + } +} + # Ansible provisioning resource "null_resource" "ansible_provision" { for_each = toset(var.kdevops_nodes) @@ -121,6 +158,7 @@ resource "null_resource" "ansible_provision" { type = "ssh" host = lambdalabs_instance.kdevops[each.key].ip user = local.ssh_user + port = var.ssh_config_port private_key = file(pathexpand(var.ssh_config_privkey_file)) } @@ -145,7 +183,8 @@ resource "null_resource" "ansible_provision" { depends_on = [ lambdalabs_instance.kdevops, - null_resource.ansible_update_ssh_config_hosts + null_resource.ansible_update_ssh_config_hosts, + null_resource.configure_ssh_port ] triggers = { diff --git a/terraform/oci/main.tf b/terraform/oci/main.tf index 15660aa02..399a05621 100644 --- a/terraform/oci/main.tf +++ b/terraform/oci/main.tf @@ -35,6 +35,13 @@ resource "oci_core_instance" "kdevops_instance" { metadata = { ssh_authorized_keys = file(var.ssh_config_pubkey_file) + user_data = base64encode(templatefile("${path.module}/../scripts/cloud-init.sh", { + user_data_log_dir = "/var/log/kdevops" + user_data_enabled = "yes" + ssh_config_user = var.ssh_config_user + ssh_config_port = var.ssh_config_port + new_hostname = element(var.kdevops_nodes, count.index) + })) } preemptible_instance_config { @@ -155,8 +162,8 @@ resource "oci_core_security_list" "kdevops_security_list" { source_type = "CIDR_BLOCK" stateless = false tcp_options { - min = 22 - max = 22 + min = var.ssh_config_port + max = var.ssh_config_port } } ingress_security_rules { diff --git a/terraform/openstack/main.tf b/terraform/openstack/main.tf index 6e31e2f07..c9037ca73 100644 --- a/terraform/openstack/main.tf +++ b/terraform/openstack/main.tf @@ -19,8 +19,8 @@ resource "openstack_compute_secgroup_v2" "kdevops_security_group" { # SSH rule { - from_port = 22 - to_port = 22 + from_port = var.ssh_config_port + to_port = var.ssh_config_port ip_protocol = "tcp" cidr = "0.0.0.0/0" } @@ -62,6 +62,13 @@ resource "openstack_compute_instance_v2" "kdevops_instances" { flavor_name = var.flavor_name key_pair = var.ssh_pubkey_name security_groups = [openstack_compute_secgroup_v2.kdevops_security_group.name] + user_data = templatefile("${path.module}/../scripts/cloud-init.sh", { + user_data_log_dir = "/var/log/kdevops" + user_data_enabled = "yes" + ssh_config_user = var.ssh_config_user + ssh_config_port = var.ssh_config_port + new_hostname = element(var.kdevops_nodes, count.index) + }) network { name = var.public_network_name } diff --git a/terraform/scripts/cloud-init.sh b/terraform/scripts/cloud-init.sh new file mode 100755 index 000000000..86c8a67ec --- /dev/null +++ b/terraform/scripts/cloud-init.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# oscheck kdevops cloud-init script. +# +# This script accepts the following variables set and passed: +# +# user_data_log_dir +# user_data_enabled +# +# new_hostname +# +# Note: terraform passed variables must be in the form: dollar{variable}, +# where dollar is $ and provides an implicit restriction on bash variables to +# not use this same form for bash variables *not* coming from terraform. This +# restriction applies even to bash comments such as this one. Terraform +# processes these variables prior to giving the file to the host. We cannot +# use this form of variables for user_data scripts then. Or another way to +# say it, terraform implicates restrictions on user_data bash scripts to +# only one way to use bash variables because of how it processes its own +# variables. +# +# Note 2: "let" returns non-zero if the argument passed is 0, use "let" to +# increment variables with care in your bash scripts if using "set -e" + +ADMIN_LOG="${user_data_log_dir}/admin.txt" +USERDATA_ENABLED="${user_data_enabled}" + +NEW_HOSTNAME="${new_hostname}" + +set -e + +run_cmd_admin() +{ + if $@ ; then + DATE=$(date) + echo "$DATE --- $@" >> $ADMIN_LOG + return 0 + else + DATE=$(date) + echo "$DATE --- Return value: $? --- Command failed: $@ --- " >> $ADMIN_LOG + return 1 + fi +} + +mkdir -p ${user_data_log_dir} + +if [ "$USERDATA_ENABLED" != "yes" ]; then + run_cmd_admin echo "cloud-init: kdevops script user data processing disabled" + exit 0 +fi + +run_cmd_admin echo "cloud-init: kdevops script user data processing enabled" + +# Configure SSH port if not using default port 22 +SSH_PORT="${ssh_config_port}" +if [ "$SSH_PORT" != "22" ]; then + run_cmd_admin echo "Configuring SSH to listen on port $SSH_PORT" + + # Update sshd_config to use alternate port + run_cmd_admin sed -i '/^[#[:space:]]*Port/d' /etc/ssh/sshd_config + echo "Port $SSH_PORT" | run_cmd_admin tee -a /etc/ssh/sshd_config > /dev/null + + # Configure SELinux if present + if [ -d /etc/selinux ] && sestatus 2>/dev/null | grep -q "SELinux status.*enabled"; then + # Install semanage if not available (RHEL/CentOS/Rocky/AlmaLinux) + if ! command -v semanage >/dev/null 2>&1; then + run_cmd_admin yum install -y policycoreutils-python-utils 2>&1 || run_cmd_admin dnf install -y policycoreutils-python-utils 2>&1 || true + fi + + # Try to add the port first, if it fails (already exists), modify it + if command -v semanage >/dev/null 2>&1; then + run_cmd_admin semanage port -a -t ssh_port_t -p tcp $SSH_PORT 2>&1 || run_cmd_admin semanage port -m -t ssh_port_t -p tcp $SSH_PORT 2>&1 || true + run_cmd_admin echo "SELinux port configuration completed" + else + run_cmd_admin echo "WARNING: semanage not available, SELinux may block port $SSH_PORT" + fi + fi + + # Configure firewalld if present and enabled + if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-enabled firewalld >/dev/null 2>&1; then + run_cmd_admin firewall-cmd --permanent --add-port=$SSH_PORT/tcp + run_cmd_admin firewall-cmd --reload + fi + + # Configure ufw if present and active + if command -v ufw >/dev/null 2>&1 && systemctl is-active ufw >/dev/null 2>&1; then + run_cmd_admin ufw allow $SSH_PORT/tcp + fi + + # Restart sshd to apply changes + run_cmd_admin systemctl restart sshd + run_cmd_admin echo "SSH port configuration completed" +else + run_cmd_admin echo "Using default SSH port 22, no configuration needed" +fi + +# Add more functionality below if you see fit. Be sure to use a variable +# to allow to easily enable / disable each mechanism. diff --git a/terraform/shared.tf b/terraform/shared.tf index 88e87a273..488becd0f 100644 --- a/terraform/shared.tf +++ b/terraform/shared.tf @@ -44,6 +44,12 @@ variable "ssh_config_kexalgorithms" { default = "" } +variable "ssh_config_port" { + description = "SSH port to use for remote connections and firewall rules" + type = number + default = 22 +} + variable "private_net_enabled" { description = "Is the private network enabled?" default = "false"