morikawa

インフラグループの森川です。

今回は Ansible 2.0 でリリースされた block ディレクティブ について、ここ 1 年半ほど使ってみて便利だったシーンをピックアップしてみようと思います。

個人的に良くなったと思うのは以下の 3 つでした。

  • tags 設定が集約できる
  • 条件分岐の集約ができる
  • タスクの階層が見た目でも分かりやすい

これらのメリットについて、PostgreSQL サーバのインストールを例にとって説明したいと思います。

block ディレクティブの無い Playbook

block ディレクティブを用いずに書いた場合の Playbook の一例が以下となります。

なお、個々のタスクを処理内容ごとに部分的に実行可能とするため、私が Playbook を書く際は原則タスクに対して tags の設定を行うことにしており、下記の Playbook もそのようなルールで書いています。

- yum:
    name: postgresql-server
  tags:
    - postgresql-server
    - postgresql-server-install

- stat:
    path: /var/lib/pgsql/data/postgresql.conf
  register: postgresql_conf
  tags:
    - postgresql-server
    - postgresql-server-init

- shell: /usr/bin/initdb -D /var/lib/pgsql/data -E UTF-8 --no-locale
  become: yes
  become_user: postgres
  tags:
    - postgresql-server
    - postgresql-server-init
  when: postgresql_conf.stat.exists == false

- stat:
    path: /var/lib/pgsql/data/postgresql.conf
  register: postgresql_conf
  tags:
    - postgresql-server
    - postgresql-server-config

