Ubuntu

Ubuntu22.04 Webサーバーを立てる

Webサーバーを立てて欲しいというリクエストがあった。
極端に複雑なことはしないようなので、最低限のリソースでサーバーを立ち上げることにした。



広告


どんな環境を作るか

リクエスト

こんな話だった。

  • Webサーバーを立てたい。
  • コンテンツはローカルのPCで作成し、FTPでアップロードしたい。
  • このサーバーはインターネットに公開するつもり。

しまったなーと思っているのは、CGI的なものを使うかどうか聞いていなかったこと。
まぁ、後からでもどうにかなるだろう。

レスポンス

OSの指定はなかったので、Ubuntu server 22.04 LTSを選択。軽くていいんじゃないかなと。

FTPでコンテンツをアップロードするということは、日頃、このサーバーにログインして何かをすることはなさそう…
ユーザーを何人か追加するとして、その人達が作るファイルは、別の人も更新できた方が良いのだろう。
CGIが動き、www-dataユーザーでディレクトリやファイルが作られる仕組みが入るかもしれない。

編集者としてedituserというユーザーを作るとして…

  • DocumentRootは/var/www/htmlとする。
    • このディレクトリのオーナーのグループをwww-data、ユーザーをwww-dataとする。
    • www-dataグループに書き込みの権限を与える。
    • SGID(set-group-id)を設定する。
      これによって、/var/www/html以下に作成されるファイルとディレクトリは、グループがwww-dataになる。
  • edituserをwww-dataグループに所属させる。
    • www-dataグループに所属することで、/var/www/htmlディレクトリへの書き込みが可能になる。
    • プライマリグループはedituserのままにすることで、ログイン時のumaskが0002になる。
      ディレクトリは775、ファイルは664で作られるので、グループに所属するユーザー間の読み書き・実行が可能になる。

という設定の組み合わせにすることで、/var/www/htmlディレクトリ以下を、www-dataグループのユーザーで読み書きできるようにする。
ユーザーを追加するときは、edituserと同じ設定にすればいい。

このサーバーを公開するためには、適当なリバースプロキシーサーバーを立てりゃいいかな。
何より慣れていないのは、FTPサーバー。これさえクリアすれば、どうにかなるだろう。

こんな構成で引き渡すことにした。

環境構築

ホームラボで手順を整理してから、本番環境を構築する。
以下、ホームラボで行った手順。

ファイアウォールの設定

SSHのポートを開放して、ファイアウォールを有効にする。
接続元は適切なものに絞ったりする(from 192.168.0.0/24とか)。

$ sudo ufw allow to any port 22 proto tcp from any comment "SSH"
$ sudo ufw enable

ログの出力を調整する。

/etc/rsyslog.d/20-ufw.conf

# Log kernel generated UFW log messages to file
:msg,contains,"[UFW " /var/log/ufw.log

# Uncomment the following to stop logging anything that matches the last rule.
# Doing this will stop logging kernel generated UFW log messages to the file
# normally containing kern.* messages (eg, /var/log/kern.log)
& stop

※最後の & stop がコメント化されているので、先頭の#を削除して有効化。

rsyslogを再起動。

$ sudo systemctl restart rsyslog

これで、/var/log/ufw.logだけにログが出力される。

スワップ領域を増やす

メモリーが少なめの設定なので、少し大きめにとってみる。
(標準で2GBもとってあるから、十分といえば十分だけれど)

最初に作られたスワップ領域があったので、削除。

$ sudo swapoff /swap.img
$ sudo rm /swap.img

スワップ領域を作る。ここでは4GBにしてみた。

$ sudo fallocate -l 4G /swap.img
$ sudo chmod 600 /swap.img
$ sudo mkswap /swap.img
mkswap: /swap.img: insecure permissions 0644, fix with: chmod 0600 /swap.img
Setting up swapspace version 1, size = 4 GiB (4294963200 bytes)
no label, UUID=877eb6d1-662f-442e-a453-5257740c1305

スワップ領域を有効にする。

$ sudo swapon /swap.img

確認。

$ free -h
               total        used        free      shared  buff/cache   available
Mem:           1.9Gi       298Mi       1.2Gi       1.0Mi       359Mi       1.4Gi
Swap:          4.0Gi          0B       4.0Gi

再起動後もスワップ領域として使われていることが確認できた。

Apache

WebサーバーとしてApacheをインストール。
Nginxの方が性能が高いみたいだが、こちらに慣れているので。

$ sudo apt install apache2
$ sudo ufw allow to any port 80,443 proto tcp from any comment "WWW"

