--- - name: Reach Portainer via SSH tunnel through bdc hosts: localhost connection: local gather_facts: false vars: # Bastion SSH bastion_host: bdc.cci17.fr bastion_port: 17100 bastion_user: ansible # Portainer interne portainer_ip: 10.30.0.151 portainer_port: 9443 # Port local aléatoire pour éviter les collisions entre jobs local_port: "{{ 20000 + (9999 | random) }}" # Socket ControlMaster pour fermer proprement le tunnel ssh_control_socket: "/tmp/ssh-tunnel-{{ local_port }}.sock" tasks: - block: - name: Open SSH tunnel (local -> portainer via bastion) shell: | ssh -p {{ bastion_port }} \ -o ExitOnForwardFailure=yes \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o ServerAliveInterval=10 \ -o ServerAliveCountMax=3 \ -o ConnectTimeout=10 \ -M -S {{ ssh_control_socket }} \ -f -N \ -L 127.0.0.1:{{ local_port }}:{{ portainer_ip }}:{{ portainer_port }} \ {{ bastion_user }}@{{ bastion_host }} register: tunnel_open changed_when: true failed_when: tunnel_open.rc != 0 # Diagnostic utile : montre si SSH a renvoyé quelque chose - name: Debug tunnel open result (rc/stdout/stderr) debug: msg: - "ssh rc={{ tunnel_open.rc }}" - "stdout={{ tunnel_open.stdout | default('') }}" - "stderr={{ tunnel_open.stderr | default('') }}" - name: Wait for local tunnel port to be listening wait_for: host: 127.0.0.1 port: "{{ local_port }}" delay: 1 timeout: 30 - name: Verify local port is listening (extra check) shell: "ss -lnt | grep -q ':{{ local_port }} '" changed_when: false - name: HTTPS check Portainer through tunnel uri: url: "https://127.0.0.1:{{ local_port }}/" method: GET validate_certs: false return_content: false status_code: [200, 301, 302, 401, 403] register: portainer_check - name: OK debug: msg: "✅ Portainer joignable via tunnel (status {{ portainer_check.status }})" rescue: - name: Explain common causes when tunnel fails debug: msg: - "❌ Le tunnel n'a pas pu être validé." - "Causes fréquentes :" - "1) Sur OPNsense/bastion, 'Allow SSH port forwarding / TCP forwarding' n'est pas activé." - "2) Le bastion ne peut pas joindre {{ portainer_ip }}:{{ portainer_port }} (routage / firewall)." - "3) La clé SSH/credential utilisée par Semaphore n'est pas celle attendue (auth SSH)." - "Regarde le 'stderr' affiché juste avant pour l'erreur exacte." always: - name: Close tunnel if opened (ControlMaster) shell: > test -S {{ ssh_control_socket }} && ssh -p {{ bastion_port }} -S {{ ssh_control_socket }} -O exit {{ bastion_user }}@{{ bastion_host }} || true ignore_errors: true