441 Redirect SHH to jails

Use case

Create multiple jails with auto UUID names. In the inventory, compose the variables ansible_host and ansible_port to connect the jails via the redirected SSH ports. See the example 440 Configure DHCP and pf how pf is configured.

Tree

shell> tree .
.
├── ansible.cfg
├── hosts
│   ├── 05_iocage.yml
│   └── 99_constructed.yml
├── host_vars
│   └── iocage_05.yml
├── iocage.ini
└── pb-test.yml

Synopsis

  • At a managed node:

    In the playbook vbotka.freebsd.pb_iocage_ansible_clients.yml:

    • create jails

    • start jails

    • optionally, stop and destroy the jails.

  • Create dynamic inventory to redirect SSH to the jails.

  • At all created jails:

    In the playbook pb-test.yml:

    • connect to the created jails

    • display basic configuration of the jails.

Requirements

Notes

The only difference between this example and the example 442 Connection jailexec instead of ssh are the following two lines in the file hosts/05_iocage.yml

ansible_host: dict(iocage_properties.notes | split | map('split', '=')).vmm
ansible_port: iocage_ip4 | split('.') | last | int - 100 + 2200

See also

example 050 Connection jailexec

Templates at iocage_05

[iocage_05]# iocage list -lt
+------+-----------------------+------+-------+----------+--------------+--------------------+-----+----------+----------+
| JID  |         NAME          | BOOT | STATE |   TYPE   |   RELEASE    |        IP4         | IP6 | TEMPLATE | BASEJAIL |
+======+=======================+======+=======+==========+==============+====================+=====+==========+==========+
| None | ansible_client        | off  | down  | template | 15.0-RELEASE | DHCP (not running) | -   | -        | no       |
+------+-----------------------+------+-------+----------+--------------+--------------------+-----+----------+----------+
| None | ansible_client_apache | off  | down  | template | 15.0-RELEASE | DHCP (not running) | -   | -        | no       |
+------+-----------------------+------+-------+----------+--------------+--------------------+-----+----------+----------+
| None | ansible_client_pull   | off  | down  | template | 15.0-RELEASE | DHCP (not running) | -   | -        | no       |
+------+-----------------------+------+-------+----------+--------------+--------------------+-----+----------+----------+

ansible.cfg

[defaults]
gathering = explicit
callback_result_format = yaml
display_skipped_hosts = false

[connection]
pipelining = true

Inventory iocage.ini

iocage_05

[iocage]
iocage_05

[iocage:vars]
ansible_user=admin
ansible_become=true
ansible_python_interpreter=auto_silent

host_vars

host_vars/iocage_05.yml
properties:
  bpf: 1
  dhcp: 1
  vnet: 1
  notes: "vmm={{ inventory_hostname }}"

swarms:
  sw_01:
    count: 3
    template: ansible_client

Playbook output - Create and start swarms

(env) > ansible-playbook vbotka.freebsd.pb_iocage_ansible_clients.yml \
                         -i iocage.ini \
                         -t swarm \
                         -e swarm=true \
                         -e debug=true
PLAY [Create and start jails. Optionally stop and destroy jails.] **************

TASK [Get iocage facts] ********************************************************
ok: [iocage_05]

TASK [Debug cmd_create debug=true] *********************************************
ok: [iocage_05] => (item=sw_01) => 
    msg: |-
        iocage create --short --template ansible_client --count 3 bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_05 swarm=sw_01"

TASK [Create swarms] ***********************************************************
changed: [iocage_05] => (item=sw_01)

