AKSのローカルアカウントを無効化しつつ、非対話型ログインを行う

Posted on Dec 26, 2021

何の話か

Azure Kubernetes Service(AKS)でローカルアカウントを無効化できるようになり、GAしました。ローカルアカウントを無効にすると、AKSをセキュアに運用しやすくなります。検討したいところです。

だがしかし。AKSの認証全般に言えるのですが、ドキュメントが難し目です。前提知識が多め、加えて、従来の手段やその組み合わせもカバーしているため、読む立場によっては混乱します。

また、ローカルアカウントを無効にした際の考慮点、特に影響が大きい非対話型ログインに関する説明が淡白です。

無効化の背景や影響、打ち手を整理したい。これが当記事の動機です。

なぜ無効化できるようになったのか

ところで、ローカルアカウントという表現がピンとこないかもしれません。具体例は、ローカルに保存したクライアント証明書でAKSの認証ができる管理者アカウントです。“az aks get-credentials"コマンドを、”–admin"オプション付きで実行して取得する、あれです。

このコマンドは、クライアント証明書を配布する仕組みとしては有用です。いっぽうで、クライアント証明書があればAzure AD認証をパスできてしまうため、Azure ADの利点であるきめ細かな認証、監査ができません。加えて、証明書を無効にするためのローテーションも、気軽に実施できる作業ではありません。

そこで、AKSマネージドAzure AD統合AzureとKubernetesのRBAC統合など、クライアント証明書認証無しでも運用できる仕組みが整ったいま、使えないようにしたい、というニーズが高まっています。これがローカルアカウントを無効化できるようになった背景です。

無効化すると何が起こるのか

無効化は簡単です。Terraformであれば、local_account_disabledで指定するだけです。

local_account_disabled = true

なお、ローカルアカウントを封じてしまうため、管理者向けに別の認証手段が必要です。そこで、AKSマネージドAzure AD統合とRBAC統合を使います。Terraformであれば以下のように指定します。なお、admin_group_object_idsには、管理者ロールを付与したい、Azure ADグループのオブジェクトIDを指定します。

role_based_access_control {
  enabled = true
  azure_active_directory {
    managed                = true
    azure_rbac_enabled     = true
    admin_group_object_ids = ["my-aad-group-object-id"]
  }
}

では実際にクラスターを作って、挙動や設定される内容を確認してみましょう。

この記事の作業者には、以下の設定を行っています。

  • Azure RBAC: 作業者は、Azureのサブスクリプション所有者
  • Azure AD: 管理者向けグループを作り、作業者を登録
  • AKS: Azure ADに作った管理者向けグループを、admin_group_object_idsへ指定

まず、Terraformで先述した設定にてクラスターを作ります(省略)。

では、作ったクラスターにアクセスして確かめてみましょう。

kubeconfigファイルが空っぽの状態からはじめます。ちなみに、kはkubectlのエイリアスです。

$ k config get-contexts 
CURRENT   NAME   CLUSTER   AUTHINFO   NAMESPACE
$ cat ~/.kube/config 

まず、–adminオプションを使ったクレデンシャルの取得が封じられているかを確認します。

$ az aks get-credentials -g my-rg -n my-cluster --admin
(BadRequest) Getting static credential is not allowed because this cluster is set to disable local accounts.
Code: BadRequest
Message: Getting static credential is not allowed because this cluster is set to disable local accounts.

期待通り、使えなくなっていますね。

では、–adminオプション無しで実行します。

$ az aks get-credentials -g my-rg -n my-cluster
Merged "my-cluster" as current context in /home/ubuntu/.kube/config

$ k config get-contexts 
CURRENT   NAME                           CLUSTER                        AUTHINFO                                                          NAMESPACE
*         my-cluster   my-cluster   clusterUser_my-rg_my-cluster 

何かを取得し、kubeconfigファイルのコンテキストにマージしたようです。その中身を見てみましょう。

$ cat ~/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: [SENSITIVE]
    server: https://my-cluster.hcp.japaneast.azmk8s.io:443
  name: my-cluster
contexts:
- context:
    cluster: my-cluster
    user: clusterUser_my-rg_my-cluster
  name: my-cluster
