AWX で Kerberos 認証を使って Windows ホストに接続する

はじめに

Ansible で Windows ホストに対してプレイブックを実行したい場合、典型的にはホストへの接続には WinRM を利用します。この際、対象の Windows ホストに そのホストのローカルユーザで認証 する場合は、特別な工夫をしなくてもユーザ名とパスワードを使った Basic 認証や CredSSP 認証が利用できます。

一方で、対象の Windows ホストが Windows ドメインに参加していて、Ansible から ドメインユーザ で認証したい場合、WinRM の接続には Kerberos 認証 が推奨されており、そのためにはあらかじめ Ansible の実行ホストで Kerberos クライアントが正しく構成されている必要があります。

Ansible を ansible-playbook で直接実行する場合はそこまでハードルは高くないですが、AWX で実行するジョブで Kerberos 認証を使いたい 場合は、わりと混乱しがちです。Ansible Automation Controller のドキュメント に手順があるものの、あくまで AAC 用であって AWX には適合しない ものです。

本エントリでは、AWX で Kerberos 認証を使って Windows ホストに接続する 方法を紹介します。

環境

本エントリでは、Kubernetes 上で動作する AWX を前提としています。AWX のバージョンは本エントリでは 21.2.0 を使っていますが、Execution Environment(以下 EE)が登場した 18 以降であればおそらく同じ手順になるものと推測しています。AWX のデプロイ方法は 別のエントリ で過去に紹介しています。

また、エントリ中で扱うドメインその他の情報は以下の通りです。必要に応じて適宜読み替えてください。

項目
ドメイン名kurokobo.internal
ドメインコントローラkuro-ad01.kurokobo.internal
KDC サーバkuro-ad01.kurokobo.internal
操作対象の Windows ホスト(ドメインメンバ)kuro-win01.kurokobo.internal
接続に利用するドメインユーザawx@kurokobo.internal

全体像

技術的な背景の整理

作ろうとしている姿の意味は、次の技術的な仕様を把握できていると理解しやすくなります。

  • AWX でジョブを実行すると、実行するたびに新しい Pod が EE として作成される
  • Kerberos 認証はジョブの実行のたびに EE の中で行われる
  • EE の Pod はジョブが終了すると削除される

つまり、AWX で Kerberos 認証を使うには、AWX 自体のインスタンスではなく、都度作成される EE が Kerberos 認証を行えるように正しく構成されている状態 を実現する必要があります。技術的には、これは次の状態です。

  • EE に Kerberos クライアントとしての動作に必要なパッケージがインストールされている
  • EE の /etc/krb5.conf が正しく構成されている

前者は、デフォルトで krb5-libskrb5-workstation が EE のベースイメージに含まれているため、何もしなくても充足できています。しかしながら、後者の /etc/krb5.conf はデフォルトのまま なので、このファイルを何らかの手段でカスタマイズする必要があります。

この目的で使える機能として、AWX にはホストの任意のディレクトリを EE に公開する設定(AWX_ISOLATION_SHOW_PATHS、UI では Paths to expose to isolated jobs)があり、これでも動作はしますが、以下の点から Kubernetes 環境での利用はあまり推奨されません(とはいえせっかくなので参考情報としてこの場合の操作方法を本エントリ末尾に記載しています)。

  • パスの EE への公開に hostPath が利用されるため、Kubernetes クラスタのうち、EE が実行されうる全ノードのローカルにファイルを配置して管理する必要がある
  • 特定の EE のみへの設定はできず、問答無用ですべての EE が同じ hostPath を使うようになる
  • そもそも hostPath の利用は Kubernetes の世界ではセキュリティリスクの観点で利用は避けるべきとされている

そこで今回は、以前に別エントリで紹介した Container Group を使い、EE の Pod の仕様をカスタマイズ して ConfigMap 化した krb5.conf をマウントする ことで対応します。

