OpenShiftのOAuthサーバーを認証プロバイダーとするOpenLibertyをOperatorからデプロイする方法

# はじめに
当記事では、OpenShiftのOAuthサーバーを認証プロバイダとするLibertyをOperatorとしてデプロイする方法を記述します。
環境はOpenShift4.6です。

下記の記事の続編になります。

https://qiita.com/TaddyHarry/items/69c6ca9c774957159ab8

この記事ではAnsibleベースのOperator SDKを使用してOperatorをビルドしていますので、
合わせて下記を参照ください。

https://qiita.com/TaddyHarry/items/a320c0a68cde1d573d61

上記の記事によって、
静的な設定を通して、OpenShiftのOAuth Serverを認証プロバイダとして設定できている状況が前提とします。

環境をOperatorとしてデプロイして、デプロイされたOpenShiftのOAuthサーバーを認証プロバイダとするためには、
環境に依存する設定値を動的に設定する必要になります。

下記が、OperatorのAPI動作時に環境に合わせて動的に設定する情報です。

1. ServiceAccountを作成する、アノテーションに、リダイレクトURLを設定する
1. Oauth認証のためのClusterRole やClusterRole Bindingを作成し、ServiceAccountに関連付ける
1. authorizationEndpointやtokenEndpointを環境に応じて、動的に取得する
1. ServiceAccountのToken情報を取得する
1. APIサーバーのSSL証明書をOpenLibertyが動作するJVMのキーストアにロードする


# 設定について

## ServiceAccountを作成する、アノテーションに、リダイレクトURLを設定する


```yaml

- name: Get downloadapp route
  community.kubernetes.k8s_info:
    api_version: route.openshift.io/v1
    kind: route
    name: downloadapp
    namespace: "{{ ansible_operator_meta.namespace }}"
  register: downloadappinfo
- debug: 
    msg: downloadapp info is {{downloadappinfo.resources[0].spec.host}}
    
- name: Create service account
  community.kubernetes.k8s:
    state: present
    definition:
      api_version: v1
      kind: ServiceAccount
      metadata:
        name: downloadapp-sa
        namespace: "{{ ansible_operator_meta.namespace }}"
        annotations:
          serviceaccounts.openshift.io/oauth-redirecturi.first: https://{{downloadappinfo.resources[0].spec.host}}/ibm/api/social-login/redirect/openshiftLogin
```

## Oauth認証のためのClusterRole やClusterRole Bindingを作成し、ServiceAccountに関連付ける

```yaml
- name: Create ClusterRole Binding
  community.kubernetes.k8s:
    state: present
    definition:
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRole
      metadata:
        name: downloadapp-role
      rules:
        - apiGroups:
            - authentication.k8s.io
          resources:
            - subjectaccessreviews
            - tokenreviews
          verbs:
            - create
- name: Create ClusterRoleBinding Binding
  community.kubernetes.k8s:
    state: present
    definition:
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRoleBinding
      metadata:
        name: donwloadapp-rb
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: ClusterRole
        name: downloadapp-role
      subjects:
      - kind: ServiceAccount
        name: downloadapp-sa
        namespace: "{{ ansible_operator_meta.namespace }}"
```
## authorizationEndpointやtokenEndpointを環境に応じて、動的に取得する
authorizationEndpointやtokenEndpointを環境に応じて、動的に取得する方法については、OpenShiftのマニュアルに記載がありますので、下記に情報を紹介します。

https://docs.openshift.com/container-platform/4.6/authentication/configuring-internal-oauth.html#oauth-server-metadata_configuring-internal-oauth


```yaml
- name: get oauth info
  uri:
    url: https://openshift.default.svc/.well-known/oauth-authorization-server
    method: GET
    return_content: yes
    validate_certs: false
    status_code: 200
    body_format: json
  register: oauth_info
- debug:
    msg: authorization_endpoint is {{oauth_info.json.authorization_endpoint}}
- debug:
    msg: token_endpoint is {{oauth_info.json.token_endpoint}}
```
## ServiceAccountのToken情報を取得する


Podのデフォルトにおいて、コンテナの`/run/secrets/kubernetes.io/serviceaccount`というディレクトリに、
ServiceAccountのTokenが、ファイルという形にマウントされます。

`VARIABLE_SOURCE_DIRS`という環境変数に、このディレクトリを指定することで、
ファイル名をLibertyの環境変数、ファイルの内容と環境変数の値として読み込むことができます。

この仕組みによって、OAuthサーバーにアクセスするためのservice accountのtoken情報を、Libertyに渡します。

参考

https://openliberty.io/blog/2021/10/29/kube-secrets-210011.html