current-context: my-cluster
kind: Config
preferences: {}
users:
- name: clusterUser_my-rg_my-cluster
  user:
    auth-provider:
      config:
        apiserver-id: 6dae42f8-4368-4678-94ff-3960e28e3630
        client-id: my-client-id
        config-mode: '1'
        environment: AzurePublicCloud
        tenant-id: my-tenant-id
      name: azure

アクセス先のクラスターとユーザーに関する情報、その組み合わせであるコンテキストが生成されていますね。なお、.user.auth-provider.config.apiserver-idで設定されている “6dae42f8-4368-4678-94ff-3960e28e3630” は、AKSマネージドAzure AD統合機能を使うユーザーで共通のアプリケーションIDです。

このコンテキストでkubectlを使ってAKSクラスターへアクセスすると、Azure AD認証を求められます。試しにPodの一覧を取得してみましょう。

$ k get po
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code 1234ABCD to authenticate.
No resources found in default namespace.

まずAzure AD認証が求められます。表示されたコードを使ってAzure AD認証を行なうと、Podの一覧を取得できました。Podは作っていないので、No resources foundでOKです。認証が成功し、defaultネームスペースのPodをリストできることがわかります。

この流れでは、Azure ADから得たトークン情報などがkubeconfigファイルにマージされます。以降、AKSはこのトークンで認証します。詳細は、公式ドキュメントを参考にしてください。

$ cat ~/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: [SENSITIVE]
    server: https://my-cluster.hcp.japaneast.azmk8s.io:443
  name: my-cluster
contexts:
- context:
    cluster: my-cluster
    user: clusterUser_my-rg_my-cluster
  name: my-cluster
current-context: my-cluster
kind: Config
preferences: {}
users:
- name: clusterUser_my-rg_my-cluster
  user:
    auth-provider:
      config:
        access-token: [SENSITIVE]
        apiserver-id: 6dae42f8-4368-4678-94ff-3960e28e3630
        client-id: my-client-id
        config-mode: "1"
        environment: AzurePublicCloud
        expires-in: "3695"
        expires-on: "1640327651"
        refresh-token: [SENSITIVE]
        tenant-id: my-tenant-id
      name: azure

なお、AKSクラスター上でこのユーザーがどのようにロール割り当てされているのかも、気になりますよね。見てみましょう。

$ k get clusterrolebindings aks-cluster-admin-binding-aad -o yaml 
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
...
  name: aks-cluster-admin-binding-aad
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: my-aad-group-object-id

Azure ADのグループIDに対して、cluster-adminロールが割り当てられています。

以上、AKSマネージドAzure ADとRBAC統合した環境で、ローカルアカウントを無効化した場合に何が起こるのか、大事なところをおさえました。

非対話型ログインする方法

では2つ目のテーマです。ローカルアカウントを無効化すると、これまでクライアント証明書を使っていたツールに影響します。特に、Azure ADと対話型で認証できないツールが問題です。非対話型で実行したいワークフロー、ツールは多くあります。代表的なものは、GitHub ActionsやTerraformです。

例えばTerraformでKubernetesの設定を行うケースを考えてみましょう。ドキュメントで紹介されているAKS向けプロバイダー設定は、以下です。

data "azurerm_kubernetes_cluster" "example" {
  name                = "myakscluster"
  resource_group_name = "my-example-resource-group"
}

provider "kubernetes" {
  host                   = data.azurerm_kubernetes_cluster.example.kube_config.0.host
  client_certificate     = "${base64decode(data.azurerm_kubernetes_cluster.example.kube_config.0.client_certificate)}"
  client_key             = "${base64decode(data.azurerm_kubernetes_cluster.example.kube_config.0.client_key)}"
  cluster_ca_certificate = "${base64decode(data.azurerm_kubernetes_cluster.example.kube_config.0.cluster_ca_certificate)}"
}

しかし、AKSのローカルアカウントを無効化すると、client_keyとclient_certificateを取得できなくなります。つまりこの設定では認証できません。

ではどうすればいいでしょうか。ここで、Azureのドキュメントではあっさりした紹介にとどまっていた、kubeloginを使います。

