STNS環境でrootlessコンテナを実行するためのsubuidとsubgidを追加する

# はじめに
rootlessコンテナを実行する際にsubuidとsubgidがある前提で進めている記事があります．しかし，subuidとsubgidの追加方法について載っている記事はなかなか見つけにくいです．

STNS(Simple Toml Name Service)[^9]はTOML(Tom's Obvious, Minimal Language.)[^10]ベースの構成でLinuxユーザーを簡単に管理できるOSS(Open Source Software)です．STNSがあれば，複数のサーバー1つ1つにauthorized_keysを設定しなくてもSSH公開鍵認証でアクセスすることができるようになります．

# 環境
- Ubuntu 22.04.2 LTS
    - `lsb_release -a`[^1]
- STNS v2.2.12-1
    - `apt list --installed | grep stns`[^2]


# 問題点 
STNSを用いてユーザー管理をしている場合，初めてログインしたユーザーにホームディレクトリを作成する方法があります．

以下を`/etc/pam.d/sshd`の末尾に追加することでホームディレクトリを自動で作成することができます[^11]．
```config:/etc/pam.d/sshd
session    required     pam_mkhomedir.so skel=/etc/skel/ umask=0022
```

この方法では，uid，gidはSTNSで作成できてもsubuid，subgidが生成されません．
実際に以下のコマンドで何も出力されない場合はsubuid，subgidがありません．
```shell
$ grep $USER /etc/subuid /etc/subgid
```

# 解決策
ログインした際にsubuidとsubgidを生成するshell scriptを/etc/bash.bashrcに追記します．subuidとsubgidの生成には`usermod`[^6]コマンドを使用します．

追記する内容は以下に載せます．
```shell:/etc/bash.bashrc
# 1. 登録されたsubuidから1番うしろのsubuidを取得
ID_FILE='/etc/subuid'
LASTID=`cat ${ID_FILE} | tail -n 1 | cut -f 2 -d ":"`

# 2. 登録するsubuidとsubgidの始まりの値の決定
if [ -z "$LASTID" ]; then
HEADID=100000
else
HEADID=$(($LASTID + 65536))
fi

# 3. 登録するsubuidとsubgidの終わりの値の決定
TAILID=$(($HEADID + 65535))

# 4. subuidとsubgidの登録
USERID=`grep $USER /etc/subuid | cut -f 1 -d ":"`
if [ ! "$USER" = "$USERID" ]; then
sudo usermod -v "$HEADID""-""$TAILID" -w "$HEADID""-""$TAILID" $USER
echo "Create subuid & subgid: ""$HEADID""-""$TAILID"
fi
```

## subuidとsubgidの確認
```shell-session
$ grep $USER /etc/subuid /etc/subgid
/etc/subuid:g212200344:1410720:65536
/etc/subgid:g212200344:1410720:65536
```
このようにsubuidとsubgidが出力されれば成功です！


---
ここから下は解説

---

## 解説
やっていることの解説をします．
1. 登録されたsubuidから一番うしろのsubuidを取得
1. 登録するsubuidとsubgidの始まりの値の決定
1. 登録するsubuidとsubgidの終わりの値の決定
1. subuidとsubgidの登録

### 変数と言葉の対応関係
HEADID:登録するIDの始まりの値
TAILID:登録するIDの終わりの値


## 1. 登録されたsubuidから1番うしろのsubuidを取得
subuidが記録しているファイルから1番うしろの行のsubuidを取得します．コマンドで説明すると，`cat`[^3] [^4]でファイルの内容を取得し，`tail`で最後の1行を取り出し，`cut`で":"で分けた2つ目の要素を取り出します．

## 2. 登録するsubuidとsubgidの始まりの値の決定
1.で出力したIDに何も入っていない際[^5]は，登録するIDの始まりの値を100000にします．Ubuntuは`useradd`や`adduser`コマンドでユーザーを追加した際にsubuidとsubgidの始まりの値を100000と設定するため，それに合わせています．

もし，1.で出力したIDがあった際はIDとして必要な範囲，65535の次の値である65536を足した値を登録するIDの始まりの値にします．Ubuntuは`useradd`や`adduser`コマンドでユーザーを追加した際の値に合わせています．rootlessコンテナでは`apt-get`を実行した際は65534番を使用しているので，同じように割り当てられているものが65535個あるのでしょう．

subuidとsubgidの範囲が足りない際に`apt-get`すると以下のようなエラーが出ると思います．
```shell-session
[error]
setgroups 65534 error
setegid 65534 error
```

## 3. 登録するsubuidとsubgidの終わりの値の決定
65535個のsubuidとsubgidのIDを割り当てるので，登録するIDの始まりの値から65535足した[^7]値を登録するIDの終わりの値とする．

## 4. subuidとsubgidの登録
subuidが記録しているファイルからsubuidが割り振られているか確認する[^8]．subuidが割り振られている際は登録せずに処理を追える．subuidが割り振られていない際は`usermod`コマンドでsubuidとsubgidを登録する．最後にsubuidとsubgidが新しく割り振られた際はSSH公開鍵認証でアクセスしたターミナルに新しくsubuidとsubgidが割り振られたことを表示する．

# おまけ ~Ansibleでsubgidとsubuidを登録できるようにするには？~

1つ1つサーバーにログインして先程の/etc/bash.bashrcに追記するのは骨が折れると思います．なので，Ansible(IT自動化ツールの1つ)でぱぱっと入れちゃいたい人用のyaml(YAML Ain't Markup Language)ファイルを載せておきます．

```yaml:add_sub_uid_gid.yaml
- hosts: all
  tasks:
    - name: Copy sshrc file
      become: true
      ansible.builtin.blockinfile:
        path: /etc/bash.bashrc
        block: |
          ID_FILE='/etc/subuid'
          LASTID=`cat ${ID_FILE} | tail -n 1 | cut -f 2 -d ":"`
          if [ -z "$LASTID" ]; then
            LASTID=100000
          else
            LASTID=$(($LASTID + 65536))
          fi
          USERID=`grep $USER /etc/subuid | cut -f 1 -d ":"`
          TAILID=$(($LASTID + 65535))
          if [ ! "$USER" = "$USERID" ]; then
            sudo usermod -v "$LASTID""-""$TAILID" -w "$LASTID""-""$TAILID" $USER
            echo "Create subuid & subgid: ""$LASTID""-""$TAILID"
          fi
        state: present
    - name: Remove subuid
      become: true
      ansible.builtin.file:
        path: /etc/subuid
        state: absent
    - name: Remove subgid
      become: true
      ansible.builtin.file:
        path: /etc/subgid
        state: absent
    - name: Make subuid file
      become: true
      ansible.builtin.file:
        path: /etc/subuid
        state: touch
    - name: Make subgid file
      become: true
      ansible.builtin.file:
        path: /etc/subgid
        state: touch
```

hostファイルに宛先を書いておきます．sre001.localからsre003.localへ実行したい場合のhostsファイルを載せます．
```:hosts
sre001.local
sre002.local
sre003.local
```

実行コマンドです．
```shell
$ ansible-playbook -i hosts add_sub_uid_gid.yaml --ask-become-pass
```

[1]:https://www.server-memo.net/ubuntu/ubuntu_version.html#:~:text=%E3%80%8Clsb_relase%20%2Da%E3%80%8D%20%E3%80%8Clsb_release,%E3%81%95%E3%81%9B%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E5%87%BA%E6%9D%A5%E3%81%BE%E3%81%99%E3%80%82
[2]:https://atmarkit.itmedia.co.jp/ait/articles/1709/01/news025.html
[^1]: [Ubuntuのバージョンを確認する方法 | server-memo.net][1]
[^2]:[【apt】コマンド（応用編）-パッケージを検索する：Linux基本コマンドTips（140） - ＠IT][2]

[3]:https://shellscript.sunone.me/filter_etc.html
[^3]:[フィルタを使用した文字列操作 2 | UNIX & Linux コマンド・シェルスクリプト リファレンス][3]

[4]:https://eng-entrance.com/linux-command-cut
[^4]:[cutコマンドについてまとめました 【Linuxコマンド集】][4]

[5]:https://tech.kurojica.com/archives/32292/
[^5]:[シェルで変数の空文字判定 | クロジカ][5]

[6]:https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/8/html/building_running_and_managing_containers/proc_upgrading-to-rootless-containers_assembly_starting-with-containers
[^6]:[1.7. ルートレスコンテナーへのアップグレード Red Hat Enterprise Linux 8][6]

[7]:https://qiita.com/ko1nksm/items/46fa9df8031275c7dc0a
[^7][今どきのシェルスクリプトは数値計算にexprを使わない（POSIX準拠） - Qiita][7]

[8]:https://salumarine.com/check-if-strings-or-numeric-values-match-in-shell/
[^8]:[シェルスクリプトで文字列や数値が一致するか判断する][8]

[9]:https://stns.jp/
[^9]:[STNS][9]

[10]:https://github.com/toml-lang/toml#toml
[^10]:[GitHub - toml-lang/toml: Tom's Obvious, Minimal Language][10]

[11]:https://cameong.hatenablog.com/entry/20130315/1363367056
[^11]:[ssh接続が成功した時ににホームディレクトリを自動生成する - cameong’s blog][11]