CAPで作ったサービスをKyma runtimeにデプロイ(3) 認証を追加

## はじめに
この記事は[CAPで作ったサービスをKyma runtimeにデプロイ](https://qiita.com/tami/items/8a2921217eba458eeb8a)シリーズの3回目です。以下のチュートリアルを参考にしつつ、

https://developers.sap.com/mission.btp-deploy-cap-kyma.html

- CAPで作ったアプリをKymaにデプロイするにはどのような仕組みが使われているか
- 一からアプリを作成してKymaにデプロイするには何が必要か

について確認することを目的としています。
3回目の今回は、[前回](https://qiita.com/tami/items/90e353b0a62d0771b502)作成した基本的なCAPサービスに認証を追加します。

## CAPサービスに認証を追加
**ステップ**
1. アクセスコントロールを追加
1. XSUAAを追加
1. Helmの設定
1. ビルド、Dockerイメージのプッシュ
1. Kyma runtimeにデプロイ
1. デプロイ結果の確認

### 1. アクセスコントロールを追加
srv/cat-service.cdsを以下のように変更します。これにより、Booksエンティティを照会するのにViewerのロールが必要になります。

```
using my.bookshop as my from '../db/data-model';

service CatalogService {
    @readonly
    entity Books @(restrict : [{
        grant : ['READ'],
        to    : ['Viewer']
    }]) as projection on my.Books;
}
```

試しに`cds watch`でサービスをローカル実行し、Booksを開こうとすると以下の画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/0d24c6da-d7f0-da06-9f05-dee1cc956316.png)

ローカルで実行するため、.cdsrc.jsonに以下のエントリを追加します。

```json
{
    "[development]": {
        "auth": {
            "passport": {
                "strategy": "mock",
                "users": {
                    "viewer01": {
                        "password": "initial",
                        "ID": "viewer01",
                        "roles": [
                            "Viewer"
                        ]
                    }
                }
            }
        }
    }
}
```

シークレットウインドウでURLを開くとログインを求められるので、上で設定したユーザ、パスワードを入力するとデータを照会できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/87e55fd9-63f2-b971-f9d8-1d09afb9f0c5.png)

### 2. XSUAAを追加
以下のコマンドを実行します。

```
cds add xsuaa --for production
```

package.jsonの`cds`配下に`auth`セクションが追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/da8c5940-7f89-8493-6ac1-6e593e54cb07.png)

xs-security.jsonファイルが追加され、サービス定義をもとにスコープとロールテンプレートの定義が設定されます。

```json:xs-security.json
{
  "scopes": [
    {
      "name": "$XSAPPNAME.Viewer",
      "description": "Viewer"
    }
  ],
  "attributes": [],
  "role-templates": [
    {
      "name": "Viewer",
      "description": "generated",
      "scope-references": [
        "$XSAPPNAME.Viewer"
      ],
      "attribute-references": []
    }
  ]
}
```

### 3. Helmの設定
chart/values.yamlを見てみると、以下の部分が追加されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/3ba39294-12d8-0025-a690-4eee62b33713.png)
また、chartフォルダにxs-security.jsonがコピーされています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/2e2d0014-4814-2956-c471-5885ebe590d0.png)
これらをもとにどのようなリソースが作られるのかを`helm template capkyma ./chart`で見てみると、[前回]()の基本的なCAPサービスから増えたものは以下です。

|リソース  |何をするものか  |
|---|---|
|ServiceBinding (xsuaa) |CAPのサービスにxsuaaのシークレットをバインド |
|ServiceInstance (xsuaa) |xsuaaのサービスインスタンスを登録。ロールを生成 |

生成されたyamlの関連部分は以下のようになっています。ServiceInstanceの定義の中にxs-security.jsonで設定したスコープ、ロールの定義が持ち込まれているのがわかります。

```yaml
---
# Source: capkyma/charts/srv/templates/service-binding.yaml
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
  name: capkyma-srv-auth
  labels:
    helm.sh/revision: "1"
    helm.sh/chart: srv-0.1.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: srv
    app.kubernetes.io/instance: capkyma
spec:
  serviceInstanceName: capkyma-xsuaa
---
# Source: capkyma/templates/xsuaa.yaml
apiVersion: services.cloud.sap.com/v1alpha1
kind: ServiceInstance
metadata:
  name: capkyma-xsuaa
  namespace: capkyma
  labels:
    helm.sh/chart: capkyma-1.0.0
    app.kubernetes.io/name: xsuaa
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/instance: capkyma
    app.kubernetes.io/version: 1.0.0
spec:
  serviceOfferingName: xsuaa
  servicePlanName: application
  externalName: capkyma-capkyma-xsuaa
  parameters:
    attributes: []
    role-templates:
    - attribute-references: []
      description: generated
      name: Viewer
      scope-references:
      - $XSAPPNAME.Viewer
    scopes:
    - description: Viewer
      name: $XSAPPNAME.Viewer
    xsappname: capkyma
```