なお、krb5.conf を含んだ状態の EE のイメージを Ansible Builder でビルドする 手も別解として考えられますが、EE のイメージの汎用性が損なわれる点、トライアンドエラーがしにくくなる点などから、今回は採用していません。

手順の概要

前述の目的を達成するため、以下の作業を行います。

  1. 操作対象の Windows ホストの構成
    • Kerberos 認証を受け入れるよう WinRM を構成する
    • 接続に利用するドメインユーザに WinRM での操作権限を付与する
  2. Kubernetes の構成
    • Kubernetes 上の ConfigMap リソースとして krb5.conf を作成する
  3. AWX の構成
    • /etc/krb5.conf を ConfigMap からマウントするようにした Container Group を作成する
    • 接続に利用するドメインユーザの Credential を作成する
    • 操作対象の Windows ホストを Inventory に作成する
    • Job Template に Container Group、Credential、Inventory を設定する

手順

実際の手順です。同じ情報は GitHub にも置いてあります

操作対象の Windows ホストの構成

WinRM の構成

Ansible のドキュメント を参考に、WinRM を有効化します。台数が多い場合は GPO でバラまくのが常套手段ですが、手でやる場合は winrm quickconfig を叩くのがラクです。

なお、Kerberos 自体がペイロードを暗号する仕組みを持っている ため、WinRM の待ち受けは HTTP でも安全です。ドキュメント によると、HTTPS 越しの Kerberos も利用できますが、この場合は TLS での暗号化のみ実施され、Kerberos は暗号化をしなくなるようです(未検証ですが、変数 ansible_winrm_message_encryptionalways にすれば暗号化を二重にさせることもできるようです)。

今回は HTTP のみとして進めます。WinRM の Listener の状態は以下で確認できます。

> winrm enumerate winrm/config/Listener
Listener
    Address = *
    Transport = HTTP
    Port = 5985
    Hostname
    Enabled = true
    URLPrefix = wsman
    CertificateThumbprint
    ListeningOn = 127.0.0.1, ...

認証で Kerberos が有効になっていることも併せて確認します。

> winrm get winrm/config/Service
Service
    Auth
        Basic = true
        Kerberos = true
        Negotiate = true
        Certificate = false
        CredSSP = false
        CbtHardeningLevel = Relaxed
    ...

権限の構成

WinRM は、デフォルトでは ローカルの Administrators グループ に所属するユーザからのみ接続を受け付けます。このグループ以外のユーザを使いたい場合は、winrm configSDDL default で起動する SDDL の設定画面で 読み取り実行 の権限を個別に付与します。詳細は Ansible のドキュメント に記載があります。

つまり、今回はドメインユーザ awx@kurokobo.internal を利用するため、これを操作対象の Windows ホストの Administrators グループに参加させるか、あるいは、個別に権限を与える操作が必要だということです。

どちらでも動作はするので、AWX で実行させたいジョブでの管理者権限の要否に合わせて設定するとよさそうです。今回は、グループ追加では芸がないので、後者の SDDL 案にしています。

Kubernetes の構成

krb5.conf の作成

AWX が動作している Kubernetes クラスタに対する kubectl が実行できるホストで、krb5.conf を作成します。このファイルについては、Ansible のドキュメントAnsible Automation Controller のドキュメントでも触れられています。

中身は要件次第なので千差万別ですが、例えば最小限だと以下です。設定ファイル中、レルムは大文字 にします。

[realms]
  KUROKOBO.INTERNAL = {
    kdc = kuro-ad01.kurokobo.internal
    admin_server = kuro-ad01.kurokobo.internal
  }

[domain_realm]
  .kurokobo.internal = KUROKOBO.INTERNAL
  kurokobo.internal = KUROKOBO.INTERNAL

雑な説明ですが、レルムは Kerberos の世界観でのドメイン のようなもので、このファイルで Windows の世界の小文字のドメインと Kerberos の世界の大文字のレルムの対応付けを定義 しているようなイメージです。

ConfigMap の作成