TASK [Debug create swarms debug=true] ******************************************
ok: [iocage_05] => 
    out:
        changed: true
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: true
            cmd:
            - iocage
            - create
            - --short
            - --template
            - ansible_client
            - --count
            - '3'
            - bpf=1
            - dhcp=1
            - vnet=1
            - notes=vmm=iocage_05 swarm=sw_01
            delta: '0:00:00.851656'
            end: '2026-04-19 07:21:21.207287'
            failed: false
            invocation:
                module_args:
                    _raw_params: null
                    _uses_shell: false
                    argv: null
                    chdir: null
                    cmd: |-
                        iocage create --short --template ansible_client --count 3 bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_05 swarm=sw_01"
                    creates: null
                    executable: null
                    expand_argument_vars: true
                    removes: null
                    stdin: null
                    stdin_add_newline: true
                    strip_empty_ends: true
            item:
                key: sw_01
                value:
                    count: 3
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2026-04-19 07:21:20.355631'
            stderr: ''
            stderr_lines: []
            stdout: |-
                0a3ea428 successfully created!
                93d13878 successfully created!
                ffb2c114 successfully created!
            stdout_lines:
            - 0a3ea428 successfully created!
            - 93d13878 successfully created!
            - ffb2c114 successfully created!
        skipped: false

TASK [Get iocage facts] ********************************************************
ok: [iocage_05]

TASK [Debug cmd_start debug=true] **********************************************
ok: [iocage_05] => (item=sw_01) => 
    msg: |-
        iocage start 0a3ea428 ffb2c114 93d13878

TASK [Start swarms] ************************************************************
changed: [iocage_05] => (item=sw_01)

TASK [Debug start swarms debug=true] *******************************************
ok: [iocage_05] => 
    out:
        changed: true
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: true
            cmd:
            - iocage
            - start
            - 0a3ea428
            - ffb2c114
            - 93d13878
            delta: '0:00:07.786053'
            end: '2026-04-19 07:21:34.470440'
            failed: false
            invocation:
                module_args:
                    _raw_params: null
                    _uses_shell: false
                    argv: null
                    chdir: null
                    cmd: |-
                        iocage start 0a3ea428 ffb2c114 93d13878
                    creates: null
                    executable: null
                    expand_argument_vars: true
                    removes: null
                    stdin: null
                    stdin_add_newline: true
                    strip_empty_ends: true
            item:
                key: sw_01
                value:
                    count: 3
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2026-04-19 07:21:26.684387'
            stderr: |-
                No default gateway found for ipv6.
                No default gateway found for ipv6.
                No default gateway found for ipv6.
            stderr_lines:
            - No default gateway found for ipv6.
            - No default gateway found for ipv6.
            - No default gateway found for ipv6.
            stdout: |-
                * Starting 0a3ea428
                  + Started OK
                  + Using devfs_ruleset: 1000 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.10.99.106/24
                * Starting ffb2c114
                  + Started OK
                  + Using devfs_ruleset: 1001 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.10.99.107/24
                * Starting 93d13878
                  + Started OK
                  + Using devfs_ruleset: 1002 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.10.99.108/24
            stdout_lines:
            - '* Starting 0a3ea428'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1000 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.10.99.106/24'
            - '* Starting ffb2c114'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1001 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.10.99.107/24'
            - '* Starting 93d13878'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1002 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.10.99.108/24'
        skipped: false

PLAY RECAP *********************************************************************
iocage_05                  : ok=8    changed=2    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   

Jails at iocage_05

[iocage_05]# iocage list -l
+-----+----------+------+-------+------+--------------+----------------------+-----+----------------+----------+
| JID |   NAME   | BOOT | STATE | TYPE |   RELEASE    |         IP4          | IP6 |    TEMPLATE    | BASEJAIL |
+=====+==========+======+=======+======+==============+======================+=====+================+==========+
| 7   | 0a3ea428 | off  | up    | jail | 15.0-RELEASE | epair0b|10.10.99.106 | -   | ansible_client | no       |
+-----+----------+------+-------+------+--------------+----------------------+-----+----------------+----------+
| 9   | 93d13878 | off  | up    | jail | 15.0-RELEASE | epair0b|10.10.99.108 | -   | ansible_client | no       |
+-----+----------+------+-------+------+--------------+----------------------+-----+----------------+----------+
| 8   | ffb2c114 | off  | up    | jail | 15.0-RELEASE | epair0b|10.10.99.107 | -   | ansible_client | no       |
+-----+----------+------+-------+------+--------------+----------------------+-----+----------------+----------+

