feat: Add Tailscale SSH bastion/jump host support 15/73815/2 master v0.17.0
authorAnil Belur <abelur@linuxfoundation.org>
Wed, 22 Oct 2025 12:15:25 +0000 (22:15 +1000)
committerAnil Belur <abelur@linuxfoundation.org>
Wed, 22 Oct 2025 13:14:29 +0000 (23:14 +1000)
Add native SSH bastion support to all OpenStack builder templates,
enabling Packer builds through jump hosts for environments where
direct access to OpenStack networks is not available.

Changes:
- Add ssh_bastion_* variables to templates with OpenStack sources:
  * ssh_bastion_host - Bastion IP/hostname
  * ssh_bastion_username - Bastion authentication username
  * ssh_bastion_port - Bastion SSH port (default: 22)
  * ssh_bastion_agent_auth - Use SSH agent auth (default: true)
  * ssh_bastion_private_key_file - Path to private key file
  * ssh_bastion_password - Password authentication (not recommended)

- Update OpenStack source blocks in all templates to include bastion
  configuration with conditional null handling for backwards
  compatibility

- Maintain legacy ssh_proxy_host support for existing deployments

Templates updated:
- templates/builder.pkr.hcl
- templates/devstack.pkr.hcl
- templates/devstack-pre-pip-yoga.pkr.hcl
- templates/docker.pkr.hcl
- templates/windows-builder.pkr.hcl
- templates/variables.auto.pkr.hcl

All bastion variables are optional with empty string defaults,
ensuring backward compatibility with existing builds that don't
require bastion access. Variables convert to null when empty, so
Packer ignores them.

This enables CI/CD environments (GitHub Actions, Jenkins) to build
OpenStack images via ephemeral bastion hosts like Tailscale SSH or
traditional jump servers.

Issue: RELENG-5850
Change-Id: If2b18067e491346b26d03da38b0ae1957c78aca1
Signed-off-by: Anil Belur <abelur@linuxfoundation.org>
releasenotes/notes/add-ssh-bastion-support-0e994a2a7f0d710a.yaml [new file with mode: 0644]
templates/builder.pkr.hcl
templates/devstack-pre-pip-yoga.pkr.hcl
templates/devstack.pkr.hcl
templates/docker.pkr.hcl
templates/variables.auto.pkr.hcl
templates/windows-builder.pkr.hcl

diff --git a/releasenotes/notes/add-ssh-bastion-support-0e994a2a7f0d710a.yaml b/releasenotes/notes/add-ssh-bastion-support-0e994a2a7f0d710a.yaml
new file mode 100644 (file)
index 0000000..83fa331
--- /dev/null
@@ -0,0 +1,40 @@
+---
+features:
+  - |
+    Add SSH bastion/jump host support for OpenStack builder template.
+    The following new variables are now available for configuring SSH
+    bastion connections:
+
+    - ``ssh_bastion_host`` - IP address or hostname of the bastion host
+    - ``ssh_bastion_username`` - Username for bastion authentication
+    - ``ssh_bastion_port`` - SSH port on bastion (default: 22)
+    - ``ssh_bastion_agent_auth`` - Use SSH agent for authentication (default: true)
+    - ``ssh_bastion_private_key_file`` - Path to SSH private key file
+    - ``ssh_bastion_password`` - Password for bastion authentication (not recommended)
+
+    All bastion variables are optional with empty string defaults, making them
+    backward compatible with existing builds that don't require bastion access.
+
+    Example usage:
+
+    .. code-block:: bash
+
+       packer build \\
+         -var=ssh_bastion_host=100.64.183.39 \\
+         -var=ssh_bastion_username=root \\
+         -var-file=vars/ubuntu-22.04.pkrvars.hcl \\
+         templates/builder.pkr.hcl
+
+    This enables Packer builds to access OpenStack instances through a
+    bastion/jump host, which is required when direct access to OpenStack
+    networks is not available (e.g., in CI/CD environments using Tailscale
+    or other ephemeral bastion solutions).
+
+    Reference:
+    https://developer.hashicorp.com/packer/integrations/hashicorp/openstack/latest/components/builder/openstack
+upgrade:
+  - |
+    Existing builds using ``ssh_proxy_host`` will continue to work without
+    changes. The legacy proxy support is maintained for backward compatibility.
+    New deployments should use the SSH bastion variables for native jump host
+    support instead of proxy-based SSH tunneling.
index 578eefa..8dd4b46 100644 (file)
@@ -93,6 +93,43 @@ variable "ssh_proxy_host" {
   default = ""
 }
 