ブラウザで http://<IP アドレス> にアクセスすると、Apacheのデフォルトページが表示される。

/var/www/htmlディレクトリのオーナーをwww-dataに変更。
あわせて、SGID(set-group-id)を設定する。

$ sudo chown -R www-data:www-data /var/www/html
$ sudo chmod g+sw /var/www/html

$ ll -d /var/www/html
drwxrwsr-x 3 www-data www-data 4096 Sep 23 18:46 /var/www/html/

ホストの定義を新規に作成。

  • サーバー名はexample.netとしているので、本番にあわせる。
  • SSL証明書と秘密鍵はsnakeoilとしているので、本番で利用するものに差し替える。
  • リバースプロキシされたときに、接続元IPアドレスが出力できるように、X-Forwarded-Forをログに出力する。

/etc/apache2/sites-available/content.conf

<VirtualHost *:80>
        ServerName example.net
        Redirect permanent / https://example.net
</VirtualHost>

<VirtualHost *:443>
        ServerName https://example.net

        LogFormat "%h %{X-Forwarded-For}i %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined2
        ErrorLog ${APACHE_LOG_DIR}/content-error.log
        CustomLog ${APACHE_LOG_DIR}/content-access.log combined2

        SSLEngine on
        SSLCertificateFile      /etc/ssl/certs/ssl-cert-snakeoil.pem
        SSLCertificateKeyFile   /etc/ssl/private/ssl-cert-snakeoil.key

        <Directory /var/www/html>
                Require all granted
                DirectoryIndex index.html
                AllowOverride All
                Options +ExecCGI
                AddHandler cgi-script .cgi .pl
        </Directory>
</VirtualHost>

デフォルトの設定を外して、作成した設定を有効にする。

$ sudo a2dissite 000-default.conf
$ sudo a2ensite content.conf

必要なモジュールを有効にする。

$ sudo a2enmod ssl cgi http2

警告避け。

$ echo "ServerName localhost" | sudo tee /etc/apache2/conf-available/fqdn.conf
$ sudo a2enconf fqdn

設定を反映させる。

$ sudo systemctl restart apache2

.htaccessで設定を上書きできる柔軟な設定なのかなと。

編集者を追加

編集者として、対話型のコマンドでedituserを追加する。
パスワードだけを入れて、後は空としている。

