Azure Easy Auth NJS モジュールの紹介

## 概要

[Azure App Service](https://azure.microsoft.com/ja-jp/products/app-service) や [Azure Container Apps](https://azure.microsoft.com/ja-jp/products/container-apps) で利用可能な [Azure Easy Auth](https://learn.microsoft.com/ja-jp/azure/app-service/overview-authentication-authorization) のユーザー認証情報を扱うことができる [NJS (NGINX JavaScript)](https://nginx.org/en/docs/njs/) のモジュールを作ったので紹介します。

https://github.com/yaegashi/azure-easy-auth-njs

これを使うと、Azure Easy Auth が認証したユーザーのIDや所属グループのIDのクレームに基づくアクセス制御が簡単に実現できるようになります。

## しくみ

Azure Easy Auth が有効な Web サービスでは、すべてのアクセスに対して次の HTTP ヘッダが追加されます。

- `X-MS-CLIENT-PRINCIPAL`: Base64 エンコードされたすべてのクレームを含む JSON オブジェクト
- `X-MS-CLIENT-PRINCIPAL-ID`: 認証済みユーザーのID
- `X-MS-CLIENT-PRINCIPAL-NAME`: 認証済みユーザーの名前
- `X-MS-CLIENT-PRINCIPAL-IDP`: 認証済みユーザーの IdPの名前 (`aad`: Microsoft Entra ID)

これらのヘッダの値が持つ意味や実例については、次の勉強会資料の 28 ページ目からを参照してください。

https://www.docswell.com/s/yaegashi/ZJL16J-enterprise-easy-auth-app-service#p28

今回紹介する [`azure_easy_auth.js`](https://github.com/yaegashi/azure-easy-auth-njs/blob/main/njs/azure_easy_auth.js) モジュールは、上記のヘッダ情報を取得・デコードする様々な JavaScript 関数を提供するものです。

- HTTPヘッダアクセス関数
  - `getHeaderClientPrincipal()`: `X-MS-CLIENT-PRINCIPAL` ヘッダの値を取得
  - `getHeaderClientPrincipalId()`: `X-MS-CLIENT-PRINCIPAL-ID` ヘッダの値を取得
  - `getHeaderClientPrincipalName()`: `X-MS-CLIENT-PRINCIPAL-NAME` ヘッダの値を取得
  - `getHeaderClientPrincipalIdp()`: `X-MS-CLIENT-PRINCIPAL-IDP` ヘッダの値を取得
  - `decodeHeaderClientPrincipal()`: `X-MS-CLIENT-PRINCIPAL` ヘッダの値を Base64 デコードした結果のオブジェクトを取得
- クレームアクセス関数
  - `getClaimValue()`: 指定したクレームの値を取得 (複数ある場合は最初の値)
  - `getClaimValues()`: 指定したクレームのすべての値をリストで取得
  - `getClaimEmail()`: Email のクレームを取得
  - `getClaimName()`: Name のクレームを取得
  - `getClaimObjectId()`: Object ID のクレームを取得
  - `getClaimPreferredUsername()`: Preferred Username のクレームを取得
  - `getClaimGroups()`: 所属グループのクレームをリストで取得
- アクセス判定関数
  - `hasClaimGroup()`: 認証したユーザーの所属グループのクレームに特定のクレームが含まれているかどうかを判定
  - `isAuthorizedPrincipals()`: 認証したユーザーのIDクレームおよび所属グループクレームのリストと、NGINX 変数 `$authorized_principals` で指定したクレームのリストに、共通の要素が含まれる場合は `"1"` を返し、そうでない場合は `"0"` を返す

上記の関数を NGINX 設定ファイルから呼び出してアクセス許可・不許可の判定に利用するには、 [`js_set`](https://nginx.org/en/docs/http/ngx_http_js_module.html#js_set) による関数参照設定や変数による引数渡しなど、NJS 特有の記法が要求されます。次のセクションの利用例を参考にしてください。

## 使用例

### 単純なアクセス保護

`/secure/` 以下のパスがリクエストされたとき、認証されたユーザーのIDおよび所属グループのIDに特定のIDが含まれている場合のみ、アクセスを許可します。

`$authorized_principals` 変数にアクセスを許可したい特定のIDをセットし、 `$is_authorized` 変数に`isAuthorizedPrincipals()` 関数の参照をセットすることで、この関数による判定が利用できます。

```nginx
server {
    listen 80;
    server_name _;

    root /nginx/sites/default;

    js_import auth from azure_easy_auth.js;
    js_set $is_authorized auth.isAuthorizedPrincipals;

    location ~ ^/secure/ {
        set $authorized_principals "98a7b6c5-d4e3-21f0-9g8h-765432109876";
        if ($is_authorized = "0") {
            return 403 "Forbidden";
        }
    }
}
```

### パスを利用したアクセス保護

リクエストされたパス `/secure/<$secure_principal>/` から `$seure_principal` を取り出し、管理者ユーザーの ID である `$user_principal` と連結して `$authorized_principals` にセットします。

一般ユーザーは自分のために用意された場所 `/secure/<ObjectID>` のみアクセスが許可され、スーパーユーザーはすべての場所のアクセスが許可されます。

```nginx
server {
    listen 80;
    server_name _;

    root /nginx/sites/default;

    js_import auth from azure_easy_auth.js;
    js_set $is_authorized auth.isAuthorizedPrincipals;
    set $super_principal "123e4567-e89b-12d3-a456-426614174000";

    location ~ ^/secure/(?<secured_principal>[^/]+) {
        set $authorized_principals "$secured_principal,$super_principal";
        if ($is_authorized = "0") {
            return 403 "Forbidden";
        }
    }
}
```

## まとめ

今回紹介した `azure_easy_auth.js` モジュールを使えば、Microsoft Entra ID をユーザー認証に利用する企業内部向けの Web サーバーで、ユーザー ID と所属グループ ID を基準にしたきめこまかなアクセス制御の機能を NGINX に追加できます。

そのような企業内部の静的 Web サイトのホスティングサービス向けに、[公式 NGINX Docker イメージ](https://hub.docker.com/_/nginx)に `azure_easy_auth.js` モジュールを組み込んで調整したコンテナイメージを公開しています。

https://github.com/yaegashi/azure-easy-auth-njs/pkgs/container/azure-easy-auth-njs%2Fnginx

また上記イメージによるサービスを提供する Azure App Service および Azure Container Apps の GitOps リポジトリをそれぞれ公開しています。

https://github.com/yaegashi/azdops-nginx-aas

https://github.com/yaegashi/azdops-nginx-aca

これらを利用すると、様々なアクセスパターンを持つ多数の社内向け静的 Web サイトのホスティングサービスを、最適なクラウドサービスを使って低いランニングコストで運用できるようになります。その詳細については別の記事で紹介したいと思います。