krb5.conf ができたら、それを使って Kubernetes クラスタに ConfigMap を作成します。作成先は EE が動作する NameSpace です。この例では、別のエントリ の構成をベースにしているため、Namespace は awx です。

kubectl -n awx create configmap awx-kerberos-config --from-file=krb5.conf

正常に作成できると、ConfigMap の datakrb5.conf として保存されます。

$ kubectl -n awx get configmap awx-kerberos-config -o yaml
apiVersion: v1
data:
  krb5.conf: |-
    [realms]
      KUROKOBO.INTERNAL = {
        kdc = kuro-ad01.kurokobo.internal
        admin_server = kuro-ad01.kurokobo.internal
      }

    [domain_realm]
      .kurokobo.internal = KUROKOBO.INTERNAL
      kurokobo.internal = KUROKOBO.INTERNAL
kind: ConfigMap
metadata:
  ...
  name: awx-kerberos-config
  namespace: awx
  ...

AWX の構成

Container Group の作成

AWX 上で Container Group を作成します。Web UI では、Administration の下の Instance GroupsAdd し、Customize pod specification にチェックを入れて、以下の YAML 文字列をつっこみます。

apiVersion: v1
kind: Pod
metadata:
  namespace: awx
spec:
  serviceAccountName: default
  automountServiceAccountToken: false
  containers:
    - image: 'quay.io/ansible/awx-ee:latest'
      name: worker
      args:
        - ansible-runner
        - worker
        - '--private-data-dir=/runner'
      resources:
        requests:
          cpu: 250m
          memory: 100Mi
      volumeMounts:
        - name: awx-kerberos-volume
          mountPath: /etc/krb5.conf
          subPath: krb5.conf
  volumes:
    - name: awx-kerberos-volume
      configMap:
        name: awx-kerberos-config

デフォルトの Pod 定義(AWX のバージョンで若干差異があります)を踏襲しつつ、volumesvolumeMounts を足した形です。先の手順で作成した ConfigMap awx-kerberos-configkrb5.conf/etc/krb5.conf としてマウントさせています。

これで、この Container Group を使ってジョブを実行すれば、その EE ではカスタマイズ済みの /etc/krb5.conf が認識できるようになります。

Credential の構成

AWX 上で、通常の Credential をつくるのと同じ手順で Kerberos 認証に使うユーザMachine タイプ の Credential を作成します。

このとき、Username<ユーザ名>@<レルム> とします。レルム なので、つまり、@ 以降は大文字 にします。

今回はドメインユーザ awx@kurokobo.internal を使いますが、krb5.conf でドメイン kurokobo.internal はレルム KUROKOBO.INTERNAL に対応づけているため、 入力すべき文字列は awx@KUROKOBO.INTERNAL です。

Username に入力した値は、ジョブで Ansible が実行される際、kinitexpect でそのまま渡されます。

Inventory の構成

AWX 上で、Inventory に操作対象の Windows ホストを追加します。このとき、ホストの名前IP アドレスではなく FQDN にする必要があります。今回の例では、kuro-win01.kurokobo.internal です。

また、ホスト変数(またはグループ変数)で次の値を指定し、Kerberos の利用を明示します(しなくても勝手に使われますが、管理上わかりやすいのであえて書いておくほうが好きです)。

---
ansible_connection: winrm
ansible_winrm_transport: kerberos
ansible_port: 5985

5985 は WinRM の HTTP の待ち受けポートです。操作対象の Windows ホスト側で WinRM を HTTPS で構成した場合は 5986 にします(HTTPS が自己署名証明書の場合は構成次第で ansible_winrm_server_cert_validation: ignore も必要です)。

Job Template の構成

操作対象の Windows ホストに対して実行したいジョブで、以下を構成します。

  • Inventory には、先の手順で作成した 操作対象が FQDN で登録されている ものを指定します
  • Credential には、先の手順で作成した <ユーザ名>@<レルム(大文字)> のものを指定します
  • Instance Groups には、先の手順で作成した krb5.conf をマウントするようにカスタマイズした Container Group を指定します

