OpenShift の上で TCP80を Listen するコンテナが標準で動かない事の確認

# 目的

OpenShift は標準では、Port 80 を Listen するコンテナを動かす事は許可してなかったはず(どうもこれは 1023番以下の well known port の事のような気がする) ...と思ってググってみたものの情報が見つからなかった(見つけられなかった) ので、あらためて確認してみる。

# 使用したファイル類

以下のレポジトリからビルドしたものを使用

https://github.com/yuhkih/nginx-port80.git

# 実験 (動かない事の確認) 

## 実験用コンテナの作成
以下の Dockerfile をビルド

```
# FROM registry.access.redhat.com/ubi8/ubi:latest
FROM redhat/ubi8
MAINTAINER "yuhkih" 

RUN dnf install -y nginx
COPY index.html /usr/share/nginx/html/index.html
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80

CMD ["-g","daemon off;"]
ENTRYPOINT ["nginx"]
```

ビルド 

```
docker build . -t yuhkih/nginx-port80:latest 
```

Docker Hub にプッシュ

```
 docker push yuhkih/nginx-port80 
```

以上で実験用のコンテナの準備は完了。

## OpenShift の作業
OpenShift の新しいプロジェクトを作成

一般ユーザーでログイン。

```
oc login https://api.f5cluster.m51o.p1.openshiftapps.com:6443  -u yuhkih -p xxxxxxx 
```

projectを作成。

```
oc new-project testp
```

コンテナを  `oc new-app` でデプロイ

```
oc new-app --image=docker.io/yuhkih/nginx-port80 -l=app=test
```

Pod がエラーになっている。

```
$ oc get pods
NAME                            READY   STATUS             RESTARTS       AGE
nginx-port80-78f75798bc-qjftq   0/1     CrashLoopBackOff   7 (5m2s ago)   15m
$ 
```

Logを確認。 `Permission Denied` になっている。

```
$ oc logs nginx-port80-78f75798bc-qjftq
2023/06/26 02:43:10 [emerg] 1#0: bind() to 0.0.0.0:80 failed (13: Permission denied)
nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)
$ 
```


# 権限を与えて動かしてみる

## cluster-admin での作業

本筋は別の Port を Listen させる事だが、適当な`SCC (Security Context Contrain)` を与えて動かしてみる。
`project` の管理者に権限を与えるのは、`クラスター` の管理者の仕事なので `cluster-admin` の権限のユーザーで作業を行う。

`Pod` の権限は `Pod`を起動する `Serivce Account` に紐付く。何もしない場合は `default` という `Service Account` で動いている。

まず、この`Pod` を動かすための新しい`Service Account` を作成する。


`cluster-admin` ユーザーでログイン。

```
oc login https://api.f5cluster.m51o.p1.openshiftapps.com:6443  -u cluster-admin -p xxxxxxx 
```

`testp` projectに移動

```
oc project testp
```

`testsa` という Service Account を作成する

```
oc create sa testsa
```

`Service Account` に `SCC` の `anyuid` を与えてみる。

```
$ oc adm policy add-scc-to-user anyuid -z testsa
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:anyuid added: "saport80"
$ 
```

`SCC` と `SA` の紐付は `clusterrolebindings` リソースに記載される。わかりにくい。

```
$ oc describe clusterrolebindings  system:openshift:scc:anyuid
Name:         system:openshift:scc:anyuid
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  ClusterRole
  Name:  system:openshift:scc:anyuid
Subjects:
  Kind            Name      Namespace
  ----            ----      ---------
  ServiceAccount  testsa    testp
$ 
```

## projectの管理ユーザーでの作業

ここからは `cluster-admin` ではなく、`project` の管理ユーザー (`project`の作成者) で作業を行う。

`project`の作成者でログイン。

```
oc login https://api.f5cluster.m51o.p1.openshiftapps.com:6443  -u yuhkih -p xxxxxxx 
```


`Deployment` の名前を確認。(`oc new-app` で以下の Deployment が自動で作られている。便利。)

```
$ oc get deployment
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
nginx-port80   0/1     1            0           22m
$ 
```

`Deployment` に作成した `SA` の `saport80` を紐付ける。

```
oc set serviceaccount deployment nginx-port80 saport80
```



コンテナが起動したのを確認。`anyuid` で起動させる事ができた。

