heat_template_version: 2021-04-16 description: > DV2636 lab (converted from VirtualBox appliance DV2636-v2023.1). ServerA/ServerB act as gateways: uplink (was VirtualBox NAT), lanA (host-only #3, 192.168.60.0/24), transit (host-only #4, 192.168.70.0/24), lanB (host-only #5, 192.168.80.0/24). Original MACs are preserved. A bastion with the stack's only floating IP gives SSH access to all four VMs (.250 on each lab segment). parameter_groups: - label: Your settings description: The only thing you need to choose parameters: [key_name] - label: Course settings (leave as is) parameters: [external_network, ssh_allowed_cidr, lab_flavor, bastion_flavor, bastion_image, image_servera, image_clienta, image_clientb, image_serverb, lab_user, lab_mtu, dns_servers] parameters: key_name: type: string label: Your SSH key pair description: > Import your public key under Compute > Key Pairs first. It is used to log in to the bastion (user ubuntu), the only machine reachable from outside. constraints: [{custom_constraint: nova.keypair}] external_network: type: string default: External description: External network for the router and the bastion's floating IP ssh_allowed_cidr: type: string default: 0.0.0.0/0 description: Who may SSH to the bastion (restrict to BTH ranges!) lab_flavor: type: string default: dv2636.vm # 1 vCPU, 2048 MB, 25 GB bastion_flavor: type: string default: tiny # 1 vCPU, 512 MB, 5 GB - only runs sshd bastion_image: type: string default: Ubuntu 24.04 # any Ubuntu cloud image with cloud-init image_servera: {type: string, default: dv2636-v2023.1-servera} image_clienta: {type: string, default: dv2636-v2023.1-clienta} image_clientb: {type: string, default: dv2636-v2023.1-clientb} image_serverb: {type: string, default: dv2636-v2023.1-serverb} lab_user: type: string default: dv2636 description: Login user inside the lab VMs (only used in the ssh_config output; alice, bob, ... also exist) lab_mtu: type: number default: 1450 description: MTU of the tenant networks (must match the lab images' netplan) dns_servers: type: comma_delimited_list default: "1.1.1.1,8.8.8.8" resources: # ================= uplink (replaces VirtualBox NAT) ================= uplink_net: type: OS::Neutron::Net properties: name: {list_join: ['-', [{get_param: 'OS::stack_name'}, uplink]]} uplink_subnet: type: OS::Neutron::Subnet properties: network: {get_resource: uplink_net} cidr: 10.0.2.0/24 gateway_ip: 10.0.2.2 allocation_pools: [{start: 10.0.2.20, end: 10.0.2.200}] dns_nameservers: {get_param: dns_servers} router: type: OS::Neutron::Router properties: name: {list_join: ['-', [{get_param: 'OS::stack_name'}, router]]} external_gateway_info: {network: {get_param: external_network}} router_if: type: OS::Neutron::RouterInterface properties: router: {get_resource: router} subnet: {get_resource: uplink_subnet} bastion_sg: type: OS::Neutron::SecurityGroup properties: name: {list_join: ['-', [{get_param: 'OS::stack_name'}, bastion]]} rules: - {protocol: tcp, port_range_min: 22, port_range_max: 22, remote_ip_prefix: {get_param: ssh_allowed_cidr}} - {protocol: icmp, remote_ip_prefix: {get_param: ssh_allowed_cidr}} internal_sg: type: OS::Neutron::SecurityGroup properties: name: {list_join: ['-', [{get_param: 'OS::stack_name'}, uplink-internal]]} rules: - {remote_ip_prefix: 10.0.2.0/24} # anything from inside the uplink (bastion, other server) # ================= lab segments (replace host-only adapters) ================= # Subnets exist only for Neutron IP bookkeeping: no DHCP, no gateway, port security off, # so students can still renumber/route freely inside the guests. lan_a_net: type: OS::Neutron::Net properties: name: {list_join: ['-', [{get_param: 'OS::stack_name'}, lanA]]} port_security_enabled: false transit_net: type: OS::Neutron::Net properties: name: {list_join: ['-', [{get_param: 'OS::stack_name'}, transit]]} port_security_enabled: false lan_b_net: type: OS::Neutron::Net properties: name: {list_join: ['-', [{get_param: 'OS::stack_name'}, lanB]]} port_security_enabled: false lan_a_subnet: type: OS::Neutron::Subnet properties: {network: {get_resource: lan_a_net}, cidr: 192.168.60.0/24, enable_dhcp: false, gateway_ip: "", allocation_pools: [{start: 192.168.60.200, end: 192.168.60.240}]} transit_subnet: type: OS::Neutron::Subnet properties: {network: {get_resource: transit_net}, cidr: 192.168.70.0/24, enable_dhcp: false, gateway_ip: "", allocation_pools: [{start: 192.168.70.200, end: 192.168.70.240}]} lan_b_subnet: type: OS::Neutron::Subnet properties: {network: {get_resource: lan_b_net}, cidr: 192.168.80.0/24, enable_dhcp: false, gateway_ip: "", allocation_pools: [{start: 192.168.80.200, end: 192.168.80.240}]} # ================= ServerA: uplink, lanA, transit ================= servera_p1: type: OS::Neutron::Port properties: network: {get_resource: uplink_net} fixed_ips: [{subnet: {get_resource: uplink_subnet}, ip_address: 10.0.2.15}] mac_address: "08:00:27:4d:07:27" security_groups: [{get_resource: internal_sg}] servera_p2: type: OS::Neutron::Port properties: {network: {get_resource: lan_a_net}, mac_address: "08:00:27:9c:59:df", port_security_enabled: false, fixed_ips: [{subnet: {get_resource: lan_a_subnet}, ip_address: 192.168.60.100}]} servera_p3: type: OS::Neutron::Port properties: {network: {get_resource: transit_net}, mac_address: "08:00:27:a4:71:d2", port_security_enabled: false, fixed_ips: [{subnet: {get_resource: transit_subnet}, ip_address: 192.168.70.5}]} servera: type: OS::Nova::Server properties: name: ServerA image: {get_param: image_servera} flavor: {get_param: lab_flavor} networks: - port: {get_resource: servera_p1} - port: {get_resource: servera_p2} - port: {get_resource: servera_p3} # ================= ClientA: lanA ================= clienta_p1: type: OS::Neutron::Port properties: {network: {get_resource: lan_a_net}, mac_address: "08:00:27:1d:de:b9", port_security_enabled: false, fixed_ips: [{subnet: {get_resource: lan_a_subnet}, ip_address: 192.168.60.111}]} clienta: type: OS::Nova::Server properties: name: ClientA image: {get_param: image_clienta} flavor: {get_param: lab_flavor} networks: [{port: {get_resource: clienta_p1}}] # ================= ClientB: lanB ================= clientb_p1: type: OS::Neutron::Port properties: {network: {get_resource: lan_b_net}, mac_address: "08:00:27:c9:2d:2e", port_security_enabled: false, fixed_ips: [{subnet: {get_resource: lan_b_subnet}, ip_address: 192.168.80.111}]} clientb: type: OS::Nova::Server properties: name: ClientB image: {get_param: image_clientb} flavor: {get_param: lab_flavor} networks: [{port: {get_resource: clientb_p1}}] # ================= ServerB: uplink, lanB, transit ================= serverb_p1: type: OS::Neutron::Port properties: network: {get_resource: uplink_net} fixed_ips: [{subnet: {get_resource: uplink_subnet}, ip_address: 10.0.2.16}] mac_address: "08:00:27:27:f5:21" security_groups: [{get_resource: internal_sg}] serverb_p2: type: OS::Neutron::Port properties: {network: {get_resource: lan_b_net}, mac_address: "08:00:27:77:bb:1f", port_security_enabled: false, fixed_ips: [{subnet: {get_resource: lan_b_subnet}, ip_address: 192.168.80.100}]} serverb_p3: type: OS::Neutron::Port properties: {network: {get_resource: transit_net}, mac_address: "08:00:27:f9:0e:6a", port_security_enabled: false, fixed_ips: [{subnet: {get_resource: transit_subnet}, ip_address: 192.168.70.6}]} serverb: type: OS::Nova::Server properties: name: ServerB image: {get_param: image_serverb} flavor: {get_param: lab_flavor} networks: - port: {get_resource: serverb_p1} - port: {get_resource: serverb_p2} - port: {get_resource: serverb_p3} # ================= Bastion: uplink + .250 on every lab segment ================= bastion_up: type: OS::Neutron::Port properties: network: {get_resource: uplink_net} fixed_ips: [{subnet: {get_resource: uplink_subnet}, ip_address: 10.0.2.10}] security_groups: [{get_resource: bastion_sg}] bastion_lana: type: OS::Neutron::Port properties: {network: {get_resource: lan_a_net}, port_security_enabled: false, fixed_ips: [{subnet: {get_resource: lan_a_subnet}, ip_address: 192.168.60.250}]} bastion_transit: type: OS::Neutron::Port properties: {network: {get_resource: transit_net}, port_security_enabled: false, fixed_ips: [{subnet: {get_resource: transit_subnet}, ip_address: 192.168.70.250}]} bastion_lanb: type: OS::Neutron::Port properties: {network: {get_resource: lan_b_net}, port_security_enabled: false, fixed_ips: [{subnet: {get_resource: lan_b_subnet}, ip_address: 192.168.80.250}]} bastion: type: OS::Nova::Server properties: name: bastion image: {get_param: bastion_image} flavor: {get_param: bastion_flavor} key_name: {get_param: key_name} networks: - port: {get_resource: bastion_up} - port: {get_resource: bastion_lana} - port: {get_resource: bastion_transit} - port: {get_resource: bastion_lanb} user_data_format: RAW user_data: str_replace: params: "%UP%": {get_attr: [bastion_up, mac_address]} "%LANA%": {get_attr: [bastion_lana, mac_address]} "%TRAN%": {get_attr: [bastion_transit, mac_address]} "%LANB%": {get_attr: [bastion_lanb, mac_address]} "%MTU%": {get_param: lab_mtu} template: | #cloud-config hostname: bastion ssh_pwauth: false write_files: - path: /etc/cloud/cloud.cfg.d/99-no-network.cfg content: "network: {config: disabled}\n" - path: /etc/sysctl.d/99-no-forward.conf content: "net.ipv4.ip_forward = 0\n" - path: /etc/netplan/60-dv2636.yaml permissions: '0600' content: | network: version: 2 ethernets: uplink: match: {macaddress: "%UP%"} dhcp4: true lana: match: {macaddress: "%LANA%"} mtu: %MTU% addresses: [192.168.60.250/24] transit: match: {macaddress: "%TRAN%"} mtu: %MTU% addresses: [192.168.70.250/24] lanb: match: {macaddress: "%LANB%"} mtu: %MTU% addresses: [192.168.80.250/24] runcmd: - rm -f /etc/netplan/50-cloud-init.yaml - netplan apply - sysctl --system bastion_fip: type: OS::Neutron::FloatingIP depends_on: router_if properties: floating_network: {get_param: external_network} port_id: {get_resource: bastion_up} outputs: bastion_ip: value: {get_attr: [bastion_fip, floating_ip_address]} ssh_config: description: Paste into ~/.ssh/config, then e.g. "ssh dv-clienta" value: str_replace: params: "%FIP%": {get_attr: [bastion_fip, floating_ip_address]} "%U%": {get_param: lab_user} template: | Host dv-bastion HostName %FIP% User ubuntu Host dv-servera HostName 10.0.2.15 Host dv-serverb HostName 10.0.2.16 Host dv-clienta HostName 192.168.60.111 Host dv-clientb HostName 192.168.80.111 Host dv-servera dv-serverb dv-clienta dv-clientb User %U% ProxyJump dv-bastion StrictHostKeyChecking accept-new