AlmaLinux 10 総合ガイド
第7章: セキュリティとシェルスクリプト
サーバーをインターネットに接続した瞬間から、そのサーバーは世界中の攻撃者にとって潜在的なターゲットになります。「うちのサーバーなんて誰も狙わないだろう」という考えは危険です。攻撃者は特定の組織を狙うだけでなく、脆弱なサーバーを自動的にスキャンして見つけ次第侵入を試みます。
この章では、サーバーを守るための基本的なセキュリティ設定を学びます。ユーザー管理、sudo権限の適切な設定、SSHの強化、そしてLinux独自のセキュリティ機構であるSELinuxの基礎を習得します。後半では、日々の運用を効率化するシェルスクリプトの書き方を学び、セキュリティ監視を自動化する実践的なスクリプトを作成します。
7.1 ユーザー管理とアクセス制御
Linuxは、最初からマルチユーザーシステムとして設計されています。これは、複数のユーザーが同時にシステムを利用できることを意味し、そのためにユーザーごとの権限管理が非常に重要になります。
7.1.1 ユーザーとグループの概念
Linuxでは、すべてのファイルやプロセスに「誰が所有しているか」という情報が紐付けられています。これにより、あるユーザーが他のユーザーのファイルを勝手に読んだり、システムの重要なファイルを変更したりすることを防いでいます。
ユーザー(User)
システムにログインできる個別のアカウントです。各ユーザーには一意のUID(User ID)という数値が割り当てられます。
- UID 0: rootユーザー(システム管理者)
- UID 1-999: システムユーザー(サービス実行用)
- UID 1000以上: 一般ユーザー
グループ(Group)
複数のユーザーをまとめた集合です。各グループには一意のGID(Group ID)が割り当てられます。グループを使うことで、「開発チームのメンバー全員がこのディレクトリにアクセスできる」といった権限設定が簡単になります。
最小権限の原則
セキュリティの基本原則として、ユーザーには業務に必要な最小限の権限のみを付与するという考え方があります。例えば、Webアプリケーションの開発者に、データベースサーバーの管理権限は必要ありません。必要以上の権限を持つユーザーが増えると、そのアカウントが乗っ取られた際の被害が大きくなります。
7.1.2 /etc/passwd, /etc/shadow, /etc/groupの役割
ユーザーとグループの情報は、以下の3つのファイルで管理されています。
/etc/passwd – ユーザー情報
すべてのユーザーアカウント情報が格納されています。各行が1ユーザーに対応し、コロン(:)で区切られた7つのフィールドで構成されます。
[実行ユーザー: 一般ユーザー]
$ cat /etc/passwd | grep developer
developer:x:1000:1000:Developer User:/home/developer:/bin/bash
各フィールドの意味は以下の通りです。
| フィールド | 例 | 説明 |
|---|---|---|
| ユーザー名 | developer | ログインに使用する名前 |
| パスワード | x | 「x」は/etc/shadowに格納されていることを示す |
| UID | 1000 | ユーザーID |
| GID | 1000 | プライマリグループID |
| コメント | Developer User | ユーザーの説明(GECOS) |
| ホームディレクトリ | /home/developer | ログイン時の初期ディレクトリ |
| ログインシェル | /bin/bash | ログイン時に起動するシェル |
/etc/shadow – パスワード情報
パスワードのハッシュ値と有効期限などの情報が格納されています。セキュリティ上の理由から、rootユーザーのみが読み取り可能です。
[実行ユーザー: 一般ユーザー(sudo使用)]
$ sudo cat /etc/shadow | grep developer
developer:$6$rounds=4096$abc123...:19800:0:99999:7:::
パスワードフィールドの「$6$」はSHA-512アルゴリズムでハッシュ化されていることを示します。「!!」や「*」が入っている場合は、パスワードが設定されていない(ログイン不可)ことを意味します。
/etc/group – グループ情報
グループの情報が格納されています。
[実行ユーザー: 一般ユーザー]
$ cat /etc/group | grep wheel
wheel:x:10:developer
各フィールドの意味は以下の通りです。
| フィールド | 例 | 説明 |
|---|---|---|
| グループ名 | wheel | グループの名前 |
| パスワード | x | 通常は使用しない |
| GID | 10 | グループID |
| メンバー | developer | このグループに属するユーザー(カンマ区切り) |
7.1.3 useraddでユーザー作成
useraddコマンドで新しいユーザーを作成します。
基本構文
useradd [オプション] ユーザー名
主要なオプション
| オプション | 説明 | 例 |
|---|---|---|
| -m | ホームディレクトリを作成 | -m |
| -s | ログインシェルを指定 | -s /bin/bash |
| -c | コメント(説明)を設定 | -c “Web Developer” |
| -G | 追加のグループを指定 | -G wheel,developers |
| -d | ホームディレクトリのパスを指定 | -d /home/webuser |
| -u | UIDを指定 | -u 1500 |
実践例: 新規ユーザーの作成
[実行ユーザー: 一般ユーザー(sudo使用)]
# Webアプリケーション開発者用のユーザーを作成
$ sudo useradd -m -s /bin/bash -c "Web Application Developer" webdev
# 作成されたことを確認
$ id webdev
uid=1001(webdev) gid=1001(webdev) groups=1001(webdev)
# ホームディレクトリの確認
$ ls -la /home/webdev/
total 12
drwx------. 2 webdev webdev 62 Jan 31 10:00 .
drwxr-xr-x. 4 root root 36 Jan 31 10:00 ..
-rw-r--r--. 1 webdev webdev 18 Jan 31 10:00 .bash_logout
-rw-r--r--. 1 webdev webdev 141 Jan 31 10:00 .bash_profile
-rw-r--r--. 1 webdev webdev 492 Jan 31 10:00 .bashrc
-mオプションを付けないとホームディレクトリが作成されません。AlmaLinuxのデフォルト設定では自動作成されないため、一般ユーザーを作成する際は-mを忘れずに指定しましょう。7.1.4 passwdでパスワード設定
useraddで作成したユーザーにはパスワードが設定されていないため、ログインできません。passwdコマンドでパスワードを設定します。
[実行ユーザー: 一般ユーザー(sudo使用)]
# 指定ユーザーのパスワードを設定
$ sudo passwd webdev
Changing password for user webdev.
New password: # パスワードを入力(表示されない)
Retype new password: # 再入力
passwd: all authentication tokens updated successfully.
パスワードポリシー
セキュリティのために、以下のような強固なパスワードを設定しましょう。
- 12文字以上の長さ
- 大文字、小文字、数字、記号を含む
- 辞書に載っている単語をそのまま使わない
- ユーザー名や個人情報を含めない
AlmaLinux 10では、/etc/security/pwquality.confでパスワードの複雑さを設定できます。
7.1.5 usermodでユーザー情報変更
既存のユーザー情報を変更するにはusermodコマンドを使います。
グループへの追加
最もよく使うのは、ユーザーを追加のグループに所属させる操作です。
[実行ユーザー: 一般ユーザー(sudo使用)]
# webdevユーザーをwheelグループに追加
$ sudo usermod -aG wheel webdev
# 確認
$ id webdev
uid=1001(webdev) gid=1001(webdev) groups=1001(webdev),10(wheel)
-aGオプションの-a(append)を忘れると、既存のグループ所属がすべて上書きされてしまいます。グループを追加する際は必ず-aGをセットで使いましょう。ログインシェルの変更
[実行ユーザー: 一般ユーザー(sudo使用)]
# シェルを変更
$ sudo usermod -s /bin/zsh webdev
7.1.6 userdelでユーザー削除
不要になったユーザーアカウントは削除します。
[実行ユーザー: 一般ユーザー(sudo使用)]
# ユーザーのみ削除(ホームディレクトリは残る)
$ sudo userdel webdev
# ユーザーとホームディレクトリを削除
$ sudo userdel -r webdev
-rオプションを付けるとホームディレクトリとメールスプールも削除されます。退職者のアカウント削除時など、データを完全に消去したい場合に使います。ただし、削除前にバックアップが必要かどうか確認しましょう。
7.1.7 groupaddでグループ作成
プロジェクトやチーム用のグループを作成できます。
[実行ユーザー: 一般ユーザー(sudo使用)]
# developersグループを作成
$ sudo groupadd developers
# 確認
$ cat /etc/group | grep developers
developers:x:1002:
# ユーザーをグループに追加
$ sudo usermod -aG developers developer
7.1.8 ユーザーの切り替え(su, su -)
suコマンドで他のユーザーに切り替えることができます。
su と su – の違い
| コマンド | 環境変数 | カレントディレクトリ | 用途 |
|---|---|---|---|
| su ユーザー名 | 現在のユーザーの環境を引き継ぐ | 変わらない | 一時的にコマンドを実行 |
| su – ユーザー名 | 切り替え先の環境を完全に読み込む | ホームディレクトリに移動 | そのユーザーとしてログイン |
[実行ユーザー: 一般ユーザー]
# 環境変数を引き継いで切り替え
$ su webdev
Password:
$ pwd
/home/developer # 元のディレクトリのまま
$ echo $PATH
/home/developer/.local/bin:/usr/local/bin:/usr/bin:/bin # 元ユーザーのPATH
$ exit
# ログインシェルとして切り替え(推奨)
$ su - webdev
Password:
$ pwd
/home/webdev # ホームディレクトリに移動
$ echo $PATH
/home/webdev/.local/bin:/usr/local/bin:/usr/bin:/bin # webdevのPATH
基本的にはsu -(ハイフン付き)を使うことをお勧めします。環境変数の違いによる予期しない動作を避けることができます。
7.2 sudo権限の管理
rootユーザーで直接作業することは、セキュリティ上のリスクがあります。代わりに、必要なときだけ一時的に管理者権限を得るsudoを使います。
7.2.1 sudoの仕組み
sudo(superuser do)は、一般ユーザーが一時的にroot権限でコマンドを実行できる仕組みです。
なぜrootを直接使わないのか
- 監査ログ: sudoを経由すると、誰が、いつ、何を実行したかがログに記録される
- 最小権限: 必要なコマンドだけを許可できる
- 事故防止: 常にrootでいると、うっかり危険なコマンドを実行してしまうリスクがある
- パスワード管理: rootパスワードを複数人で共有する必要がない
[実行ユーザー: 一般ユーザー]
# 通常は権限エラー
$ cat /etc/shadow
cat: /etc/shadow: Permission denied
# sudoを使うと実行できる
$ sudo cat /etc/shadow
[sudo] password for developer: # 自分のパスワードを入力
root:$6$...
7.2.2 /etc/sudoersファイルの構造
sudoの設定は/etc/sudoersファイルで管理されています。このファイルは直接編集せず、後述のvisudoコマンドを使います。
基本的な書式
ユーザー ホスト=(実行ユーザー) コマンド
設定例
# rootはすべてのコマンドを実行可能
root ALL=(ALL) ALL
# wheelグループのメンバーはすべてのコマンドを実行可能
%wheel ALL=(ALL) ALL
# developerユーザーはapacheの再起動のみ許可
developer ALL=(root) /usr/bin/systemctl restart httpd
「%」で始まる名前はグループを意味します。「ALL」は「すべて」を意味する特別なキーワードです。
7.2.3 visudoコマンドでの安全な編集
/etc/sudoersは構文エラーがあると、sudoが使えなくなってしまう重要なファイルです。そのため、直接エディタで開かず、構文チェック機能付きのvisudoコマンドを使います。
[実行ユーザー: 一般ユーザー(sudo使用)]
$ sudo visudo
このコマンドを実行すると、デフォルトのエディタ(通常はvi)で/etc/sudoersが開きます。編集後、保存して終了する際に自動的に構文チェックが行われます。
# もし構文エラーがあると...
>>> /etc/sudoers: syntax error near line 25 <<<
What now?
Options are:
(e)dit sudoers file again
e(x)it without saving changes to sudoers file
(Q)uit and save changes to sudoers file (DANGER!)
エラーがあれば「e」で再編集するか、「x」で変更を破棄できます。「Q」は危険なのでやめましょう。
7.2.4 wheelグループへの追加(RHEL系の慣習)
RHEL系ディストリビューション(AlmaLinux含む)では、wheelグループがsudo権限を持つグループとして設定されています。ユーザーにsudo権限を付与する最も簡単な方法は、wheelグループに追加することです。
[実行ユーザー: 一般ユーザー(sudo使用)]
# wheelグループに追加
$ sudo usermod -aG wheel webdev
# 確認
$ id webdev
uid=1001(webdev) gid=1001(webdev) groups=1001(webdev),10(wheel)
# webdevでログインし直すとsudoが使える
$ su - webdev
$ sudo whoami
[sudo] password for webdev:
root
su - webdevでも同様の効果があります。7.2.5 ユーザー個別のsudo権限設定
wheelグループに追加せず、個別にsudo権限を設定したい場合は、/etc/sudoers.d/ディレクトリに設定ファイルを作成します。
[実行ユーザー: 一般ユーザー(sudo使用)]
# backupユーザー用の設定ファイルを作成
$ sudo visudo -f /etc/sudoers.d/backup
# 以下の内容を記述
backup ALL=(root) /usr/bin/tar, /usr/bin/rsync
これにより、backupユーザーはtarとrsyncコマンドのみをroot権限で実行できます。
7.2.6 コマンド単位の権限制限
セキュリティの観点から、特定のコマンドのみを許可する設定を推奨します。
# Webサーバー管理者はhttpd関連のみ許可
webadmin ALL=(root) /usr/bin/systemctl start httpd, \
/usr/bin/systemctl stop httpd, \
/usr/bin/systemctl restart httpd, \
/usr/bin/systemctl status httpd
# 引数まで制限(restart httpdのみ許可)
webadmin ALL=(root) /usr/bin/systemctl restart httpd
7.2.7 パスワードなしsudo(慎重に)
自動化スクリプトなどで、パスワード入力なしでsudoを実行したい場合があります。これはNOPASSWDオプションで設定できますが、セキュリティリスクがあるため慎重に使用してください。
# バックアップスクリプト用(特定コマンドのみ)
backup ALL=(root) NOPASSWD: /usr/local/bin/backup.sh
# 非推奨:全コマンドでパスワードなし
# developer ALL=(ALL) NOPASSWD: ALL
NOPASSWD: ALLは、そのユーザーのアカウントが乗っ取られた場合に、攻撃者にroot権限を与えることになります。本番環境では、必要なコマンドのみに限定してください。7.2.8 sudo実行ログの確認
sudoの実行履歴は/var/log/secureに記録されます。
[実行ユーザー: 一般ユーザー(sudo使用)]
$ sudo grep sudo /var/log/secure | tail -5
Jan 31 10:30:15 server sudo[12345]: developer : TTY=pts/0 ; PWD=/home/developer ; USER=root ; COMMAND=/usr/bin/cat /etc/shadow
Jan 31 10:32:20 server sudo[12346]: developer : TTY=pts/0 ; PWD=/home/developer ; USER=root ; COMMAND=/usr/bin/systemctl restart httpd
誰が、いつ、どのコマンドを実行したかが記録されているため、セキュリティ監査に役立ちます。
自分のsudo権限を確認
[実行ユーザー: 一般ユーザー]
$ sudo -l
User developer may run the following commands on server:
(ALL) ALL
7.3 SSHによるリモートアクセス
サーバーへのリモートアクセスにはSSH(Secure Shell)を使います。SSHは通信を暗号化するため、パスワードやコマンドが盗聴される心配がありません。
7.3.1 SSHとは何か
SSHは、ネットワーク経由でサーバーに安全に接続するためのプロトコルです。以前使われていたtelnetやrshは通信が暗号化されておらず、現在は使用すべきではありません。
SSHの主な機能
- 暗号化通信: すべての通信が暗号化される
- 認証: パスワード認証、公開鍵認証など
- ポートフォワーディング: トンネリング機能
- ファイル転送: scp、sftpによるファイル転送
AlmaLinux 10ではOpenSSH 9.9が提供されています。
7.3.2 sshクライアントの使い方
基本的な接続
[実行ユーザー: 一般ユーザー]
# ユーザー名@ホスト名で接続
$ ssh developer@192.168.1.100
The authenticity of host '192.168.1.100 (192.168.1.100)' can't be established.
ED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.1.100' (ED25519) to the list of known hosts.
developer@192.168.1.100's password:
Last login: Sat Jan 31 10:00:00 2026 from 192.168.1.50
$
初回接続時には、サーバーの鍵のフィンガープリントが表示されます。これは中間者攻撃を防ぐための仕組みで、正しいサーバーに接続していることを確認するものです。
ポート番号を指定して接続
[実行ユーザー: 一般ユーザー]
# デフォルトは22番ポート。変更している場合は-pで指定
$ ssh -p 2222 developer@192.168.1.100
7.3.3 sshdサービスの確認
SSHサーバー(sshd)が動作していることを確認します。
[実行ユーザー: 一般ユーザー(sudo使用)]
$ sudo systemctl status sshd
● sshd.service - OpenSSH server daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; preset: enabled)
Active: active (running) since Sat 2026-01-31 10:00:00 JST; 1h ago
Docs: man:sshd(8)
man:sshd_config(5)
Main PID: 1234 (sshd)
Tasks: 1 (limit: 23456)
Memory: 4.0M
CPU: 123ms
CGroup: /system.slice/sshd.service
└─1234 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"
7.3.4 /etc/ssh/sshd_configの基本設定
SSHサーバーの設定は/etc/ssh/sshd_configで行います。セキュリティを高めるために、以下の設定を推奨します。
[実行ユーザー: 一般ユーザー(sudo使用)]
$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
$ sudo vi /etc/ssh/sshd_config
推奨設定
# ポート番号(変更する場合)
Port 22
# rootでの直接ログインを禁止【重要】
PermitRootLogin no
# パスワード認証(公開鍵設定後に無効化を推奨)
PasswordAuthentication yes
# 公開鍵認証を有効化
PubkeyAuthentication yes
# 空パスワードを禁止
PermitEmptyPasswords no
# ログイン試行のタイムアウト
LoginGraceTime 60
# 最大認証試行回数
MaxAuthTries 3
PermitRootLogin noは必ず設定してください。rootでの直接ログインを許可すると、攻撃者の標的になりやすく、成功した場合の被害も甚大です。7.3.5 公開鍵認証の設定
パスワード認証よりも安全な公開鍵認証を設定します。公開鍵認証では、秘密鍵を持っている人だけがログインできます。
公開鍵認証の仕組み
- クライアントで鍵ペア(秘密鍵と公開鍵)を生成
- 公開鍵をサーバーに配置(~/.ssh/authorized_keys)
- 秘密鍵はクライアントで厳重に保管
- 接続時、秘密鍵を持っていることで本人確認
Step 1: 鍵ペアの生成(クライアント側)
[実行ユーザー: 一般ユーザー(クライアントPC)]
# Ed25519アルゴリズムで鍵を生成(推奨)
$ ssh-keygen -t ed25519 -C "developer@example.com"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/developer/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase): # パスフレーズを設定(推奨)
Enter same passphrase again:
Your identification has been saved in /home/developer/.ssh/id_ed25519
Your public key has been saved in /home/developer/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx developer@example.com
パスフレーズは秘密鍵を保護するための追加のパスワードです。設定することを強く推奨します。
ssh-keygenの主なオプション
| オプション | 説明 | 推奨値 |
|---|---|---|
| -t | 鍵の種類 | ed25519(推奨)またはrsa |
| -b | 鍵のビット長(RSAの場合) | 4096 |
| -C | コメント(識別用) | メールアドレスなど |
| -f | 出力ファイル名 | デフォルトで可 |
Step 2: 公開鍵をサーバーに転送
[実行ユーザー: 一般ユーザー(クライアントPC)]
# ssh-copy-idコマンドで簡単に転送
$ ssh-copy-id developer@192.168.1.100
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/developer/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
developer@192.168.1.100's password:
Number of key(s) added: 1
Step 3: 公開鍵認証でログイン確認
[実行ユーザー: 一般ユーザー(クライアントPC)]
$ ssh developer@192.168.1.100
Enter passphrase for key '/home/developer/.ssh/id_ed25519': # パスフレーズを入力
Last login: Sat Jan 31 11:00:00 2026 from 192.168.1.50
$
パーミッションの重要性
SSHは、鍵ファイルのパーミッションが適切でないとエラーになります。
[実行ユーザー: 一般ユーザー]
# サーバー側のパーミッション確認・設定
$ chmod 700 ~/.ssh
$ chmod 600 ~/.ssh/authorized_keys
# クライアント側
$ chmod 700 ~/.ssh
$ chmod 600 ~/.ssh/id_ed25519
$ chmod 644 ~/.ssh/id_ed25519.pub
| ファイル/ディレクトリ | パーミッション | 説明 |
|---|---|---|
| ~/.ssh/ | 700 (rwx——) | 所有者のみアクセス可 |
| ~/.ssh/authorized_keys | 600 (rw——-) | 所有者のみ読み書き可 |
| ~/.ssh/id_ed25519(秘密鍵) | 600 (rw——-) | 所有者のみ読み書き可 |
| ~/.ssh/id_ed25519.pub(公開鍵) | 644 (rw-r–r–) | 誰でも読み取り可 |
7.3.6 パスワード認証の無効化(セキュリティ強化)
公開鍵認証が正常に動作することを確認したら、パスワード認証を無効化してセキュリティを強化します。
[実行ユーザー: 一般ユーザー(sudo使用)]
$ sudo vi /etc/ssh/sshd_config
# 以下の設定を変更
PasswordAuthentication no
7.3.7 設定変更後のサービス再起動
sshd_configを変更したら、設定を反映させます。
[実行ユーザー: 一般ユーザー(sudo使用)]
# 設定ファイルの構文チェック
$ sudo sshd -t
# エラーが表示されなければOK
# サービス再起動
$ sudo systemctl restart sshd
# 現在のSSH接続は維持されるので、別のターミナルで接続テスト
# 問題があれば現在の接続で修正できる
7.3.8 SSHトラブルシューティング
SSHで接続できない場合の確認ポイントです。
クライアント側でのデバッグ
[実行ユーザー: 一般ユーザー]
# -vオプションで詳細情報を表示
$ ssh -v developer@192.168.1.100
debug1: Connecting to 192.168.1.100 [192.168.1.100] port 22.
debug1: Connection established.
...
よくある問題と対処
| 症状 | 原因 | 対処 |
|---|---|---|
| Connection refused | sshdが起動していない、ポートが違う | systemctl status sshd、ポート確認 |
| Permission denied (publickey) | 鍵が正しくない、パーミッションエラー | 鍵のパス、authorized_keysのパーミッション確認 |
| Connection timed out | ファイアウォール、ネットワーク問題 | firewall-cmd、ネットワーク疎通確認 |
| Host key verification failed | サーバーの鍵が変わった | known_hostsから該当エントリを削除 |
サーバー側のログ確認
[実行ユーザー: 一般ユーザー(sudo使用)]
$ sudo journalctl -u sshd -f
# リアルタイムでSSHのログを監視
7.4 SELinuxの基礎
SELinux(Security-Enhanced Linux)は、Linuxカーネルに組み込まれた強力なセキュリティ機構です。「よく分からないから無効化する」という誘惑に負けず、基本を理解して活用しましょう。
7.4.1 SELinuxとは何か(強制アクセス制御)
通常のLinuxパーミッション(rwx)は任意アクセス制御(DAC: Discretionary Access Control)と呼ばれ、ファイルの所有者が権限を自由に設定できます。しかし、これだけでは不十分な場合があります。
例えば、Webサーバー(httpd)が乗っ取られた場合、httpdプロセスの権限で読み取れるすべてのファイルにアクセスされてしまいます。
SELinuxは強制アクセス制御(MAC: Mandatory Access Control)を提供します。これは、システム管理者が定めたポリシーに基づいて、プロセスがアクセスできるリソースを厳密に制限する仕組みです。
【通常のパーミッション(DAC)のみ】
httpd(apache)プロセス
↓ 読み取り可能
/etc/passwd, /home/*, /var/log/* ... 広範囲にアクセス可能
【SELinux有効時(MAC)】
httpd(apache)プロセス
↓ httpd_sys_content_t のみ読み取り可能
/var/www/html/* のみ ... アクセス範囲を限定
7.4.2 なぜSELinuxが必要なのか
SELinuxが守ってくれる具体的なシナリオを紹介します。
シナリオ1: Webアプリケーションの脆弱性
攻撃者がWebアプリの脆弱性を突いて任意のコマンドを実行できたとしても、SELinuxが有効であれば、httpdプロセスは/etc/passwdや/etc/shadowを読み取れません。
シナリオ2: 設定ミスによる情報漏洩
誤って機密ファイルをWebディレクトリに置いてしまっても、適切なSELinuxコンテキストが設定されていなければ、httpdからはアクセスできません。
シナリオ3: 権限昇格攻撃の防止
侵入したプロセスが他のサービスやユーザーの権限を奪おうとしても、SELinuxポリシーで許可されていない操作はブロックされます。
7.4.3 SELinuxの3つのモード
SELinuxには3つの動作モードがあります。
| モード | 動作 | 用途 |
|---|---|---|
| Enforcing | ポリシー違反をブロックし、ログに記録 | 本番環境(推奨) |
| Permissive | ポリシー違反をログに記録するが、ブロックしない | 問題調査、設定確認 |
| Disabled | SELinuxを完全に無効化 | 非推奨(本番では使わない) |
7.4.4 getenforceで現在のモード確認
[実行ユーザー: 一般ユーザー]
$ getenforce
Enforcing
より詳細な情報はsestatusで確認できます。
[実行ユーザー: 一般ユーザー]
$ sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
7.4.5 setenforceで一時的なモード変更
問題調査のために一時的にPermissiveモードに変更できます。
[実行ユーザー: 一般ユーザー(sudo使用)]
# Permissiveモードに変更
$ sudo setenforce 0
$ getenforce
Permissive
# Enforcingモードに戻す
$ sudo setenforce 1
$ getenforce
Enforcing
この変更は一時的で、再起動すると設定ファイルの値に戻ります。
7.4.6 /etc/selinux/configでの永続設定
SELinuxモードを永続的に変更するには、設定ファイルを編集します。
[実行ユーザー: 一般ユーザー(sudo使用)]
$ sudo vi /etc/selinux/config
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=enforcing
# SELINUXTYPE= can take one of these values:
# targeted - Targeted processes are protected.
# minimum - Modification of targeted policy. Only selected processes are protected.
# mls - Multi Level Security protection.
SELINUXTYPE=targeted
SELINUX=disabledに変更すると、システム上のすべてのファイルからSELinuxラベルが失われます。再度有効化する際には、ファイルシステム全体のラベル再設定(relabel)が必要となり、長時間を要します。本番環境では絶対に無効化しないでください。7.4.7 SELinuxコンテキストの概念
SELinuxでは、すべてのプロセスとファイルにセキュリティコンテキスト(ラベル)が付けられています。
コンテキストの構造
ユーザー:ロール:タイプ:レベル
例: system_u:object_r:httpd_sys_content_t:s0
最も重要なのはタイプフィールドです。SELinuxポリシーは主にタイプに基づいてアクセス制御を行います。
| フィールド | 例 | 説明 |
|---|---|---|
| ユーザー | system_u | SELinuxユーザー(Linuxユーザーとは別) |
| ロール | object_r | ファイルはobject_r、プロセスは様々 |
| タイプ | httpd_sys_content_t | アクセス制御の核心(最重要) |
| レベル | s0 | MLS(Multi-Level Security)用 |
7.4.8 ls -Zでコンテキスト確認
-ZオプションでSELinuxコンテキストを表示できます。
[実行ユーザー: 一般ユーザー]
# ファイルのコンテキスト
$ ls -Z /var/www/html/
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 index.html
# プロセスのコンテキスト
$ ps -eZ | grep httpd
system_u:system_r:httpd_t:s0 1234 ? 00:00:00 httpd
httpdプロセス(httpd_t)は、httpd_sys_content_t タイプのファイルにアクセスできるように設定されています。
7.4.9 semanageとrestoreconでの修正
ファイルのSELinuxコンテキストが正しくない場合、修正する必要があります。
restorecon – デフォルトコンテキストに復元
[実行ユーザー: 一般ユーザー(sudo使用)]
# ファイルのコンテキストをデフォルトに復元
$ sudo restorecon -v /var/www/html/index.html
Relabeled /var/www/html/index.html from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:httpd_sys_content_t:s0
# ディレクトリ以下を再帰的に復元
$ sudo restorecon -Rv /var/www/html/
semanage fcontext – カスタムコンテキストの設定
デフォルト以外のディレクトリをWebコンテンツとして公開する場合などに使います。
[実行ユーザー: 一般ユーザー(sudo使用)]
# /srv/webapp をWebコンテンツとして設定
$ sudo semanage fcontext -a -t httpd_sys_content_t "/srv/webapp(/.*)?"
# 設定を適用
$ sudo restorecon -Rv /srv/webapp/
# 確認
$ ls -Zd /srv/webapp/
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 /srv/webapp/
semanage port – ポートのコンテキスト設定
標準以外のポートでサービスを動かす場合に必要です。
[実行ユーザー: 一般ユーザー(sudo使用)]
# httpdが8080ポートを使えるように設定
$ sudo semanage port -a -t http_port_t -p tcp 8080
# 現在の設定を確認
$ sudo semanage port -l | grep http
http_port_t tcp 8080, 80, 443, 488, 8008, 8009, 8443
7.4.10 setseboolでブール値変更
SELinuxポリシーには、特定の機能のON/OFFを切り替えるブール値があります。
[実行ユーザー: 一般ユーザー(sudo使用)]
# httpd関連のブール値を確認
$ sudo getsebool -a | grep httpd
httpd_can_network_connect --> off
httpd_can_network_connect_db --> off
httpd_enable_homedirs --> off
...
# httpdがネットワーク接続できるように変更
$ sudo setsebool httpd_can_network_connect on
# 永続的に変更(再起動後も有効)
$ sudo setsebool -P httpd_can_network_connect on
よく使うブール値
| ブール値 | 説明 |
|---|---|
| httpd_can_network_connect | httpdが外部ネットワークに接続可能にする |
| httpd_can_network_connect_db | httpdがDBサーバーに接続可能にする |
| httpd_enable_homedirs | httpdがユーザーのホームディレクトリにアクセス可能にする |
| httpd_read_user_content | httpdがユーザーコンテンツを読み取り可能にする |
7.4.11 SELinux問題の段階的対処法
SELinuxが原因でアプリケーションが動かない場合の対処手順を説明します。
Step 1: Permissiveモードで動作確認
[実行ユーザー: 一般ユーザー(sudo使用)]
# 一時的にPermissiveに変更
$ sudo setenforce 0
# アプリケーションが動作するか確認
# 動作すれば、SELinuxが原因と特定できる
Step 2: audit.logから原因を特定
[実行ユーザー: 一般ユーザー(sudo使用)]
# SELinux拒否のログを確認
$ sudo ausearch -m AVC -ts recent
----
time->Sat Jan 31 12:00:00 2026
type=AVC msg=audit(1706677200.123:456): avc: denied { read } for pid=1234 comm="httpd" name="config.php" dev="sda1" ino=123456 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=1
このログは「httpdがuser_home_tタイプのconfig.phpを読もうとして拒否された」ことを示しています。
Step 3: audit2whyで原因分析
[実行ユーザー: 一般ユーザー(sudo使用)]
$ sudo ausearch -m AVC -ts recent | audit2why
type=AVC msg=audit(1706677200.123:456): avc: denied { read } for ...
Was caused by:
Missing type enforcement (TE) allow rule.
You can use audit2allow to generate a loadable module to allow this access.
Step 4: 適切な設定変更
原因に応じて対処します。
# ケース1: ファイルのコンテキストが間違っている
$ sudo restorecon -v /path/to/file
# ケース2: カスタムディレクトリを使っている
$ sudo semanage fcontext -a -t httpd_sys_content_t "/custom/path(/.*)?"
$ sudo restorecon -Rv /custom/path/
# ケース3: 特定の機能を有効にする必要がある
$ sudo setsebool -P httpd_can_network_connect on
Step 5: Enforcingに戻して検証
[実行ユーザー: 一般ユーザー(sudo使用)]
$ sudo setenforce 1
$ getenforce
Enforcing
# アプリケーションが正常に動作することを確認
7.5 基本的なセキュリティ対策
ここまでで学んだユーザー管理、SSH、SELinuxに加えて、サーバーを守るための基本的なセキュリティ対策を紹介します。
7.5.1 システムアップデートの定期実行
セキュリティ脆弱性は日々発見されています。定期的なアップデートは最も基本的かつ重要なセキュリティ対策です。
[実行ユーザー: 一般ユーザー(sudo使用)]
# 利用可能なアップデートを確認
$ sudo dnf check-update
# セキュリティアップデートのみ適用
$ sudo dnf update --security
# すべてのアップデートを適用
$ sudo dnf update -y
本番環境では、まずテスト環境でアップデートの影響を確認してから適用することをお勧めします。
7.5.2 不要なサービスの停止
稼働しているサービスが多いほど、攻撃対象が増えます。使わないサービスは停止・無効化しましょう。
[実行ユーザー: 一般ユーザー(sudo使用)]
# 稼働中のサービスを確認
$ sudo systemctl list-units --type=service --state=running
# 有効化されているサービスを確認
$ sudo systemctl list-unit-files --type=service --state=enabled
# 不要なサービスを停止・無効化
$ sudo systemctl stop cups
$ sudo systemctl disable cups
Minimal Installで不要になりやすいサービス例
- cups(印刷サービス)- サーバーでは不要なことが多い
- avahi-daemon(ネットワーク探索)- サーバーでは不要なことが多い
- bluetooth(Bluetooth)- サーバーでは不要
7.5.3 ファイアウォールの適切な設定
第6章で学んだfirewalldの設定を見直し、必要なポートのみを開放します。
[実行ユーザー: 一般ユーザー(sudo使用)]
# 現在の設定を確認
$ sudo firewall-cmd --list-all
# 不要なサービスを削除
$ sudo firewall-cmd --permanent --remove-service=cockpit
# 設定を反映
$ sudo firewall-cmd --reload
7.5.4 ログ監視の重要性
攻撃の兆候を早期に発見するために、ログの監視は欠かせません。
定期的に確認すべきログ
/var/log/secure– 認証関連(ログイン試行)/var/log/audit/audit.log– SELinux、システム監査/var/log/messages– システム全般
[実行ユーザー: 一般ユーザー(sudo使用)]
# 失敗したログイン試行を確認
$ sudo grep "Failed password" /var/log/secure | tail -10
Jan 31 12:00:00 server sshd[1234]: Failed password for invalid user admin from 192.168.1.200 port 54321 ssh2
Jan 31 12:00:01 server sshd[1235]: Failed password for invalid user root from 192.168.1.200 port 54322 ssh2
7.5.5 fail2banの紹介(SSH攻撃対策)
fail2banは、ログを監視して不正なアクセス試行を検出し、該当するIPアドレスを自動的にブロックするツールです。
fail2banはEPELリポジトリから提供されています。
[実行ユーザー: 一般ユーザー(sudo使用)]
# EPELリポジトリを有効化(まだの場合)
$ sudo dnf install epel-release
# fail2banをインストール
$ sudo dnf install fail2ban fail2ban-firewalld
fail2banの詳細な設定は本ガイドの範囲を超えますが、SSH接続に対するブルートフォース攻撃を防ぐ効果的なツールとして覚えておいてください。
7.5.6 セキュリティチェックリスト
サーバーを運用する際に確認すべき基本的なセキュリティ項目をまとめました。
| カテゴリ | チェック項目 | 確認コマンド/方法 |
|---|---|---|
| アップデート | システムが最新か | sudo dnf check-update |
| ユーザー | 不要なユーザーがいないか | cat /etc/passwd |
| ユーザー | wheelグループのメンバーは適切か | getent group wheel |
| SSH | rootログインが禁止されているか | grep PermitRootLogin /etc/ssh/sshd_config |
| SSH | 公開鍵認証が有効か | grep PubkeyAuthentication /etc/ssh/sshd_config |
| SSH | パスワード認証が無効か(推奨) | grep PasswordAuthentication /etc/ssh/sshd_config |
| SELinux | Enforcingモードか | getenforce |
| ファイアウォール | 必要なポートのみ開放しているか | sudo firewall-cmd --list-all |
| サービス | 不要なサービスが動いていないか | sudo systemctl list-units --type=service --state=running |
| ログ | 異常なログイン試行がないか | sudo grep "Failed password" /var/log/secure |
7.6 シェルスクリプト入門
ここからは、日々の運用作業を効率化するシェルスクリプトについて学びます。セキュリティ監視やバックアップなど、定型作業を自動化することで、ミスを減らし、時間を節約できます。
7.6.1 なぜシェルスクリプトを書くのか
シェルスクリプトのメリット
- 繰り返し作業の自動化: 毎日同じコマンドを打つ必要がなくなる
- 手作業のミス防止: 手順を間違えることがなくなる
- 複雑な処理の一括実行: 複数のコマンドを組み合わせて実行できる
- ドキュメント化: スクリプト自体が手順書になる
- 再現性: 誰が実行しても同じ結果が得られる
例えば、「毎日深夜にログをバックアップして、ディスク使用量をチェックして、結果をメールで送る」という作業を手動で行うのは大変ですが、スクリプト化すれば自動で実行できます。
7.6.2 シェルスクリプトの基本構造
最小限のスクリプト
#!/bin/bash
# これはコメントです
echo "Hello, World!"
シェバン(Shebang)
1行目の#!/bin/bashはシェバンと呼ばれ、このスクリプトをどのプログラムで実行するかを指定します。bashスクリプトでは必ず#!/bin/bashを書きましょう。
コメント
#から行末まではコメントとして扱われ、実行されません。スクリプトの説明や注意事項を記述するのに使います。
スクリプトの作成と実行
[実行ユーザー: 一般ユーザー]
# スクリプトファイルを作成
$ vi hello.sh
# 実行権限を付与
$ chmod +x hello.sh
# 実行
$ ./hello.sh
Hello, World!
実行権限(chmod +x)を付けないと、Permission deniedエラーになります。
7.6.3 変数の使い方
変数の定義と参照
#!/bin/bash
# 変数の定義(=の前後にスペースを入れない)
NAME="AlmaLinux"
VERSION=10
# 変数の参照($を付ける)
echo "OS: $NAME"
echo "Version: ${VERSION}"
[実行ユーザー: 一般ユーザー]
$ ./script.sh
OS: AlmaLinux
Version: 10
${変数名}という書き方もできます。例えば${NAME}_serverは「AlmaLinux_server」になりますが、$NAME_serverは「NAME_server」という変数を参照しようとします。コマンド置換
コマンドの実行結果を変数に格納できます。
#!/bin/bash
# 現在の日時を取得
TODAY=$(date +%Y-%m-%d)
HOSTNAME=$(hostname)
echo "日付: $TODAY"
echo "ホスト: $HOSTNAME"
[実行ユーザー: 一般ユーザー]
$ ./script.sh
日付: 2026-01-31
ホスト: server
環境変数との違い
- シェル変数: スクリプト内でのみ有効
- 環境変数: 子プロセスにも引き継がれる(
exportで設定)
#!/bin/bash
# シェル変数
LOCAL_VAR="local"
# 環境変数として設定
export GLOBAL_VAR="global"
7.6.4 条件分岐(if文)
基本構文
if [ 条件 ]; then
# 条件が真のときの処理
elif [ 別の条件 ]; then
# 別の条件が真のときの処理
else
# いずれも偽のときの処理
fi
ファイルの存在確認
#!/bin/bash
FILE="/etc/passwd"
if [ -f "$FILE" ]; then
echo "$FILE は存在します"
else
echo "$FILE は存在しません"
fi
ファイルテスト演算子
| 演算子 | 意味 |
|---|---|
| -f FILE | 通常ファイルが存在する |
| -d DIR | ディレクトリが存在する |
| -e PATH | パスが存在する(ファイル/ディレクトリ問わず) |
| -r FILE | 読み取り可能 |
| -w FILE | 書き込み可能 |
| -x FILE | 実行可能 |
| -s FILE | ファイルサイズが0より大きい |
文字列の比較
#!/bin/bash
STR1="hello"
STR2="world"
if [ "$STR1" = "$STR2" ]; then
echo "同じ文字列です"
else
echo "異なる文字列です"
fi
# 文字列が空かどうか
if [ -z "$STR1" ]; then
echo "STR1は空です"
fi
if [ -n "$STR1" ]; then
echo "STR1は空ではありません"
fi
| 演算子 | 意味 |
|---|---|
| STR1 = STR2 | 等しい |
| STR1 != STR2 | 等しくない |
| -z STR | 文字列が空 |
| -n STR | 文字列が空ではない |
数値の比較
#!/bin/bash
NUM1=10
NUM2=20
if [ "$NUM1" -lt "$NUM2" ]; then
echo "$NUM1 は $NUM2 より小さい"
fi
| 演算子 | 意味 |
|---|---|
| -eq | 等しい(equal) |
| -ne | 等しくない(not equal) |
| -lt | より小さい(less than) |
| -le | 以下(less than or equal) |
| -gt | より大きい(greater than) |
| -ge | 以上(greater than or equal) |
7.6.5 繰り返し処理(for文、while文)
for文の基本
#!/bin/bash
# リストを順番に処理
for name in alice bob charlie; do
echo "Hello, $name!"
done
# ファイルを順番に処理
for file in /var/log/*.log; do
echo "Processing: $file"
done
# 数値の範囲
for i in {1..5}; do
echo "Number: $i"
done
while文の基本
#!/bin/bash
COUNT=1
while [ $COUNT -le 5 ]; do
echo "Count: $COUNT"
COUNT=$((COUNT + 1))
done
実用例: ファイルを1行ずつ読み込む
#!/bin/bash
while IFS= read -r line; do
echo "行: $line"
done < /etc/hosts
7.6.6 関数の定義と使用
繰り返し使う処理は関数にまとめると便利です。
#!/bin/bash
# 関数の定義
print_header() {
echo "================================"
echo "$1"
echo "================================"
}
check_service() {
local service_name=$1
if systemctl is-active --quiet "$service_name"; then
echo "$service_name: 稼働中"
return 0
else
echo "$service_name: 停止中"
return 1
fi
}
# 関数の呼び出し
print_header "サービス状態チェック"
check_service "sshd"
check_service "httpd"
7.6.7 コマンドライン引数(, , $@)
スクリプト実行時に引数を渡すことができます。
#!/bin/bash
echo "スクリプト名: $0"
echo "第1引数: $1"
echo "第2引数: $2"
echo "引数の数: $#"
echo "すべての引数: $@"
[実行ユーザー: 一般ユーザー]
$ ./script.sh arg1 arg2 arg3
スクリプト名: ./script.sh
第1引数: arg1
第2引数: arg2
引数の数: 3
すべての引数: arg1 arg2 arg3
| 変数 | 意味 |
|---|---|
| $0 | スクリプト名 |
| $1, $2, … | 第1引数、第2引数、… |
| $# | 引数の数 |
| $@ | すべての引数(個別) |
| $* | すべての引数(1つの文字列) |
7.6.8 エラーハンドリング
スクリプトの信頼性を高めるために、エラー処理は重要です。
終了ステータス($?)
コマンドは実行結果として終了ステータスを返します。0が成功、それ以外が失敗を示します。
#!/bin/bash
ls /nonexistent 2>/dev/null
if [ $? -eq 0 ]; then
echo "成功"
else
echo "失敗(終了ステータス: $?)"
fi
set -e(エラー時に停止)
コマンドが失敗したらスクリプトを停止します。
#!/bin/bash
set -e
echo "これは実行される"
ls /nonexistent # ここでエラー、スクリプト停止
echo "これは実行されない"
set -u(未定義変数でエラー)
未定義の変数を使おうとするとエラーになります。タイプミスを防げます。
#!/bin/bash
set -u
echo "$UNDEFINED_VAR" # エラー: UNDEFINED_VAR: unbound variable
set -x(デバッグ出力)
実行されるコマンドを表示します。デバッグに便利です。
#!/bin/bash
set -x
NAME="test"
echo "Name is $NAME"
[実行ユーザー: 一般ユーザー]
$ ./script.sh
+ NAME=test
+ echo 'Name is test'
Name is test
推奨: スクリプトの冒頭に設定
#!/bin/bash
set -euo pipefail
# これにより:
# -e: コマンド失敗時に停止
# -u: 未定義変数でエラー
# -o pipefail: パイプ内のエラーを検出
7.7 実践: サーバー監視スクリプトの作成
ここまでで学んだ知識を活かして、実際に使えるサーバー監視スクリプトを作成します。
7.7.1 ディスク使用量チェックスクリプト
ディスク使用量が閾値を超えたら警告を出すスクリプトです。
[実行ユーザー: 一般ユーザー]
$ vi ~/scripts/check_disk.sh
#!/bin/bash
# ディスク使用量チェックスクリプト
# 使用量が閾値を超えたら警告を出力
set -euo pipefail
# 設定
THRESHOLD=80
LOG_FILE="/var/log/disk_check.log"
# ログ出力関数
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# メイン処理
main() {
log_message "ディスク使用量チェック開始"
# dfコマンドでマウントポイントごとの使用率を取得
df -h | awk 'NR>1 {print $5, $6}' | while read usage mount; do
# %を除去して数値化
usage_num=${usage%\%}
# 数値でない場合はスキップ
if ! [[ "$usage_num" =~ ^[0-9]+$ ]]; then
continue
fi
# 閾値チェック
if [ "$usage_num" -ge "$THRESHOLD" ]; then
log_message "警告: $mount の使用率が ${usage_num}% です(閾値: ${THRESHOLD}%)"
fi
done
log_message "ディスク使用量チェック完了"
}
# 実行
main
[実行ユーザー: 一般ユーザー]
# 実行権限を付与
$ chmod +x ~/scripts/check_disk.sh
# 実行
$ ~/scripts/check_disk.sh
2026-01-31 12:00:00 - ディスク使用量チェック開始
2026-01-31 12:00:00 - ディスク使用量チェック完了
7.7.2 サービス稼働確認スクリプト
重要なサービスが稼働しているかチェックするスクリプトです。
#!/bin/bash
# サービス稼働確認スクリプト
# 指定したサービスの状態を確認し、停止していれば警告
set -euo pipefail
# 監視対象のサービス一覧
SERVICES=("sshd" "firewalld" "chronyd")
# ログ出力関数
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# サービスチェック関数
check_service() {
local service=$1
if systemctl is-active --quiet "$service"; then
log_message "OK: $service は稼働中です"
return 0
else
log_message "警告: $service は停止しています!"
return 1
fi
}
# メイン処理
main() {
local error_count=0
log_message "サービス稼働確認開始"
log_message "========================================"
for service in "${SERVICES[@]}"; do
if ! check_service "$service"; then
error_count=$((error_count + 1))
fi
done
log_message "========================================"
if [ "$error_count" -gt 0 ]; then
log_message "結果: ${error_count}個のサービスに問題があります"
exit 1
else
log_message "結果: すべてのサービスが正常に稼働しています"
exit 0
fi
}
# 実行
main
7.7.3 ログファイルのバックアップスクリプト
ログファイルを日付付きでバックアップするスクリプトです。
#!/bin/bash
# ログバックアップスクリプト
# 指定ディレクトリのログを圧縮してバックアップ
set -euo pipefail
# 設定
SOURCE_DIR="/var/log"
BACKUP_DIR="/backup/logs"
DATE=$(date +%Y%m%d)
RETENTION_DAYS=30
# ログ出力関数
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# バックアップ実行
do_backup() {
local backup_file="${BACKUP_DIR}/logs_${DATE}.tar.gz"
# バックアップディレクトリ作成
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
log_message "バックアップディレクトリを作成: $BACKUP_DIR"
fi
# すでに今日のバックアップがあるか確認
if [ -f "$backup_file" ]; then
log_message "警告: 今日のバックアップは既に存在します: $backup_file"
return 0
fi
# tarで圧縮バックアップ
log_message "バックアップ開始: $SOURCE_DIR -> $backup_file"
tar -czf "$backup_file" -C "$SOURCE_DIR" . 2>/dev/null || true
log_message "バックアップ完了: $(du -h "$backup_file" | cut -f1)"
}
# 古いバックアップの削除
cleanup_old_backups() {
log_message "古いバックアップを削除中(${RETENTION_DAYS}日以上前)"
find "$BACKUP_DIR" -name "logs_*.tar.gz" -mtime +${RETENTION_DAYS} -delete 2>/dev/null || true
log_message "クリーンアップ完了"
}
# メイン処理
main() {
log_message "========================================"
log_message "ログバックアップ処理開始"
do_backup
cleanup_old_backups
log_message "ログバックアップ処理完了"
log_message "========================================"
}
# 実行
main
7.7.4 cronでの定期実行設定
作成したスクリプトをcronで定期実行するように設定します。
[実行ユーザー: 一般ユーザー]
# crontabを編集
$ crontab -e
# 以下の行を追加
# ディスクチェック: 毎時0分に実行
0 * * * * /home/developer/scripts/check_disk.sh >> /home/developer/logs/disk_check.log 2>&1
# サービスチェック: 5分ごとに実行
*/5 * * * * /home/developer/scripts/check_service.sh >> /home/developer/logs/service_check.log 2>&1
# ログバックアップ: 毎日午前3時に実行
0 3 * * * /home/developer/scripts/backup_logs.sh >> /home/developer/logs/backup.log 2>&1
cron構文のおさらい(第5章参照):
分 時 日 月 曜日 コマンド
┬ ┬ ┬ ┬ ┬
│ │ │ │ └─ 曜日 (0-7, 0と7は日曜)
│ │ │ └──── 月 (1-12)
│ │ └─────── 日 (1-31)
│ └────────── 時 (0-23)
└───────────── 分 (0-59)
7.7.5 スクリプトのデバッグ方法
スクリプトが期待通りに動かない場合のデバッグ方法です。
方法1: bash -x で実行
[実行ユーザー: 一般ユーザー]
$ bash -x ~/scripts/check_disk.sh
+ set -euo pipefail
+ THRESHOLD=80
+ LOG_FILE=/var/log/disk_check.log
+ main
+ log_message 'ディスク使用量チェック開始'
...
方法2: スクリプト内で部分的にデバッグ
#!/bin/bash
# デバッグしたい部分だけset -xを有効化
set -x
echo "この部分はデバッグ出力される"
set +x
echo "この部分はデバッグ出力されない"
方法3: echoで変数の中身を確認
#!/bin/bash
RESULT=$(some_command)
echo "DEBUG: RESULT = $RESULT" # デバッグ用
【コラム6】SELinuxを無効化して上司に怒られた話
入社2ヶ月目のことです。先輩が設定したWebアプリケーションを、新しいサーバーに移行する作業を任されました。
ファイルをコピーして、Apacheを起動して、「よし完了!」と思ってブラウザでアクセスすると、なぜか「403 Forbidden」。
パーミッションを確認しても問題ない。Apacheの設定も間違っていない。ログを見ると「Permission denied」と書いてあるけど、ファイルのパーミッションは問題ないはず…
困ってGoogle検索すると、同じ症状の人がたくさんいて、解決策として「SELinuxを無効化する」という記事がいくつも出てきました。
「よく分からないけど、みんなこれで解決してるし…」と思い、/etc/selinux/configを編集してSELINUX=disabledに変更、再起動。
するとあっさり動くようになりました。「よかった〜」と安心して、その日は退社。
翌朝、出社すると上司に呼ばれました。
「なんでSELinux無効化したの?」
監視システムがSELinuxの状態をチェックしていて、無効化したことがすぐにバレていたのです。
上司からは厳しく指導されました。
- 「セキュリティ監査でSELinux無効化が見つかったら大問題になる」
- 「分からないから無効化するのは、鍵がかかりにくいからドアを取り外すようなもの」
- 「本番環境で同じことをしたら、顧客データが漏洩するリスクがある」
その後、上司と一緒に正しい対処法を学びました。
- まず
setenforce 0でPermissiveモードにして動作確認 ausearch -m AVCでSELinuxの拒否ログを確認audit2whyで原因を分析restoreconでファイルのコンテキストを修正setenforce 1でEnforcingに戻して動作確認
原因は単純で、先輩のサーバーからscpでコピーしたファイルに、正しいSELinuxコンテキストが設定されていなかっただけでした。restorecon -Rv /var/www/html/の一発で解決する問題を、SELinux無効化という最悪の方法で「解決」していたのです。
あれから数年経ちますが、SELinuxを無効化したいという誘惑に駆られたときは、あの日の上司の言葉を思い出します。
「分からないから無効化」は最悪の選択。「分からないなら調べる・聞く」が正解。
今では、SELinuxのエラーが出たらむしろ「何かおかしなことが起きている」というサインだと捉え、感謝するようになりました。SELinuxは敵ではなく、サーバーを守ってくれる頼もしい味方なのです。
章末まとめ
この章では、サーバーセキュリティの基礎とシェルスクリプトによる自動化について学びました。
ユーザー管理
- Linuxはマルチユーザーシステムであり、適切なユーザー/グループ管理が重要
useradd、usermod、userdelでユーザーを管理- 最小権限の原則: 必要最小限の権限のみを付与する
sudo権限
- rootで直接作業せず、sudoを使う(監査、最小権限、事故防止)
- wheelグループに追加することでsudo権限を付与
visudoで安全に/etc/sudoersを編集- パスワードなしsudoは慎重に(本番では特定コマンドのみに限定)
SSH
- パスワード認証よりも公開鍵認証が安全
ssh-keygenで鍵ペアを生成、ssh-copy-idで公開鍵を転送PermitRootLogin noでrootログインを禁止【重要】- 公開鍵認証設定後、パスワード認証を無効化【推奨】
- ~/.sshディレクトリとファイルのパーミッションに注意
SELinux
- 強制アクセス制御(MAC)によりプロセスのアクセスを制限
- Enforcingモードで運用(本番環境では絶対に無効化しない)
getenforce、setenforceでモード確認・変更- 問題発生時: Permissive → audit.log確認 → 適切な設定 → Enforcing
restorecon、semanage、setseboolで設定
基本的なセキュリティ対策
- 定期的なシステムアップデート
- 不要なサービスの停止
- ファイアウォールの適切な設定
- ログの監視
シェルスクリプト
- シェバン(
#!/bin/bash)を必ず記述 - 変数、条件分岐、ループ、関数で処理を構築
set -euo pipefailでエラーハンドリングを強化- cronで定期実行を設定
bash -xでデバッグ
練習問題
問題1: 以下の要件を満たすユーザー「webmaster」を作成するコマンドを書いてください。
- ホームディレクトリを作成
- ログインシェルは/bin/bash
- wheelグループに所属
- コメント「Web Server Administrator」
解答例を見る
# ユーザー作成
$ sudo useradd -m -s /bin/bash -G wheel -c "Web Server Administrator" webmaster
# パスワード設定
$ sudo passwd webmaster
# 確認
$ id webmaster
uid=1002(webmaster) gid=1002(webmaster) groups=1002(webmaster),10(wheel)
問題2: SSH公開鍵認証を設定する際の正しい手順を、以下から選んで順番に並べてください。
A. サーバーの~/.ssh/authorized_keysに公開鍵を追加
B. クライアントでssh-keygenを実行
C. サーバーのsshd_configでPubkeyAuthentication yesを確認
D. パーミッションを設定(~/.ssh: 700, authorized_keys: 600)
解答例を見る
正しい順番: B → A → D → C
- B: クライアントで鍵ペアを生成(ssh-keygen)
- A: 公開鍵をサーバーに配置(ssh-copy-idが便利)
- D: パーミッションを適切に設定
- C: サーバー側でPubkeyAuthenticationが有効か確認
実際にはssh-copy-idを使えばA, Dは自動で行われます。
問題3: 以下のsshd_config設定の各項目が何を意味するか説明してください。
PermitRootLogin no
PasswordAuthentication no
MaxAuthTries 3
解答例を見る
- PermitRootLogin no: rootユーザーでの直接SSHログインを禁止。攻撃者はrootを狙うため、これを禁止することでセキュリティが向上
- PasswordAuthentication no: パスワードによる認証を禁止。公開鍵認証のみを許可することで、ブルートフォース攻撃を防止
- MaxAuthTries 3: 認証試行の最大回数を3回に制限。これを超えると接続が切断される
問題4: SELinuxがEnforcingモードで動作しているか確認し、一時的にPermissiveモードに変更するコマンドを書いてください。また、永続的にモードを変更する場合はどうすればよいですか?
解答例を見る
# 現在のモード確認
$ getenforce
Enforcing
# 一時的にPermissiveに変更
$ sudo setenforce 0
$ getenforce
Permissive
# Enforcingに戻す
$ sudo setenforce 1
永続的にモードを変更する場合は、/etc/selinux/configファイルのSELINUX=行を編集します。ただし、本番環境ではdisabledにすべきではありません。
問題5: 以下の要件を満たすシェルスクリプトを作成してください。
- 引数でファイルパスを受け取る
- そのファイルが存在すれば「ファイルが存在します」と表示
- 存在しなければ「ファイルが存在しません」と表示して終了ステータス1で終了
解答例を見る
#!/bin/bash
set -euo pipefail
# 引数チェック
if [ $# -ne 1 ]; then
echo "使用法: $0 <ファイルパス>"
exit 1
fi
FILE=$1
# ファイル存在チェック
if [ -f "$FILE" ]; then
echo "ファイルが存在します: $FILE"
exit 0
else
echo "ファイルが存在しません: $FILE"
exit 1
fi
問題6: 以下のスクリプトでset -eが設定されている場合、どこで処理が停止しますか?また、set -eがない場合はどうなりますか?
#!/bin/bash
set -e
echo "Step 1"
ls /nonexistent_directory
echo "Step 2"
cat /etc/passwd > /dev/null
echo "Step 3"
解答例を見る
set -eがある場合:
「Step 1」が表示された後、ls /nonexistent_directoryでエラー(終了ステータス2)となり、スクリプトは停止します。「Step 2」「Step 3」は実行されません。
set -eがない場合:
すべてのechoが実行されます。lsコマンドはエラーを出力しますが、スクリプトは停止せず次の行に進みます。
Step 1
ls: cannot access '/nonexistent_directory': No such file or directory
Step 2
Step 3
次章への橋渡し
この章では、サーバーを守るためのセキュリティ設定と、運用を効率化するシェルスクリプトについて学びました。ユーザー管理、SSH認証の強化、SELinuxの活用により、多層的な防御を構築できるようになりました。
次の第8章では、ログ管理とトラブルシューティングについて学びます。サーバー運用において、問題が発生したときに素早く原因を特定し、解決する能力は非常に重要です。ログの読み方、問題の切り分け方法、そして実際のトラブルシューティング手順を習得します。
この章で作成した監視スクリプトもログを出力していますが、それらのログをどう読み、どう活用するかを次章で詳しく学びます。セキュリティの監視とトラブルシューティングは表裏一体の関係にあり、次章の知識と組み合わせることで、より堅牢なサーバー運用が可能になります。