kubeloginは、client-go credential プラグインのAzure向け実装です。client-goは代表的なKubernetesクライアントで、kubectlやTerraform Kubernetesプロバイダーでも使われています。kubeloginは、client-goがAzure ADからトークンなどのクレデンシャルを取得できるようサポートします。非対話型ログインはそのシナリオのひとつです。

使い方から入るとピンときやすいと思います。例えばTerraformのKubernetesプロバイダーでは、以下のように使います。

provider "kubernetes" {
  host                   = data.azurerm_kubernetes_cluster.example.kube_config.0.host
  cluster_ca_certificate = "${base64decode(data.azurerm_kubernetes_cluster.example.kube_config.0.cluster_ca_certificate)}"

  exec {
    api_version = "client.authentication.k8s.io/v1beta1"
    command     = "kubelogin"
    args = [
      "get-token",
      "--login",
      "azurecli",
      "--server-id",
      "6dae42f8-4368-4678-94ff-3960e28e3630"
    ]
  }
}

kubeloginは実行可能なプログラムです。パスを通し、client-goが実行できるようにしておきます。そして"get-token"は、Azure ADからトークンを得るサブコマンドです。また、"–login azurecli"オプションで、Azure CLIの認証情報を利用します。すでにAzure CLIでAzureにログインしている状態であれば、改めてAzure AD認証をする必要がありません。これが非対話型ログインできる理由です。

なお、kubeloginにはget-tokenの他に、convert-kubeconfigサブコマンドがあります。このサブコマンドは、kubeconfigファイルに、プラグインを使うように設定を書き込みます。例えばkubectlを非対話型ログインで使うことができる、というわけです。

ではconvert-kubeconfigサブコマンドも試してみましょう。kubeconfigファイルの変化を見たいため、az aks get-credentialsコマンドを–overwrite-existingオプションで実行し、すでに取得したトークンなど認証情報をリセットします。

$ az aks get-credentials -g my-rg -n my-cluster --overwrite-existing 
Merged "my-cluster" as current context in /home/ubuntu/.kube/config

$ cat ~/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: [SENSITIVE]
    server: https://my-cluster.hcp.japaneast.azmk8s.io:443
  name: my-cluster
contexts:
- context:
    cluster: my-cluster
    user: clusterUser_my-rg_my-cluster
  name: my-cluster
current-context: my-cluster
kind: Config
preferences: {}
users:
- name: clusterUser_my-rg_my-cluster
  user:
    auth-provider:
      config:
        apiserver-id: 6dae42f8-4368-4678-94ff-3960e28e3630
        client-id: my-client-id
        config-mode: '1'
        environment: AzurePublicCloud
        tenant-id: my-tenant-id
      name: azure

kubelogin convert-kubeconfigを実行し、kubeconfigファイルの変化を見ます。

$ kubelogin convert-kubeconfig --login azurecli --server-id 6dae42f8-4368-4678-94ff-3960e28e3630

$ cat ~/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: [SENSITIVE]
    server: https://my-cluster.hcp.japaneast.azmk8s.io:443
  name: my-cluster
contexts:
- context:
    cluster: my-cluster
    user: clusterUser_my-rg_my-cluster
  name: my-cluster
current-context: my-cluster
kind: Config
preferences: {}
users:
- name: clusterUser_my-rg_my-cluster
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      args:
      - get-token
      - --server-id
      - 6dae42f8-4368-4678-94ff-3960e28e3630
      - --login
      - azurecli
      command: kubelogin
      env: null
      provideClusterInfo: false

先ほど見たTerraformと同様のプラグイン設定が入っていますね。これで、実行環境がAzure CLIで認証済みであれば、追加の認証なくkubectlを実行できる、というわけです。なお、kubeloginはAzure CLIだけでなくサービスプリンシパルやマネージドIDもサポートしています。必要に応じて確認してください。

なお、非対話型ログインが必要なワークフローでは、kubeconfigファイルは普段使いとは分ける、または使い捨てることが多いでしょう。その場合、KUBECONFIG環境変数にkubeconfigファイルへのパスを設定し、kubeloginを実行してください。

ワークフローのサンプル

ワークフローのサンプルとして、GitHub Actionsでの使い方を紹介しておきます。以下のIssueのコメントを参考にして下さい。ダウンロードするkubeloginのバージョンなど、見直しをお忘れなく。

Unable to use AAD Cluster with non-admin users