```
$ oc get pods
NAME                            READY   STATUS              RESTARTS        AGE
nginx-port80-54746447ff-n4k5t   0/1     ContainerCreating   0               9s
nginx-port80-78f75798bc-qjftq   0/1     CrashLoopBackOff    11 (113s ago)   32m
$ oc get pods
NAME                            READY   STATUS    RESTARTS   AGE
nginx-port80-54746447ff-n4k5t   1/1     Running   0          32s
$ 
```

本筋はコンテナで公開するポートを変更しておく事。
コンテナレベルで変更しても公開するポートは 80/443 にできるし、後々のトラブルも減る。

# restricted-v2

SCC は、現在 14種類ある。

```
 oc get scc
NAME                              PRIV    CAPS                      SELINUX     RUNASUSER          FSGROUP     SUPGROUP    PRIORITY     READONLYROOTFS   VOLUMES
anyuid                            false   <no value>                MustRunAs   RunAsAny           RunAsAny    RunAsAny    10           false            ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
hostaccess                        false   <no value>                MustRunAs   MustRunAsRange     MustRunAs   RunAsAny    <no value>   false            ["configMap","csi","downwardAPI","emptyDir","ephemeral","hostPath","persistentVolumeClaim","projected","secret"]
hostmount-anyuid                  false   <no value>                MustRunAs   RunAsAny           RunAsAny    RunAsAny    <no value>   false            ["configMap","csi","downwardAPI","emptyDir","ephemeral","hostPath","nfs","persistentVolumeClaim","projected","secret"]
hostnetwork                       false   <no value>                MustRunAs   MustRunAsRange     MustRunAs   MustRunAs   <no value>   false            ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
hostnetwork-v2                    false   ["NET_BIND_SERVICE"]      MustRunAs   MustRunAsRange     MustRunAs   MustRunAs   <no value>   false            ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
machine-api-termination-handler   false   <no value>                MustRunAs   RunAsAny           MustRunAs   MustRunAs   <no value>   false            ["downwardAPI","hostPath"]
node-exporter                     true    <no value>                RunAsAny    RunAsAny           RunAsAny    RunAsAny    <no value>   false            ["*"]
nonroot                           false   <no value>                MustRunAs   MustRunAsNonRoot   RunAsAny    RunAsAny    <no value>   false            ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
nonroot-v2                        false   ["NET_BIND_SERVICE"]      MustRunAs   MustRunAsNonRoot   RunAsAny    RunAsAny    <no value>   false            ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
pcap-dedicated-admins             false   ["NET_ADMIN","NET_RAW"]   RunAsAny    RunAsAny           RunAsAny    RunAsAny    <no value>   false            ["awsElasticBlockStore","azureDisk","azureFile","cephFS","cinder","configMap","csi","downwardAPI","emptyDir","ephemeral","fc","flexVolume","flocker","gcePersistentDisk","gitRepo","glusterfs","iscsi","nfs","persistentVolumeClaim","photonPersistentDisk","portworxVolume","projected","quobyte","rbd","scaleIO","secret","storageOS","vsphere"]
privileged                        true    ["*"]                     RunAsAny    RunAsAny           RunAsAny    RunAsAny    <no value>   false            ["*"]
restricted                        false   <no value>                MustRunAs   MustRunAsRange     MustRunAs   RunAsAny    <no value>   false            ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
restricted-v2                     false   ["NET_BIND_SERVICE"]      MustRunAs   MustRunAsRange     MustRunAs   RunAsAny    <no value>   false            ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
splunkforwarder                   true    ["*"]                     RunAsAny    RunAsAny           RunAsAny    RunAsAny    <no value>   false            ["*"]
$ 

# 全部で 14種類ある
$ oc get scc | grep -v NAME | wc -l
14
$ 
```

良く見ると `restricted-v2` では、　`NET_BIND_SERVICE` が Capability として追加されている。

これの `restricted-v2`は、OpenShift のデフォルトなので、何もしなくても Port 80 を LISTEN する Pod を起動できたのでは？と思ったのですが、以下の Kubernetes の issue があるため、そのままでは動作しないそう。

https://github.com/kubernetes/kubernetes/issues/56374

中身を見ると長い経緯があるため、簡単には解決しないような気が...

`NET_BIND_SERVICE` を許可した動作をさせたい場合は、`spec.containers.securityContext.capabilities.add` 等を使って Deployment に定義する事で動作させることができた。

```
<省略>
        app: test
        deployment: nginx-port80
    spec:
      containers:
      - image: docker.io/yuhkih/nginx-port80
        imagePullPolicy: IfNotPresent
        name: nginx-port80
        ports:
        - containerPort: 80
          protocol: TCP
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext:
        capabilities:
          add: ["NET_BIND_SERVICE"]
<省略>
```