$ sudo adduser edituser
Adding user `edituser' ...
Adding new group `edituser' (1001) ...
Adding new user `edituser' (1001) with group `edituser' ...
Creating home directory `/home/edituser' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for edituser
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]

編集者をwww-dataグループに追加する。
ask ubuntu / How to create an FTP/SFTP user to manage /var/www which is owned by www-data

$ sudo usermod -a -G www-data edituser
$ groups edituser
edituser : edituser www-data

※プライマリーグループはedituserのままでOK。

vsftpd

FTPサーバーとして、vsftpdをインストール。
接続元は適切なものに絞ったりする(from 192.168.0.0/24とか)。

$ sudo apt install vsftpd
$ sudo ufw allow to any port 21 proto tcp from any comment "FTP"
$ sudo ufw allow to any port 61001:61005 proto tcp from any comment "FTP PASV"

※ufwの設定を削除するときは、ufw と allow の間に delete を入れればOK。設定値を変えた場合に、作り直しは可能。

幾つかの設定を入れる。

/etc/vsftpd.conf

write_enable=YES
local_umask=002
pasv_min_port=61001
pasv_max_port=61005

※ポートの最初と最後はファイアウォールの設定とあわせて、新規に追加する。

安全接続を目指すなら、以下を設定してFTPSにする。
証明書と秘密鍵にはsnakeoilを使っているが、Apache同様、本番で使用するものに差し替えてもいい。
結局、作業用の接続なのでsnakeoilで暗号化するのでも、問題はないはずだ。

rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
ssl_enable=YES
strict_ssl_read_eof=NO
strict_ssl_write_shutdown=NO

※変更と追記。Qiita / vsftpdにアップロードしたら"426 Failure reading network stream."エラーが出るときの解決法

テストは、一旦ssl_enableをNOにしてコマンドベースで行い、後からFFFTPとかを使ってFTPSを試すのが簡単だと思う。

設定を反映。

$ sudo systemctl restart vsftpd

Windows 11からアクセスしてみる。FTPのみ。

>ftp <IPアドレス>
<IPアドレス> に接続しました。
220 (vsFTPd 3.0.5)
200 Always in UTF8 mode.
ユーザー (<IPアドレス>:(none)): edituser
331 Please specify the password.
パスワード:
230 Login successful.
ftp> cd /var/www/html
250 Directory successfully changed.
ftp> ls -l
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rw-r--r--    1 33       33          10671 Sep 22 07:23 index.html
226 Directory send OK.
ftp: 71 バイトが受信されました 0.00秒 71.00KB/秒。
ftp> quit
221 Goodbye.

Ubuntu 22.04からアクセスしてみる。

FTPの場合

$ ftp <IPアドレス>
Connected to <IPアドレス>.
220 (vsFTPd 3.0.5)
Name (<IPアドレス>:rohhie): edituser
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> cd /var/www/html
250 Directory successfully changed.
ftp> ls
229 Entering Extended Passive Mode (|||61002|)
150 Here comes the directory listing.
-rw-r--r--    1 33       33          10671 Sep 22 07:23 index.html
226 Directory send OK.
ftp> quit
221 Goodbye.

FTPSの場合

$ sudo apt install lftp

設定方法はこちらで教えてくれた。
Qiita / 【Linux】ftpへのファイル転送

~/.lftprc

set ftp:ssl-auth TLS
set ftp:ssl-force true
set ftp:ssl-allow yes
set ftp:ssl-protect-list yes
set ftp:ssl-protect-data yes
set ftp:ssl-protect-fxp yes
set ssl:verify-certificate no
set cmd:fail-exit yes

$ lftp -u edituser ftp://<IPアドレス>
Password:
lftp edituser@<IPアドレス>:~> cd /var/www/html
cd ok, cwd=/var/www/html
lftp edituser@<IPアドレス>:/var/www/html> ls
-rw-rw-r--    1 1001     33            213 Sep 23 19:04 index.html
-rw-rw-r--    1 1001     33            378 Sep 23 19:04 test.php
-rwxrwxr-x    1 1001     33            652 Sep 23 19:04 test.pl
lftp edituser@<IPアドレス>:/var/www/html> quit

とりあえずのテストはここまで。
読み書きのテストは次で行う。

テスト

テストページを作成して動作を確かめる。

ポータルの作成

とりあえず、index.htmlを待避しておきますか。

$ sudo mv /var/www/html/index.html /var/www

edituserになってポータルページを作成。

$ sudo su - edituser

/var/www/html/index.html

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
</head>
<body>
        <h1>CGIテスト</h1>
        <p>テスト1: <a href="/test.pl">Perl</a></p>
        <p>テスト2: <a href="/test.php">PHP</a></p>
</body>
</html>

PerlのCGIをテスト

PerlのCGIを作成して動かしてみる。

/var/www/html/test.pl

#!/usr/bin/perl
use strict;

my $host_name = &GetHostByAddr($ENV{'REMOTE_ADDR'});
my($ipaddr) = @_;

print <<PAGE;
Content-type: text/html\n
<!doctype html>
<html lang=\"ja\">
<head>
<meta charset=\"utf-8\">
</head>
<body>
<h1>テスト Perl</h1>
<p>あなたのIPアドレスは $ENV{'REMOTE_ADDR'} です。</p>
<p>あなたのホスト名は、$host_name です。</p>
<p></p>
<p><a href="/">トップページへ</a></p>
</body>
</html>
PAGE
exit;

sub GetHostByAddr {
                my($ip) = @_;
                my @addr = split(/\./, $ip);
                my $packed_ip = pack("C4", $addr[0], $addr[1], $addr[2], $addr[3]);
                my($name) = gethostbyaddr($packed_ip, 2);
                return $name;
}

実行権限を付ける。

$ chmod +x /var/www/html/test.pl

https://<IP アドレス>にアクセスし、CGIを起動してみる。
ホームラボでは、IPアドレスから逆引きしてホスト名が取得できるようになっている。
逆引きできなければ、ホスト名が空になる。

動いているようだ。

PHPを作成

PHPでページを作成して動かしてみる。

/var/www/html/test.php

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
</head>
<body>
<h1>テスト PHP</h1>
<?php
$value = apache_getenv("REMOTE_ADDR");
echo "<p>あなたのIPアドレスは $value です。</p>\n";
$value = gethostbyaddr($value);
echo "<p>あなたのホスト名は、$value です。</p>\n";
?>
<p></p>
<p><a href="/">トップページへ</a></p>
</body>
</html>

PHPのCGIを動かすのに、PHPと追加のモジュールが必要。

追加モジュールをインストールすると、MPM(マルチプロセッシングモジュールがeventからpreforkに変わる。
これによって、HTTP/2からHTTP/1.1に変わるので、注意。
もし、HTTP/2で通信したいなら、PHP-FPMで動作させる必要があるが、ちょっと手間は掛かる。

ここではpreforkで動作確認し、PHPを使わない場合には、eventに戻す手順を記しておく。

phpと追加のモジュールをインストールする。
root権限が必要なので、いつものユーザーに戻ってから、インストール。

$ exit
$ sudo apt install php libapache2-mod-php

これで追加モジュールが有効になり、Apacheも再起動するので、テストページからCGIを起動する。
https://<IP アドレス>

テストができたので、MPMをeventに戻す。

$ sudo a2dismod mpm_prefork php8.1
$ sudo a2enmod mpm_event
$ sudo systemctl restart apache2

この操作でHTTP/2の通信が復活し、PHPは動かなくなる。

PHPを使わないなら、アンインストールする。

$ sudo apt remove php libapache2-mod-php
$ sudo apt autoremove

やっぱりPHPを使う!となったら、phpとlibapache2-mod-phpをインストールして、以下を実行。

$ sudo a2dismod mpm_event
$ sudo a2enmod php8.1
$ sudo systemctl restart apache2

※php8.1を有効にすると、mpm_preforkが自動的に有効になる。

FTPの確認

FFFTPでファイルのアップロードも試してみよう。
バージョンは…1.99aとか、いつのだろう?

良い感じに見える。

ファイルをダウンロードしてみたところ、サイズもぴったり、問題はなさそうだ。
(ASCIIだと変換がうまくいかなかったのは、バージョンが古すぎるからだろう)

サーバーのファイルを消してみたところ、きれいに消えた。

アップロードして、パーミッションを設定してみたところ、こちらも問題なく実行できた。
(test.phpに実行権限は不要…ミスってます)

アップロードしたファイルが正しく動くか確認。
必要な権限さえ設定してあれば、問題なく動く。

これでリクエストには応えられるだろう。

FTPSの確認

基本的にはFTPと同じことをやってみる話。
FFFTPの1.99aで試している。

スクリーンショットを撮り忘れたのだけれど、最初にこのサーバーに接続して良いか聞かれる。
それにOKすると、そのサーバーを記憶してくれるようで、その後にその質問はされない。

ファイルの削除は問題なし。

ファイルのアップロードでは、ファイルがアップロードできているのに、2回目にトライしていて、以下のようなメッセージが表示された。

426 Failure reading network stream.

Skipを選択した状態でDo Allボタンを押せば、ファイルのアップロードは完了するが、動きがおかしい。
FFFTPの最新バージョン(この日は5.8だった)をダウンロードしてきて試したが、結果は同じだった。

この問題はvsftpdの設定で回避できたので、ここに記載した。

やったこと

改行コードを確認する

久しぶりに改行コードの問題が発生した。
ブラウザでスクリプトにアクセスしたところで、こんなエラーが発生。

/var/log/apache2/content-error.log

End of script output before headers: perl.cgi

原因は、改行コードがCR+LFだったからなんだけれど、改行コードを調べる方法が分からなかった。
以前は、vimで開くと改行コードが見えた気がするんだけれど。

fileコマンドで改行コードが確認できるというので実行してみたところ、そのような表示はされなかった。

$ file perl.cgi
perl.cgi: Perl script text executable

そこで、dos2unixコマンドで確認してみることにした。

$ sudo apt install dos2unix

ファイルをチェックしてみる。

パラメーターが-iの場合、左から、DOSの行数、UNIXの行数、MACの行数、byte order mark、テキストorバイナリー、ファイル名の順で表示される。
パラメーターが-icの場合、変換が必要なファイル名だけが表示される。

$ dos2unix -i perl.cgi
      19       0       0  no_bom    text    perl.cgi
$ dos2unix -ic perl.cgi
perl.cgi

改行コードがLFの場合には、以下の表示だった。

$ dos2unix -i perl.cgi
       0      19       0  no_bom    text    perl.cgi
$ dos2unix -ic perl.cgi

ファイルをバイナリー形式で表示して調べる方法もあるけれど、これがスマートなのかなと思われた。

プライマリグループを変更する

プライマリグループは以下で変更ができた。

$ sudo usermod -g www-data edituser
$ groups edituser
edituser : www-data edituser

この時、edituserのホームディレクトリは以下のように変化した。

edituser@hoge:~$ ll
total 28
drwxr-x--- 3 edituser www-data 4096 Sep 23 04:29 ./
drwxr-xr-x 4 root     root     4096 Sep 22 07:59 ../
-rw------- 1 edituser www-data  933 Sep 23 04:29 .bash_history
-rw-r--r-- 1 edituser www-data  220 Sep 22 07:59 .bash_logout
-rw-r--r-- 1 edituser www-data 3771 Sep 22 07:59 .bashrc
drwx------ 2 edituser www-data 4096 Sep 23 04:02 .cache/
-rw-r--r-- 1 edituser www-data  807 Sep 22 07:59 .profile

ただ、umaskの問題があるので、プライマリグループはedituserに戻している。
本編では、そもそもプライマリグループを変更しない手順にした。

umaskが0002

ファイルやディレクトリを作ったとき、どんな権限が付くのか確認していて、グループに書き込み権限がないことに気付いた。

$ touch test.txt
$ mkdir test.dir
$ ll
total 12
drwxr-xr-x 3 edituser www-data 4096 Sep 23 04:31 ./
drwxr-x--- 4 edituser www-data 4096 Sep 23 04:30 ../
drwxr-xr-x 2 edituser www-data 4096 Sep 23 04:31 test.dir/
-rw-r--r-- 1 edituser www-data    0 Sep 23 04:30 test.txt

このとき、edituserのプライマリグループをwww-dataにしていた。

$ groups
www-data edituser

umaskを確認してみると、0002ではなく、0022だった。

$ umask
0022

この動きは、以下で決まっていた。
ask ubuntu / Why does a user's umask values differ between two systems?

/etc/login.defs

...
#
# Login configuration initializations:
#
#       ERASECHAR       Terminal ERASE character ('\010' = backspace).
#       KILLCHAR        Terminal KILL character ('\025' = CTRL/U).
#       UMASK           Default "umask" value.
#
# The ERASECHAR and KILLCHAR are used only on System V machines.
#
# UMASK is the default umask value for pam_umask and is used by
# useradd and newusers to set the mode of the new home directories.
# 022 is the "historical" value in Debian for UMASK
# 027, or even 077, could be considered better for privacy
# There is no One True Answer here : each sysadmin must make up his/her
# mind.
#
# If USERGROUPS_ENAB is set to "yes", that will modify this UMASK default value
# for private user groups, i. e. the uid is the same as gid, and username is
# the same as the primary group name: for these, the user permissions will be
# used as group permissions, e. g. 022 will become 002.
#
# Prefix these values with "0" to get octal, "0x" to get hexadecimal.
#
ERASECHAR       0177
KILLCHAR        025
UMASK           022
...
#
# Enable setting of the umask group bits to be the same as owner bits
# (examples: 022 -> 002, 077 -> 007) for non-root users, if the uid is
# the same as gid, and username is the same as the primary group name.
#
# If set to yes, userdel will remove the user's group if it contains no
# more members, and useradd will create by default a group with the name
# of the user.
#
USERGROUPS_ENAB yes
...

DeepL先生の翻訳によれば、USERGROUPS_ENABの説明はこうだった。

uid が gid と同じで、username がプライマリグループ名と同じ場合、非 root ユーザーの umask グループビットを所有者ビットと同じにする (例: 022 -> 002, 077 -> 007)。

yesに設定すると、userdelはそのユーザーのグループにメンバーがいなくなった場合に削除し、useraddはデフォルトでそのユーザーの名前のグループを作成します。

確かに、ユーザーを作ると、デフォルトでそのユーザーの名前のグループが作られるようになっている。

ログイン時にプライマリグループの名前がチェックされ、状態によりumaskは0022か0002になるが、それはこの仕組みによるものだった。
思想が統一されて今の状態になっているんだなと感じる。

これを自分ルールで変更しようと思うなら、/etc/profileに処理を書けば良いようだ。

さいごに

整理しながら雑なサーバー構築だなと思うけれど、バースト的なアクセスはなさそうなので、これくらいでだいたいうまくいくんじゃないかな。
後はどんなコンテンツが載るのかだけれど、必要なものがあったらチョイチョイ足していけばいい。

いままでSGIDとSUID、Sticky BITを理解せずに使っていて、なんかやばめの権限設定でSambaを運営していたりする。
家庭内でやってることだし、ファイルの消失がなければいいやとほったらかしだが、今回この辺りをちょっとかじったので、少し分かる範囲を広げたいところ。

何かが分かった気になっても、次から次へとナゾが襲いかかってくる感じで、全くクリアができないネトゲのようだ。
いつかどこかですっきりするのかな。

広告

コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他