NFLabs. エンジニアブログ

セキュリティやソフトウェア開発に関する情報を発信する技術者向けのブログです。

Ansibleで踏み台サーバを経由してPlaybookを実行できるようにした話

TL;DR

  • Windows
    • WinRMはHTTP(S)でLISTENしている
    • 踏み台サーバにHTTP proxyを設置し、WinRMのリクエストを中継することができる
  • Linux
    • オプションを適切に設定すると多段SSHができるようになる
  • AnsibleでlocalhostIPアドレスを取得する方法
    1. {{ hostvars.localhost.ansible_default_ipv4.address }} を使う
      • hosts: localhost のTaskを1回以上実行しておくこと
    2. {{ ansible_fact.ens224.ipv4.address }}を使う
      • ens224は環境によって変わる

0. ことの始まり

Ansibleを使った環境構築の設定中にNW構成の変更が入り、踏み台経由で各種サーバにログインできるようにする必要が生じた。

その環境にはLinuxWindowsがそれぞれ存在するような構成だったので、SSHとWinRMをそれぞれ踏み台経由で実行できなければならない。

問題のNW構成図

f:id:nflabs_admin:20210826195048p:plain
NW構成図

1. 設計

1.1. SOCKS Proxy案

SOCKS Proxyを経由すれば実現できるのではという初期案。

https://www.bloggingforlogging.com/2018/10/14/windows-host-through-ssh-bastion-on-ansible/

SOCKSの通信用にクライアントモジュールを増やさなければならないのでAnsibleホストの環境構築の手間が増えそう。

1.2. HTTP Proxy案

WinRMが裏でHTTPSを使っていることを利用し、HTTP Proxyを経由させる案

http://graceful-life.hatenablog.com/entry/2014/12/25/095413

踏み台にSquidを入れるだけなのですべてAnsibleのPlaybook内で完結し、事前の準備が難しくない。

Squidの設定も参考になりそうなものを見つけたので、こちらの案を試すことにする。

https://github.com/bz0/ansible-squid

2. 実装

2.1. 踏み台にSquidインストールする

  • 上記githubにある squid.confroles/bastion/files/以下に配置する
    • アクセスを許可するIPアドレスを指定の場所に記入しておく
  • インストールするためのPlay
- name: Install squid
  yum:
    name: squid
    disable_gpg_check: no
    state: installed

- name: Update squid.conf
  copy:
    src: ./squid.conf
    dest: /etc/squid/squid.conf
    owner: root
    group: root
    mode: 0644

- name: Open tcp/3128
  iptables:
    chain: INPUT
    protocol: tcp
    match: tcp
    destination_port: '3128'
    jump: ACCEPT

- name: Restart squid
  service:
    name: squid
    enabled: yes
    state: restarted

2.2. Proxy経由でSSH/WinRMを実行する

ここではinventoryで指定する同一グループ内にWindows/Linuxが混在しない構成を想定している。

(childrenを使ってグループの階層構造を作ることは構わないが、その場合はPlaybookの作り方を考える必要がある)

  • inventory/group_vars/all.yml を設定する
ansible_winrm_proxy: "http://{{ vars.groups.bastion[0] }}:3128"
ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ProxyCommand="sshpass -p {{ bastion_pass }} ssh -W %h:%p -q {{ bastion_user }}@{{ vars.groups.bastion[0] }}"'
  • inventory/group_vars/bastion.yml を設定する
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
  • Ansibleを流すターゲットOSに応じて以下の設定をPlaybookに入れておく
    • Windows: ansible_connection: winrm
    • Linux: ansible_connection: ssh

2.3. Ansible実行ホストを自動的に許可する

これまでの実装では、SquidのallowリストにAnsibleの実行ホストのIPアドレスがハードコードされているため、移植性に難がある。

そこで、Squidのallowリストに実行ホストのIPアドレスを自動的に挿入する方法を考える。

2.3.1. Ansibleに実行ホストのIPアドレスを知る手段はあるのか

