AWX to GCP VM : Establish and Maintain IAP Tunnel for Subsequent Playbook Execution

I setup a constrained example consisting of 2 VMs hosted in GCE and a Linux PC acting as the control node. The VMs are setup using metadata based authentication with an SSH key setup at the project level and are internal-only. The VMs have outbound connectivity via a NAT router. The control node will use the feature of the GCloud CLI to establish a tunnel through the IAP service to connect to the VMs on port 22 which is allowed by the default firewall rules. This same setup should work for Windows VMs using WinRM by forwarding the port for WinRM instead of 22 through the IAP service and allowing it to the VM from IAP’s well known IP range as the source.

An inventory like:

[project]
managed-node-1
managed-node-2

[project:vars]
gce_project = project

[us_central1:children]
project

[us_central1:vars]
gce_zone = us-central1

[all:vars]
ansible_ssh_common_args="-o ‘ProxyCommand gcloud compute start-iap-tunnel %h 22 --listen-on-stdin --project {{ gce_project }} --zone {{ gce_zone }}’"
ansible_user = selvaraj

A playbook like:

---
- name: Activate the `gcloud` CLI
  hosts: localhost

  tasks:
    - name: Activate the `gcloud` CLI
      ansible.builtin.command:
        argv:
          - gcloud
          - auth
          - activate-service-account
          - --key-file
          - '{{ lookup("ansible.builtin.env", "GCE_CREDENTIALS_FILE_PATH") }}'
      changed_when: false

- name: Take action on GCP based managed nodes
  hosts: all

  tasks:
    - name: Print hostname
      ansible.builtin.debug:
        var: ansible_hostname

ansible-navigator configured like ( ansible-navigator.yml ):

The mode is set to stdout really just so I could copy and paste the play output easier

---
ansible-navigator:
  execution-environment:
    environment-variables:
      set:
        ANSIBLE_HOST_KEY_CHECKING: false
        GCE_CREDENTIALS_FILE_PATH: /.gce.json
    image: localhost/selvaraj:2
    volume-mounts:
      - src: ~/.gce.json
        dest: /.gce.json
        options: Z
  mode: stdout
  playbook-artifact:
    enable: false

Ran via:

ansible-navigator run playbook.yml --inventory hosts

Results in:

$ ansible-navigator run playbook.yml --inventory hosts

PLAY [Activate the `gcloud` CLI] *************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************************************************************************************************************
ok: [localhost]

TASK [Activate the `gcloud` CLI] *************************************************************************************************************************************************************************************************************
ok: [localhost]

PLAY [Take action on GCP based managed nodes] ************************************************************************************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************************************************************************************************************
[WARNING]: Platform linux on host managed-node-1 is using the discovered Python interpreter at /usr/bin/python3.11, but future installation of another Python interpreter could change the meaning of that path. See
https://docs.ansible.com/ansible-core/2.18/reference_appendices/interpreter_discovery.html for more information.
ok: [managed-node-1]
[WARNING]: Platform linux on host managed-node-2 is using the discovered Python interpreter at /usr/bin/python3.11, but future installation of another Python interpreter could change the meaning of that path. See
https://docs.ansible.com/ansible-core/2.18/reference_appendices/interpreter_discovery.html for more information.
ok: [managed-node-2]

TASK [Print hostname] ************************************************************************************************************************************************************************************************************************
ok: [managed-node-1] => {
    "ansible_hostname": "managed-node-1"
}
ok: [managed-node-2] => {
    "ansible_hostname": "managed-node-2"
}

PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
managed-node-1             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
managed-node-2             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0