Linux の一般ユーザが sudo を使える仕組みをハンズオンで学ぶ

本記事は [フューチャー Advent Calendar 2023](https://qiita.com/advent-calendar/2023/future) 10日目の記事です。

前日の9日目は、[@domori](https://qiita.com/domori) さんの「[ISUCON入門](https://qiita.com/domori/items/d5078f7c34e013b65aae)」でした。パフォーマンスチューニング力を競う「[ISUCON](https://isucon.net/)」の魅力発信に加えて、今後も ISUCON 対策で実践した内容を投稿されるとのことなので、どんなチューニングネタがあるのか楽しみになる記事でした！私もチャンスがあれば、ISUCON のような「技術スキルを競い合うイベント」に参加したいなと思いました。

# はじめに

普段の開発は Mac オンリーにしたいのですが、現在は訳あって Windows で開発しています。Windows での開発事情に疎いため絶賛キャッチアップ中なのですが、WSL2（Windows Subsystem for Linux 2）を使えば「ほぼ Linux 環境」として環境構築とコーディングが進められることに（今更ながら）気付き、「[もろもろの設定](https://future-architect.github.io/articles/20230216a/)」に WSL2 を加えて、プロダクト開発を進めています。

WSL2 で Ubuntu や CentOS の環境構築を進める中で、何度か「Permission denied」にぶつかり、「ああ、sudo を忘れていたな」を繰り返す中で「**そういえば、sudoって、どういう権限制御になっているんだっけ？**」の疑問が浮かびましたので、Linux の知識整理を兼ねて sudo の実行制御を本記事にまとめました。

## TL;DR

Q. Linux の一般ユーザが sudo を使える理由は？
A. `/etc/sudores` に記載されているからです。

もう少しだけ踏み込むと
* `/etc/sudores` 、及び、`/etc/sudoers.d` 配下のファイルに、sudo の実行制御が記載されています
* 特定のユーザ、または、特定のグループに所属する全ユーザの sudo 実行権限は、上記ファイルを確認すれば分かります

## Linux 前提知識

sudo の解説に入る前に、前提知識として必要なところを説明します。

* ユーザ情報
* グループ情報

### ユーザ情報の確認

まずは、Linux の「ユーザ」についてです。
各ユーザの ID と所属グループは、以下のコマンドにより確認できます。

```bash
# コマンド
$ id                # 現在ログイン中のユーザの情報
$ id <user_name>    # <user_name>で指定したユーザの情報
```

実行例

```bash
$ id
uid=1000(main-user) gid=1000(main-user) groups=1000(main-user),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),116(netdev)
```

```bash
$ id sub-user
uid=1002(sub-user) gid=1002(sub-user) groups=1002(sub-user)
```

また、ユーザの一覧は `/etc/passwd` に記載されています。
（※以下、本記事に記載されたファイルパスについて、一部の Linux ディストリビューションではパスが異なる場合があります）

```bash
$ sudo cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
main-user:x:1000:1000:,,,:/home/main-user:/bin/bash
sub-user:x:1002:1002:,,,:/home/sub-user:/bin/bash
```

各項目は「:」ごとに分かれて、それぞれは以下を意味しています。

```bash
ユーザ名:旧パスワード欄(現在は「x」で統一):ユーザID:グループID:コメント:ホームディレクトリ:デフォルトシェル
```

## 所属グループの確認

続いて「グループ」の確認方法です。
ユーザは必ず1つ以上のグループに所属します。

```bash
# コマンド
$ groups                # 現在ログイン中のユーザが所属するグループ一覧
$ groups <user_name>    # <user_name>で指定したユーザが所属するグループ一覧
```

実行例

```bash
$ groups
main-user adm dialout cdrom floppy sudo audio dip video plugdev netdev
```

```bash
$ groups sub-user
sub-user : sub-user
```

グループの一覧は、`/etc/group` に記載されています。

```bash
$ cat /etc/group
root:x:0:
...
sudo:x:27:main-user
main-user:x:1000:
sub-user:x:1002:
```

それぞれの項目は、以下内容が対応しています。

```bash
グループ名:旧パスワード欄(現在は「x」で統一):グループID:グループ所属ユーザ
```

各ユーザを最低でも1つのグループに所属させるため、多くの Linux ディストリビューションでは「一般ユーザの追加時に、ユーザと同名のグループを作成して、そのグループにユーザ所属させる」という形を取ります。このため、グループ一覧にユーザ名と同名のグループが見つかります。（もちろん、全てのユーザを同じグループに追加する例外ケースもあります。）また、ユーザ名とグループ名が同名の場合は、「グループ所属ユーザ」の記入をスキップできます。（ex. main-userユーザはmain-userグループに所属していますが、`/etc/group` に記載されたmain-userグループの「グループ所属ユーザ」には、main-userユーザが記載されていない）

以上で、前提知識の説明は終了です。

## 新規ユーザを作成して、sudo を使えるようにする

それでは、ハンズオンに入ります。

本ハンズオンでは、Linux に新規ユーザを作成し、そのユーザが sudo を使えるようになるまでのプロセスを辿ります。これにより「Linux ユーザが sudo を実行できる仕組み」を見ていきます。
各種コマンド操作のため「sudo が実行できるユーザ」で、以下の操作を進めてください。

### 新規ユーザを作成する

まず、新しいユーザ（今回は、`beginner`）を追加します。

```bash
$ sudo adduser beginner
Adding user `beginner' ...
Adding new group `beginner' (1003) ...
Adding new user `beginner' (1003) with group `beginner' ...
Creating home directory `/home/beginner' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for beginner
Enter the new value, or press ENTER for the default
        Full Name []:
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:
Is the information correct? [Y/n] Y
```

追加後、ユーザ情報とグループ情報を確認します。

```bash
$ id beginner
uid=1003(beginner) gid=1003(beginner) groups=1003(beginner)

$ sudo cat /etc/passwd | grep beginner
beginner:x:1003:1003::/home/beginner:/bin/sh
```

```bash
$ groups beginner
beginner : beginner

$ cat /etc/group | grep beginner
beginner:x:1003:
```

`beginner` は、ユーザ名と同じ名前のグループに所属していますね。
この状況で `beginner` にスイッチして、`sudo` を実行してみます。

```bash
$ su beginner       #beginnerにスイッチ
Password:

$ cat /etc/passwd
cat: /etc/passwd: Permission denied

$ sudo cat /etc/passwd     #sudoでファイルの中身を見ようとするが、見れない。
[sudo] password for beginner:
beginner is not in the sudoers file.  This incident will be reported.

$ exit         #beginnerからログアウト
exit
```

アカウント作成後に、追加で何も設定していないため `sudo` は実行できません。
エラーメッセージの「sudoers file」がキーポイントです。

### sudo を使えるようにする

先ほど追加したユーザ `beginner` が `sudo` を実行できるように設定します。
`sudo` の実行制御は `/etc/sudores` に記載されていますので、まずは設定ファイルの中身を確認します。

```
$ sudo cat /etc/sudoers
~

# User privilege specification
root    ALL=(ALL:ALL) ALL

~

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL

~
```

読み方

```:sudores
どのユーザへの設定か 接続ホストはどこか = (どのユーザとして:どのグループとして) どのような操作を許可するのか
```

`sudores` の読み方を踏まえると、
現在の設定では「rootユーザ」と「sudoグループへの所属ユーザ」に権限が与えられていることが分かります。

```:sudores
# rootユーザは、どのホストからの接続でも、すべての操作ができる
root    ALL=(ALL:ALL) ALL

# sudoグループの所属ユーザは、どのホストからの接続でも、すべての操作ができる
%sudo   ALL=(ALL:ALL) ALL
```

今やりたいことは「`beginner` が `sudo` を使えるようにする」ことです。
`sudoers` には「sudoグループの所属ユーザは、すべての操作ができる」と設定されているので、

* `beginner` を `sudoグループ` に所属させる
* `sudo` コマンドを実行する 

を試してみます。

#### `beginner` を `sudoグループ` に所属させる

まずは、sudo グループの状況を確認します。

```bash
$ cat /etc/group | grep sudo
sudo:x:27:main-user
```

このコマンドにより、コマンド実行時点でsudoグループに所属しているユーザの一覧が確認できます。
さっそく、`beginner`をグループに追加します。

```bash
$ sudo usermod -aG sudo beginner

$ groups beginner
beginner : beginner sudo

$ cat /etc/group | grep sudo
sudo:x:27:main-user,beginner
```

sudoグループに、`beginner` が追加されました。

#### `sudo` コマンドを実行する

`beginner` は `sudoers` に設定された条件を満たしたので、`sudo`が実行可能になりました。

```bash
$ su beginner
Password:

$ sudo cat /etc/passwd
[sudo] password for beginner:       #先ほどは正しいパスワードを入力しても閲覧できなかったが、現時点では閲覧可能に
root:x:0:0:root:/root:/bin/bash
...
main-user:x:1000:1000:,,,:/home/main-user:/bin/bash
beginner:x:1003:1003:,,,:/home/beginner:/bin/bash
```

「[この時点](#新規ユーザを作成する)」ではエラーが出ていましたが、グループに所属したことで `sudo` が実行できるようになりました。

もちろんグループから外せば、元通り `sudo` は実行できなくなります。

```bash
$ exit      #beginnerからログアウト
exit

$ sudo gpasswd -d beginner sudo     #beginnerをsudoグループから外す
Removing user beginner from group sudo

$ cat /etc/group | grep sudo    #sudoグループにbeginnerは残っていない
sudo:x:27:main-user

$ groups beginner       #beginnerの所属グループはbeginnerのみ
beginner : beginner

$ su beginner
Password:   #beginnerにスイッチ

$ sudo cat /etc/passwd
beginner is not in the sudoers file.  This incident will be reported.
```

以上により、
任意のユーザを sudo グループに追加することで、sudo コマンドが実行可能となることが分かりました。

## 特定ユーザ専用の定義を追加する

ここまでは「`sudores` に記載されてるグループに、`sudo` を実行したいユーザを所属させる」という方法を取りました。
もう1つの方法として、root ユーザのように「特定のユーザの設定を記載する」ことでも `sudo` の実行権限を与えることが可能です。

`sudores` の編集は `visudo` を使います。`vim` などのテキストエディタによる編集も可能ですが、`sudores` 自体が `sudo` コマンドの動作を制御する重要なファイルなため、誤った編集によりシステムを不安定にする可能性があります。`visudo` にはファイルを保存する際に構文エラーをチェックしたり、同時編集による問題を防ぐ機能がありますので、`sudores` の編集にはこのエディタを利用します。

```:/etc/sudores
$ sudo cat /etc/sudoers
#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
~
@includedir /etc/sudoers.d
```

また、設定ファイルの注意書きに「`/etc/sudores` を直接編集するのではなく、`/etc/sudoers.d/` に追加してください」とありますので、この指示に従います。

```bash
$ sudo visudo -f /etc/sudoers.d/privilegedUsers
```

```:/etc/sudoers.d/privilegedUsers
beginner        ALL=(ALL:ALL) ALL
```

`visudo` はファイル保存直前に構文チェックが起動します。文法ミスがあれば編集を再開して修正しましょう。

```bash
$ sudo visudo -f /etc/sudoers.d/privilegedUsers
/etc/sudoers.d/privilegedUsers:1:14: syntax error
beginner        ALL-(ALL:ALL) ALL   #「=」にすべきところ「-」となっている
             ^
What now?
Options are:
  (e)dit sudoers file again
  (x)it without saving changes to sudoers file
  (Q)uit and save changes to sudoers file (DANGER!)
```

`beginner` の設定追記が完了したら、ユーザを切り替えて動作確認します。

```bash
$ su beginner
Password:
```

念のため、sudoグループに所属していないことを確認します。

```bash
$ groups
beginner    #beginnerユーザが所属するのは、beginnerグループのみ

$ cat /etc/group | grep sudo
sudo:x:27:main-user     #sudoグループに所属するのは、main-userのみ
```

以下のコマンドにより、`/etc/sudoers.d/privilegedUsers` への設定通り、`beginner` が `sudo` を実行出来ることが確認できました。

```bash
$ sudo cat /etc/passwd
[sudo] password for beginner:
root:x:0:0:root:/root:/bin/bash
~
main-user:x:1000:1000:,,,:/home/main-user:/bin/bash
beginner:x:1003:1003:,,,:/home/beginner:/bin/bash
```

ここまでの作業を整理すると

①グループに権限を付与して、そのグループへの所属を通して、ユーザにsudo実行権限を与える
②ユーザに直接直接sudo実行権限を付与する

の2パターンが確認してきました。


以降からは、`sudores` で使える便利なオプションを解説します。

## 「NOPASSWD」の利用

「NOPASSWD」を設定すると、`sudo` コマンド実行時のパスワード要求をスキップできます。

ex. `beginner` に NOPASSWD を設定する

```bash
sudo visudo -f /etc/sudoers.d/privilegedUsers
```

```:/etc/sudoers.d/privilegedUsers
beginner        ALL=(ALL:ALL) NOPASSWD:ALL
```

NOPASSWD設定後からは、 `sudo` 利用時のパスワード要求が無くなります。

```bash
$ su beginner
Password:

$ sudo cat /etc/passwd  #パスワード要求なし
~
main-user:x:1000:1000:,,,:/home/main-user:/bin/bash
beginner:x:1003:1003:,,,:/home/beginner:/bin/bash
```

利用場面には注意ですが、動作検証を優先する場合などで活用できます。

## 「エイリアス」の利用

`sudores` には、以下4つのエイリアスがあります。

* User_Alias
* Runas_Alias
* Host_Alias
* Cmnd_Alias

例えば、`sudores` の記載内容が以下の状態だったとします。

```:/etc/sudoers.d/privilegedUsers
eins-user   ALL=(ALL:ALL) ALL
zwei-user   ALL=(ALL:ALL) ALL
drei-user   ALL=(ALL:ALL) ALL
vier-user   ALL=(ALL:ALL) ALL
```

これらはエイリアスを使うことで、以下のようにまとめられます。

```:/etc/sudoers.d/privilegedUsers
User_Alias USERS = eins-user,zwei-user,drei-user,view-user
USERS   ALL=(ALL:ALL) ALL
```

エイリアスの利用により、ファイルの見通しが良くなりました。

# おわりに

本記事では「**そういえば、sudoって、どういう権限制御になっているんだっけ？**」という疑問から、sudo 権限周りの設定方法についてハンズオン形式で解説してきました。このような「枯れた技術への質問」であれば ChatGPT が高い精度で回答してくれるのは間違いありませんが、1つ1つのコマンドを実際に試すことで「知っている」から「使える」にまで昇華することは、引き続き重要だなと感じました。

[フューチャー Advent Calendar 2023](https://qiita.com/advent-calendar/2023/future) はまだまだ続きます。明日の記事も、お楽しみに！