Linux/CentOS Stream8でサーバ構築/Webサーバ編

Webサーバ関連(2022-03-11)


前回の メール編 に引き続き、CentOS Stream8での作業となります。*1


wgetコマンドのインストール

指定したURLからコンテンツをゲットするためのコマンドです。

$ dnf install wget

Webサーバにアクセスできるかを確認したり、どこかからファイルをダウンロードする時などに使えます。

Apache httpd のインストール

言わずと知れたWebサーバの老舗。

とりあえずインストールします。

$ dnf install httpd

それから/etc/httpd配下にあるconfigを設定します。

conf/httpd.conf

Listen 80                # 動作確認用に、とりあえず起動
# Listen 127.0.0.1:8080  # 後述のプロキシ(squid)起動後はこちら

...(中略)...

ServerName サーバ名

# ここに限らず全体的に「cgi-bin」は狙われやすいので
# 自分の使うWikiやCGIツール上の制約がなければ
# 「mv cgi-bin cgi-bon」とかで置き換えてしまった方が無難だと思う
ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"

  ...(中略)...
<IfModule mime_module>
  ...(中略)...
   AddHandler cgi-script .php  # うちのサイトではCGIとしてphpを使っています
   # 他に必要なCGI関連の設定は以下を参照
   # https://httpd.apache.org/docs/2.2/ja/howto/cgi.html
  ...
</IfModule>

...
# 以下を追記、ゆるいセキュリティ対応
LimitRequestBody 102400  # 初期値は無制限、これは100Kまでに制限する例
LimitRequestFields 50    # 初期値は100、もっとずっと減らして良い
ServerTokens Prod        # 初期値はFull、応答ヘッダの情報は最小限にしたい。
TraceEnable Off          # 初期値はOn、TRACEメソッドを許可する理由は特に無いので。
 
# あと、うちの環境だとプロキシ(squid)を使うので
# LogFormat "%{%y%m%d/%H:%M:%S}t %{X-Forwarded-For}i ...
# のように転送前の情報が分かるように、ログにX-Forwarded-Forを追加しています


それからスレッド起動(mpm起動)ではなくプロセス起動(prefork起動)に変更します。

conf.modules.d/00-mpm.conf

LoadModule mpm_prefork_module modules/mod_mpm_prefork.so # この行を有効にする
# LoadModule mpm_event_module modules/mod_mpm_event.so # こっちはコメントアウト

PHPはスレッドセーフではありません、そしてパッケージからいれた httpdの初期値はスレッド起動(MPM)でした。
そのため起動時にこんなエラーが(以下、/var/log/messages より)。

Apache is running a threaded MPM, but your PHP Module is not compiled to be threadsafe.  You need to recompile PHP.

Webサーバで大量のアクセスをさばく場合はプロセス(prefork)ではなくスレッド(mpm)にするべきでしょうけれど、そこまでする必要は無いので素直にprefork起動に変えました。
「The mpm module (prefork.c) is not supported by mod_http2. The mpm determines how things are processed in your server.~This is an advisory warning.」とか httpd/error_logで文句言われましたが、知ったこっちゃ無いです。

あと、この時点ではhttpsに未対応です。うちの環境ではSSL/TLS接続はリバースプロキシ(squid)側で受けつけます。

…もともとsquidに任せるつもりではあったんですが、「/usr/sbin/httpd -M」や「ls /etc/httpd/modules/」を見てみても mod_sslとかのSSL/TLSに必要なものが見当たりませんでした。
もし httpdでSSL/TLSを受ける必要があるならソースからビルドする必要があるかもしれません。


設定が終わったら、ファイアウォールを開けて、Webブラウザなどで動作確認します。

$ firewall-cmd --add-service=http --zone=public --permanent
$ firewall-cmd --add-service=https --zone=public --permanent
$ firewall-cmd --reload
$ firewall-cmd --list-all


そして起動します。

$ systemctl enable httpd
$ systemctl start  httpd

最近のブラウザだとデフォルトでhttps(=443)に繋ぎそうなので、http(=80)の方になるように気をつけつつ動作確認します。
うまくいかない場合はサーバ内で「wget localhost:80」で繋がるか試せば httpdの問題なのかネットワーク上の問題なのか切り分けができるかもしれません。

余談:phpでどれをインストールするか

WebサイトでCGIを使う場合は php も入れることが多いと思います。
以下、2022/03 現在での例ですが、

$ dnf info php
Name         : php
Version      : 7.2.24

ところが、Remiを指定して探すと

$ dnf --enablerepo=remi-safe search php
(以下、抜粋)
php.x86_64 : PHP scripting language for creating dynamic web sites
php72.x86_64 : Package that installs PHP 7.2
php73.x86_64 : Package that installs PHP 7.3
php74.x86_64 : Package that installs PHP 7.4
(以下、省略)

