お名前.com VPS でDockerを使用しmodsecurity-crs:nginx、WordPress、Djangoを動かしている。
ネットで調べると自分の環境と違ったり、断片的な情報を間違って理解することが多いので
改めて整理したほうがいいと思い。
Docker+modsecurity+Nginxの設定を個人的メモとしてまとました。
私が誤解していることもあると思いますのでご了承ください。
環境
パソコン Windows11
VPS OS Ubuntu 20.04.3 LTS、
Docker image: owasp/modsecurity-crs:nginx
Nginxの設定を見る
コンテナ(私の一連のお名前.comVpsシリーズの記事の場合waf-nginx)の中に入って
catで中身を確認して、解説はGeminiに聞いた。
nginx.conf
cat /etc/nginx/nginx.conf
load_module modules/ngx_http_modsecurity_module.so;
worker_processes auto;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
keepalive_timeout 60s;
sendfile on;
resolver 127.0.0.11 valid=5s;
include /etc/nginx/conf.d/*.conf;
}
1. モジュールロード
load_module modules/ngx_http_modsecurity_module.so;
この行は、Nginxにngx_http_modsecurity_module.so
という動的モジュールを読み込むように指示します。このモジュールは、Webアプリケーションファイアウォール(WAF)であるModSecurityとの統合を提供し、Webサーバーをさまざまな攻撃から保護するのに役立ちます。
このモジュールは通常、Nginxインストールパスのmodules
ディレクトリにあります(例:一部のシステムでは/etc/nginx/modules
)。
2. ワーカプロセス
worker_processes auto;
この行は、Nginxが生成するワーカプロセスの数を設定します。ワーカプロセスは、受信したHTTPリクエストと接続を処理します。
値をauto
に設定すると、Nginxはシステムのハードウェアリソース(通常はCPUコアの数)に基づいて、最適なワーカプロセスの数を自動的に決定します。
3. PIDファイル
pid /tmp/nginx.pid;
この行は、NginxがプロセスID(PID)ファイルを書き込む場所を指定します。PIDファイルには、Nginxマスタープロセスのユニーク識別子が含まれます。このファイルは、システムツールやスクリプトによってNginxを管理するために使用されることがよくあります。
この場合、PIDファイルは/tmp/nginx.pid
に書き込まれます。
4. イベント
events {
worker_connections 1024;
}
このブロックは、Nginxがネットワークイベント(受信接続とデータ)を処理する方法に関する設定を定義します。
このブロック内には1行あります。
worker_connections 1024;
この行は、各ワーカプロセスが処理できる同時接続の最大数を設定します。これを1024
に設定すると、各ワーカーは最大1024の同時接続を処理できます。
5. HTTP
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
keepalive_timeout 60s;
sendfile on;
resolver 127.0.0.11 valid=5s;
include /etc/nginx/conf.d/*.conf;
}
このブロックは、NginxのHTTPサーバー機能に特化した設定を含みます。
include /etc/nginx/mime.types;
– MIME(Multipurpose Internet Mail Extensions)タイプを定義するファイルをインクルードします。MIMEタイプはファイル拡張子をコンテンツタイプにマッピングし、ブラウザがWebコンテンツを正しくレンダリングするのに役立ちます。default_type application/octet-stream;
– 特定のタイプが定義されていないファイルのデフォルトMIMEタイプを設定します。application/octet-stream
は、バイナリデータを示す汎用タイプです。keepalive_timeout 60s;
– Nginxがアイドル接続を開いたままにする時間を定義します。この設定により、同じクライアントからの後続のリクエストは60秒以内に既存の接続を再利用でき、パフォーマンスが向上します。sendfile on;
– Nginxがオペレーティングシステムのファイル転送メカニズムを活用して、ディスクからクライアントにファイルを効率的に転送できるようにする技術を有効にします。resolver 127.0.0.11 valid=5s;
– Nginxがホスト名を解決するために使用するDNSリゾルバーを定義します。ここでは127.0.0.11
(典型的な設定ではない可能性があります)に設定され、5秒の検証タイムアウトを指定します。include /etc/nginx/conf.d/*.conf;
–/etc/nginx/conf.d
ディレクトリにある.conf
拡張子を持つすべての設定ファイルをインクルードします。これらのファイルには、Nginxが特定のドメインやURL
プロセスとはプログラムで書いたコードが実際に仕事をしている実態という認識で私はいます。
ワーカプロセスを、このお名前.comVPSシリーズの最終形態で実際に動いているときに確認した。
ワーカプロセスの数(多分、子プロセスの数)はworker_processes auto;の設定で
CPUコアの数(2個)と同じになっているのかも。
NginxはWAFの入っているコンテナとWordPressの前のコンテナで2つ使っている。
なので2つの親プロセスと、それぞれに子プロセスが2つある。
systemctl status
CGroup: /
├─379 bpfilter_umh
├─docker
略
│ ├─7***Djangoのコンテナのプロセス***9
│ │ ├─1777778 bash
│ │ ├─1777777 python manage.py runserver 0.0.0.0:8006
略
│ ├─15***WAFの入ってるNginxのコンテナ***82
│ │ ├─1777775 nginx: master process nginx -g daemon off;
│ │ ├─1777755 nginx: worker process
│ │ └─1777756 nginx: worker process
略
│ ├─51***WordPressのコンテナ***45
│ │ ├─1777732 php-fpm: master process (/usr/local/etc/php-fpm.conf)
│ │ ├─1777709 php-fpm: pool www
│ │ ├─1777756 php-fpm: pool www
│ │ └─1777796 php-fpm: pool www
略
│ └─08***WordPressの前のNginxのコンテナ***61
│ ├─1777720 nginx: master process nginx -g daemon off;
│ ├─1777701 nginx: worker process
│ └─1777702 nginx: worker process
この各子プロセスが最大1024までのクライアントからのリクエストを
うまくあしらって後段のDjangoのプロセス
(python manage.py runserver 0.0.0.0:800*)とやりとりし
最後はクライアントにレスポンスを送っていると思われる。
ここのしくみはよくわかっていないので知りたいところです。
この辺を調べるとコネクションというキーワードがよく出てくるが、
worckerプロセス絡めた仕組みが知りたいです。
ソケットというのもよく見る。ソケットについては、
後でじっくり読ませていただきたいので見つけたものを貼っておきます。
https://envader.plus/article/27
https://zenn.dev/ganariya/articles/socket-slide-illustration-go-implement
https://qiita.com/Michinosuke/items/0778a5344bdf81488114
https://qiita.com/lymansouka2017/items/2d2e78a37b9f1f8fb3a0
https://qiita.com/MoriokaReimen/items/5c4256ef620499a88bb3
https://www.youtube.com/watch?v=xbAFhCxgEQI
ファイルディスクリプタとソケットの関連がわかる
https://xtech.nikkei.com/it/article/COLUMN/20071031/285990/?rt=nocnt
この辺、いろんな話に脱線してしまって的が定まらないが、
いろいろ見ていくと的が絞れて来る。一つの情報源で決めつけるとなかなか解決しない。
特に最近は生成AIの答えは特に注意が必要だと思っています。
WAFのプロセスがコンテナの中に見当たらない。
モジュールという形で取り込まれているので、
多分、Nginxのワーカーの中に拡張機能みたいな形で入っていると推測する。
WordpressのほうはWordPressの処理の本体のphp-fpmのプロセスも
Nginxのプロセス同様に複数見られた。
1Gのメモリでいっぱい、いろんなことするのが目的で、
アクセス数も少ないので、この辺を調整してメモリ使用量を減らせる可能性もあるが
現段階ではデフォルトのままにした。
Nginxのworckerの話は、
Nginxのアーキテクチャを理解する - QiitaNginxのアーキテクチャについて調べてたことをまとめた。用語のおさらいプロセス(Process)プログラムの実行単位であり、CPU時間単位で割り振られる。状態(ステート)があり、現在処理中…が参考になります。
こちら↓でもいろいろ実験しました。
よく使うところがincludeされている/etc/nginx/conf.d/*.confだと思われる。
cd /etc/nginx/conf.d/
ls
default.conf default.conf.bak jikken1.conf logging.conf modsecurity.conf
default.conf.bakはバックアップ、jikken1.confは自分で書いた設定なので
他の3つを見ていく。
目次へ
modsecurity.conf
cat modsecurity.conf
modsecurity on;
modsecurity_rules_file /etc/modsecurity.d/setup.conf;
modsecurity on;はmodsecurityをon/offできそうだが、
ここで変更して、この設定を反映させるのが大変で断念した。
dockerでupやrestartをするとデフォルト値で上書きされてしまってうまくいかなかった。
コンテナの中に入ってNginx単独で設定を更新させる方法だとうまくいくかもしれません。
systemctlを使う方法は私が使っているコンテナはalpineなので
確かデフォルトで入ってなかったような気がしたので断念しました。
docker-compose.ymlのenvironment:でOFFさせることもできるが。
offするのはWordPressを使うときが多かったのでルール追加でoffせずに使うことができた。
modsecurity_rules_file /etc/modsecurity.d/setup.conf;
はmodsecurityのルールを設定していると思われる。
cat /etc/modsecurity.d/setup.conf
# Note: the plugin rules will be uncommented when the container starts,
# depending on whether the respective files exist. This works around
# the issue that ModSecurity doesn't support optional includes on NGiNX.
# Allow custom rules to be specified in:
# /opt/modsecurity/rules/{before,after}-crs/*.conf
Include /etc/modsecurity.d/modsecurity.conf
Include /etc/modsecurity.d/modsecurity-override.conf
Include /etc/modsecurity.d/owasp-crs/crs-setup.conf
Include /etc/modsecurity.d/owasp-crs/plugins/*-config.conf
Include /etc/modsecurity.d/owasp-crs/plugins/*-before.conf
Include /etc/modsecurity.d/owasp-crs/rules/*.conf
Include /etc/modsecurity.d/owasp-crs/plugins/*-after.conf
こちらもこのファイルを変更したり、*.confをincludeさせて設定しようとしたが
dockerでupやrestartをすると上書きされてしまってうまくいかなかった。
なのでルール追加でやろうと思います。
目次へ
default.conf
cat default.conf
# Nginx configuration for both HTTP and SSL
server_tokens off;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 8080 default_server;
server_name localhost;
略}
server {
listen 8443 ssl;
server_name localhost;
略}
後半はデバッグ用(ポート8080、8443)の設定のようなので省略。
1. Server Tokensの無効化
server_tokens off;
この行は、Nginxサーバーが送信するHTTPヘッダーからServer
とX-Powered-By
トークンを削除します。これらのトークンは、サーバーのバージョンやソフトウェアスタックに関する情報を公開します。攻撃者はこの情報を利用して、サーバーの脆弱性を特定し、攻撃を仕掛ける可能性があります。
2. HTTP Upgradeヘッダーの処理
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
このブロックは、Upgrade
HTTPヘッダーに基づいて、接続を維持するか閉じるかを制御します。
default upgrade;
–Upgrade
ヘッダーが存在する場合は、接続を維持します。'' close;
–Upgrade
ヘッダーが存在しない場合は、接続を閉じます。
この設定は、WebSocketsなどの新しいプロトコルをサポートする一方で、不要な接続を閉じ、リソースを節約するのに役立ちます。
logging conf
cat logging.conf
# The "combined" log format is predefined
log_format main '$realip_remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
access_log /dev/null combined;
error_log /var/log/nginx/error.log warn;
1. ログフォーマットの定義
log_format main '$realip_remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
- この行は、”main”という名前のログフォーマットを定義します。
- このフォーマットは、各ログエントリに以下の情報を含みます:
$realip_remote_addr
: クライアントのIPアドレス$remote_user
: リクエストを行ったユーザー名(認証されている場合)$time_local
: ローカル時間形式でのアクセス時間$request
: リクエストされたURLとHTTPメソッド$status
: HTTPステータスコード$body_bytes_sent
: クライアントに送信されたレスポンスボディのサイズ$http_referer
: リクエスト元ページのURL$http_user_agent
: クライアントのユーザーエージェント$http_x_forwarded_for
: クライアントのIPアドレス(プロキシ経由の場合)
2. アクセスログの設定
access_log /var/log/nginx/access.log main;
access_log /dev/null combined;
- 最初の行は、
/var/log/nginx/access.log
ファイルにアクセスログを記録し、そのログフォーマットを “main” に設定します。 - 2行目は、デフォルトの “combined” ログフォーマットでのログ記録を無効化します (nullデバイスに送信)。
3. エラーログの設定
error_log /var/log/nginx/error.log warn;
- この行は、
/var/log/nginx/error.log
ファイルにエラーログを記録し、ログレベルを “warn” に設定します。このログレベルでは、警告レベル以上のエラーが記録されます。
access.logをホスト(SSH接続の場合Vsコードの左のツリー)にコピーし中身を見た。
sudo docker cp コンテナID:/var/log/nginx/access.log ./
ダウンロードしてサイズを見たら36kb
NGINX Access Logs and Error Logsにlogの使い方が書いてありそう。
ここ↓は分かりやすい。"combined" ログフォーマットについても書かれている。
https://beyondjapan.com/blog/2024/02/nginx-access-log/
NGINX Access Logs and Error Logsを見ると
各serverのlocationにaccess_logやerror_logを記述するように書かれている。
デフォルトのままだとクライアントのアクセスログが何も入ってないので
locationに書くとクライアントのアクセスログが書かれるのかもしれない。
erroe.logも見ているものと違った。
しかし、
sudo docker logs コンテナID -f 2>/dev/null
で表示されるログやエラーと内容が全然違う。
また、docker logsで表示されるものは、どこからきているのか。
この辺は次項。
目次へ
Docker logs
ログの削除
どうやらdocker logsは上のNginxのログとは別物のようです。
参考:https://www.kagoya.jp/howto/cloud/container/dockerlog/
Dockerでは各コンテナのログが、
以下の場所にJSON形式※で保存されているようなので実際に見ました。
/var/lib/docker/containers/コンテナID/コンテナID-json.log
rootユーザーでないと見れないのでrootにして。
cd /var/lib/docker/containers
lsで見るとコンテナIDが64桁だった。
123456789012*************************************************099
これは
IMAGE ID … イメージが持つ固有のイメージ ID(64桁)
ショート ID (12桁)があるみたい。(64桁の最初の12桁と同じだった。)
docker psで表示されるのは12桁のほうみたいです。例123456789012
cd 123456789012*************************************************099
lsすると確かに
123456789012*************************************************099-json.log
がありました。
tail 123456789012*************************************************099-json.log
で最後の数行の中身を表示してみて、その中の1個がこれです。
私がログはどこにあるのだろうと探していたのはこれでした。
{"log":"123.456.7.89 - - [22/Mar/2024:14:11:38 +0900] \"GET / HTTP/1.0\" 200 274263 \"-\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36\" \"-\"\n","stream":"stdout","time":"2024-03-22T05:11:38.995785392Z"}
ファイルサイズを確認すると
du -ch ファイル名 | grep total$ | cut -f 1
9.0Mでした。
var/lib/dockerに移動して
du -ah containersを実行すると各コンテナの使用しているサイズが分かります。
コンテナIDがついているから、コンテナを削除すればlogも削除されると思います。
どこかにアクセスして
sudo docker logs waf-nginx -f 2>/dev/nullをしても何も表示されない。
sudo docker logs waf-nginx -f 1>/dev/nullでは
error from daemon in stream: Error grabbing logs: open /var/lib/docker/containers/123456789012*************************************************099/123456789012*************************************************099-json.log: no such file or directory
touch 123456789012*************************************************099-json.log
で空のファイルを作った。
パーミッションを確認すると
-rw-r--r--
元からあるほかのファイルは
-rw-r-----
chmod 640 123*-json.logで同じにしておいた。
そのまま、アクセスしたりしてログに記録されるかみたがダメだった。
結局コンテナを削除してsudo docker compose up -dしたら復活した。
以下はファイルのサイズを0にできたが、上と同じでlogは増えない。
sudo truncate -s 0 /var/lib/docker/containers/xxxxx/yyyyy-json.log
参考:https://qiita.com/TachiTech/items/ae4f5da70cadc392516e
しかしsudo docker restart コンテナ名でリスタートしたらlogがでるようになった。
多分、ファイルを消すほうもrestartすればいいかも。
またログのフォーマットを変更したかったが分からなかった。
長くて見にくい場合はログの表示形式を変更すると見やすくなる。
方法→https://kikuichige.com/24890/#toc5
目次へ
ログのローテート設定
2024/5/27ディスクの使用量を確認した。
df -h
Filesystem Size Used Avail Use% Mounted on
udev 466M 0 466M 0% /dev
tmpfs 97M 1.6M 95M 2% /run
/dev/vda1 78G 22G 56G 29% / 下で対策したことで→78G 19G 59G 25% /
この記事↓で測ったときは7.6Gだった。
sudo du -sh /*
これにより、ルートディレクトリ直下の各ディレクトリの合計サイズが表示されます。
1番大きかったのが18G /var
sudo find /var -type f -exec du -h {} + | sort -rh | head -n 10
これにより、/varディレクトリ内の上位10個の大きなファイルが表示されます。
2.8G /var/lib/docker/containers/略-json.log
Nginxのコンテナのlogが2.8Gあった。
前項でやったようにlogを削除したら15G /varに減った。
ログが出なかったけど
sudo docker restart コンテナ名でリスタートしたらlogがでるようになった。
こちら↓を参考にローテートの設定をします。
https://it-web-life.com/docker_container_log_clear/
sudo vi /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {"max-size": "10m", "max-file": "3"}
}
ESC→:wq
sudo cat /etc/docker/daemon.json
で確認
sudo docker ps
で動いているコンテナを確認しておく。
以下のdockerをstopしてstartしたときstopしたままのコンテナがあった。
設定を更新する。
sudo docker-compose stop
sudo systemctl restart docker
sudo docker-compose start
stopしたままのコンテナがあるので
sudo docker ps -a
で直前に動いていたものと比較してstopしてるものは
sudo docker start コンテナ名
で起動。
ログは出力するのは確認できた。
しかしsudo docker logs コンテナ名 -fで
リアルタイムのログ出力が出なくなったが対象のコンテナを再起動したら出力するようになった。
sudo docker restart コンテナ名
でもChatGptに聞いた
sudo docker inspect --format='{{json .HostConfig.LogConfig.Config}}' コンテナ名
でコンテナにログオプションが正しく設定されているか確認すると
{"max-size":"10m","max-file":"3"}でなければならないのに{}だった。つまり空だった。
コンテナを削除して新しく作ったら{"max-size":"10m","max-file":"3"}になった。
sudo docker stop コンテナID
sudo docker rm コンテナID
sudo docker-compose up -d
1日後
sudo ls -l /var/lib/docker/containers/452略3b
で確認したら
-rw-r----- 1 root root 1631774 May 31 07:49 452略3b-json.log
-rw-r----- 1 root root 10015635 May 31 06:38 452略3b-json.log.1
-rw-r----- 1 root root 10002497 May 31 05:15 452略3b-json.log.2
10Mを超えるとlog.1、log.2にコピーされてる。
sudo docker logs~ で表示するのは.logだけみたいです。
log.1をcatで見たけどjsonのままで見にくいです。
また10Mだと1時間でいっぱいになってる。
ブログのアクセス1時間に1回あればいいほうなのに、
ほとんど攻撃のログとログの内容に無駄な項目が多くて、すぐいっぱいになるのだと思います。
ということで"max-size"を "200m"に変更しました。
このまま様子見してみます。
うまくいかないようだったら
手動消去はうまくいっているので、溜まったら手動消去かな。
ディスクは余裕があるが、問題になるのは、この巨大logファイルに対して
うっかりgrepで検索してサーバーのメモリがパンパンになってしまうことです。
なので大きくならないうちに手動消去するか
logを検索するときは部分的にダウンロードしてローカルでVsコードで検索しようと思います。
こちら↓を参考にローテートの設定をします。
https://it-web-life.com/docker_container_log_clear/
sudo vi /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {"max-size": "10m", "max-file": "3"}
}
ESC→:wq
sudo cat /etc/docker/daemon.json
で確認
sudo docker ps
で動いているコンテナを確認しておく。
以下のdockerをstopしてstartしたときstopしたままのコンテナがあった。
設定を更新する。
sudo docker-compose stop
sudo systemctl restart docker
sudo docker-compose start
stopしたままのコンテナがあるので
sudo docker ps -a
で直前に動いていたものと比較してstopしてるものは
sudo docker start コンテナ名
で起動。
ログは出力するのは確認できた。
しかしsudo docker logs コンテナ名 -fで
リアルタイムのログ出力が出なくなったが対象のコンテナを再起動したら出力するようになった。
sudo docker restart コンテナ名
でもChatGptに聞いた
sudo docker inspect --format='{{json .HostConfig.LogConfig.Config}}' コンテナ名
でコンテナにログオプションが正しく設定されているか確認すると
{"max-size":"10m","max-file":"3"}でなければならないのに{}だった。つまり空だった。
コンテナを削除して新しく作ったら{"max-size":"10m","max-file":"3"}になった。
sudo docker stop コンテナID
sudo docker rm コンテナID
sudo docker-compose up -d
このまま様子見してみます。
うまくいかないようだったら
手動消去はうまくいっているので、溜まったら手動消去かな。
ディスクは余裕があるが、問題になるのは、この巨大logファイルに対して
うっかりgrepで検索してサーバーのメモリがパンパンになってしまうことです。
なので大きくならないうちに手動消去するか
logを検索するときは部分的にダウンロードしてローカルでVsコードで検索しようと思います。
du -ah /var/lib/docker/containers
でサイズを見たらみんな小さかったので15Gは、ログ以外みたいです。
du -h /var | sort -rh | head -n 10
で/varの上位10個表示
13G /var/lib
12G /var/lib/docker
9.5G /var/lib/docker/overlay2 ←これが大きい
2.4G /var/lib/docker/volumes
/var/lib/docker/overlay2
が多いのでChatGPTに聞いた。↓
Dockerイメージとかが入っているなら、こんなものか。
/var/lib/docker/overlay2の役割(ChatGPT談)
- ディレクトリの内容:
/var/lib/docker/overlay2
には、Dockerイメージとコンテナのファイルシステムのレイヤが格納されています。各レイヤはディレクトリとして表され、その中にはファイルシステムの差分が保存されています。 - レイヤの構造:
- 各レイヤは一意のIDを持ち、そのIDに対応するディレクトリが
/var/lib/docker/overlay2
内に作成されます。 - これらのレイヤを重ね合わせることで、コンテナの完全なファイルシステムが構成されます。
- 各レイヤは一意のIDを持ち、そのIDに対応するディレクトリが
ということでログは、多分増えなくなったと思う。
サイズの大きい/var/lib/docker/overlay2はイメージを増やさなければ増えないので
今回やったログの増殖を抑えておけばディスクは、今後は、そんなに増えないと思われる。
また、私の環境ではOSは用意されているのでymlにローテートの設定を書けない。
ローテートの設定が消えるのはVPSを再起動したときだと思うので
そのときは、またviで書くのを忘れないようにしようと思います。
目次へ
所感
ログがどうなっているのか疑問だったがDocker独自のログがあることが分かってすっきりした。
設定を反映させたはずなのに反映されてないときは
書く場所が違うか、
docker compose upやrestartなどによるデフォルトで上書きされている場合が多いようです。
今回、まとめたことで、このように原因が少しわかった。
生成AI(Gemini)を使って解説文を書いたが、役に立つときもあれば
全く的外れな場合もあり、回答を信じて突き進むと、えらい遠回りになることを改めて認識した。
目次へ
イチゲをOFUSEで応援する(御質問でもOKです)Vプリカでのお支払いがおすすめです。
MENTAやってます(ichige)
コメント