IBM Cloud: private-only環境のROKS(Red Hat OpenShift on IBM Cloud)にポート443のみを使ってocコマンドでログインする方法

#1. はじめに
OpenShiftでocコマンドを使って接続する際には、API Endpointにアクセスする必要がある。また、API Endpointにアクセスするためには、認証トークンが必要になる。一般的にはUsername/Passwordを指定して認証するが、ocコマンドを実行する度に認証をする必要がないのは実際には認証に伴ってOpenShiftによって発行される認証トークンを使っているからである。

`oc login -u <username> -p <password> yyyy <API Endpoint> --loglevel=10`を実行してみると、まずAPI EndpointからOAuth認証用のEndpointを取得した後、そのEndpointを使って認証トークンを取得し、再度API Endpointにアクセスしていることがわかる。

Red Hat OpenShift on IBM Cloud(ROKS)におけるprivate only環境では

- API EndpointおよびOAuth認証用Endpointが共に、VPC内部からしかアクセスできない(166.8.0.0/14などの[IBM Cloud管理のサービス用subnet](https://cloud.ibm.com/docs/vpc?topic=vpc-build-se-connectivity-using-vpn)から割り当てられる)。Direct Link経由でもインターネットからも直接はアクセスできない。
- API EndpointおよびOAuth認証用Endpointのポート番号が30000-32767のいずれかである（クラスターをデプロイするまで割り当てられる番号はわからない）。

という制約があり、企業ネットワークとIBM Cloud環境をDirect Linkで接続していても、敷居が高いことがある。VPN Clientを導入することができれば、[IBM Cloud: private-only環境のROKS(Red Hat OpenShift on IBM Cloud)にClient-to-Server VPNを使ってアクセスする方法](https://qiita.com/testnin2/items/e506b3bf7fe5b95f60ff)を使ってアクセスするのが最も楽だが、そもそも

- 端末にVPN Softwareを導入することがNG
- 端末がインターネットに繋がっていないのでVPNを実行できない
- 会社のルールとしてポート番号が443以外はアクセス許可していない

などの制約がある環境も多い。そこで、今回は以下のような構成をすることでポート443しかアクセスできない環境でもアクセスできる構成を試してみた。


- ROKS上にAPI EndpointにアクセスするためのRouteを作成する。これにより、API Endpointにポート443でアクセス可能となる。
- ROKS上にOAuth認証用のEndpointにアクセスするためのRouteを作成する。これにより、OAuth認証用のEndpointにもポート443でアクセス可能となる。
- `oc login -u <username> -p <password> yyyy <Routeで公開したAPI Endpoint>`だと、ocコマンドはオリジナルのOauth認証用Endpointにredirectされて認証を試みるため、上記で作成したRouteを通らない。先にOAuth認証用のEndpointから認証トークンを手動で取得しておき、その上で`oc login --token xxxxx <Routeで公開したAPI Endpoint>`を実行することとする。
- 認証トークン取得方法については、[こちら](https://cloud.ibm.com/docs/openshift?topic=openshift-access_cluster#access_api_key)を参考にした。


#2. 事前準備

以下は、VPC上のVSIなどで事前に構成しておく作業となる。

##2-1: API Endpointのrouteを作成


```
$ ibmcloud oc cluster get --cluster privonly-syasuda-roksvpc
Retrieving cluster privonly-syasuda-roksvpc...
OK

Name:                           privonly-syasuda-roksvpc
ID:                             xxxxxxxxxxxxxxxxxxxx
State:                          normal
Status:                         All Workers Normal
Created:                        2021-07-08 09:13:01 +0900 (2 months ago)
Resource Group ID:              xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Resource Group Name:            Default
Pod Subnet:                     172.17.0.0/18
Service Subnet:                 172.21.0.0/16
Workers:                        2
Worker Zones:                   jp-tok-1, jp-tok-2
Ingress Subdomain:              privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud
Ingress Secret:                 privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000
Ingress Status:                 healthy
Ingress Message:                All Ingress components are healthy
Public Service Endpoint URL:    https://c100-e.private.jp-tok.containers.cloud.ibm.com:31811
Private Service Endpoint URL:   https://c100.private.jp-tok.containers.cloud.ibm.com:31811
Pull Secrets:                   enabled in the default namespace
VPCs:                           xxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Master
Status:     Ready (3 weeks ago)
State:      deployed
Health:     normal
Version:    4.7.23_1529_openshift
Location:   Tokyo
URL:        https://c100-e.private.jp-tok.containers.cloud.ibm.com:31811
```

ここで、一番最後にある`URL`の箇所の`https://c100-e.private.jp-tok.containers.cloud.ibm.com:31811`がAPI Endpointである。
以下で、このAPI EndpointのIPアドレスを調べる。

```
$ host c100-e.private.jp-tok.containers.cloud.ibm.com
c100-e.private.jp-tok.containers.cloud.ibm.com is an alias for c100.private.jp-tok.containers.cloud.ibm.com.
c100.private.jp-tok.containers.cloud.ibm.com is an alias for prod-jp-tok-tugboat1.jp-tok.serviceendpoint.cloud.ibm.com.
prod-jp-tok-tugboat1.jp-tok.serviceendpoint.cloud.ibm.com has address 166.9.44.16
prod-jp-tok-tugboat1.jp-tok.serviceendpoint.cloud.ibm.com has address 166.9.40.22
prod-jp-tok-tugboat1.jp-tok.serviceendpoint.cloud.ibm.com has address 166.9.42.24
```

このIPアドレスとポート番号を元に、以下のようなYAMLファイルを作成する。

```yaml:api-via-alb.yaml
apiVersion: v1
kind: Service
metadata:
  name: api-via-alb
spec:
  ports:
  - protocol: TCP
    port: 8443

---

apiVersion: v1
kind: Endpoints
metadata:
  name: api-via-alb
subsets:
  - addresses:
      - ip: 166.9.42.24
      - ip: 166.9.40.22
      - ip: 166.9.44.16
    ports:
      - port: 31811
```

このYAMLファイルを実行して、本来のAPI Endpointにアクセスするためのrouteを作成する。

```
$ oc create api-via-alb.yaml
$ oc create route passthrough api-via-alb --service api-via-alb
$ oc get svc,endpoints,route
NAME                                TYPE           CLUSTER-IP       EXTERNAL-IP                            PORT(S)    AGE
service/api-via-alb                 ClusterIP      172.21.137.168   <none>                                 8443/TCP   17h

NAME                                  ENDPOINTS                                               AGE
endpoints/api-via-alb                 166.9.42.24:31811,166.9.40.22:31811,166.9.44.16:31811   17h

NAME                                     HOST/PORT                                                                                                                PATH   SERVICES        PORT    TERMINATION   WILDCARD
route.route.openshift.io/api-via-alb     api-via-alb-default.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud            api-via-alb     <all>   passthrough   None
```

##2-2: OAuth認証用 Endpointのrouteを作成

OAuth認証用Endpointは、API Endpointから取得できる。

```OAuth認証用Endpointの取得
$ curl -sk https://c100-e.private.jp-tok.containers.cloud.ibm.com:31811/.well-known/oauth-authorization-server | jq -r .issuer
https://c100-e.private.jp-tok.containers.cloud.ibm.com:30701
```

以下のYAMLファイルを作成する（URL自体はAPI Endpointと同じであり、ポート番号だけが異なるはずである）。

```yaml:oauth-via-alb.yaml
apiVersion: v1
kind: Service
metadata:
  name: oauth-via-alb
spec:
  ports:
  - protocol: TCP
    port: 8443

---

apiVersion: v1
kind: Endpoints
metadata:
  name: oauth-via-alb
subsets:
  - addresses:
      - ip: 166.9.42.24
      - ip: 166.9.40.22
      - ip: 166.9.44.16
    ports:
      - port: 30701
```

このYAMLファイルを実行して、本来のOAuth認証用のEndpointにアクセスするためのrouteを作成する。

```
$ oc create oauth-via-alb.yaml
$ oc create route passthrough oauth-via-alb --service oauth-via-alb
$ oc get svc,endpoints,route
NAME                                TYPE           CLUSTER-IP       EXTERNAL-IP                            PORT(S)    AGE
service/api-via-alb                 ClusterIP      172.21.137.168   <none>                                 8443/TCP   17h
service/oauth-via-alb               ClusterIP      172.21.239.220   <none>                                 8443/TCP   17h

NAME                                  ENDPOINTS                                               AGE
endpoints/api-via-alb                 166.9.42.24:31811,166.9.40.22:31811,166.9.44.16:31811   17h
endpoints/oauth-via-alb               166.9.42.24:30701,166.9.40.22:30701,166.9.44.16:30701   17h

NAME                                     HOST/PORT                                                                                                                PATH   SERVICES        PORT    TERMINATION   WILDCARD
route.route.openshift.io/api-via-alb     api-via-alb-default.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud            api-via-alb     <all>   passthrough   None
route.route.openshift.io/oauth-via-alb   oauth-via-alb-default.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud          oauth-via-alb   <all>   passthrough   None
```

これで事前準備は完了

#3. ocコマンドによるログイン

事前にIBM CloudのAPI Keyを取得しておく。
API EndpointおよびOAuth認証用Endpointは上記で作成したrouteのアドレスを入力する。

```shell:環境変数の設定
$ OPENSHIFT_APIKEY=apikey:<API_Key>
$ OPENSHIFT_API_ENDPOINT=https://api-via-alb-default.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud
$ OPENSHIFT_OAUTH_ENDPOINT=https://oauth-via-alb-default.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud
```


```shell:認証トークンの取得
$ OPENSHIFT_TOKEN=$(curl -skI -u ${OPENSHIFT_APIKEY} -H "X-CSRF-Token: 1" "${OPENSHIFT_OAUTH_ENDPOINT}/oauth/authorize?client_id=openshift-challenging-client&response_type=token" | grep Location | sed -e "s/.*access_token=//"|sed -e "s/\&.*//")

```

初回ログインの時だけ以下のような警告が出るので受け入れる。


```shell:ocコマンドによるログイン
$ oc login --token=${OPENSHIFT_TOKEN} ${OPENSHIFT_API_ENDPOINT}
The server is using a certificate that does not match its hostname: x509: certificate is valid for kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.cluster.local, kube-apiserver, kube-apiserver.master-xxxxxxxxxxxxxxxxxxxx.svc, kube-apiserver.master-xxxxxxxxxxxxxxxxxxxx.svc.cluster.local, openshift, openshift.default, openshift.default.svc, openshift.default.svc.cluster.local, c100.private.jp-tok.containers.cloud.ibm.com, c100.private.jp-tok.containers.cloud.ibm.com, c100-e.jp-tok.containers.cloud.ibm.com, c100-e.private.jp-tok.containers.cloud.ibm.com, c3j463dt0o165mnm7be0.vpe.private.jp-tok.containers.cloud.ibm.com, localhost, not api-via-alb-default.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud
You can bypass the certificate check, but any data you send to the server could be intercepted by others.
Use insecure connections? (y/n): y

Logged into "https://api-via-alb-default.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud:443" as "IAM#shiyasu@jp.ibm.com" using the token provided.

You have access to 68 projects, the list has been suppressed. You can list all projects with ' projects'

Using project "default".
Welcome! See 'oc help' to get started.
```


どうしてもこの警告が出るのが嫌だったら、、、、以下のいずれかを行なって`c100-e.private.jp-tok.containers.cloud.ibm.com`にアクセスするというのも１つの方法。

- オリジナルのAPI EndpointのURL（今回だと`c100-e.private.jp-tok.containers.cloud.ibm.com`）のCNAMEを`api-via-alb-default.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud`に端末が利用するDNSで構成しておく
- `/etc/hosts`に`c100-e.private.jp-tok.containers.cloud.ibm.com`のIPアドレスとして`api-via-alb-default.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud`のIPアドレスを書いておく