テスト

ここまでの作業が問題なければ、ジョブは成功するはずです。例えば ansible.windows.win_ping だけのプレイブックを流してみるとよいでしょう。

---
- name: Test Kerberos Authentication
  hosts: kuro-win01.kurokobo.internal
  gather_facts: false
  tasks:

    - name: Ensure windows host is reachable
      ansible.windows.win_ping:

Job Template の Verbosity4 (Connection Debug) にすると、Kerberos のために kinit が呼び出されたことをログで確認できます。

TASK [Ensure windows host is reachable] ****************************************
...
<kuro-win01.kurokobo.internal> ESTABLISH WINRM CONNECTION FOR USER: awx@KUROKOBO.INTERNAL on PORT 5985 TO kuro-win01.kurokobo.internal
calling kinit with pexpect for principal awx@KUROKOBO.INTERNAL
...
ok: [kuro-win01.kurokobo.internal] => {
    "changed": false,
    "invocation": {
        "module_args": {
            "data": "pong"
        }
    },
    "ping": "pong"
}

トラブルシュート

とはいえ、えてしてうまくいかないものです。コケたら愚直にひとつずつ設定を確認していくしかありませんが、こういうプレイブックを流すと少しわかりやすくなります。

---
- name: Debug Kerberos Authentication
  hosts: localhost
  gather_facts: false
  tasks:

    - name: Ensure /etc/krb5.conf is mounted
      ansible.builtin.debug:
        msg: "{{ lookup( 'file', '/etc/krb5.conf' ) }}"

    - name: Pause for specified minutes for debugging
      ansible.builtin.pause:
        minutes: 10

実行後、ansible.builtin.pause が効いている 10 分間、EE の Bash を触って調査 ができます。EE の Pod(automation-job- で始まる名前の Pod)を特定して、kubectl exec -it します。

$ kubectl -n awx get pod
NAME                                               READY   STATUS    RESTARTS   AGE
awx-postgres-0                                     1/1     Running   0          41h
awx-76445c946f-btfzz                               4/4     Running   0          41h
awx-operator-controller-manager-7594795b6b-565wm   2/2     Running   0          41h
automation-job-42-tdvs5                            1/1     Running   0          4s
$ kubectl -n awx exec -it automation-job-42-tdvs5 -- bash
bash-4.4$

もし、そもそも EE が起動する前にジョブが失敗してしまうのであれば、Container Group の設定の誤りか、ConfigMap の設定の誤りが考えられます。

krb5.conf のマウントの確認

EE の Bash が触れたら、/etc/krb5.conf に意図したファイルが意図した中身でマウントされていることを確認します。意図していない状態であれば、Job Template か Container Group か ConfigMap の設定の確認が必要です。

bash-4.4$ cat /etc/krb5.conf
[realms]
  KUROKOBO.INTERNAL = {
    kdc = kuro-ad01.kurokobo.internal
    admin_server = kuro-ad01.kurokobo.internal
  }

[domain_realm]
  .kurokobo.internal = KUROKOBO.INTERNAL

KDC への到達性の確認

KDC の名前解決ができることと、ネットワーク的な到達性を確認します。いろいろコマンドが無いうえに sudo もできないのでパッケージも足せませんが、こういうときは Busybox を持ってくるとラクです(ただし busybox pingbusybox traceroute は権限の都合で EE 内では使えません)。

# Busybox のダウンロードと実行権限の付与
bash-4.4$ curl -o busybox https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox
bash-4.4$ chmod +x busybox

# 名前解決の確認(ドメイン名)
bash-4.4$ ./busybox nslookup kurokobo.internal
Server:         10.43.0.10
Address:        10.43.0.10:53
Name:   kurokobo.internal
Address: ...

