目次
はじめに
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-libs
と krb5-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 のイメージの汎用性が損なわれる点、トライアンドエラーがしにくくなる点などから、今回は採用していません。
手順の概要
前述の目的を達成するため、以下の作業を行います。
- 操作対象の Windows ホストの構成
- Kerberos 認証を受け入れるよう WinRM を構成する
- 接続に利用するドメインユーザに WinRM での操作権限を付与する
- Kubernetes の構成
- Kubernetes 上の ConfigMap リソースとして
krb5.conf
を作成する
- Kubernetes 上の ConfigMap リソースとして
- 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_encryption
を always
にすれば暗号化を二重にさせることもできるようです)。
今回は 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 の data
に krb5.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 Groups
で Add
し、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 のバージョンで若干差異があります)を踏襲しつつ、volumes
と volumeMounts
を足した形です。先の手順で作成した ConfigMap awx-kerberos-config
の krb5.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 が実行される際、kinit
に expect
でそのまま渡されます。
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 の Verbosity
を 4 (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 ping
や busybox 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]
で canonicalize
を true
にする(またはコマンドライン引数で -C
を付ける)と、実は kinit
でのチケット発行が小文字のレルムでもできるようになります。……が、Windows 側の Kerberos や GSSAPI の実装で小文字が通用する範囲がいまいちわからないので、本エントリではおとなしく慣習に迎合して大文字にしています。
おわりに
AWX で Kerberos 認証を使う方法を紹介しました。大規模な環境では、操作対象側を GPO で設定してしまえば便利に使えそうですね。