てくなべ (tekunabe)

ansible / network / automation

[Ansible] Cisco ACI モジュールの署名ベース認証方式の仕組みと準備とPlaybookの書き方

【目次】

■ はじめに

Ansible は Cisco ACI にも対応して、多数のモジュールがあります。APICREST API を叩く仕様になっています。

Cisco ACI Guide という Ansible の公式ドキュメントにも記載されていますが、認証方式は 2通りあります。

  1. パスワードベース認証方式 (Password-based authentication)
  2. 署名ベース認証方式 (Signature-based authentication using certificates)

この記事では、署名ベース認証方式の仕組み、環境の準備、Playbook の書き方を説明します。 少々長くなるので先にまとめます。

  • パスワードベース認証では、DoS対策機能にひっかかってしまうことがあるため、自動化においては署名ベース認証方式のほうが吉
  • 準備として、予めAnsible 側で証明書(公開鍵)と秘密鍵のペアを生成し、証明書を APIC に登録しておく
  • Ansible から APIC へのリクエスト時に URL などを元にした署名を Cookie にセットし、APIC で検証、認証する
  • 各 ACI モジュールでは、署名ベース認証用の certificate_nameprivate_key オプションを利用する
  • certificate_name のデフォルト値は private_key の指定方法によって異なる

前提環境

  • APIC: Cisco DevNet Sandbox (APIC 4.1)
  • Ansible 2.8.6
  • 署名ベース認証の対象ユーザー: admin
    • 手順の検証容易性のために admin を利用していますが、運用環境では他のユーザーを利用を推奨

(参考)パスワードベース認証方式の制限

パスワードベース認証方式は、Web GUI と同じ認証方式なので手軽で便利ですが、Password-based authentication の Note や、Known issues には、「パスワード認証は、リクエストの頻度によっては(D)DoS対策の機能にひっかかり、HTTP 503 でログインエラーになる」旨が書かれています。署名ベース認証方式は、この問題の解決にもなります。

Password-based authentication also may trigger anti-DoS measures in ACI v3.1+ that causes session throttling and results in HTTP 503 errors and login failures.

  

Starting with ACI v3.1 the APIC will actively throttle password-based authenticated connection rates over a specific threshold. This is as part of an anti-DDOS measure but can act up when using Ansible with ACI using password-based authentication. Currently, one solution is to increase this threshold within the nginx configuration, but using signature-based authentication is recommended.




■ 仕組み

署名ベース認証方式は、SSH 接続時の公開鍵認証や SSL/TLS におけるクライアント認証とは異なる方式です。

ACI の公式ドキュメントの以下のページや、Ansible のソースコードをもとにして分かった仕組みについてまとめます。

予めしておくこと

予め、以下の流れで準備しておきます。

f:id:akira6592:20191102204740p:plain:w500
秘密鍵と証明書を作成して、APICに証明書を登録しておく

  1. ユーザーの自己署名の証明書(公開鍵含む)と秘密鍵を生成する
  2. 証明書を APIC 側に登録する

より具体的な手順は、後述の「環境準備の手順」で説明します。

リクエストごとに行っていること

リクエストごとに以下の流れで署名データを作成して認証しています。

f:id:akira6592:20191102205029p:plain
署名データなどをCookieに付加してAPIC側で検証

リクエストごとに以下の流れで署名データを作成、付加して認証しています。

  1. Ansible 側で、リクエストの HTTP メソッド(GET/POST/DELETEなど)と、URL、ペイロード(実データのjson/xml)を文字列連結する
  2. Ansible 側で、1 の文字列データのハッシュ値を sha256 で計算する
  3. Ansible 側で、2 のハッシュ値に対して、ユーザーの秘密鍵(A)を用いて署名を生成する
  4. Ansible 側で、Cookie に、署名や、証明書のDN(Distinguished Name: どのユーザーのどの証明書か)などを付加する。DN には、後述するモジュールの usernamecertificate_name オプションの名前が利用される
  5. Ansible から APICREST API のリクエストをする
  6. APIC 側で、送られてきたリクエストの HTTP メソッド(GET/POST/DELETEなど)と、URL、ペイロード(実データのjson/xml)を文字列連結する(Ansible側の 1 と同様)
  7. APIC 側で、5 の文字列データのハッシュ値を sha256 で計算する(Ansible側の 2 と同様)
  8. APIC 側で、リクエストの Cookie 内の証明書のDN(どのユーザーのどの証明書か)から、ユーザーの証明書(公開鍵)を特定
  9. APIC 側で、リクエストの Cookie 内の署名を、ユーザーの証明書(公開鍵)で復号し、元のハッシュ値を計算する
  10. APIC 側で、7 と 9 結果のハッシュ値が一致することの確認をもってユーザー認証する

APIC 側の処理はユーザー側(Ansible)で行っていることからの推測

仕組みは以上です。




■ 環境準備の手順

実際に、Ansible の ACI モジュールで署名ベースの認証を利用するために必要な手順です。

Ansible 側でユーザー側で証明書(公開鍵)と秘密鍵を生成する

Asible 公式ドキュメントの Generate certificate and private key に従い、証明書(公開鍵)と秘密鍵を生成します。有効期限などはセキュリティポリシーに応じて変えます。

$ openssl req -new -newkey rsa:1024 -days 36500 -nodes -x509 -keyout admin.key -out admin.crt -subj '/CN=Admin/O=Your Company/C=US'

なお、subject に指定する値は、認証そのものには影響しません。

APIC 側でユーザーの証明書を登録する

手動で登録する方法と、Ansible で登録する方法があります。

手動で登録する場合

今回利用している Cisco DevNet Sandbox の環境(APIC 4.1)では、Ansible 公式ドキュメントの Configure your local user のステップでは、証明書登録画面にたどり着けませんでした。

