ibmcloud: Red Hat OpenShift on IBM Cloud(ROKS)が任意のnamespaceからpull-secretの構成なしで外部レジストリ(icr.io)にアクセスできる理由

# 1. そもそもこの記事を書く上でのきっかけ


IBM Cloud monitoring(Sysdig)やIBM Cloud Log Analysis(LogDNA/MEZMO)をUI上からROKSに導入すると`ibm-observe`というnamespaceにDaemon Setが作成される。利用されるイメージは、以下の通り、`icr.io`という外部レジストリーを利用している。

```txt:環境情報
[root@new-syasuda-tok1-vpc1 ~]# oc version
Client Version: 4.11.0-202210250857.p0.g142cb44.assembly.stream-142cb44
Kustomize Version: v4.5.4
Server Version: 4.11.12
Kubernetes Version: v1.24.6+5157800
```

```
[root@new-syasuda-tok1-vpc1 ~]# oc get ds -n ibm-observe
NAME           DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
logdna-agent   2         2         2       2            2           <none>          50d
sysdig-agent   2         2         2       2            2           <none>          50d

[root@new-syasuda-tok1-vpc1 ~]# oc get pods -n ibm-observe
NAME                 READY   STATUS    RESTARTS   AGE
logdna-agent-2p6p4   1/1     Running   0          30h
logdna-agent-75pxm   1/1     Running   0          30h
sysdig-agent-b9tlg   1/1     Running   0          30h
sysdig-agent-tx6q7   1/1     Running   0          30h

[root@new-syasuda-tok1-vpc1 ~]# oc get pods logdna-agent-2p6p4 -n ibm-observe -o yaml|grep -i image
    image: icr.io/ext/logdna-agent:stable
    imagePullPolicy: Always
  imagePullSecrets:
    image: icr.io/ext/logdna-agent:stable
    imageID: icr.io/ext/logdna-agent@sha256:a52f2e44ad7db9f88cb3da9565772734449ec97c8c6182b4369d42928c3eaa23

[root@new-syasuda-tok1-vpc1 ~]# oc get pods sysdig-agent-b9tlg  -n ibm-observe -o yaml|grep -i image
    image: icr.io/ext/sysdig/agent
    imagePullPolicy: Always
  imagePullSecrets:
    image: icr.io/ext/sysdig/agent:latest
    imageID: icr.io/ext/sysdig/agent@sha256:45b10702667b33b0663dd23164fc9f928945083060c14c3dd1e4b538b9348942
```


一方、icr.ioはpublic registryではない。以下のように認証なしにはアクセスできない。よって、どこかに認証情報が構成されている必要がある。

```
[root@new-syasuda-tok1-vpc1 ~]# skopeo inspect docker://icr.io/ext/istio/examples-bookinfo-reviews-v3:1.15.0
FATA[0001] Error parsing image name "docker://icr.io/ext/istio/examples-bookinfo-reviews-v3:1.15.0": unable to retrieve auth token: invalid username/password: unauthorized: The login credentials are not valid, or your IBM Cloud account is not active.

[root@new-syasuda-tok1-vpc1 ~]# skopeo inspect docker://private.icr.io/ext/istio/examples-bookinfo-reviews-v3:1.15.0
FATA[0001] Error parsing image name "docker://private.icr.io/ext/istio/examples-bookinfo-reviews-v3:1.15.0": unable to retrieve auth token: invalid username/password: unauthorized: The login credentials are not valid, or your IBM Cloud account is not active.
```

