Kyma runtimeのCommunity Code Challengeをやってみた - Week3

この記事は chillSAP [夏の自由研究2022](https://note.com/chillsap/n/n62740563aee0)、8/6の記事として執筆しています。

## はじめに
この記事は、[Kyma runtimeのCommunity Code Challengeをやってみた](https://qiita.com/tami/items/8ffad45ef1ee0f54e8c4)シリーズの3回目です。このシリーズでは、[Kyma runtimeのCode Challenge](https://blogs.sap.com/2022/07/04/sap-community-code-challenge-lets-set-sail-for-cloud-native-island/)に参加して、開発の方法やその裏の仕組みについてわかったことをまとめていきます。

## Week3の課題
Week3の課題は以下のようになっています。Week3の目的は、Week2で作成したサービスに認証を追加することです。
リンク：[Week 3 (July 20th, 2022 - July 27th, 2022)](https://github.com/SAP-samples/sap-community-code-challenge-cloud-native/blob/main/Challenges.md#week-3-july-20th-2022---july-27th-2022)

1. 公式ドキュメント[Expose and Secure a Workload with a JSON Web Token](https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/44bb2d3596554bf4b94ea344e40937dd.html?q=api%20rules)を読む
1. Week2で作成したサービスに認証をつけて公開する

## Week3で学んだこと
### 1. APIに認証を追加する方法
Kyma runtimeのアプリケーションに認証を追加するには、API RuleにaccessStrategiesを設定します。以下の例ではリクエストヘッダに渡されたJWTを使用して認証が行われる設定になっています。アプリケーションのコードを変更することなく、設定だけで認証が追加できるのです。この仕組みはApprouterにも似ているのかなと思いましたが、こちらの方が簡単ですね。

```yaml:deployment.yaml
apiVersion: gateway.kyma-project.io/v1alpha1
kind: APIRule
metadata:
  name: httpbin
  namespace: $NAMESPACE
spec:
  service:
    name: httpbin
    port: 8000
    host: httpbin.$DOMAIN_TO_EXPOSE_WORKLOADS
  gateway: kyma-system/kyma-gateway
  rules:
    - accessStrategies:
      - handler: jwt
        config:
          jwks_urls:
          - $JWKS_URI
      methods:
        - GET
      path: /.* 
```

### 2. JWTを発行する方法
公式ドキュメントの"Prerequisites"を読んだ時点で、「これってトライアルではできないのでは？」と思いました。
> You have an SAP Cloud Identity Services - Identity Authentication tenant.

ドキュメントに書かれていた方法はIASに作成したアプリケーションにJWTを発行させるというものだったからです。しかし、JWTを発行する方法は複数あったので大丈夫でした。

#### ①OpenID Connectに準拠したIdentity Providerを使う
JWTの発行および認証には、OpenID Connectに準拠したIdentity Providerを使用することができます。IASでなくても問題ありません。こちらの[ブログ](https://blogs.sap.com/2022/03/03/security-in-kyma-environment-whats-new-for-sap-btp-kyma-runtime/)にはPingOne、Okta、XSUAAを使った例が載っていました。

私はまずXSUAAを使ってやってみました。jwks_urlsに指定するのは、`＜XSUAAサービスインスタンスのキーにあるurl＞` + `/token_keys`です。`/token_keys`というエンドポイントの存在を初めて知りましたが、ここにアクセスするとXSUAAの公開鍵が見られます。この公開鍵を使ってJWTの認証が行われるようです。

```yaml:deployment.yaml
apiVersion: gateway.kyma-project.io/v1alpha1
kind: APIRule
metadata:
  name: my-service
spec:
  gateway: kyma-gateway.kyma-system.svc.cluster.local
  service:
    name: my-service
    port: 8081
    host: my-service
  rules:
    - path: /.*
      methods: ["GET"]
      accessStrategies:
        - handler: jwt
          config:
            jwks_urls:
            - https://xxxxxtrial.authentication.eu10.hana.ondemand.com/token_keys
```

認証の設定後にAPIに直接アクセスすると、以下のようなUnauthorizedの画面が出ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/47011d47-cef3-a85e-7005-540f6b208516.png)
アクセスするためにはXSUAAサービスインスタンスのクライアントID、シークレットを使用してトークンを取得し、リクエストヘッダに渡す必要があります。Posmanで実行する場合は以下のようにAuthorizationを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/77e88e69-1d56-2663-c659-51d2cf3cd98d.png)
|項目  |設定値  |
|---|---|
|Type  |OAuth2.0  |
|Grant Type  |Client Credentials  |
|Access Token URL  |サービスキーに設定されたurl + /oauth/token  |
|Client ID  |サービスキーに設定されたclientid  |
|Client Secret  |サービスキーに設定されたclientsecret  |

##### （補足）Kyma runtimeでのXSUAAサービスインスタンスの作り方
Cloud Foundryと同じように、Kyma runtimeでもXSUAAサービスインスタスを作ることができます。以下のUIから作る方法と、設定ファイルをもとにkubectlで作成する方法があります。今回はkubectlを使って作成してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/6198970a-a0a7-4e7a-c393-38aef4e76d10.png)
手順はCode Challengeの[こちらの投稿](https://groups.community.sap.com/t5/application-development/sap-community-code-challenge-let-s-set-sail-for-cloud-native/m-p/6766/emcs_t/S2h8ZW1haWx8dG9waWNfc3Vic2NyaXB0aW9ufEw2MUVPMU1aUUNSMTVPfDY3NjZ8U1VCU0NSSVBUSU9OU3xoSw#M144)を参考にしました。まずXSUAAのサービスインスタンスを作り、次にサービスバインディングを作成します。サービスバインディングはCloud Foundryでいうところのサービスキーだと理解しました。

- **サービスインスタンスの作成**
以下のyamlファイルを作成します。ファイル名は任意です。

```yaml:xsuaa-instance.yaml
apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
  name: my-service-xsuaa
spec:
  serviceOfferingName: xsuaa
  servicePlanName: application
```
デプロイします。

```
kubectl apply -f my-service\xsuaa-instance.yaml
```

- **サービスバインディングの作成**
以下のyamlファイルを作成します。ファイル名は任意です。

```yaml:xsuaa-binding.yaml
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
  name: xsuaa-service-binding
spec:
  serviceInstanceName: my-service-xsuaa
  secretName: xsuaa-service-binding
```

デプロイします。

```
kubectl apply -f my-service\xsuaa-binding.yaml
```

Kyma runtimeにサービスインスタンスとサービスバインディングが登録されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/ea2229ee-ac75-b458-72b3-47eae0c262a5.png)
サービスバインディングを開き、シークレットをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/8c03744a-0d70-857c-3e58-a1ccddb74b16.png)
clientidやclientsecretが表示されます。この画面ではBase64でエンコードされているので、デコードして使います。なお、認証用のURLはCloud Foundryで作成したインスタンスと同じでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/905f4f35-604a-5899-6a13-ac40b0f428ca.png)