### 4. ビルド、Dockerイメージのプッシュ
[前回]()と同様の手順でビルド、Dockerイメージのプッシュを行います。

### 5. Kyma runtimeにデプロイ
前回と同様、以下のコマンドでKyma runtimeにデプロイします。

```
helm upgrade <リリース名> <chartフォルダのパス> --install

# 例
helm upgrade capkyma ./chart --install
```

以下のメッセージが表示されればデプロイは成功です。

```
The release "capkyma" is installed in namespace "capkyma".

Your services are available at:
  - https://capkyma-srv-capkyma.a2985eb.kyma.shoot.live.k8s-hana.ondemand.com
```

Booksエンティティにアクセスしてみると、以下のエラーになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/639f09fd-71ec-6ca2-f24b-0792f58cfc74.png)

### 6. デプロイ結果の確認
Kyma runtimeにServiceInstanceを作成すると、Cloud Foundry側にもサービスインスタンスが登録されます。ただし、Cloud Fondryで作ったインスタンスと違い、ここから操作したりサービスキーを見ることはできません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/8e98c9e0-1e3f-89d5-2acf-a92d08b9996b.png)
ロールテンプレートで定義したロールもできています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/45d257b1-0fa5-592f-ac04-ab6f4c949e23.png)

Kyma runtime側から見ると以下のようにXSUAAのサービスインスタンスが登録されています。また、サービスバインディングをドリルダウンしていくとシークレットを見ることもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/0e7c0044-0f26-ba1a-49a7-6c787ea44f54.png)


## Kyma runtimeにStandalone Approuterを立てる
認証されていないユーザはBooksエンティティにアクセスできなくなりました。次回の記事でUI5アプリを作成し、BTPのLaunchpad経由でCAPサービスにアクセスします。このときは、LaunchpadのManaged Approuterが認証を行ってくれます。
今回はManaged Approuterではなく、Standalone Approuterを使用して認証を行ってみます。

Standalone ApprouterのためのDocker Imageが最近（2022年8月）公開されたため、これを使用してみます。

**SAPブログ**

https://blogs.sap.com/2022/08/19/sap-application-router-docker-image-publicly-available/

今回は以下の手順を参考に、Kyma runtimeにStandalone Approuterを立ててApprouter経由でCAPサービスにアクセスしてみます。

https://github.com/SAP-samples/kyma-runtime-extension-samples/tree/main/standalone-approuter

**前提**
前のステップで作成したXSUAAサービスインスタンスは、xs-security.jsonに`tenant-mode`の指定がないためデフォルトで"shared"になるようです。このままだとApprouterをデプロイしたときに"TENANT_HOST_PATTERNの指定がない"というエラーになってしまうため、"dedicated"と明示する必要があります。また、認証後にリダイレクトする先として`oauth2-configuration.redirect-uris`にApprouterのURLを追加します。

【備忘】Launchpadから実行する際は、`oauth2-configuration.redirect-uris`にLaunchpadのURLを追加する必要があります。

```json:xs-security.json
{
  "tenant-mode": "dedicated",
  "scopes": [
    {
      "name": "$XSAPPNAME.Viewer",
      "description": "Viewer"
    }
  ],
  "attributes": [],
  "role-templates": [
    {
      "name": "Viewer",
      "description": "generated",
      "scope-references": [
        "$XSAPPNAME.Viewer"
      ],
      "attribute-references": []
    }
  ],
  "oauth2-configuration": {
    "redirect-uris": [
      "https://my-approuter.a2985eb.kyma.shoot.live.k8s-hana.ondemand.com/**",
      "http://localhost:5000/**"
    ]
  }
}
```
tenant-modeを変える場合、XSUAAサービスインスタンスを一旦削除して再登録する必要があります。以下のコマンドでデプロイしたものを全て削除することができます。

```
helm uninstall <リリース名>

# 例
helm uninstall capkyma
```
そのうえで、再度デプロイします。


**ステップ**
1. DestinationとRouteの設定を作成する
1. Approuterをデプロイする
1. APIRuleによりApprouterを公開する
1. ApprouterからCAPサービスにアクセスする

Approuter関連の設定ファイルを格納するため、プロジェクトのルートに`approuter`というフォルダを作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/9ae401cc-3ad4-d5c6-b90a-cb6e7e41c47b.png)