しかし、以下のようにこのDaemon Setに紐づいているService Accountのpull secretsにはicr.ioに関する認証情報は含まれていない。一般的には、[default namespaceに存在するall-icr-ioというsecretを該当のnamespaceにコピーして使うようにガイドされている](https://cloud.ibm.com/docs/openshift?topic=openshift-registry)が、他にそういうsecretがコピーされている様子もない。では、どこでicr.ioへの認証は行われているのだろうか？　それとも、`ibm-observe`というnamespaceだけ何か特別な設定がされているのだろうか？という疑問が生じたのがこの記事を書くきっかけである。

```
[root@new-syasuda-tok1-vpc1 ~]# oc get ds logdna-agent -n ibm-observe -o jsonpath='{.spec.template.spec.serviceAccount}{"\n"}'
logdna-agent

[root@new-syasuda-tok1-vpc1 ~]# oc describe sa logdna-agent -n ibm-observe
Name:                logdna-agent
Namespace:           ibm-observe
Labels:              <none>
Annotations:         <none>
Image pull secrets:  logdna-agent-dockercfg-lmqwp
Mountable secrets:   logdna-agent-dockercfg-lmqwp
Tokens:              logdna-agent-token-b2qgh
Events:              <none>

[root@new-syasuda-tok1-vpc1 ~]# oc get secrets logdna-agent-dockercfg-lmqwp -n ibm-observe -o jsonpath='{.data.\.dockercfg}' | base64 -d | jq
{
  "172.21.75.245:5000": {
    "username": "serviceaccount",
    "password": "<data>",
    "email": "serviceaccount@example.org",
    "auth": "<data>"
  },
  "image-registry.openshift-image-registry.svc.cluster.local:5000": {
    "username": "serviceaccount",
    "password": "<data>",
    "email": "serviceaccount@example.org",
    "auth": "<data>"
  },
  "image-registry.openshift-image-registry.svc:5000": {
    "username": "serviceaccount",
    "password": "<data>",
    "email": "serviceaccount@example.org",
    "auth": "<data>"
  }
}
```
```
[root@new-syasuda-tok1-vpc1 ~]# oc get ds sysdig-agent -n ibm-observe -o jsonpath='{.spec.template.spec.serviceAccount}{"\n"}'
sysdig-agent

[root@new-syasuda-tok1-vpc1 ~]# oc describe sa sysdig-agent -n ibm-observe
Name:                sysdig-agent
Namespace:           ibm-observe
Labels:              <none>
Annotations:         <none>
Image pull secrets:  sysdig-agent-dockercfg-5f55h
Mountable secrets:   sysdig-agent-dockercfg-5f55h
Tokens:              sysdig-agent-token-4474r
Events:              <none>

[root@new-syasuda-tok1-vpc1 ~]# oc get secrets sysdig-agent-dockercfg-5f55h -n ibm-observe -o jsonpath='{.data.\.dockercfg}' | base64 -d | jq
{
  "172.21.75.245:5000": {
    "username": "serviceaccount",
    "password": "<data>",
    "email": "serviceaccount@example.org",
    "auth": "<data>"
  },
  "image-registry.openshift-image-registry.svc.cluster.local:5000": {
    "username": "serviceaccount",
    "password": "<data>",
    "email": "serviceaccount@example.org",
    "auth": "<data>"
  },
  "image-registry.openshift-image-registry.svc:5000": {
    "username": "serviceaccount",
    "password": "<data>",
    "email": "serviceaccount@example.org",
    "auth": "<data>"
  }
}
```

openshift-configなどのnamespaceにも当然含まれていない。

```
[root@new-syasuda-tok1-vpc1 ~]# oc get secrets -n openshift-config
NAME                       TYPE                                  DATA   AGE
builder-dockercfg-td579    kubernetes.io/dockercfg               1      510d
builder-token-wrmmf        kubernetes.io/service-account-token   4      510d
builder-token-xzrc2        kubernetes.io/service-account-token   4      510d
default-dockercfg-45r68    kubernetes.io/dockercfg               1      510d
default-token-7kv57        kubernetes.io/service-account-token   4      510d
default-token-dhczn        kubernetes.io/service-account-token   4      510d
deployer-dockercfg-t4zq2   kubernetes.io/dockercfg               1      510d
deployer-token-fz5jv       kubernetes.io/service-account-token   4      510d
deployer-token-xjglw       kubernetes.io/service-account-token   4      510d
htpasswd-secret            Opaque                                1      497d
pull-secret                kubernetes.io/dockerconfigjson        1      510d

[root@new-syasuda-tok1-vpc1 ~]# oc get secrets pull-secret -n openshift-config -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d  | jq
{
  "auths": {
    "cloud.openshift.com": {
      "auth": "<data>",
      "email": "ibmocp@us.ibm.com"
    },
    "quay.io": {
      "auth": "<data>",
      "email": "ibmocp@us.ibm.com"
    },
    "registry.connect.redhat.com": {
      "auth": "<data>",
      "email": "ibmocp@us.ibm.com"
    },
    "registry.redhat.io": {
      "auth": "<data>",
      "email": "ibmocp@us.ibm.com"
    }
  }
}
```


# 2. 任意のnamespaceからもicr.io上のイメージを取得できてしまう。

試しに、新規にprojectを作成し、そこにicr.io上のイメージを指定してPodを動かそうとしてみたが、驚くべきことに、無事イメージを取得できてしまった。当然該当するデフォルトのsecretにはicr.ioに対する認証情報は含まれていない。よって、これはIBM Cloud monitoring(Sysdig)やIBM Cloud Log Analysis(LogDNA/MEZMO)だけで何か特別に行われている構成ではなく、プラットフォーム全体で有効な設定であることが想像される。

```
[root@new-syasuda-tok1-vpc1 ~]# oc new-project testproject1
[root@new-syasuda-tok1-vpc1 ~]# oc run samplepod1 --image=icr.io/ext/istio/examples-bookinfo-reviews-v3:1.15.0
[root@new-syasuda-tok1-vpc1 ~]# oc get pods
NAME                         READY   STATUS    RESTARTS     AGE
samplepod1                   1/1     Running   0            30s

[root@new-syasuda-tok1-vpc1 ~]# oc describe pod samplepod1
（途中略）
  Type    Reason          Age   From               Message
  ----    ------          ----  ----               -------
  Normal  Scheduled       33s   default-scheduler  Successfully assigned syasuda1/samplepod1 to 10.0.0.25 by kube-scheduler-794dfbddfd-7sgsv
  Normal  AddedInterface  31s   multus             Add eth0 [172.17.34.53/32] from k8s-pod-network
  Normal  Pulled          31s   kubelet            Container image "icr.io/ext/istio/examples-bookinfo-reviews-v3:1.15.0" already present on machine
  Normal  Created         31s   kubelet            Created container samplepod1
  Normal  Started         31s   kubelet            Started container samplepod1

[root@new-syasuda-tok1-vpc1 ~]# oc get pods samplepod1 -o jsonpath='{.spec.imagePullSecrets[*].name}{"\n"}'
default-dockercfg-lkb8m

[root@new-syasuda-tok1-vpc1 ~]# oc get secrets
NAME                       TYPE                                  DATA   AGE
builder-dockercfg-wwwpt    kubernetes.io/dockercfg               1      15m
builder-token-x4p8g        kubernetes.io/service-account-token   4      15m
default-dockercfg-lkb8m    kubernetes.io/dockercfg               1      15m
default-token-mrv6r        kubernetes.io/service-account-token   4      15m
deployer-dockercfg-nd2kh   kubernetes.io/dockercfg               1      15m
deployer-token-9vjcg       kubernetes.io/service-account-token   4      15m


[root@new-syasuda-tok1-vpc1 ~]# oc get secrets default-dockercfg-lkb8m -o jsonpath='{.data.\.dockercfg}' | base64 -d | jq
{
  "172.21.75.245:5000": {
    "username": "serviceaccount",
    "password": "<data>",
    "email": "serviceaccount@example.org",
    "auth": "<data>"
  },
  "image-registry.openshift-image-registry.svc.cluster.local:5000": {
    "username": "serviceaccount",
    "password": "<data>",
    "email": "serviceaccount@example.org",
    "auth": "<data>"
  },
  "image-registry.openshift-image-registry.svc:5000": {
    "username": "serviceaccount",
    "password": "<data>",
    "email": "serviceaccount@example.org",
    "auth": "<data>"
  }
}
```

ちなみに、icr.ioではなく、private.icr.ioを利用した場合は以下のように失敗するため、これはicr.ioだけ許容される操作なのであろうと想像される。

```
[root@new-syasuda-tok1-vpc1 ~]# oc run samplepod2 --image=private.icr.io/ext/istio/examples-bookinfo-reviews-v3:1.15.0

[root@new-syasuda-tok1-vpc1 ~]# oc describe pod samplepod2
（途中略）
Events:
  Type     Reason          Age                      From               Message
  ----     ------          ----                     ----               -------
  Normal   Scheduled       36s                      default-scheduler  Successfully assigned testproject1/samplepod2 to 10.0.0.25 by kube-scheduler-794dfbddfd-7sgsv
  Normal   AddedInterface  36s                      multus             Add eth0 [172.17.34.31/32] from k8s-pod-network
  Normal   BackOff         6s (x2 over 34s)         kubelet            Back-off pulling image "private.icr.io/ext/istio/examples-bookinfo-reviews-v3:1.15.0"
  Warning  Failed          6s (x2 over 34s)         kubelet            Error: ImagePullBackOff
  Normal   Pulling         <invalid> (x3 over 35s)  kubelet            Pulling image "private.icr.io/ext/istio/examples-bookinfo-reviews-v3:1.15.0"
  Warning  Failed          <invalid> (x3 over 34s)  kubelet            Failed to pull image "private.icr.io/ext/istio/examples-bookinfo-reviews-v3:1.15.0": rpc error: code = Unknown desc = unable to retrieve auth token: invalid username/password: unauthorized: The login credentials are not valid, or your IBM Cloud account is not active.
  Warning  Failed          <invalid> (x3 over 34s)  kubelet            Error: ErrImagePull
```

# 3. アクセスできる理由はノード構成にある

結局、この辺りはkubeletのノード側の構成にあるのではないだろうかと想像して、ノードの中を探ってみたらビンゴだった。

```txt:ノードにログイン
[root@new-syasuda-tok1-vpc1 ~]# oc debug -t  node/10.0.0.25 -- chroot /host
```

```txt:一般的なkubeletの構成情報は/var/lib/kubelet配下にあるが、以下のように/var/data/kubeletにシンボリックリンクが張られている。
sh-4.4# ls -ld /var/lib/kubelet
lrwxrwxrwx. 1 root root 17 Oct  4 17:35 /var/lib/kubelet -> /var/data/kubelet
```

```txt:/var/data/kubeletをルートディレクトリとしてkubeletは起動されている
sh-4.4# ps -ef|grep kubelet.conf |grep -v grep
root       7978      1  9 Oct04 ?        5-08:36:25 kubelet --config=/etc/kubernetes/kubelet.conf --root-dir=/var/data/kubelet --v=2 --kubeconfig=/etc/kubernetes/kubelet-kubeconfig --hostname-override=10.0.0.25 --cloud-provider=external --container-runtime=remote --container-runtime-endpoint=/var/run/crio/crio.sock --runtime-cgroups=/podruntime.slice
```
```txt:/var/data/kubelet配下にconfig.jsonがある
sh-4.4# ls -l /var/data/kubelet
total 32
-rw-------.  1 root root 3533 Oct  4 17:36 config.json
-rw-------.  1 root root   62 Oct  4 17:36 cpu_manager_state
drwxr-xr-x.  2 root root 4096 Oct  4 17:36 device-plugins
-rw-------.  1 root root   61 Oct  4 17:36 memory_manager_state
drwxr-x---.  2 root root 4096 Oct  4 17:36 plugins
drwxr-x---.  2 root root 4096 Oct  4 17:36 plugins_registry
drwxr-x---.  2 root root 4096 Oct  4 17:36 pod-resources
drwxr-x---. 60 root root 4096 Nov 24 23:45 pods
```
```txt:icr.ioに関する認証情報が含まれていた！
sh-4.4# cat /var/data/kubelet/config.json | jq
{
  "auths": {
    "cloud.openshift.com": {
      "auth": "<data>"
    },
    "quay.io": {
      "auth": "<data>"
    },
    "registry.connect.redhat.com": {
      "auth": "<data>"
    },
    "registry.redhat.io": {
      "auth": "<data>"
    },
    "icr.io": {
      "auth": "<data>"
    },
    "registry.bluemix.net": {
      "auth": "<data>"
    },
    "registry.au-syd.bluemix.net": {
      "auth": "<data>"
    },
    "registry.eu-gb.bluemix.net": {
      "auth": "<data>"
    },
    "registry.eu-de.bluemix.net": {
      "auth": "<data>"
    },
    "registry.ng.bluemix.net": {
      "auth": "<data>"
    }
  }
}
```

```txt:認証情報はiamapikey:<キー情報>の形式で記述されたものをbase64でエンコードして保管されている。
sh-4.4# cat /var/data/kubelet/config.json | jq -r '.auths."icr.io".auth' | base64 -d
iamapikey:<API Key情報>

```

icr.ioにアクセスできるからといっても、当然構成されているAPIKeyの権限でアクセスできるイメージにしかアクセスできないはずである。おそらくicr.io/extなどのイメージしかアクセスできないのだろう。念のため、この情報を使ってicr.io(IBM Cloud Container Registry)にアクセスできることも確認してみた。

```txt:worker nodeにあるconfig.jsonを使ってICRにログインする
[root@new-syasuda-tok1-vpc1 ~]# ibmcloud login --apikey $(oc debug -t node/10.0.0.25 -- chroot host cat /var/data/kubelet/config.json | jq -r '.auths."icr.io".auth' | base64 -d| cut -f 2 -d ":")


[root@new-syasuda-tok1-vpc1 ~]# ibmcloud cr info

Plugin version                    1.0.2
Container Registry                icr.io
Container Registry API endpoint   https://icr.io/api
IBM Cloud API endpoint            https://cloud.ibm.com
IBM Cloud account details         Argonauts Production 531277 (e223e119c9be31669e5688bb376411f7)

[root@new-syasuda-tok1-vpc1 ~]# ibmcloud cr images --restrict ext/sysdig/agent | grep -e Tag -e latest
Repository                Tag        Digest         Namespace   Created         Size     Security status
icr.io/ext/sysdig/agent   latest     45b10702667b   ext         2 weeks ago     696 MB   -

[root@new-syasuda-tok1-vpc1 ~]# ibmcloud cr images --restrict ext/logdna-agent | grep -e Tag -e stable
Repository                Tag                                                          Digest         Namespace   Created         Size     Security status
icr.io/ext/logdna-agent   stable                                                       48deacca9636   ext         1 day ago       125 MB   -
icr.io/ext/logdna-agent   v2-stable                                                    48deacca9636   ext         1 day ago       125 MB   -

[root@new-syasuda-tok1-vpc1 ~]# ibmcloud cr images --restrict ext/istio/examples-bookinfo-reviews-v3 | grep -e Tag -e 1.15.0
Repository                                      Tag      Digest         Namespace   Created       Size     Security status
icr.io/ext/istio/examples-bookinfo-reviews-v3   1.15.0   e454cab754cf   ext         3 years ago   329 MB   -
```