Skip to content

libre-devops/terraform-azurerm-bastion

Repository files navigation

Terraform Azure Bastion

Bastion hosts that default to the free Developer SKU (one attribute and you can connect), scaling to Basic, Standard, and Premium when you need them.

CI Release Terraform Registry License


Overview

Bastion hosts keyed by name. The default SKU is Developer: the lightweight shared-pool offering that just attaches to a virtual network (set virtual_network_id and you are done), needs no AzureBastionSubnet, no public IP, no scale units, and costs nothing, which makes the module the fastest way to get secure VM access going.

Scaling up is per host: sku = "Basic" | "Standard" | "Premium" with an ip_configuration (the dedicated AzureBastionSubnet at /26 or larger, plus a public_ip_address_id that comes from the public-ip module: this module never creates public IPs, it only accepts them as inputs). Standard adds scale_units, zones, and the feature toggles (file copy, IP connect, Kerberos, shareable links, tunneling); Premium adds session recording and may omit the public IP for a private-only bastion. Every SKU/feature mismatch fails the plan with a specific message rather than at the API.

Usage

module "bastion" {
  source  = "libre-devops/bastion/azurerm"
  version = "~> 4.0"

  resource_group_id = module.rg.ids["rg-ldo-uks-prd-001"]
  location          = "uksouth"
  tags              = module.tags.tags

  bastion_hosts = {
    # The Developer default: one attribute.
    "bas-ldo-uks-prd-001" = {
      virtual_network_id = module.network.vnet_id
    }

    # Scaled up when needed.
    "bas-ldo-hub-uks-prd-001" = {
      sku = "Standard"
      ip_configuration = {
        subnet_id            = module.network.subnet_ids["AzureBastionSubnet"]
        public_ip_address_id = module.public_ip.public_ip_ids["pip-bas-ldo-uks-prd-001"]
      }
      tunneling_enabled = true
    }
  }
}

Examples

  • examples/minimal - a Developer bastion attached to a vnet: the one-attribute path.
  • examples/complete - a Developer host alongside a Standard host with scale units, zones, the feature toggles, and the public IP passed in from the public-ip module (one bastion per vnet, so each gets its own). Premium session recording is exercised in the mocked tests; a Premium host adds nothing to the example but cost.

Developing

Local work needs PowerShell 7+ and just, because the recipes wrap the LibreDevOpsHelpers PowerShell module (the same engine the libre-devops/terraform-azure action runs in CI). Install just with brew install just, or uv tool add rust-just then uv run just <recipe>.

Run just to list recipes: just update-ldo-pwsh (install or force-update LibreDevOpsHelpers from PSGallery), just validate, just scan (Trivy only), just pwsh-analyze (PSScriptAnalyzer only), just plan, just apply, just destroy, just e2e, just test, and just docs (the plan/apply/destroy recipes mirror the action, including the storage firewall dance; just e2e applies an example then always destroys it, defaulting to minimal, so nothing is left running). Releasing is also just: just increment-release [patch|minor|major] bumps, tags, and publishes a GitHub release, and the Terraform Registry picks up the tag.

Security scan exceptions

This module is scanned with Trivy; HIGH and CRITICAL findings fail the build. Any waiver is a deliberate, reviewed decision, never a way to quiet a finding that should be fixed. Waivers live in .trivyignore.yaml (the machine-applied source of truth, passed to Trivy with --ignorefile) and are mirrored in a table here so the reason is auditable.

There are currently no exceptions: the module and its examples scan clean. A bastion exists to REMOVE public management-port exposure, so there is nothing to waive.

To add an exception: add an entry to .trivyignore.yaml (id, optional paths to scope it, and a statement recording why), then add a matching row here recording the reason. Both the file and the table are reviewed in the pull request.

Reference

The Requirements, Providers, Inputs, Outputs, and Resources below are generated by terraform-docs.

Requirements

Name Version
terraform >= 1.9.0, < 2.0.0
azurerm >= 4.0.0, < 5.0.0

Providers

Name Version
azurerm >= 4.0.0, < 5.0.0

Modules

No modules.

Resources

Name Type
azurerm_bastion_host.this resource

Inputs

Name Description Type Default Required
bastion_hosts The bastion hosts to create, keyed by name. The default SKU is DEVELOPER: the lightweight (free)
shared-pool offering that just attaches to a virtual network (set virtual_network_id), needs no
AzureBastionSubnet, no public IP, and no scale units, so getting going is one attribute. Scale up per
host by setting sku:

- Basic: needs ip_configuration { subnet_id (an AzureBastionSubnet, /26 or larger), and a
public_ip_address_id from the public-ip module (this module never creates public IPs) }.
- Standard: Basic plus scale_units (2-50) and the optional features file_copy_enabled,
ip_connect_enabled, kerberos_enabled, shareable_link_enabled, tunneling_enabled.
- Premium: Standard plus session_recording_enabled, and public_ip_address_id may be omitted for a
private-only bastion.

copy_paste_enabled works on every SKU (default true). zones is supported on Standard and Premium.
Feature/SKU mismatches fail the plan with a specific message rather than at the API.
map(object({
sku = optional(string, "Developer")
virtual_network_id = optional(string)

ip_configuration = optional(object({
name = optional(string, "bastion-ipconfig")
subnet_id = string
public_ip_address_id = optional(string)
}))

scale_units = optional(number)
zones = optional(list(string))

copy_paste_enabled = optional(bool, true)
file_copy_enabled = optional(bool)
ip_connect_enabled = optional(bool)
kerberos_enabled = optional(bool)
shareable_link_enabled = optional(bool)
tunneling_enabled = optional(bool)
session_recording_enabled = optional(bool)

tags = optional(map(string))
}))
{} no
location Azure region for the bastion hosts. string n/a yes
resource_group_id Resource id of the resource group to create the bastion hosts in. The name is parsed from it (pass the rg module's ids output). string n/a yes
tags Tags applied to every bastion host (merged with any per-host tags). map(string) {} no

Outputs

Name Description
bastion_hosts The bastion hosts, keyed by name. Full resource objects (all attributes).
dns_names Map of bastion name to the bastion DNS name.
ids Map of bastion name to resource id.
ids_zipmap Map of bastion name to { name, id }, for easy composition with other modules.
names Map of bastion name to name (convenience passthrough).
private_only Map of bastion name to whether the host is private-only (Premium without a public IP).
resource_group_name The resource group the bastion hosts live in, parsed from resource_group_id.

About

πŸšͺ Azure Bastion hosts defaulting to the free Developer SKU: one attribute and you can connect, scaling to Basic, Standard and Premium per host

Topics

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors