Authorization Management Service (AMS)を利用したCAPの権限管理

## はじめに
この記事は [SAP Advent Calendar 2024](https://adventar.org/calendars/10423) の12月4日分の記事として執筆しています。

Authorization Management Service (AMS)とは、Cloud Identity Servicesの中にある、エンドユーザのアプリケーション利用権限を管理するためのコンポーネントです。「認証・認可」の**認可**の部分を担います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/26592aac-ac4e-2749-b41e-75bb9a310fcc.png)
出典：[Authorization Management Service in SAP Cloud Identity Service](https://community.sap.com/t5/technology-blogs-by-sap/authorization-management-service-in-sap-cloud-identity-service/ba-p/13881014)

[SAP Blog](https://community.sap.com/t5/technology-blogs-by-sap/authorization-management-service-in-sap-cloud-identity-service/ba-p/13881014)や[CAPのドキュメント](https://cap.cloud.sap/docs/guides/security/authorization#use-ams-as-authorization-management-system-on-sap-btp)では、SAP BTPは認可の管理をXSUAAからAMSへ移行しつつあると述べられています。BTPでのアプリケーション開発や権限管理を行うにあたっては、気になる情報です。そこでこの記事では、AMSを利用した権限管理の概要と、CAPでAMSを利用した権限管理をする方法について紹介します。

## AMSを利用した権限管理の仕組み
### アクセス権限の管理にポリシーを利用する
AMSでは、**ポリシー**を利用してアプリケーションへのアクセス権限を管理します。ポリシーとは、ユーザがリソースに対して特定のアクションを実行する権限があるかどうかを決定するためのルールのセットです。ポリシーをユーザに割り当てることで、ユーザはポリシーで許可された権限を持つようになります。

ポリシーの例（[Authorization Management Service in SAP Cloud Identity Service](https://community.sap.com/t5/technology-blogs-by-sap/authorization-management-service-in-sap-cloud-identity-service/ba-p/13881014)より抜粋)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/caafc36a-c36b-5355-5664-4bbcc4157c71.png)

既存の複数のポリシーをまとめたポリシーを作成することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/9c7af9b9-f5b8-52aa-16d7-7859ca8cc7ec.png)

### ポリシー = グループとなる
SAP Cloud Identity Serviceでは、デフォルトではポリシーを利用した権限管理は有効化されていません。テナントの設定で有効化する必要があります（参考：[Configure Authorizations Based on Policies](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/beta-configure-authorizations-based-on-policies)）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/a0524150-82bf-e135-8d1f-c24d574d11e9.png)

ポリシーを有効化することにより、定義されたポリシーがグループとして表示されるようになります。ユーザにグループを割り当てることで、ポリシーも自動的に割り当たることになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/bba296e1-c3c5-966a-3cdc-b3cc074fa23f.png)


:::note info
**ポリシーがたくさんある場合は？**
複数のアプリケーションでそれぞれポリシーを定義している場合、個別のポリシー（グループ）をユーザに割り当てる必要があるのでしょうか。

ロールコレクションを使っている場合は、複数のロールコレクションを1つのグループにマッピングしておくことで、ユーザには1つのグループの割りてで済んでいました。同じようなことはポリシーでも実現できるでしょうか。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/2c1b7249-073c-279f-e4ab-9cb62b6b45f7.png)

複数のポリシーをまとめたポリシーを作成することは可能です。ただ、アプリケーションをまたいでまとめることはできないので、ロールコレクションを使用した方法に比べて多くのポリシー（グループ）をユーザに割り当てる必要がありそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/c9695965-44cc-7b16-941b-33d4336a983a.png)
:::

## CAPの権限管理もXSUAAからAMSに移行するらしい
CAPの[ドキュメント](https://cap.cloud.sap/docs/guides/security/authorization#use-ams-as-authorization-management-system-on-sap-btp)によると、新規に開発するアプリケーションでは、AMSの利用が推奨されるとのことです。

>SAP BTPでは現在、XSUAAを用いた認可の管理を、AMSを統合したソリューションに置き換えつつあります。AMSはSAP Cloud Identity（SCI）に統合されており、認証、認可、ユーザーのプロビジョニングと管理を一元化して提供します。
新たに構築するアプリケーションでは、一般的にAMSの使用が推奨されます。

しかし、ドキュメントの中でAMSについて記述があるのはこの部分のみで（2024年11月現在）、具体的にどのように設定するかについては記載がありません。

## チュートリアルがあった
以下にチュートリアルがあったので、実施してみました。**しかし、ビルドでエラーになってしまいデプロイできていません。**

https://github.com/SAP-samples/btp-developer-guide-cap/tree/main/documentation/xsuaa-to-ams

**発生したエラー**
`mbt build`を実行すると、以下のエラーが出ます。

```
TypeError [ERR_INVALID_ARG_TYPE]: The "paths[0]" argument must be of type string. Received undefined
    at Object.resolve (node:path:1101:7)
    at Object.to (/home/miyasuta/projects/incident-management-2411/node_modules/@sap/cds-dk/lib/build/plugin.js:136:37)
    at Object.compileCds (/home/miyasuta/projects/incident-management-2411/node_modules/@sap/ams-dev/src/cap/capAmsFlow.js:27:30)
    at async AmsBuildPlugin.build (/home/miyasuta/projects/incident-management-2411/node_modules/@sap/ams-dev/src/cap/AmsBuildPlugin.js:39:3)
    at async Promise.allSettled (index 0)
    at async BuildTaskEngine._executePipeline (/home/miyasuta/projects/incident-management-2411/node_modules/@sap/cds-dk/lib/build/buildTaskEngine.js:159:29)
    at async BuildTaskEngine._executeBuildTasks (/home/miyasuta/projects/incident-management-2411/node_modules/@sap/cds-dk/lib/build/buildTaskEngine.js:150:25)
    at async BuildTaskEngine.processTasks (/home/miyasuta/projects/incident-management-2411/node_modules/@sap/cds-dk/lib/build/buildTaskEngine.js:59:33)
    at async build (/home/miyasuta/projects/incident-management-2411/node_modules/@sap/cds-dk/lib/build/index.js:19:16)
    at async Object.build (/home/miyasuta/projects/incident-management-2411/node_modules/@sap/cds-dk/bin/build.js:171:5) {
  code: 'ERR_INVALID_ARG_TYPE'
}
```

同様のエラーがissueとして上がっていますが、未解決（というか、放置されている状態）です。

https://github.com/SAP-samples/btp-developer-guide-cap/issues/118

### CAPでAMSを使うための手順
現状まだうまくいっていませんが、参考としてチュートリアルをベースにCAPでAMSを使うためにの手順について説明します。まだAMSが出始めということもあり`cds add`コマンドなどで設定できず、全てマニュアルで設定する必要があります。これが大変でした。

前提とするCAPのサービス定義は以下のようになっています。

```
using { sap.capire.incidents as my } from '../db/schema';

/**
 * Service used by support personell, i.e. the incidents' 'processors'.
 */
service ProcessorService { 
    entity Incidents as projection on my.Incidents;

    @readonly
    entity Customers as projection on my.Customers;
}

annotate ProcessorService.Incidents with @odata.draft.enabled; 
annotate ProcessorService with @(requires: 'support');

/**
 * Service used by administrators to manage customers and incidents.
 */
service AdminService {
    entity Customers as projection on my.Customers;
    entity Incidents as projection on my.Incidents;
}

annotate AdminService with @(requires: 'admin');
```


#### 1. dependencyの追加
以下のモジュールをインストールします。

```
npm install @sap/ams
npm install @sap/ams-dev --save-dev
```

[@sap/ams](https://www.npmjs.com/package/@sap/ams)はSAP Cloud Identity Services経由で認証されたユーザの権限チェックを行います。[@sap/ams-dev](https://www.npmjs.com/package/@sap/ams-dev#deploy-dcl-1)は開発時の権限チェック、および**dcl**という拡張子でポリシーを記述したファイルの生成を行います。

#### 2. ローカル実行のための設定
ローカル実行時の権限チェックのため、以下の設定を`package.json/cds/requires/[development]/auth`に追加します。

```json
  "ams" : {
    "autoCompile" : true,
    "cache" : {
          "TTL" : 0
      }
    }
```

ローカル実行用のテストユーザを`package.json/cds/requires/[development]/auth/users`に追加します。`"policies": [...]`でポリシーを指定するところが従来との違いです（ポリシーには`cap.`のプリフィックスがつくことに注意）。

```json:例
    "users": {
        "alice": {
          "policies" : [
              "cap.support"
          ]
        },
        "bob": {
        }
    }
```

ここまでで"cds"セクション全体は以下のようになります。
```json
"cds": {
    "requires": {
      "[development]": {
        "auth": {
          "ams" : {
            "autoCompile" : true,
            "cache" : {
                  "TTL" : 0
            }
          },
          "kind": "mocked",
          "users": {
            "alice": {
              "policies" : [
                  "cap.support"
              ]
            },
            "bob": {
            }
          }
        }
      },
      "[production]": {
        "auth": "xsuaa",
        "db": "hana"
      }
    }
```
`cds watch`を実行すると、以下のフォルダが生成されます。
- ams/dcl
- gen/srv/ams/dcl

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/9c48e14c-d752-7cf9-4065-741d41a1734f.png)

`gen/srv/ams/dcl/cap/basePolicies.dcl`には、サービスに設定したアノテーションをもとに自動生成されたポリシーが設定されます。

```
// ----------------------------------------------------------------------------------
// Generated from a CAP model by the SAP AMS Dev Plugin (@sap/ams-dev)
// ----------------------------------------------------------------------------------

POLICY "support" {
    GRANT "support" ON $SCOPES;
}

POLICY "admin" {
    GRANT "admin" ON $SCOPES;
}
```

`ams/dcl`は空です。ここには追加のポリシーを格納することができると思われます（チュートリアルでは使用していませんでした）。

ProcessorServiceに"alice"というユーザでログインすると、以下のエラーが出ました。`support`のロールが割り当たっていないため権限がないとみなされています。

```
[cds] - Error: Forbidden
    at requires_check (/home/miyasuta/projects/incident-management-2411/node_modules/@sap/cds/lib/srv/protocols/http.js:54:32)
    at Layer.handle [as handle_request] (/home/miyasuta/projects/incident-management-2411/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/home/miyasuta/projects/incident-management-2411/node_modules/express/lib/router/index.js:328:13)
    at /home/miyasuta/projects/incident-management-2411/node_modules/express/lib/router/index.js:286:9
    at Function.process_params (/home/miyasuta/projects/incident-management-2411/node_modules/express/lib/router/index.js:346:12)
    at next (/home/miyasuta/projects/incident-management-2411/node_modules/express/lib/router/index.js:280:10)
    at http_log (/home/miyasuta/projects/incident-management-2411/node_modules/@sap/cds/lib/srv/protocols/http.js:42:59)
    at Layer.handle [as handle_request] (/home/miyasuta/projects/incident-management-2411/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/home/miyasuta/projects/incident-management-2411/node_modules/express/lib/router/index.js:328:13)
    at /home/miyasuta/projects/incident-management-2411/node_modules/express/lib/router/index.js:286:9 {
  code: 403,
  reason: "User 'alice' is lacking required roles: [support]",
  user: User {
    id: 'alice',
    tenant: 't1',
    roles: { admin: 1 },
    policies: [ 'cap.support' ],
    features: [ 'isbn' ],
    attr: {},
    tokenInfo: { getPayload: [Function: getPayload] }
  },
  required: [ 'support' ]
```
amsでの権限チェックになっているか怪しいですが、一旦package.jsonに`support`のロールも追加します。

```json
          "users": {
            "alice": {
              "policies" : [
                  "cap.support"
              ],
              "roles": [
                "support"
              ]
            },
            "bob": {
            }
          }
```
その結果、"alice"でデータを表示することができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/86f67434-9f17-7cf4-2817-c968bf6b6687.png)

#### 3. デプロイのための設定
Cloud Foundryにデプロイするための設定を行います。

##### 3.1. ias-config.jsonを追加
プロジェクトのルートに`ias-config.json`ファイルを追加します。`<unique-id>`には、Cloud Identity Services上でアプリケーションを識別するための一意なIDを指定します。
```json:ias-config.json
    {
      
        "authorization": {
          "enabled":true
        },
        "provided-apis": [
            {
              "name": "incidents-api",
              "description": "api exposed by incident mgmt app"
            }
          ],
        "display-name": "incident-ias-<unique-id>"
    }
```
##### 3.2. production用のauth設定
`package.json/cds/requires/[production]/auth`に`ias`を指定します。XSUAAの代わりにIASで認証するということです。

```json
  "cds": {
    "requires": {
      "[development]": {...},
      "[production]": {
        "auth": "ias",
        "db": "hana"
      }
    }
```

##### 3.3. mta.yamlの設定
**①XSUAAの代わりにIASを使う**
`resources`セクションで`xsuaa`の代わりに`identity`（IAS）のサービスを指定します。`path`には3.1.で定義した`ias-config.json`を指定します。

```yaml
resources:
  - name: incident-management-auth
    parameters:
      path: ./ias-config.json
      service-plan: application
      service: identity
    type: org.cloudfoundry.managed-service
```
**②サービスモジュールの設定**
サービスモジュールに以下の設定を追加します。

```yaml
- name: incident-management-srv
  type: nodejs
  path: gen/srv
  requires:
  - name: incident-management-db
  - name: incident-management-auth
  # 追加 -----------------------------------
    parameters:
      config:
         credential-type: "X509_GENERATED"
  properties:
    AMS_DCL_ROOT: ams/dcl
  #----------------------------------------  
  ```

**③ams-policies-deployerを追加**
ポリシーをデプロイするため、以下のモジュールを追加します。

```yaml
- name: incident-management-ams-policies-deployer
  type: javascript.nodejs
  path: gen/policies
  parameters:
    buildpack: nodejs_buildpack
    no-route: true
    no-start: true
    tasks:
      - name: deploy-dcl
        command: npm start
        memory: 512M
  requires:
    - name: incident-management-auth
      parameters:
        config:
          credential-type: X509_GENERATED
          app-identifier: policy-deployer
```

**④destination-contentの設定**
UIを含んだプロジェクトの場合、destination-contentを以下のように調整します。

:::note 
このチュートリアルはXSUAAベースでアプリケーションを作ってからAMS向けに調整するという手順のため、XSUAA絡みの設定が残っています。それを削除するステップです。
:::

 - XSUAAのサービスインスタンスのバインディングを削除
 - XSUAAのためのdestinationを削除

最終的に、destination-contentの設定は以下のようになります。

```yaml
- name: incident-management-destination-content
  type: com.sap.application.content
  requires:
    - name: incident-management-destination-service
      parameters:
        content-target: true
    - name: incident-management_html_repo_host
      parameters:
        service-key:
          name: incident-management_html_repo_host-key
    parameters:
      content:
        instance:
          destinations:
          - Name: incidents_incidents_management_html_repo_host
            ServiceInstanceName: incident-management-html5-app-host-service
            ServiceKeyName: incident-management_html_repo_host-key
            sap.cloud.service: incidents
          existing_destinations_policy: ignore
    build-parameters:
      no-source: true
```

**⑤サービス宛先にパラメータを追加**
`resources`セクションのdestinationサービスの定義の中にあるCAPのサービス宛先に、`HTML5.IASDependencyName: incidents-api`というパラメータを追加します。`incidents-api`というのは、ias-config.jsonの中で`provided-apis[0].name`に指定したのと同じ値です。

```yaml
- name: incident-management-destination-service
  type: org.cloudfoundry.managed-service
  parameters:
    config:
      HTML5Runtime_enabled: true
      init_data:
        instance:
          destinations:
          - Authentication: NoAuthentication
            Name: ui5
            ProxyType: Internet
            Type: HTTP
            URL: https://ui5.sap.com
          - Authentication: NoAuthentication
            HTML5.IASDependencyName: incidents-api
            HTML5.DynamicDestination: true
            HTML5.ForwardAuthToken: true
            Name: incident-management-srv-api
            ProxyType: Internet
            Type: HTTP
            URL: ~{srv-api/srv-url}
          existing_destinations_policy: update
      version: 1.0.0
    service: destination
    service-name: incident-management-destination-service
    service-plan: lite
```

##### 3.4. xs-app.jsonの設定
UIを含んだプロジェクトの場合、`app/<appname>/xs-app.json`ファイルで各ルートに指定された`authenticationType`を`xsuaa`から`ias`に変更します。

```json
   {
   "welcomeFile": "/index.html",
   "authenticationMethod": "route",
   "routes": [
     {
       "source": "^/odata/(.*)$",
       "target": "/odata/$1",
       "destination": "incident-management-srv-api",
       "authenticationType": "ias",
       "csrfProtection": false
     },
     {
       "source": "^/resources/(.*)$",
       "target": "/resources/$1",
       "authenticationType": "none",
       "destination": "ui5"
     },
     {
       "source": "^/test-resources/(.*)$",
       "target": "/test-resources/$1",
       "authenticationType": "none",
       "destination": "ui5"
     },
     {
       "source": "^(.*)$",
       "target": "$1",
       "service": "html5-apps-repo-rt",
       "authenticationType": "ias"
     }
   ]
 }
 ```

#### 4. デプロイ後
現状ビルド・デプロイができていませんが、[チュートリアル](https://github.com/SAP-samples/btp-developer-guide-cap/blob/main/documentation/xsuaa-to-ams/3-deploy-to-cf.md#check-your-ias-application-and-uploaded-ams-policies)によれば、デプロイ後は以下の状態になります。

- IASテナントにアプリケーションができる（下図の③）
- アプリケーションのAuthorization Policiesタブにアップロードしたポリシーが登録されている（下図の④）…`gen/srv/ams/dcl/cap/basePolicies.dcl`で定義されたもの
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/77910976-d020-9463-1ab5-86b90163e3c6.png)

登録されたポリシーにユーザを割り当てることにより、サービスを利用する権限が割り当たります。チュートリアルではさらに、SAP Build Work Zoneからサービスを利用できるようにするための設定などが続きます。

## 権限管理の流れはどのように変わるのか
XSUAAを利用した方法とAMSを利用した方法とで、アプリケーション（CAP）の権限定義からユーザへ割り当てるまでの流れがどのように変わるのかを比較してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/c93eed5e-ba4f-1251-305d-dda56e845a5a.png)

XSUAAを使用する場合はアプリケーション、BTPコックピット、Cloud Identity Servicesの3か所での作業があります。これに対し、AMSを使用する場合はアプリケーションとCloud Identity Servicesの2か所で済むことがわかります。
 
## おわりに
AMSを利用した権限管理はまだ登場したばかりのため、開発時とデプロイ後に多くのマニュアル設定が必要です。現時点ではAMSの利用はかなりハードルが高い（というか無理）です。この点は、おそらく来年には`cds add`コマンドやプラグインなどが追加されて、使いやすくなると考えられます。
そのうえで、AMSを使うメリットはXSUAAを上回るのでしょうか。確かに「ロールコレクションとグループのマッピング」という、BTPコックピットで行っていた作業は不要になるものの、Cloud Identity Services側でユーザに多くのグループ（ポリシー）の割り当てが必要になる可能性があります（「ポリシーがたくさんある場合は？」参照）。
個人的にはまだAMSに懐疑的なのですが、SAPはそちらに向かうといっているので、来年ドキュメントがもう少し充実したら改めてチャレンジしたいです。

## 参考
- [Authorization Management Service in SAP Cloud Identity Service](https://community.sap.com/t5/technology-blogs-by-sap/authorization-management-service-in-sap-cloud-identity-service/ba-p/13881014)
- [Granular Application Access Control in SAP Cloud Identity Services: A Step-by-Step Guide](https://community.sap.com/t5/technology-blogs-by-members/granular-application-access-control-in-sap-cloud-identity-services-a-step/ba-p/13880394)