#### ②Kyma runtimeのOAuth2 Clientを使う
Week3の課題を提出した他の人が使っていた方法がこちらでした。画面ショットを見ると、accessStrategies>handlerの設定が`jwt`ではなく`oauth2_introspection`になっています。また、URLの指定もありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/36c40e97-1d8d-46d5-903a-d5987a8c63d9.png)

こちらの方法では、Kyma runtimeに登録したOAuth2 Clientを使って認証を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/866c2e23-5c3c-a17c-2802-6078efa60c7b.png)

deployment.yamlの設定は以下のようになります。

```yaml
apiVersion: gateway.kyma-project.io/v1alpha1
kind: APIRule
metadata:
  name: my-service
spec:
  gateway: kyma-gateway.kyma-system.svc.cluster.local
  service:
    name: my-service
    port: 8081
    host: my-service
  rules:
    - path: /.*
      methods: ["GET"]
      accessStrategies:
        - handler: oauth2_introspection      
```

OAuth2 Clientを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/837f583c-5d76-d9da-2c18-2d9efeed4e00.png)
以下のように入力します。Scopeが必須のため設定していますが、今回は使用していません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/6ac83068-41e0-6dee-43aa-5f5ebdc05220.png)
作成されたOAuth2 Clientにclient_id, client_secretが表示されます。Base64でエンコードされているので、デコードして使います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d32f7465-7c8f-871f-0444-c6901b06caba.png)
Postmanからアクセスするには、以下のように2ステップでリクエストを実行します。

- **トークンを取得**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/378aa413-1670-4572-aa37-593663d1913e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b92fd693-175f-fbce-3467-14165e2c1589.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/90f05027-359d-4717-d30b-263e3d745aa8.png)

|タブ  |項目  |設定値  |
|---|---|---|
|-  |URL  |※下記参照  |
|-  |メソッド  |POST  |
|Authorization  |Grant Type  |Basic Auth|
|  |Username  |OAuth2 Clientのclient_id|
|  |Password  |OAuth2 Clientのclient_secret|
|Headers  |Content-Type  |application/x-www-form-urlencoded|
|Body  |  |grant_type=client_credentials|

※トークン取得用のURLは以下のようになります。
APIのURLが`https://my-service.a2985eb.kyma.shoot.live.k8s-hana.ondemand.com`の場合

- `my-service`を`oauth2`に変更
- 末尾に`/oauth2/token`を追加する

結果：`https://oauth2.a2985eb.kyma.shoot.live.k8s-hana.ondemand.com/oauth2/token`

上記リクエストを実行すると、access_tokenが返ってきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/60d22c39-adb7-dacb-f05f-43869e937c76.png)

- **APIにアクセス**
ヘッダのAuthorizationに`Bearer <上記で取得したaccess_token>`を設定してリクエストを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/664968ba-eb50-24fe-34c8-92a63a49a652.png)

## 参考
- [Dynamic OData from “any” API through SAP BTP Kyma](https://blogs.sap.com/2021/09/09/dynamic-odata-from-any-api-through-sap-btp-kyma/)
- [Kyma for Dymmies [2]: First Simple Microservice with Security](https://blogs.sap.com/2020/12/10/kyma-for-dymmies-2-first-simple-microservice-with-security/)
- [Kyma for Dymmies [3]: calling secured service with REST client](https://blogs.sap.com/2020/12/14/kyma-for-dymmies-3-calling-secured-service-with-rest-client/)