IBM MQのコンテナ環境での認証/認可を考える

# はじめに

IBM MQはV9.1でコンテナをサポート､V9.2でOpenShift Operatorをサポートした｡
サポート当初は､コンテナで利用するにあたってはまだまだ使いづらい部分があったが､9.2のContinuous Delivery(CD)リリースでもコンテナ環境における機能拡充を続け､十分利用に足る最低限の機能が追加された｡
ここでは､MQをコンテナ環境(主にOpenShift/k8s)で利用するにあたって､MQへの接続における認証・認可周りについて整理する｡コンテナ環境でバインディング接続(ローカル接続)を行うケースは通常想定されないため､ここではクライアント接続に限定して考える｡またDQM(QMGR間)接続についてもここでは特段言及しない｡

# MQが提供する認証・認可

## 認可

MQに接続するアプリケーションの認可はOAM(Object authority manager)で設定することになる｡OAM自体の制御は環境変数`MQSNOAUT`で設定する｡  
OpenShiftのOperatorで構成されるQMGRでは､デフォルトで`MQSNOAUT=yes`が設定される｡一度OAMを無効にすると､有効化にはQMGRの再作成が必要となる｡
OAMを有効にするには**環境変数を削除する**｡ `MQSNOAUT=no`では無いので注意｡

:::note info
コンテナ環境におけるOAMの構成