- lineinfile:
    dest:   /var/lib/pgsql/data/postgresql.conf
    regexp: ^[#\s]*listen_addresses\s*=
    line:   "listen_addresses = '*'"
  tags:
    - postgresql-server
    - postgresql-server-config
  when: postgresql_conf.stat.exists == true

- lineinfile:
    dest:   /var/lib/pgsql/data/postgresql.conf
    regexp: ^[#\s]*log_filename\s*=
    line:   "log_filename = 'postgresql-%Y-%m-%d_%H%M%S'"
  tags:
    - postgresql-server
    - postgresql-server-config
  when: postgresql_conf.stat.exists == true

- lineinfile:
    dest:   /var/lib/pgsql/data/postgresql.conf
    regexp: ^[#\s]*log_filename\s*=
    line:   "log_filename = 'postgresql-%Y-%m-%d_%H%M%S'"
  tags:
    - postgresql-server
    - postgresql-server-config
  when: postgresql_conf.stat.exists == true

- service:
    name:    postgresql
    state:   started
    enabled: yes
  tags:
    - postgresql-server
    - postgresql-server-start

block ディレクティブで書き換えた Playbook

上記 Playbook を block ディレクティブを使って書き直すと、例えば以下のようになります。

- block: 

  - yum:
      name: postgresql-server
    tags: postgresql-server-install

  - block: 

    - stat:
        path: /var/lib/pgsql/data/postgresql.conf
      register: postgresql_conf

    - shell: /usr/bin/initdb -D /var/lib/pgsql/data -E UTF-8 --no-locale
      become: yes
      become_user: postgres
      when: postgresql_conf.stat.exists == false

    tags: postgresql-server-init

  - block: 

    - stat:
        path: /var/lib/pgsql/data/postgresql.conf
      register: postgresql_conf

    - block:

      - lineinfile:
          dest:   /var/lib/pgsql/data/postgresql.conf
          regexp: ^[#\s]*listen_addresses\s*=
          line:   "listen_addresses = '*'"

      - lineinfile:
          dest:   /var/lib/pgsql/data/postgresql.conf
          regexp: ^[#\s]*log_filename\s*=
          line:   "log_filename = 'postgresql-%Y-%m-%d_%H%M%S'"

      - lineinfile:
          dest:   /var/lib/pgsql/data/postgresql.conf
          regexp: ^[#\s]*log_filename\s*=
          line:   "log_filename = 'postgresql-%Y-%m-%d_%H%M%S'"

      when: postgresql_conf.stat.exists == true

    tags: postgresql-server-config

  - service:
      name:    postgresql
      state:   started
      enabled: yes
    tags: postgresql-server-start

  tags:
    - postgresql-server

block ディレクティブにより得られるメリット

tags 設定が集約できる

block ディレクティブがない状態では、個々のタスクごとに tags を書く以外には、rule や include 単位で tags を設定する方法しか無く、一つの Playbook ファイル内に記載するタスクを役割ごとに分けたり、特定の処理群に tags を設定するのは結構な手間でした。

block ディレクティブにより、複数のタスクは block 内に含め、その block に対して tags を設定する方法が取れるようになったため、tags を記述する手間が省けるとともに、特定の tags に含まれるタスクがどれか、といったことも視覚的に把握しやすくなりました。

上記の例でいうと、 postgresql-server-config など綺麗にまとめることができています。

条件分岐の集約ができる

tags 以外に、when 句による条件分岐についても block ディレクティブで集約が可能です。

上記の例では、設定ファイルの変更について /var/lib/pgsql/data/postgresql.conf の存在を確認した結果を用いて、対象の設定ファイルの書き換えを行っています。

従来の場合タスクごとに when の記載が必要ですが、block を利用することで when の記載が 1 箇所で済みます。また、視覚的にも把握がしやすいメリットがあります。

タスクの階層が見た目でも分かりやすい

tags や when の記載の有無を別にしても、block によりタスクをグルーピングすることができるため、従来のようにタスクが全てフラットに並ぶ書き方に比べ、一連の処理群についてまとまりを視覚的に把握しやすいと言えるでしょう。

まとめ

今回は Ansible 2.0 で追加された block ディレクティブについて、実際に使ってきた経験を踏まえて簡単な記事にさせてもらいました。

ソースコードの読みやすさが大切なように、 Playbook も読みやすさは重要と思います。最近私が Playbook を記述する際はほぼ必ず block ディレクティブは利用するようにしていますし、過去書いたものも時間があれば block ディレクティブで書き直すなどしています。

余談ですが、block に rescue や always を組み合わせて利用することで、タスクの例外処理も可能になります。これまで ignore_errors を用いたり結果のステータスを保存した変数で条件分岐していたりしていた部分もより簡潔にかけるはずなので、活用してみたいと考えています。

たまたまトライコーンの場合はインフラ周りのエンジニアからサーバ構築・運用のコスト軽減のために Ansible の導入を進めてきましたが、最近は開発系のエンジニアもいじるようになってくれており、徐々にサーバの構成管理もコード化が進んできていて、良い感じです。

それでは

参考サイト

Tags:

uzuki

開発グループの卯月です。

最近(といっても半年程前ですが)、私が担当しているプロダクトに TOTP (RFC 6238) で2要素認証を導入しました。
TOTP を扱うライブラリや技術的な資料は複数存在しますが、TOTP に対応した2段階認証アプリの仕様にあわせた otpauth URI 生成方法について、イメージが掴みやすい HowTo 記事が無かったので、その辺りを書いていきたいと思います。

なお、TOTP 認証の実装方法や技術的な解説は本記事では紹介しませんのでご了承ください。

※本記事は2017年6月8日時点での情報を元に作成しています。

どの2段階認証アプリをサポートするか

利用者は基本的にスマホの2段階認証アプリを利用するかと思います。
2段階認証アプリは無料アプリとして無数に存在しているので、内製するよりかは既存アプリを利用した方が良いです。

本記事では、下記2つのアプリで利用可能な otpauth URI のフォーマットについて紹介します。

そもそも otpauth URI って何?

TOTP の設定を2段階認証アプリ側に読み込ませるための URI です。
サーバ側で otpauth URI を生成し、QR コードで表示→スマホで読み込ませ2段階認証アプリに保存する流れになるかと思います。
なお、この otpauth URI には秘密鍵が含まれますので、傍受の危険性のある経路での送信や不必要に保存させることはやめましょう。(例:非暗号化経路でのメール送信等)
QRコードで読み込ませる場合であれば比較的傍受され難いですし、キャッシュは除き2段階認証アプリ以外には保存されないのでお勧めです。

詳しい仕様は下記をご覧ください。

otpauth URI はどう生成すれば良いの?

結論から言うと下記フォーマットに準拠していれば、Google Authenticator / IIJ SmartKey をサポートできます。
otpauth://totp/[issuer]:[accountname]?secret=[secret]&issuer=[issuer]&algorithm=SHA1&digits=6&period=30

では何故このフォーマットで Google Authenticator / IIJ SmartKey がサポートできるのかを説明します。

ベースとなる URI は Google Authenticator / IIJ SmartKey 共通です。
otpauth://[TYPE]/[LABEL]?[PARAMETERS]

TYPE

Google Authenticator では hotp / totp どちらとも対応しています。
IIJ SmartKey では totp のみ対応しています。

今回は TOTP 認証を行いたいので、[TYPE] には「totp」を指定します。

LABEL

Google Authenticator では、accountname のみ または issuer と accountname をコロン(:)で連結したものが利用できます。
IIJ SmartKey では、issuer と accountname をコロン(:)で連結したものが利用できます。

IIJ SmartKey の方が制限きついので、[LABEL] には「[issuer]:[accountname]」を指定します。

issuer(発行者)
Google Authenticator / IIJ SmartKey 共に、値は URL エンコード (ABNF) する必要があります。
基本的にはサービス名にすると良いでしょう。

accountname(アカウント名)
Google Authenticator / IIJ SmartKey 共に、値は URL エンコード (ABNF) する必要があります。
基本的には利用者のアカウント名やログイン ID にすると良いでしょう。

PARAMETERS

secret(秘密鍵)
Google Authenticator / IIJ SmartKey 共に指定必須であり、値は秘密鍵を Base32 エンコードした文字列で指定する必要があります。

これは共通仕様なので、そのまま指定します。

issuer(発行者)
Google Authenticator / IIJ SmartKey 共に、値は LABEL で指定した issuer を指定する必要があります。
Google Authenticator では強く推奨しているだけですが、IIJ SmartKey では指定必須です。

IIJ SmartKey で指定必須なので、必ず指定するようにします。
なお、[LABEL] 内の issuer と同じ値を指定するよう気を付けましょう。

algorithm(ハッシュアルゴリズム)
Google Authenticator / IIJ SmartKey 共に指定は任意(デフォルト値は SHA1)です。
Google Authenticator では、値に SHA1 / SHA256 / SHA512 が指定できますが無視されます。
IIJ SmartKey では、値に SHA1 / SHA256 / SHA512 / MD5 が指定できます。

Google Authenticator では指定しても無視されデフォルト値である「SHA1」が利用されます。
今後デフォルト値が変更となる可能性もなくはないので、念のため「SHA1」を指定しておきましょう。

digits(表示されるトークンの表示桁数)
Google Authenticator / IIJ SmartKey 共に指定は任意(デフォルト値は 6)です。
Google Authenticator では、値に 6 / 8 が指定できますが無視されます。
IIJ SmartKey では、値に 6 / 8 が指定できます。

Google Authenticator では指定しても無視されデフォルト値である「6」が利用されます。
今後デフォルト値が変更となる可能性もなくはないので、念のため「6」を指定しておきましょう。

period(更新間隔(秒))
Google Authenticator / IIJ SmartKey 共に指定は任意(デフォルト値は 30)です。
Google Authenticator では、値に数値が指定できますが無視されます。
IIJ SmartKey では、値に数値が指定できます。

Google Authenticator では指定しても無視されデフォルト値である「30」が利用されます。
今後デフォルト値が変更となる可能性もなくはないので、念のため「30」を指定しておきましょう。

counter
Google Authenticator の TYPE が hotp の時に指定するパラメータなので今回は指定しません。

icon
IIJ SmartKey のオプションパラメータなので今回は指定しません。

まとめ

どのアプリも otpauth URI の仕様は大枠では共通ですが、細かい箇所が結構異なっていますので、サポートしたいアプリの仕様はちゃんと確認することをお勧めします。

再掲しますが、Google Authenticator / IIJ SmartKey は下記フォーマットでサポートできます!
otpauth://totp/[issuer]:[accountname]?secret=[secret]&issuer=[issuer]&algorithm=SHA1&digits=6&period=30

Tags: , ,

1 / 6812345...102030...最後 »