のような感じで新しいバージョンがいっぱい出て来るので、場合によっては
「dnf --enablerepo=remi-safe search php74」のように新しいバージョンを入れる必要があるので注意しましょう。

…だけど、php74を入れても「/etc/httpd/modules/libphp7.so」は自動で入らないんですよね。
「dnf install php」するとphpの7.2が入った上で libphp7.so も勝手に増えるんですが。

余談:Digest認証を使う例

こんな風に httpd.confを設定することで、アクセス時にパスワード認証を要求できます。

<Directory "指定ディレクトリ">
     AuthType Digest
     AuthName "realm_name"
     AuthUserFile /etc/httpd/htdigestファイルを置いたPATH
     Require user user_name
</Directory>
$ htdigest [-c] htdigest realm_name user_name
(-c を指定すると新規作成になります)
作成された htdigest ファイルを上記AuthUserFileで指定したPATHに置く

なんでこれをメモしたかといえば、
ざっと見た限りだとWordPressってファイル名・ディレクトリ名の変更ができないっぽいんですよね。
だからせめて、"/var/www/WordPressを置いた場所/wp-admin" にDigest認証くらいかけたいな、と。

私が使っている Pukiwikiの場合は index.php を別の名前でコピーして PKWK_READONLY 有無で分けるだけで編集者用ページを作れます*2
つまり、編集用のURLを隠すのが簡単です。

WordPressは世界で圧倒的にシェアがある分、不正アクセスの大半はWordPress狙いなので*3、使う場合はDigest認証にかぎらず何でも良いので、セキュリティ的にどう対策するのか工夫してみた方が良いと思います。

余談:Listen loccalhost:8080 とした場合に失敗する件

squidを起動した後に httpd.conf で Listen hocalhost:8080 した場合、httpdの起動に失敗しました。

Address already in use: AH00072: make_sock: could not bind to address 127.0.0.1:8080

これはどうやら /etc/hosts で localhostとしてIPv4(127.0.0.1)とIPv6(::1)の二つあるときに、両方で bindを試みて失敗するようです。
試しに /etc/hosts で IPv6の行をコメントアウトした場合、または httpd.confで「Listen 127.0.0.1:8080」とした場合はともに起動できました。

squid のインストール

リバースプロキシとして使うためにインストールします。

Webブラウサ  <-->  squid  <-->  apache-httpd
  というように通信の仲介をするのが
  プロキシ(リバースプロキシ)のお仕事です


性能改善やら保守やらの目的で通信の分散や切り替えに利用できるプロキシサーバですが、私の場合は不正アクセスに対するフィルターがけが主な目的です。
あと、さっきインストールした httpdがまだ ssl未対応なのが分かったのでその肩代わりも squidでやります。

もちろんWebサーバ(httpd)の保守作業が必要になった場合に、作業中に通信を別のサーバに切り替えるといったリバースプロキシらしい目的にも使えます。
ともかく、インストールしていきます。

$ dnf install squid

それから squid.confを設定します。

/etc/squid/etc/squid.conf

# http_access deny all # 前半のこれはコメントアウト。
                       # 自分でルールを追記した後にあらためてこれを書きます

# 以下、https://wiki.squid-cache.org/ConfigExamples/Reverse/BasicAccelerator あたりを参考に
http_port 80 accel defaultsite=localhost
https_port 443 accel tls-cert=証明書 tls-key=秘密鍵 defaultsite=localhost protocol=HTTPS
        # letsencrypt の場合はそれぞれ
        # cert=/etc/letsencrypt/live/ドメイン名/fullchain.pem
        # tls-key=/etc/letsencrypt/live/ドメイン名/privkey.pem

cache deny all # キャッシュはしないで毎回Webサーバへ取りにいく

httpd_suppress_version_string on # エラーページでsquidの情報を隠す
# なお、error_directory で自前のエラーページを作成した方がもっと情報を隠せます

# ログ書式は特に時刻がデフォルトだと秒なので、変える事を推奨
# logformat httplog 任意のフォーマット   # ログ書式
# (フォーマット例:%tl %>a [%>Hs] %Ss %>st %<st [%>rm %>ru ] [%{User-Agent}>h] [%{Referer}>h])
# access_log daemon:ログのPATH  httplog  # 書式と保存場所を指定
# logfile_rotate 30                      # ログのローテーション日数
# ---このlogfile_rotateを踏まえて、cronとかでローテーションをかける---
# 例: 0 0 * * * /usr/sbin/squid -k rotate