# 名前解決の確認(KDC)
bash-4.4$ ./busybox nslookup kuro-ad01.kurokobo.internal
Server:         10.43.0.10
Address:        10.43.0.10:53
Name:   kuro-ad01.kurokobo.internal
Address: ...

# 到達性の確認(88 番ポート)
bash-4.4$ ./busybox nc -v -w 1 kuro-ad01.kurokobo.internal 88
kuro-ad01.kurokobo.internal (...:88) open

手動でのチケット発行可否の確認

kinit コマンドを手でたたくと、チケットの発行可否を確認できます。実際のジョブの中でも同等の操作が行われているため、まずはこれが通る状態にするのが重要です。

# kinit コマンドに <ユーザ名>@<レルム> を渡して実行しパスワードを入力
# レルムは大文字である点に注意
bash-4.4$ kinit awx@KUROKOBO.INTERNAL
Password for awx@KUROKOBO.INTERNAL:

# エラーなく通ったら発行されたチケットを klist で確認できる
bash-4.4$ klist                      
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: awx@KUROKOBO.INTERNAL

Valid starting     Expires            Service principal
07/02/22 12:32:28  07/02/22 22:32:28  krbtgt/KUROKOBO.INTERNAL@KUROKOBO.INTERNAL
        renew until 07/03/22 12:32:21

エラーごとの対処方法

GitHub のリポジトリ によくあるエラーと確認ポイントを書いているので、併せてどうぞ。

また、Ansible の Kerberos 関連のページにもトラブルシュートについて記載 があり、参考にできます。

技術的な補足

いくつか余談めいたメモです。

AWX_ISOLATION_SHOW_PATHS と Container Group

デフォルトでは、AWX の AWX_ISOLATION_SHOW_PATHS の設定は、Instance Group に対してのみ機能し、Container Group では機能しません。すなわち、Kubernetes 環境ではデフォルトでは機能しません(推測ですが AAC 向けっぽさがありますね)。Kubernetes 環境でも機能させたい場合は、AWX_MOUNT_ISOLATED_PATHS_ON_K8S(UI では Expose host paths for Container Groups)を true(有効)にする必要があります。

AWX_ISOLATION_SHOW_PATHS を使って実装する場合は、本エントリの手順は以下のようにもう少し簡略化できます。ただし、全 EE が無条件で同じ hostPath を使うようになるため、副作用やリスクには配慮が必要です。

  • ConfigMap と Container Group は作成しない
  • EE が実行されうる全ノードの(kubelet がアクセスできる)任意のパスに krb.conf を配置する
  • AWX_MOUNT_ISOLATED_PATHS_ON_K8S(UI では Expose host paths for Container Groups)を true(有効)にする
  • AWX_ISOLATION_SHOW_PATHS(UI では Paths to expose to isolated jobs)に /path/to/local/krb5.conf:/etc/krb5.conf:O を追加する

レルムの大文字小文字

MIT Kerberos のドキュメント でも RFC4120 でも、レルム名の大文字と小文字が区別されることは明記されているものの、大文字でなければならないとはされておらず、慣習的(by convention)な推奨(recommended)とされているだけです。

そんなわけで、/etc/krb5.conf 内を全部小文字にして [libdefaults]canonicalizetrue にする(またはコマンドライン引数で -C を付ける)と、実は kinit でのチケット発行が小文字のレルムでもできるようになります。……が、Windows 側の Kerberos や GSSAPI の実装で小文字が通用する範囲がいまいちわからないので、本エントリではおとなしく慣習に迎合して大文字にしています。

おわりに

AWX で Kerberos 認証を使う方法を紹介しました。大規模な環境では、操作対象側を GPO で設定してしまえば便利に使えそうですね。

@kurokobo

くろいです。ギターアンサンブルやら音響やらがフィールドの IT やさんなアルトギター弾き。たまこう 48 期ぎたさん、SFC '07 おんぞう、新日本ギターアンサンブル、Rubinetto。今は野良気味。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です