代わりに、以下の手順で証明書を登録します。

  1. Admin > AAA > Users でユーザー一覧を開き、対象のユーザーをクリックします。なお、署名ベース認証に対応しているの Local User のみです。

    f:id:akira6592:20191102205454p:plain
    対象ユーザーの選択

  2. 対象ユーザ設定画面内の User Certificates+ をクリックします。

    f:id:akira6592:20191102205530p:plain
    証明書追加ボタン

  3. Create X509 Certificate 画面の NameData を入力して、Submit ボタンをクリックします。

    f:id:akira6592:20191102205605p:plain
    証明書の名前の指定とデータの貼り付け

フィールド名 説明
Name 証明書の名前を指定します。ユーザー名と異なる名前でも構いません。後述する Ansible モジュールで指定する certificate_name が指す名前です。
Data Ansible 側で生成した証明書のデータ(ここでは admin.crt の内容)を貼り付けます。

Ansible で登録する場合

aci_aaa_user_certificate モジュールによって、ユーザーの証明書を登録作業自身を Ansible で実行できます。

# 証明書を登録する Playbook。このタスクの認証自体はパスワードベース。
- name: Ensure we have a certificate installed
  aci_aaa_user_certificate: 
    host: my-apic-1
    username: admin
    password: my-password

    aaa_user: admin   # 証明書を登録する対象のユーザー名
    certificate_name: admin   # 証明書の名前、Create X509 Certificate 画面の Name 相当
    certificate: "{{ lookup('file', 'pki/admin.crt') }}"  # 証明書データ、Create X509 Certificate 画面の Data 相当

引用元: 公式ドキュメント - Configure your local user、コメントは追記

環境の準備は以上です。




■ 署名ベース認証方式を利用するPlaybook の書き方

次は 署名ベース認証方式を利用するPlaybook の書き方についてです。

Playbook

署名ベース認証では、証明書の名前 certificate_name と、秘密鍵のパスまたはデータ private_key というオプションを利用します。

---
- hosts: apic
  gather_facts: no

  tasks:
    - name: tenant
      aci_tenant:
        host: "{{ ansible_host }}"
        username: admin               # ユーザー名
        certificate_name: admin       # 証明書の名前
        private_key: admin.key    # 秘密鍵
        validate_certs: no
        tenant: test_tenant01
        state: present

各オプションと証明書設定画面のフィールドの関係

オプションと証明書設定画面のフィールドの関係は以下のとおりです。

f:id:akira6592:20191102211020p:plain:w500
各オプションと証明書設定画面のフィールドの関係

  • username オプション
    • ユーザ名を指定
    • ユーザー名と証明書の名前が同じであれば usernamecertificate_name のどちらかの指定のみでも可
  • certificate_name オプション
    • 証明書の名前を指定
    • APIC-側でユーザーの証明書を登録する」の手順で name フィールドに指定した名前。今回の場合は admin
    • ユーザー名と証明書の名前が同じであれば usernamecertificate_name のどちらかの指定のみでも可
  • private_key オプション

certificate_name オプションのデフォルト値は、private_key オプションの指定の仕方によります。

private_key の指定方法 certificate_name のデフォルト
データそのものを指定した場合(file lookup plugin 含む) username で指定したユーザー名
秘密鍵のパスを指定した場合 秘密鍵basename から拡張子を除外した値(admin.key なら admin

対応するコードは aci.py のこのあたりです。(当初、各モジュールのページにちゃんと書いてあったことに気が付かずにコードを読んでいました・・)

いくつか具体例をあげます。

      # 1. わかりやすさ重視ですべて明示する場合
      username: admin
      certificate_name: admin
      private_key: "{{ lookup('file', 'crt/admin.key') }}"

      # 2. ユーザー名と証明書の名前が同じの場合
      ## certificate_name 省略パターン
      username: admin
      private_key: "{{ lookup('file', 'crt/admin.key') }}"

      ## username 省略パターン
      certificate_name: admin
      private_key: "{{ lookup('file', 'crt/admin.key') }}"

      # 3. ユーザー名と証明書の名前が異なる場合
      username: admin
      certificate_name: admincert
      private_key: "{{ lookup('file', 'crt/admin.key') }}"

      # 4. ユーザー名、証明書の名前、秘密鍵ファイルのファイル名が同じの場合
      private_key: "crt/admin.key"

補足

  • パスワード認証では usernamepaswword オプションを利用します。
  • validate_certs オプションはユーザー認証には直接関係ありません。APIC への HTTPS 接続する際のサーバー証明書の確認に関するオプションです。

Playbook の実行

Playbook を実行した例を掲載します。

$ ansible-playbook -i inventory.ini tenant.yml 

PLAY [apic] **********************************************************************

TASK [tenant] ********************************************************************

changed: [apic01]

PLAY RECAP ***********************************************************************
apic01            : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

無事に、署名ベース認証方式を利用して、Ansible の ACI モジュールを実行できました。




■ まとめ

APIC が備える認証方式のうち、署名ベース認証方式の仕組み、準備、Playbook の書き方についてまとめました。 以下、冒頭に掲載したまとめを再掲します。

  • パスワードベース認証では、DoS対策機能にひっかかってしまうことがあるため、自動化においては署名ベース認証方式のほうが吉
  • 準備として、予めAnsible 側で証明書(公開鍵)と秘密鍵のペアを生成し、証明書を APIC に登録しておく
  • Ansible から APIC へのリクエスト時に URL などを元にした署名を Cookie にセットし、APIC で検証、認証する
  • 各 ACI モジュールでは、署名ベース認証用の certificate_nameprivate_key オプションを利用する
  • certificate_name のデフォルト値は private_key の指定方法によって異なる