Auomatic deployment of a jump host on OpenStack

In this sub-section, is shown how to automatically deploy a bastion host in an OpenStack environment using Terraform, followed by direct configuration through an Ansible role. The repository documenting all steps is available at this GitHub repository link.

The Ansible role you’ll find can be used standalone or as part of an automated deployment pipeline together with the Terraform module in the terraform directory, which handles the infrastructure provisioning on OpenStack.

Tip

Choose this installation method only if you already have some knowledge of Terraform and Ansible. Otherwise, follow the full guide.

Start by cloning the repository on any VM you want:

git clone https://github.com/Laniakea-elixir-it/ansible-role-vpn-bastion

Note

If you plan to use the full automated workflow (Terraform + Ansible), you can skip this Ansible-only section and jump directly to the Terraform part. If instead you want to configure an existing VM manually or test the PAM/OIDC setup, run this Ansible role as described below.

Ansible configuration

This playbook turns an Ubuntu 22.04 VM into a bastion that accepts SSH logins via OpenID Connect (device code flow) using the pam_oauth2_device module.

Note

If you are manually configuring the VM to act as a bastion host, ensure that the instance meets all the specified requirements. However, if you are using the Terraform integration, these manual steps are not necessary as the configuration is handled automatically.

The specific version of the PAM module used is: pam_oauth2_device.

The playbook performs the following tasks:

  1. Builds and installs the pam_oauth2_device PAM module.

  2. Writes /etc/pam_oauth2_device/config.json for your chosen IdP.

  3. Sends the device-code URL via SMTP (disabled by default).

  4. Creates ~/.ssh/authorized_keys for the im user if a public key is provided.

By cloning the repository, the Ansible section contains:

├─ inventory
├─ site.yml
├─ group_vars/
|  ├─ bastion.yml          # public settings (non-secret)
|  └─ bastion.vault.yml    # secrets (managed through Ansible Vault)
├─ templates/
|  └─ pam_config.json.j2
└─ .gitignore

Inside the directory you will find:

  • an inventory file

  • a site.yml for installation and PAM configuration

  • a group_vars directory containing settings and secrets

  • templates used to configure your IdP

Note

Make sure the following requirements are met:

  1. Target host (can be your vm): Ubuntu 22.04 VM reachable via SSH (with sudo-capable user, e.g. ubuntu).

  2. Controller: Ansible ≥ 2.15.

  3. OIDC client: client_id and client_secret registered at your IdP.

  4. (Optional) SMTP credentials for delivering device-code URLs by email.

Ansible configuration

First edit the inventory file and set your bastion’s public IP and SSH user:

[bastion]
bastion1 ansible_host=BASTION_PUBLIC_IP ansible_user=ubuntu

Then choose your IdP and fill the provider endpoints. Open group_vars/bastion.yml. IAM endpoints are already filled in; for other IdPs, replace the placeholders:

idp_provider: "iam"   # or lifescience | egi

 ...

oidc_providers:
  iam:
    device_endpoint:   "https://.../devicecode"
    token_endpoint:    "https://.../token"
    userinfo_endpoint: "https://.../userinfo"
  lifescience:
    device_endpoint:   "FILL_ME"
    token_endpoint:    "FILL_ME"
    userinfo_endpoint: "FILL_ME"
  egi:
    device_endpoint:   "FILL_ME"
    token_endpoint:    "FILL_ME"
    userinfo_endpoint: "FILL_ME"

Then is important to modify the bastion.vault.yml and insert your sensible data.

Warning

Put secrets into the Vault file. Never commit the Vault file.

# OIDC client (confidential)
client_id: "YOUR_OIDC_CLIENT_ID"
client_secret: "YOUR_OIDC_CLIENT_SECRET"

# SMTP password (only if enable_email: true in bastion.yml)
smtp:
  smtp_password: "YOUR_SMTP_PASSWORD"

# Create local UNIX users before enabling PAM (must include the OIDC preferred_username)
preferred_username: "your_oidc_username"
extra_local_users:
  - "im"            # technical jump user (optional)
  # - "anotheruser" # add more if needed

# SSH public key for the 'im' user (optional)
jump_user_pubkey: "ssh-rsa AAAA... comment"

Once configured, run the playbook:

(Optional) enable email for device code/URL:

enable_email: true
smtp:
  smtp_server_url: "smtps://smtp.gmail.com:465"
  smtp_username: "your-smtp-user"
  # smtp_password goes in bastion.vault.yml

Then encrypt your valut and run the playbook:

ansible-playbook -i inventory site.yml

Now you should have a fully functional and configured bastion host on your BASTION_PUBLIC_IP.

Create the Jump Host with Terraform

This procedure defines and deploys a Virtual Machine (VM) on OpenStack, configured to act as a Bastion Host (jump host). It serves as a secure SSH entry point to access resources in private networks.

Running the configuration will create:

  1. An OpenStack keypair for SSH access.

  2. A bastion VM in OpenStack with both public and private NICs.

  3. An Ansible inventory file pointing to the VM with the correct SSH key and IP.

  4. A fully configured bastion host (via Ansible).

Note

Ensure the following requirements:

  • Terraform: ≥ 1.14.0

  • Ansible: ≥ 2.15

  • OIDC client: client_id + client_secret from your IdP

  • (Optional) SMTP credentials

Structure of the repository

Cloning the repository gives the following Terraform structure:

terraform_bastion/
    ├─ main.tf
    ├─ terraform.tfvars
    └─ variables.tf
  • main.tf contains the configuration and all required fields.

  • variables.tf defines and documents all variables.

  • terraform.tfvars contains sensitive values and must be kept private.

Warning

If you fork the repository, never commit terraform.tfvars. Add it to .gitignore or encrypt and use a vault.

Terraform configuration

The main.tf and variables.tf files are already setted, you need to modify the terraform.tfvars with your sensible configurations:

When you set all the configuration run the commant for terraform, and it will create and configure the bastion host for you:

terraform init
terraform apply