その謎を解明するため、我々はAnsibleの奥地へと向かったーー。

まずは変数をダンプ

hosts: bastion
tasks:
  - debug:
      msg: "{{ vars }}"

vars の出力は非常に長いので割愛するが、ここでは特に何も見つからない。

外部変数で自分のIPアドレスを設定してから変数ダンプ

- hosts: localhost
  vars:
    ansible_host_ipv4_address: '192.168.0.2'
  tasks:
    - debug:
        var: ansible_host_ipv4_address
      register: test_var

hosts: bastion
tasks:
    - debug:
        var: test_var

    - debug:
      msg: "{{ vars }}"

別の hosts で指定したPlayの変数を参照することができた。

しかし、実行ホストのIPアドレスが自動で設定されている様子はない。

vars以外に変数を見る手段は無いのか?

- hosts: localhost
  tasks:
    - debug:
        msg: "test"

- hosts: bastion
  tasks:
    - debug:
        var: hostvars

まだ見つからない。

が、hostvarsIPアドレスをkeyにした連想配列になっているらしい。

f:id:k-ohsawa_nfl:20210705174413p:plain

hostvarsにAnsibleホストのkeyがあるかどうか確かめる

- hosts: localhost
  tasks:
    - debug:
        msg: "test"

- hosts: bastion
  tasks:
    - debug:
        var: hostvars.localhost

意外とあっさり見つかった。

f:id:k-ohsawa_nfl:20210705174502p:plain

f:id:k-ohsawa_nfl:20210705174513p:plain

IPアドレスを取得してみる

- hosts: localhost
  tasks:
    - debug:
        msg: "test"

- hosts: bastion
  tasks:
    - debug:
        var: hostvars.localhost.ansible_default_ipv4.address

意図したとおりIPアドレスを取得できた。

f:id:k-ohsawa_nfl:20210705174535p:plain

綺麗に整理する

- hosts: bastion
  tasks:
    - debug:
        var: hostvars.localhost.ansible_default_ipv4.address

???

f:id:k-ohsawa_nfl:20210705174551p:plain

2.3.2. 結論

Ansible実行ホストのIPアドレスを取得するには以下のように、初めに hosts: localhost のPlayを実行しておく必要がある。

- hosts: localhost
  tasks:
    - debug:
        msg: "test"

- hosts: bastion
  tasks:
    - debug:
        var: hostvars.localhost.ansible_default_ipv4.address

2.3.3. Squidの設定に反映させる

  • roles/bastion/files/squid.confroles/bastion/templates/squid.conf.j2 に移動する
  • IPアドレスをハードコードしていた場所を変数化する
# アクセスを許可するIPアドレスを設定して下さい
acl myacl src xxx.xxx.xxx.xxx
http_access allow myacl

# アクセスを許可するIPアドレスを設定して下さい
acl myacl src {{ hostvars.localhost.ansible_default_ipv4.address }}
http_access allow myacl
  • squidを設定するタスクで copy モジュールを使っている箇所を差し替える
- name: Update squid.conf
  template:
    src: ./squid.conf.j2
    dest: /etc/squid/squid.conf
    owner: root
    group: root
    mode: 0644
  • 踏み台の設定を行う前にhosts: localhostのPlayを実行しておく
- hosts: localhost
  tasks:
    - debug:
        msg: "touch"

2.3.4. 余談

{{ ansible_fact.ensXXX.ipv4.address }} とすれば hosts: localhost のPlayを実行する必要はない。

ただし、ensXXX は環境によってNICの番号が変わるデメリットがあるため、状況に応じて使い分けられると良い。

例えば hostvars.localhost.ansible_default_ipv4.address にはdefault routeに設定されているNICIPアドレスが設定されるため、踏み台サーバにそのNICからアクセスできない場合には、NICを直接指定する本手法が有効である。

3. おわりに

特定のサーバ経由でしかアクセスできないインスタンスWindows/Linux問わず)に対してAnsibleを実行できるようになった。