+variable "ssh_bastion_host" {
+  type        = string
+  default     = ""
+  description = "Bastion/jump host for SSH access to OpenStack instances"
+}
+
+variable "ssh_bastion_username" {
+  type        = string
+  default     = ""
+  description = "Username for bastion host authentication"
+}
+
+variable "ssh_bastion_port" {
+  type        = number
+  default     = 22
+  description = "SSH port on bastion host"
+}
+
+variable "ssh_bastion_agent_auth" {
+  type        = bool
+  default     = true
+  description = "Use SSH agent for bastion authentication"
+}
+
+variable "ssh_bastion_private_key_file" {
+  type        = string
+  default     = ""
+  description = "Path to SSH private key file for bastion authentication"
+}
+
+variable "ssh_bastion_password" {
+  type        = string
+  default     = ""
+  sensitive   = true
+  description = "Password for bastion host authentication (not recommended)"
+}
+
 variable "ssh_user" {
   type = string
 }
@@ -140,7 +177,18 @@ source "openstack" "builder" {
   networks                = ["${var.cloud_network}"]
   region                  = "${var.cloud_region}"
   source_image_name       = "${var.base_image}"
+
+  # Legacy proxy support (kept for backwards compatibility)
   ssh_proxy_host          = "${var.ssh_proxy_host}"
+
+  # Bastion/Jump host support
+  ssh_bastion_host              = var.ssh_bastion_host != "" ? var.ssh_bastion_host : null
+  ssh_bastion_username          = var.ssh_bastion_username != "" ? var.ssh_bastion_username : null
+  ssh_bastion_port              = var.ssh_bastion_port
+  ssh_bastion_agent_auth        = var.ssh_bastion_agent_auth
+  ssh_bastion_private_key_file  = var.ssh_bastion_private_key_file != "" ? var.ssh_bastion_private_key_file : null
+  ssh_bastion_password          = var.ssh_bastion_password != "" ? var.ssh_bastion_password : null
+
   ssh_username            = "${var.ssh_user}"
   use_blockstorage_volume = "${var.vm_use_block_storage}"
   user_data_file          = "${var.cloud_user_data}"
index 842a1a2..40c3e9d 100644 (file)
@@ -80,6 +80,43 @@ variable "ssh_proxy_host" {
   default = ""
 }
 
+variable "ssh_bastion_host" {
+  type        = string
+  default     = ""
+  description = "Bastion/jump host for SSH access to OpenStack instances"
+}
+
+variable "ssh_bastion_username" {
+  type        = string
+  default     = ""
+  description = "Username for bastion host authentication"
+}
+
+variable "ssh_bastion_port" {
+  type        = number
+  default     = 22
+  description = "SSH port on bastion host"
+}
+
+variable "ssh_bastion_agent_auth" {
+  type        = bool
+  default     = true
+  description = "Use SSH agent for bastion authentication"
+}
+
+variable "ssh_bastion_private_key_file" {
+  type        = string
+  default     = ""
+  description = "Path to SSH private key file for bastion authentication"
+}
+
+variable "ssh_bastion_password" {
+  type        = string
+  default     = ""
+  sensitive   = true
+  description = "Password for bastion host authentication (not recommended)"
+}
+
 variable "source_ami_filter_name" {
   type    = string
   default = null
@@ -145,7 +182,18 @@ source "openstack" "devstack-pre-pip-yoga" {
   networks                = ["${var.cloud_network}"]
   region                  = "${var.cloud_region}"
   source_image_name       = "${var.base_image}"
+
+  # Legacy proxy support (kept for backwards compatibility)
   ssh_proxy_host          = "${var.ssh_proxy_host}"
+
+  # Bastion/Jump host support
+  ssh_bastion_host              = var.ssh_bastion_host != "" ? var.ssh_bastion_host : null
+  ssh_bastion_username          = var.ssh_bastion_username != "" ? var.ssh_bastion_username : null
+  ssh_bastion_port              = var.ssh_bastion_port
+  ssh_bastion_agent_auth        = var.ssh_bastion_agent_auth
+  ssh_bastion_private_key_file  = var.ssh_bastion_private_key_file != "" ? var.ssh_bastion_private_key_file : null
+  ssh_bastion_password          = var.ssh_bastion_password != "" ? var.ssh_bastion_password : null
+
   ssh_username            = "${var.ssh_user}"
   use_blockstorage_volume = "${var.vm_use_block_storage}"
   user_data_file          = "${var.cloud_user_data}"
index 771d79d..48feb6a 100644 (file)
@@ -83,6 +83,43 @@ variable "ssh_proxy_host" {
   default = ""
 }
 
+variable "ssh_bastion_host" {
+  type        = string
+  default     = ""
+  description = "Bastion/jump host for SSH access to OpenStack instances"
+}
+
+variable "ssh_bastion_username" {
+  type        = string
+  default     = ""
+  description = "Username for bastion host authentication"
+}
+
+variable "ssh_bastion_port" {
+  type        = number
+  default     = 22
+  description = "SSH port on bastion host"
+}
+
+variable "ssh_bastion_agent_auth" {
+  type        = bool
+  default     = true
+  description = "Use SSH agent for bastion authentication"
+}
+
+variable "ssh_bastion_private_key_file" {
+  type        = string
+  default     = ""
+  description = "Path to SSH private key file for bastion authentication"
+}
+
+variable "ssh_bastion_password" {
+  type        = string
+  default     = ""
+  sensitive   = true
+  description = "Password for bastion host authentication (not recommended)"
+}
+
 variable "source_ami_filter_name" {
   type    = string
   default = null
@@ -147,7 +184,18 @@ source "openstack" "devstack" {
   networks                = ["${var.cloud_network}"]
   region                  = "${var.cloud_region}"
   source_image_name       = "${var.base_image}"
+
+  # Legacy proxy support (kept for backwards compatibility)
   ssh_proxy_host          = "${var.ssh_proxy_host}"
+
+  # Bastion/Jump host support
+  ssh_bastion_host              = var.ssh_bastion_host != "" ? var.ssh_bastion_host : null
+  ssh_bastion_username          = var.ssh_bastion_username != "" ? var.ssh_bastion_username : null
+  ssh_bastion_port              = var.ssh_bastion_port
+  ssh_bastion_agent_auth        = var.ssh_bastion_agent_auth
+  ssh_bastion_private_key_file  = var.ssh_bastion_private_key_file != "" ? var.ssh_bastion_private_key_file : null
+  ssh_bastion_password          = var.ssh_bastion_password != "" ? var.ssh_bastion_password : null
+
   ssh_username            = "${var.ssh_user}"
   use_blockstorage_volume = "${var.vm_use_block_storage}"
   user_data_file          = "${var.cloud_user_data}"
index 249d695..d157dbf 100644 (file)
@@ -98,6 +98,43 @@ variable "ssh_proxy_host" {
   default = ""
 }
 
+variable "ssh_bastion_host" {
+  type        = string
+  default     = ""
+  description = "Bastion/jump host for SSH access to OpenStack instances"
+}
+
+variable "ssh_bastion_username" {
+  type        = string
+  default     = ""
+  description = "Username for bastion host authentication"
+}
+
+variable "ssh_bastion_port" {
+  type        = number
+  default     = 22
+  description = "SSH port on bastion host"
+}
+
+variable "ssh_bastion_agent_auth" {
+  type        = bool
+  default     = true
+  description = "Use SSH agent for bastion authentication"
+}
+
+variable "ssh_bastion_private_key_file" {
+  type        = string
+  default     = ""
+  description = "Path to SSH private key file for bastion authentication"
+}
+
+variable "ssh_bastion_password" {
+  type        = string
+  default     = ""
+  sensitive   = true
+  description = "Password for bastion host authentication (not recommended)"
+}
+
 variable "ssh_user" {
   type = string
   default = null
@@ -146,7 +183,18 @@ source "openstack" "docker" {
   networks                = ["${var.cloud_network}"]
   region                  = "${var.cloud_region}"
   source_image_name       = "${var.base_image}"
+
+  # Legacy proxy support (kept for backwards compatibility)
   ssh_proxy_host          = "${var.ssh_proxy_host}"
+
+  # Bastion/Jump host support
+  ssh_bastion_host              = var.ssh_bastion_host != "" ? var.ssh_bastion_host : null
+  ssh_bastion_username          = var.ssh_bastion_username != "" ? var.ssh_bastion_username : null
+  ssh_bastion_port              = var.ssh_bastion_port
+  ssh_bastion_agent_auth        = var.ssh_bastion_agent_auth
+  ssh_bastion_private_key_file  = var.ssh_bastion_private_key_file != "" ? var.ssh_bastion_private_key_file : null
+  ssh_bastion_password          = var.ssh_bastion_password != "" ? var.ssh_bastion_password : null
+
   ssh_username            = "${var.ssh_user}"
   use_blockstorage_volume = "${var.vm_use_block_storage}"
   user_data_file          = "${var.cloud_user_data}"
index 5fd45ef..03a3b36 100644 (file)
@@ -108,6 +108,37 @@ variable "ssh_proxy_host" {
   default = ""
 }
 
+variable "ssh_bastion_host" {
+  type    = string
+  default = ""
+}
+
+variable "ssh_bastion_username" {
+  type    = string
+  default = ""
+}
+
+variable "ssh_bastion_port" {
+  type    = number
+  default = 22
+}
+
+variable "ssh_bastion_agent_auth" {
+  type    = bool
+  default = true
+}
+
+variable "ssh_bastion_private_key_file" {
+  type    = string
+  default = ""
+}
+
+variable "ssh_bastion_password" {
+  type    = string
+  default = ""
+  sensitive = true
+}
+
 variable "ssh_user" {
   type = string
   default = null
index c5277d5..f744cc2 100644 (file)
@@ -83,6 +83,43 @@ variable "ssh_proxy_host" {
   default = null
 }
 
+variable "ssh_bastion_host" {
+  type        = string
+  default     = ""
+  description = "Bastion/jump host for SSH access to OpenStack instances"
+}
+
+variable "ssh_bastion_username" {
+  type        = string
+  default     = ""
+  description = "Username for bastion host authentication"
+}
+
+variable "ssh_bastion_port" {
+  type        = number
+  default     = 22
+  description = "SSH port on bastion host"
+}
+
+variable "ssh_bastion_agent_auth" {
+  type        = bool
+  default     = true
+  description = "Use SSH agent for bastion authentication"
+}
+
+variable "ssh_bastion_private_key_file" {
+  type        = string
+  default     = ""
+  description = "Path to SSH private key file for bastion authentication"
+}
+
+variable "ssh_bastion_password" {
+  type        = string
+  default     = ""
+  sensitive   = true
+  description = "Password for bastion host authentication (not recommended)"
+}
+
 variable "ssh_user" {
   type    = string
   default = null