### 1. DestinationとRouteの設定を作成する
config.yamlを以下の内容で作成します。{CLUSTER_DOMAIN}の部分には、自分のkyma runtimeのドメインを指定します。（例：`a2985eb.kyma.shoot.live.k8s-hana.ondemand.com`）

```yaml:approuter/config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: destinations
data:
  destinations: >-
    [
        {
          "name": "capkyma-srv",
          "url": "https://capkyma-srv-capkyma.{CLUSTER_DOMAIN}",
          "forwardAuthToken": true
        }
    ]
---
apiVersion: v1
data:
  xs-app.json: |-
    {
        "authenticationMethod": "route",
        "sessionTimeout": 10,
        "login" : {
          "callbackEndpoint": "/catalog"
        },
        "routes": [
            {
              "source": "^/catalog/(.*)$",
              "target": "/catalog/$1",
              "destination": "capkyma-srv",
              "authenticationType": "xsuaa",
              "csrfProtection": false
            },          
            {
              "source": "^/(.*)$",
              "destination": "capkyma-srv",
              "target": "/",
              "authenticationType": "xsuaa"
            }
        ]
    }
kind: ConfigMap
metadata:
  name: xs-app
```

以下のコマンドでデプロイします。（以降も同様です）

```
kubectl apply -f approuter/config.yaml
```

### 2. Approuterをデプロイする
deployment.yamlを以下の内容で作成し、デプロイします。

```yaml:approuter/deployment.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: my-approuter
spec:
  host: my-approuter
  trafficPolicy:
    loadBalancer:
      consistentHash:
        httpCookie:
          name: JSESSIONID
          path: /
          ttl: 0s
---
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: my-approuter
  name: my-approuter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-approuter
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: my-approuter
    spec:
      volumes:
      - name: xs-app
        configMap:
          name: xs-app
      - name: capkyma-srv-auth
        secret:
          secretName: capkyma-srv-auth
      containers:
      - image: sapse/approuter:11.3.2
        name: my-approuter
        imagePullPolicy: Always
        ports:
        - containerPort: 5000
        resources: {}
        volumeMounts:
        - name: xs-app
          mountPath: /app/xs-app.json
          subPath: xs-app.json
          readOnly: true
        - name: capkyma-srv-auth
          mountPath: /etc/secrets/sapcp/xsuaa/capkyma-xsuaa
          readOnly: true
        env:
        - name: destinations
          valueFrom:
            configMapKeyRef:
              name: destinations
              key: destinations
status: {}
---
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: my-approuter
  name: my-approuter
spec:
  ports:
  - port: 5000
    protocol: TCP
    targetPort: 5000
  selector:
    app: my-approuter
status:
  loadBalancer: {}
```

### 3. APIRuleによりApprouterを公開する
api-rule.yamlを以下の内容で作成し、デプロイします。

```yaml:approuter/api-rule.yaml
apiVersion: gateway.kyma-project.io/v1alpha1
kind: APIRule
metadata:
  labels:
    app: my-approuter
  name: my-approuter
spec:
  gateway: kyma-gateway.kyma-system.svc.cluster.local
  service:
    host: my-approuter
    name: my-approuter
    port: 5000
  rules:
    - path: /.*
      methods: ["GET", "POST", "PUT", "DELETE"]
      accessStrategies:
        - handler: noop
      mutators:
        - handler: header
          config:
            headers:
              x-forwarded-host: "my-approuter.{CLUSTER_DOMAIN}"
```

### 4. ApprouterからCAPサービスにアクセスする
Kyma runtimeのダッシュボードを開き、APIRulesからmy-approuterのURLにアクセスします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/949606e4-31d0-87e3-072b-453aa86b94d4.png)
認証の画面が開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/e7a0d24e-e469-a574-b16b-fd49322c1f55.png)
認証後、以下の画面が表示されれば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/025f2316-a9cc-3f19-73e8-630f04b805a0.png)
$metadataは表示できましたが、BooksエンティティにアクセスしようとするとForbiddenとなります。これはまだロールをユーザに割り当てていないためです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/bc7527cc-1bec-deeb-7abb-cf8397132ec7.png)
BTPロールコックピットからロールコレクションを作成し、Vierwerのロールを割り当てます。作成したロールコレクションを自分のユーザに割り当てます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/36d2d74a-d2be-dd84-a63d-6ddacf73f085.png)
再ログインするとBooksエンティティが表示できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/0a86a2dd-a34d-e029-71b6-239c6baa3d65.png)