https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/

https://docs.openshift.com/container-platform/3.11/dev_guide/service_accounts.html



## APIサーバーのSSL証明書をOpenLibertyが動作するJVMのキーストアにロードする

Libertyから、OpenShift上のOauthサーバーにアクセスするにあたり、
SSL証明書が、Libertyが使用するキーストアにロードされていないと、エラーが発生します。

そのエラーを防ぐために、以下の変数をserver.envに指定することで、
Liberty起動時に、Pod上のコンテナにマウントされた証明書を、キーストアにロードします。

```text:server.env
cert_defaultKeyStore=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
```

参考

https://openliberty.io/blog/2020/01/17/openshift-oauth-server-social-login-liberty-20001.html


## Libertyのserver.xmlとAnsibleベースのオペレータでのPodの設定

OpenShiftのOAuthサーバーを認証プロバイダーに設定するために必要な変数と取得についての要約です。

| 変数 | 補足 |
|:-:|:-:|
| serviceAccount  | オペレータのステップで作成し、Libertyに引き渡す  |
| token  |  VARIABLE_SOURCE_DIRS から、Pod上のファイル内容を、Libertyに引き渡す |
| authEndpointUrl  | オペレータのステップでOpenShitに対するRestAPIで動的取得  |
| tokenEndpointUrl  | オペレータのステップでOpenShitに対するRestAPIで動的取得  |
| userApiUrl  |  固定値 |
| VARIABLE_SOURCE_DIRS  | serviceaccountのtoken情報を取得するために必要。固定値。コンテナ上のserviceaccount情報  |
| cert_defaultKeyStore|　OpenShiftのAPIサーバーのSSL証明書をVMにロードするための情報。固定値。コンテナ上のserviceaccount情報 |

Libertyのserver.xmlとAnsibleベースのOperatorの設定の抜粋です。

```xml:server.xml
    <!-- These variables are updated by OpenShift/Kubernetes Environment Variable when creating a pod  /-->
    <variable name="serviceAccount" defaultValue="system:serviceaccount:default:xxxxxxx"  />
    <variable name="token" defaultValue="Token for Service Account specified in serviceAccount variable"/>
    <variable name="authEndpointUrl" defaultValue="https://oauth-openshift.apps.xxx.com/oauth/authorize"  />
    <variable name="tokenEndpointUrl" defaultValue="https://oauth-openshift.apps.xxx.com/oauth/token"  />
    <variable name="userApiUrl" defaultValue="https://openshift.default.svc/apis/authentication.k8s.io/v1/tokenreviews"  />
    
    <oauth2Login id="openshiftLogin"
    scope="user:info"
    clientId="${serviceAccount}"
    clientSecret="${token}"
    authorizationEndpoint="${authEndpointUrl}"
    tokenEndpoint="${tokenEndpointUrl}"
    userNameAttribute="username"
    groupNameAttribute="groups"
    userApiToken="${token}"
    userApiType="kube"
    userApi="${userApiUrl}">
  </oauth2Login>
```


```yaml
- name: Create pod for collecting the tar file
  community.kubernetes.k8s:
    state: present
    definition:
      kind: Pod
      apiVersion: v1
      metadata:
        name: downloadapp
        namespace: '{{ ansible_operator_meta.namespace }}'
        labels:
          app: downloadapp
      spec:
        restartPolicy: Always
        containers:
          - resources: {}
            name: download-app
            ports:
              - containerPort: 9080
                protocol: TCP
              - containerPort: 9443
                protocol: TCP
            imagePullPolicy: Always
            volumeMounts:
              - name: storage
                mountPath: /app/upload
            image: quay.io/xxxx/download-app:latest
            env:
              - name: downloadBaseDir
                value: "/app/upload"
              - name: authEndpointUrl
                value: "{{oauth_info.json.authorization_endpoint}}"
              - name: tokenEndpointUrl
                value: "{{oauth_info.json.token_endpoint}}"
              - name: serviceAccount
                value: "system:serviceaccount:{{ansible_operator_meta.namespace}}:downloadapp-sa"
              - name: VARIABLE_SOURCE_DIRS
                value: "/run/secrets/kubernetes.io/serviceaccount"                
        serviceAccount: downloadapp-sa
        volumes:
          - name: storage
            persistentVolumeClaim:
              claimName: pvc-supportfiles
```

# まとめ
上記の設定によって、
OpenShiftのOAuthサーバーを認証プロバイダーとする、OpenShiftのコンテナで動作するLiberty上のJavaアプリケーションを、
Operatorでデプロイすることができます。

忘備録に近い記述で恐縮ですが、誰かの役に立てば幸いです。