Inventory hosts

hosts/05_iocage.yml
plugin: vbotka.freebsd.iocage
host: iocage_05
user: admin
sudo: true
get_properties: true
hooks_results:
  - /var/db/dhclient-hook.address.epair0b
compose:
  ansible_host: dict(iocage_properties.notes | split | map('split', '=')).vmm
  ansible_port: iocage_ip4 | split('.') | last | int - 100 + 2200
  ansible_user: "'admin'"
  ansible_python_interpreter: "'auto_silent'"
  iocage_tags: dict(iocage_properties.notes | split | map('split', '='))

Note

  • In the example 440 Configure DHCP and pf, the variables ssh_rdr_start=2200 and dhcp_ip_start=100 are used in the playbook pb-pf-setup.yml to calculate the ports to redirect SSH from, and to create the file pf-rdr-ssh.conf.

  • For example, from the controller, the following command connects to the jail at <bsd_dhcpd_subnet>.106:

    shell> ssh -p 2206 admin@iocage_05
    
  • See the variable bsd_dhcpd_subnet in the example 440 Configure DHCP and pf

hosts/99_constructed.yml
plugin: ansible.builtin.constructed
keyed_groups:
  - prefix: swarm
    key: iocage_tags.swarm
  - prefix: vmm
    key: iocage_tags.vmm

Display inventory

(env) > ansible-inventory -i hosts --graph
@all:
  |--@ungrouped:
  |--@swarm_sw_01:
  |  |--0a3ea428
  |  |--93d13878
  |  |--ffb2c114
  |--@vmm_iocage_05:
  |  |--0a3ea428
  |  |--93d13878
  |  |--ffb2c114

Playbook pb-test.yml

- name: Test the SSH redirection to the jails.
  hosts: swarm_sw_01
  gather_facts: false
    
  tasks:
    
    - ansible.builtin.debug:
        msg: |
          ansible_connection: {{ ansible_connection }}
          ansible_host: {{ ansible_host }}
          ansible_port: {{ ansible_port }}
          ansible_user: {{ ansible_user }}

          iocage_ip4: {{ iocage_ip4 }}
          iocage_tags: {{ iocage_tags }}

Playbook output - Test SSH redirection

(env) > ansible-playbook pb-test.yml -i hosts
PLAY [Test the SSH redirection to the jails.] **********************************

TASK [ansible.builtin.debug] ***************************************************
ok: [0a3ea428] => 
    msg: |-
        ansible_connection: ssh
        ansible_host: iocage_05
        ansible_port: 2206
        ansible_user: admin

        iocage_ip4: 10.10.99.106
        iocage_tags: {'vmm': 'iocage_05', 'swarm': 'sw_01'}
ok: [93d13878] => 
    msg: |-
        ansible_connection: ssh
        ansible_host: iocage_05
        ansible_port: 2208
        ansible_user: admin

        iocage_ip4: 10.10.99.108
        iocage_tags: {'vmm': 'iocage_05', 'swarm': 'sw_01'}
ok: [ffb2c114] => 
    msg: |-
        ansible_connection: ssh
        ansible_host: iocage_05
        ansible_port: 2207
        ansible_user: admin

        iocage_ip4: 10.10.99.107
        iocage_tags: {'vmm': 'iocage_05', 'swarm': 'sw_01'}

PLAY RECAP *********************************************************************
0a3ea428                   : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
93d13878                   : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ffb2c114                   : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Hint

The below play stops and destroys the jails in swarms

ansible-playbook vbotka.freebsd.pb_iocage_ansible_clients.yml \
                 -i iocage.ini \
                 -t swarm_destroy \
                 -e swarm_destroy=true