#-----------------------
## 今回 squidを使っている理由と言っても過言では無い部分
## 指定した正規表現にかかったアクセスはTCPリセットで強制断する
# acl bad_url urlpath_regex  URL指定の正規表現
# http_access deny bad_url
# deny_info TCP_RESET bad_url
#
## 逆にホワイトリスト方式で絞る案。こっちで書いた方が楽かも?
# acl good_url urlpath_regex URL指定の正規表現
# http_access deny !good_url
#-----------------------

# 自分のサイトのドメイン名へのアクセスのみを許可する
# 不正アクセスで片っ端から漁るときは「IPアドレス直打ち」で来るので、それらを拒絶する
acl web_client dstdomain ドメイン名(うちのサイトの場合はnull-i.net www.null-i.netなど)
http_access allow web_client

# httpd に(localhost:8080 でListenさせて)転送する
cache_peer 127.0.0.1 parent 8080 0 no-digest no-query originserver login=PASS name=my_accel
cache_peer_access my_accel allow web_client
http_access deny all # すべてのルールに漏れたものは最後に deny

# おまじない1。UDPポートでグローバルIPへLISTENされるのを防ぐ。
# 本来は ICPや DNSの連携に使うらしいけど、うちでは使わない想定なので。
udp_incoming_address 127.0.0.1

# おまじない2。以下のようなエラーが出る場合に回避する。
## comm_udp_sendto FD x, (family=2) xx.xx.xx.xx:53: (22) Invalid argument
## idnsSendQuery FD x: sendto: (22) Invalid argument
udp_outgoing_address 自分のIP

以前は cache_peer に no-digest は付けていなかったのですが、ログに「/squid-internal-periodic/store_digest」が頻発するようになってしまったので今回から追加しました。*4

最後のおまじない部分は必要に応じてどうぞ。
udp_outgoing_address のエラーについては wiki.squid-cache.org を参照。
上記コメントのログ例 xx.xx.xx.xxの部分で/etc/resolv.conf の nameserverが入った警告が頻発する事象を回避します。

これで起動します。

$ systemctl start squid
$ systemctl enable squid

特に acl 部分の書き方は慣れだと思います。
上記例では正規表現 urlpath_regex、dstdomain を使っていますが、他の種類のフィルターもありますので、使いやすい設定でお試しください。

はじめてWebサーバを構築する方は、半年後くらいに(もっと早いかも?)アクセスログを確認してみれば無差別な不正アクセス攻撃の量に戦慄すると思います。
放置すると増えるので、まめに阻止しましょう。(参考まで:フィルタで許可するべきリクエスト?

あとは、さらに nginx を入れてTCPポート8081で apache-httpd と同時に起動して、保守作業中は nginx 側に振り分けるみたいな使い方もできます。
私の場合は「不正アクセスを別サイトにご案内する目的」でもsquidを活用しています。

余談:URLを日本語に戻す

ブラウザ上でURLへ「index.html?ぬるいねっと」と入力すると、
ふつうは「index.html?%E3%81%AC%E3%82%8B~」のように変換されるのですが、
/var/log/squid/access.log 上では「index.html?%25E3%2581%25AC%25E3%2582%258B~」のように、
「%」に対してさらにエスケープ変換されるので注意が必要です。

通常のURLを日本語に戻す例

$ echo %E3%81%AC | perl -MURI::Escape -pe 'print uri_unescape $_'
$ echo %E3%81%AC | perl -e 'while(<STDIN>){ s/%([0-9a-fA-F]{2})/pack("H2",$1)/ge; printf("%s\n", $_); }'


squidログを日本語に戻す例

$ echo %25E3%2581%25AC |  perl -MURI::Escape -pe 's/%25/%/g; print uri_unescape $_'
$ echo %25E3%2581%25AC |  perl -e 'while(<STDIN>){ s/%25/%/g; s/%([0-9a-fA-F]{2})/pack("H2",$1)/ge; printf("%s\n", $_); }'




以上、Webサーバの設定でした。

つぎはその他、 セキュリティ関連のパッケージを入れます。


*1 お手持ちのOSバージョンは「cat /etc/redhat-release」で確認できます。
*2 というのがPukiwikiパッケージ内のREADME.txtに書いてあって、私もそれに習いました。
*3 WordPressを使っているかどうかは関係なしに世界中のbotが wp-admin/*phpを片っ端から狙ってくるのがアクセスログを見れば確認できると思います。むかしWordPressの脆弱性が出たときはもう、本当に、ひどかったです。
*4 挙動が変わった理由は分かりません、squidのバージョンが変わったからなのか、前回はソースからビルドしたからなのか…ただ、前回も急にデフォルトの動作が変わった部分があったので、つまり毎回動作確認と調整が必要なのでしょう、OSSですし。