OAMの構成や構成情報の確認には通常､`setmqaut`や`dspmqaut`を利用するが､コンテナ環境では､QMGR作成後にコンテナ内でコマンド実行するような構成手順となるため適さない｡  
他の構成手段として､mqscコマンドの[SET AUTHREC](https://www.ibm.com/docs/en/ibm-mq/9.2?topic=reference-set-authrec-set-authority-records-multiplatforms)を利用することで､構成情報を`.mqsc`ファイルとして保持できる｡`.mqscファイル`はMQの[AutoConfig機能](https://www.ibm.com/docs/ja/ibm-mq/9.3?topic=commands-automatic-configuration-from-mqsc-script-startup)でQMGR起動時にファイルに記載したMQオブジェクトの定義文を読み込み､キューやチャネルと併せて構成することが可能となる｡
構成ファイルはConfigMapまたはSecret経由で渡すため､IaCとして構成をgitなどで管理することも可能となる｡
:::

## 認証

MQが提供する認証方式を認証の3要素で整理する｡

- Something You Know
  - CONNAUTH (パスワード認証)
    - CONNAUTHは､AUTHINFOオブジェクトで定義された定義に従い､OSまたはLDAPサーバーをID/PWリポジトリとしたパスワード認証を行う
    - パスワードはクライアントアプリケーションから渡す
- Something You Have
  - TLS証明書
    - OpenShiftクラスタ外からの接続の場合はTLS証明書が必須
      - passthroughタイプのRoute(TLS必須)を利用する必要があるため
- Something You Are
  - CHLAUTH
    - ADDRESSMAP(IPアドレス)
      - OpenShift/k8sクラスタ内での接続の場合､IPアドレスはエフェメラルなので､接続元IPでの認証には適さない
    - USERMAP(ユーザー名)
      - 通常､OpenShiftではコンテナが稼働するユーザーIDはOpenShiftで採番されたUIDが利用されるため､rootや特定のUIDを利用しない構成を取る｡
        - DockerfileでUSER指定されているようなコンテナを動かす場合は､SCC(Security Context Constraints: k8sのPod Security Policiesがベース)でnonrootを指定する
        - sccの適用は基本的にはセキュリティリスクとなるため､利用は避けたい
        - チェックされるのはUIDだけなので､**本当にその人なのか**の確認ができないため､USERMAP単体の利用は推奨できない
    - QMGRMAP(QMGR名)
      - 利用がQMGR間接続に限定される
    - SSLPEERMAP(TLSのDN名)
      - CHLAUTHは各タイプの条件に従ってMCAUSERの値をマップするため､クライアント毎に細かく制御する場合には多数の証明書を管理する必要がある

## 各機能の相互作用

認証・認可機能としてMQが提供する各機能がどのように作用かは[CHLAUTH および CONNAUTH の相互作用](https://www.ibm.com/docs/ja/ibm-mq/9.3?topic=records-interaction-chlauth-connauth)で説明されているが~~お世辞にも理解しやすいとは言えないので~~､もう少し整理してみる｡

### 解決されるユーザー名

それぞれの認証方式で解決(後段のOAMの認可で利用)されるID

- CHLAUTH
  - 各MAP(SSLPEERMAP, ADDRESSMAP, USERMAP, QMGRMAP)で指定するユーザーに解決される
      - アプリケーションが渡すID/PWは使用されない
  - :no_good: チャネル名が分かれば接続ができてしまうため､単独での利用では認証として弱い
- CONNAUTH
    - ~~コンテナ環境ではユーザー/pwのリポジトリとしてOSのものは利用できないため､**LDAPが必須**｡(以下はLDAPを利用した場合の挙動)~~ MQ V9.2.1で､qm.ini ServiceスタンザにUserExternalオプションが追加され､LDAPなしでユーザーに対するOAM設定が可能となる｡[Permitting non-operating system users in the Obuject Authority Manager](https://www.ibm.com/docs/en/ibm-mq/9.2?topic=wnim9-whats-new-in-mq-921-multiplatforms-base-advanced-entitlement#q134940___nonosuser)
    - アプリケーションはQMGRに以下のユーザー情報を渡す
        - プロセスの実行ユーザーID
        - 接続認証用のユーザーID(とpw)
    - CONNAUTH単独の場合
        - ADOPTCTX(YES)の場合
            - 接続認証用のユーザーIDで認証を行い､その結果としてLDAPから返されたユーザーID(AUTHINFOオブジェクトのSHORTUSRで解決されるユーザー名属性)で認可(OAM)が行われる
        - ADOPTCTX(NO)の場合
            - アプリケーションの実行ユーザーIDで認証が行われる
- CHLAUTH + CONNAUTH
    - AUTHINFOオブジェクトのADOPTCTX属性と､qm.iniのChlauthEarlyAdopt属性によって挙動が変わる
    - ADOPTCTX(YES)の場合
        - CONNAUTHで解決したユーザーID(SHORTUSER属性で解決されるもの)を利用してOAMでの認可を行う
        - CHLAUTHのUSERMAPでの探索に利用されるユーザーはqm.iniの`ChlauthEarlyAdopt`の設定で異なる
            - ChlauthEarlyAdopt=Y
                - CONNAUTHで解決したユーザーID(SHORTUSER属性で解決されるもの)を利用してCHLAUTHのマッピングを行う
            - ChlauthEarlyAdopt=N
                - アプリケーションの実行ユーザーIDを用いてCHLAUTHのマッピングを行う
        - いずれのケースでも､CHLAUTHでのMCAUSERのマッピングは無視され､AUTHINFOオブジェクトのSHORTUSER属性で解決されるユーザーを利用する
        - CHLAUTHの`USERSRC`が`NOACCESS`(接続不可)になっていないかのチェックのみ実施する
    - ADOPTCTX(NO)の場合
        - 接続認証用のID/pwはLDAPでの認証のみに使用し､後続の処理では使用しない｡CHLAUTHの各種MAPで解決したMCAUSERで認可を行う
- TLS証明書(w/CHLAUTH)
    - 証明書を持っている==正規のユーザーである証明(認証)
        - :ok_woman:: CHLAUTHのSSLPEERMAPで証明書を利用したマッピングでMCAUSERを解決し､そのユーザーで認可を実施する

# MQのコンテナ環境における認証/認可の実装パターン

やっと本題｡ MQには色々な認証機能があり､各機能の設定によって構成のバリエーションが様々である｡ここでは､分かり易さと構成のシンプルさを重視した構成パターンを紹介する｡

## LDAPを利用したCONNAUTH+CHLAUTH併用

- 以下のコンセプトで構成する
    - 認証・認可に使用するユーザーはアプリケーションから渡されるユーザーIDのみを使用する(ADOPTCTX=YESかつChlauthEarlyAdopt=Y)
    - AUTHINFOのSHORTUSR属性を､USERFIELDと同じにすることで､CONNAUTHでの認証の結果として返されるユーザーIDをそのままにする
    - CONNAUTHで､認証済みの特定のユーザーのみがチャネルアクセスできるようにする

:::note warn
CHLAUTHの併用は必須か?
CHLAUTHでのブロックまでしなくても､id/pwで認証の要素は満たすため､CHLAUTHの構成は過剰に思えるかもしれない｡

CONNAUTHだけだと､LDAPに登録されたMQクライアントアプリ用途ではないユーザー(LDAPが他システムと共用しているようなケース)でも認証は通してしまうが､OAMで権限を与えなければMQに対する操作はできない｡
とは言え､本来の認証・認可としては､認証の時点でブロックするのがお作法としては正しい気がするので､CHLAUTHの併用はしたほうが良い｡
:::

### CONNAUTH定義

方針としては
1. CONNAUTHの結果として返されるユーザーIDは､アプリケーションから渡されるものと同一にするため､認証に使用するユーザーIDフィールドを指定する`USERFIELD`属性と､`SHORTUSR`属性を同一にする
1. クライアント接続における認証を必須にするため､`CHCKCLNT`属性は`REQUIRED`にする
```bash
DEFINE AUTHINFO(OPENLDAP) AUTHTYPE(IDPWLDAP) ADOPTCTX(YES) CONNAME('<open_ldap_server_location>(389)') SHORTUSR('uid') USRFIELD('uid') BASEDNU('dc=my,dc=company,dc=com') LDAPUSER('cn=admin,dc=my,dc=company,dc=com') LDAPPWD('<admin_password>') CHCKCLNT(REQUIRED) SECCOMM(NO) REPLACE
```

### CHLAUTH

方針としては､すべてをブロックするエントリを作成し､必要なものだけ穴あけするようにする｡
1. デフォルトで定義されるCHLAUTHオブジェクトは削除する
1. すべてのチャネルにおけるすべての接続を防ぐエントリを作成する
1. 必要なチャネルに対して､必要なユーザーのみ接続を許可するエントリを作成する

```bash
* デフォルトオブジェクトの削除
SET CHLAUTH(*) TYPE(BLOCKUSER) ACTION(REMOVEALL)
SET CHLAUTH(SYSTEM.*) TYPE(ADDRESSMAP) ACTION(REMOVEALL)
SET CHLAUTH(SYSTEM.ADMIN.SVRCONN) TYPE(ADDRESSMAP) ACTION(REMOVEALL)
* すべてのチャネル接続をブロックする
SET CHLAUTH(*) TYPE(ADDRESSMAP) ADDRESS(*) USERSRC(NOACCESS) WARN(NO) ACTION(REPLACE)
* 必要な接続に対する穴あけをする
SET CHLAUTH(QM01.SVRCONN) TYPE(USERMAP) CLNTUSER('jmsapp1') USERSRC(MAP) MCAUSER('appuser') CHCKCLNT(REQUIRED) 
ACTION(REPLACE)
SET CHLAUTH(QM01.SVRCONN) TYPE(USERMAP) CLNTUSER('jmsapp2') USERSRC(MAP) MCAUSER('appuser') CHCKCLNT(REQUIRED) 
ACTION(REPLACE)
```

### OAM

アプリケーションの実行に必要な権限を付与する
```bash
SET AUTHREC OBJTYPE(QMGR) PRINCIPAL('jmsapp1') AUTHADD(CONNECT,INQ)
SET AUTHREC PROFILE(QL.APP1.**) OBJTYPE(QUEUE) PRINCIPAL('jmsapp1') AUTHADD(PUT,GET)
SET AUTHREC OBJTYPE(QMGR) PRINCIPAL('jmsapp2') AUTHADD(CONNECT,INQ)
SET AUTHREC PROFILE(QL.APP2.**) OBJTYPE(QUEUE) PRINCIPAL('jmsapp2') AUTHADD(PUT,GET)
```

### CONNAUTH, CHLAUTHの有効化

```bash
ALT QMGR CONNAUTH(OPENLDAP) CHLAUTH(ENABLED)
REFRESH SECURITY TYPE(CONNAUTH)
```

## TLS証明書を利用した認証認可

コンテナ環境ではローカルOSのユーザーを利用できないため､LDAPを使うことになるが､LDAPが無いもしくは利用したくないケースも考えられる｡また､ポリシーとして､すべての通信のTLS化が求められるケースでは､証明書での認証に加えてパスワード認証は実装コストが高かったり､アプリケーションの改修が必要なケースも考えられる｡
MQではTLSによる相互認証を行うことができる｡証明書による認証と､CHLAUTHのSSLPEERMAPを利用して証明書に対してユーザーをマッピングし､そのユーザーに対するOAM認可を行うことで認証・認可を実現する｡

### UserExternalの有効化

通常は､OAMではOSまたは､LDAPのユーザーに対する認可を行う｡しかし､この構成ではOSもLDAPのユーザーも利用できない｡そこで､MQ V9.2.1で実装されたUserExternalを利用して､どちらも利用しない構成を取る｡

qm.iniのServiceスタンザに以下を追加する
```ini
Service:
    Name=AuthorizationService
    EntryPoints=14
    SecurityPolicy=UserExternal
```

### TLSチャネル構成

クライアントとの相互認証を行うため､`SSLCAUTH`を`REQUIRED`にする
```bash
DEFINE CHANNEL('QM01.SVRCONN') CHLTYPE(SVRCONN) SSLCAUTH(REQUIRED) SSLCIPH('ANY_TLS12_OR_HIGHER') REPLACE
```
### CHLAUTH構成

大方針としては､CONNAUTH+CHLAUTH併用パターンと同じ｡ 
ここでは､すべての証明書での接続をブロックするエントリと､特定のCN､OUを持つ証明書(`SSLPEER`属性)で､指定されたCAで署名された証明書(`SSLCERTI`属性)を利用した接続のユーザーを`jmsapp1`にマッピングするエントリを追加する｡

```bash
SET CHLAUTH('QM01.SVRCONN') TYPE(SSLPEERMAP) SSLPEER('CN=*') USERSRC(NOACCESS) ACTION(REPLACE)

SET CHLAUTH('QM01.SVRCONN') TYPE(SSLPEERMAP) SSLPEER('CN=app1,OU=team1') SSLCERTI('CN=applicationCA,OU=Certificate Authority') USERSRC(MAP) MCAUSER('jmsapp1') ACTION(REPLACE)
```

あとは､`jmsapp1`に対して､適切なOAM権限を付与すれば良い｡

### CONNAUTHの無効化

CONNAUTHは利用しないため無効化する

```bash
ALTER QMGR CONNAUTH('') CHLAUTH(ENABLED)
REFRESH SECURITY TYPE(CONNAUTH)
```

### 証明書管理

CHLAUTH+CONNAUTH併用のケースと比較して､相互認証の手間やCA認証局の用意､証明書管理といった追加のコストが発生するが､ [cert-manager](https://cert-manager.io/)などを利用することで､管理コストを下げることはできる｡
(既にLDAPがある環境でなければ､LDAPの新規構築や運用などのコストが発生するのは同じ)
外部リソースに依存しない､MQで完結する､更にはID/パスワードを渡すためのアプリケーションの改修コストを考えると､証明書を利用する認証も十分選択肢に入る｡(ついでに暗号化もできるし､OpenShiftでは､クラスター外からの接続にはpassthrough Route(=TLS前提)が必須となるので､CHLAUTH+CONNAUTHだと更にTLS化も(片系認証でいいとは言え)必要になる｡)

# 最後に

MQのコンテナ利用における認証・認可の構成について紹介した｡
MQコンテナは一部の構成が行えない(QMGR作成時にしか行えない､ログファイルサイズの変更とか)ことや､メトリクスで欲しい情報が拾えない(CURDEPTHとか)といったところで､痒いところに手が届かない部分もまだあるが､cloud native環境におけるメッセージ基盤を担う事ができるミドルウェアである｡

[NativeHA](https://qiita.com/takzo/items/0c9899aca5ed8abc602f)や[NFSを利用したMulti Instance構成](https://qiita.com/takzo/items/81459fae5fe113ff68a0)といったMQの高可用性を取ることができるのでぜひ参考にしていただきたい｡