タグ: Linux Page 7 of 26

Redmine5.1のコードブロックにコピーボタンを実装。

概要

性質上、RedmineのチケットやWikiにコードを貼り付ける機会は多々あると思います。

と、コードで囲まれている部分の脇にコピーボタンをつけて、コピペの手間を軽減します。

確認した環境

Redmine 5.1

さっくりとした手順

  1. 設定ファイル(4個分)のバックアップを取ります。
  2. パッチファイル(4個分)を作成します。
  3. 4つのファイルに対してパッチを適用します。
  4. Webサービスを再起動します。

ファイルのバックアップ

  • app/views/journals/new.js.erb
sudo cp -pi /redmine/root/directory/app/views/journals/new.js.erb /path/to/backup/directory/new.js.erb.$(date +%Y%m%d)
  • app/views/journals/update.js.erb
sudo cp -pi /redmine/root/directory/app/views/journals/update.js.erb /path/to/backup/directory/update.js.erb.$(date +%Y%m%d)
  • public/javascripts/application.js
sudo cp -pi /redmine/root/directory/public/javascripts/application.js /path/to/backup/directory/application.js.erb.$(date +%Y%m%d)
  • public/stylesheets/application.css
sudo cp -pi /redmine/root/directory/public/stylesheets/application.css /path/to/backup/directory/application.css.erb.$(date +%Y%m%d)

/redmine/root/directoryは、自分の環境に合わせます。(var/lib/redmineなど)

また、/path/to/backup/directtoryは任意のバックアップディレクトリを指定します。

ファイルのバックアップ確認

  • app/views/journals/new.js.erb
diff -u /path/to/backup/directory/new.js.erb.$(date +%Y%m%d) /redmine/root/directory/app/views/journals/new.js.erb
  • app/views/journals/update.js.erb
diff -u /path/to/backup/directory/update.js.erb.$(date +%Y%m%d) /redmine/root/directory/app/views/journals/update.js.erb
  • public/javascripts/application.js
diff -u /path/to/backup/directory/application.js.erb.$(date +%Y%m%d) /redmine/root/directory/public/javascripts/application.js
  • public/stylesheets/application.css
diff -u /path/to/backup/directory/application.css.erb.$(date +%Y%m%d) /redmine/root/directory/public/stylesheets/application.css

それぞれ、バックアップしたファイル → バックアップ元ファイルで差分(diff)を取り、エラーがないこと(差分が無いこと)でバックアップを確認します。

パッチファイル作成

cd /hoge && pwd

任意の作業ディレクトリに移動します。

  • new.js.erb.patch
tee new.js.erb.patch > /dev/null << 'EOF'
--- new.js.erb
+++ new.js.erb.patch
@@ -11,3 +11,4 @@
 $('#issue_private_notes').prop('checked', true);
 <% end %>

+addCopyButtonToPreTag();
EOF
  • update.js.erb.patch
tee update.js.erb.patch > /dev/null << 'EOF'
--- update.js.erb
+++ update.js.erb.patch
@@ -7,6 +7,7 @@
   $("#journal-<%= @journal.id %>-notes").replaceWith('<%= escape_javascript(render_notes(@journal.issue, @journal, :reply_links => authorize_for('issues', 'edit'))) %>');
   $("#journal-<%= @journal.id %>-notes").show();
   $("#journal-<%= @journal.id %>-form").remove();
+  addCopyButtonToPreTag();
   var journal_header = $("#change-<%= @journal.id %>>div.note>h4.note-header");
   var journal_updated_info = journal_header.find("span.update-info");
   if (journal_updated_info.length > 0) {
EOF
  • application.js.patch
tee application.js.patch > /dev/null << 'EOF'
--- application.js
+++ application.js.patch
@@ -1123,6 +1123,49 @@
   });
 });

+function addCopyButtonToPreTag() {
+  $('.wiki .copy-contents').remove();
+  var copyContents =
+    $("<div class='copy-contents'>").append(
+      $("<button class='code-copy-button' title='Copy' onclick='copyText($(this).parent().next(\"pre\"), $(this).parent());'>")
+      .append("<span class='icon-only icon-copy'>")
+    );
+  $('.wiki pre').before(copyContents);
+  $('.code-copy-button[title]').tooltip({
+      show: {
+        delay: 400
+      },
+      position: {
+        my: "center bottom-5",
+        at: "center top"
+      }
+  });
+}
+function copyText(target, copyEl) {
+  // Selecting strings in 2 ways for cross-browser support
+  // 1. Use select();
+  copyEl.append("<textarea class='tmp'>");
+  var tmp = copyEl.find('.tmp');
+  tmp.val(target.text());
+  tmp.select();
+  // 2. Use createRange();
+  var range = document.createRange();
+  range.selectNode(target[0]);
+  window.getSelection().removeAllRanges();
+  window.getSelection().addRange(range);
+  // Copy and Cleanup
+  var copied = document.execCommand('copy');
+  window.getSelection().removeAllRanges();
+  tmp.remove();
+  // Show copied messages
+  if (copied){
+    copyEl.append("<div class='copied-message'>Copied.</div>");
+    var copiedMessage = copyEl.find('.copied-message');
+    copiedMessage.show();
+    copiedMessage.fadeOut('slow', function() { $(this).remove(); });
+  }
+}
+
 function inlineAutoComplete(element) {
     'use strict';

@@ -1241,6 +1284,7 @@
 $(document).ready(setupAttachmentDetail);
 $(document).ready(setupTabs);
 $(document).ready(setupFilePreviewNavigation);
+$(document).ready(addCopyButtonToPreTag);
 $(document).ready(setupWikiTableSortableHeader);
 $(document).on('focus', '[data-auto-complete=true]', function(event) {
   inlineAutoComplete(event.target);
EOF
  • application.css.patch
tee application.css.patch > /dev/null << 'EOF'
--- _old/application.css
+++ application.css.patch
@@ -449,6 +449,30 @@
   height: initial;
 }

+.copy-contents {
+  position: relative;
+}
+.copy-contents .code-copy-button {
+  position: absolute;
+  display: flex;
+  right: -13px;
+  top: 0px;
+  border: none;
+  background-color: transparent;
+}
+.copy-contents .copied-message {
+  position: absolute;
+  display: flex;
+  right: -13px;
+  top: 20px;
+  display: none;
+  color:#505050;
+}
+.copy-contents .tmp {
+  position: fixed;
+  left: 200%;
+}
+
EOF

パッチファイルの所有権変更

  • 変更前確認
ls -l *.patch

上記、作成した4つのパッチがあり、作業アカウントが所有者になっていることを確認

  • 所有者変更
sudo chown www-data:www-data *.patch

→ redmineの実行ユーザを指定します。

  • 変更後確認
ls -l *.patch

4つのパッチの所有者がredminの実行ユーザーになっていることを確認

パッチ適用

  • app/views/journals/new.js.erb にパッチ適用
sudo -u www-data patch /redmine/root/directory/app/views/journals/new.js.erb < /hoge/new.js.erb.patch
  • /app/views/journals/update.js.erb にパッチ適用
sudo -u www-data patch /redmine/root/directory/app/views/journals/update.js.erb < /hoge/update.js.erb.patch
  • public/javascripts/application.js にパッチ適用
sudo -u www-data patch /redmine/root/directory/public/javascripts/application.js < /hoge/application.js.patch
  • public/stylesheets/application.css にパッチ適用
sudo -u www-data patch /redmine/root/directory/public/stylesheets/application.css < /hoge/application.css.patch

それぞれ、ファイルがある箇所を絶対パスで実行してください。特に、application.jsとapplication.cssの取り違えに注意ください。

パッチが上手くいかないときは?

エディタなどを利用して、

https://www.redmine.org/attachments/25075

の内容を、追記してください。

diffによるパッチ適用確認

  • app/views/journals/new.js.erb
diff -u /path/to/backup/directory/new.js.erb.$(date +%Y%m%d) /redmine/root/directory/app/views/journals/new.js.erb
  • app/views/journals/update.js.erb
diff -u /path/to/backup/directory/update.js.erb.$(date +%Y%m%d) /redmine/root/directory/app/views/journals/update.js.erb
  • public/javascripts/application.js
diff -u /path/to/backup/directory/application.js.erb.$(date +%Y%m%d) /redmine/root/directory/public/javascripts/application.js
  • public/stylesheets/application.css
diff -u /path/to/backup/directory/application.css.erb.$(date +%Y%m%d) /redmine/root/directory/public/stylesheets/application.css

として、作成したパッチファイルと同じかを確認します。

設定反映

  • Webサービス再起動
sudo systemctl restart apache2.service
  • Webサービス再起動確認
systemctl status apache2.service

active(running)を確認します。

上記適用後、

  1. コードブロックにコピーボタンがあり、
  2. ボタンクリックで内容がクリップボードに保存される

ことが確認できれば設定完了です。

MySQLのデータベースを暗号化してバックアップするスクリプト。

概要

以前も作成していた、MySQLのデータベースのバックアップを自動的に取得するスクリプトを少々リファインさせました。

変数指定により、複数のDBを任意にバックアップできます。

スクリプトの動き

  • サーバ内にあるDBのバックアップを取得し、暗号化して指定ディレクトリに保存します。
  • 複合化のパスワードはスクリプトが自動生成し、暗号化と同時に別ディレクトリに保存します。
  • cronの自動実行を前提としているため、古い暗号化ファイルと複合化のパスワードは一定期間後に削除を行います。

動作を確認したサーバ

  • Ubuntu 24.04
  • MySQL 8.0.39

前準備

アカウントファイル

任意のディレクトリに、db_account.txtといった、DBにユーザー名とパスワードを記したファイルを作成しておきます。

[client]
user = db_user
password = "password"
  • パーミッションは400にします。(chmod 400 db_account.txt)
  • 取り扱いは慎重に行ってください。

バックアップDBの格納先

  1. 冗長性があり
  2. 機密性が保たれる

場所を指定してください。筆者はクラウドストレージ(wasabi)をマウントしています。

パスワードの格納先

/home/hoge/dbrestore_password のように、複合化のパスワードを格納するディレクトリを作っておきます。
こちらも運用に合わせ、適切に保管ができる場所を指定します。

それぞれ、スクリプト実行アカウントがアクセス/実行できるものにします。

スクリプト

スクリプト内容

  • スクリプトファイル名(例)
    • mysql_db_backup.sh

変数などは間違いの無いように指定ください。

#!/bin/bash

## 変数ここから ##
# $HOMEの変数を指定します。
HOME_DIR="/home/hoge"
# SQLをバックアップするディレクトリ(保管先)を指定します。運用に合わせて指定ください。
backup_dir="/path/to/backup/directory"
# 保持するバックアップの世代を日数で指定します。
keep_days=7
# ファイルに付与する日付/作業ディレクトリ名/バックアップファイル名を指定します。
current_date=$(date +%Y%m%d)
backup_name="backup_mysql_${current_date}"
zip_file="backup_mysql.${current_date}.zip"
# アカウントファイルを指定します。運用に合わせて指定ください。
credentials_file="${HOME_DIR}/script/config/db_account/db_account.txt"
# パスワードを記録するファイル名を指定します。運用に併せてして指定ください。
password_dir="${HOME_DIR}/dbrestore_password"
password_file="${password_dir}/db-restore.${current_date}.txt"
# redmineのデータベース名を指定します。
database_name=database
# バックアップ時に指定するオプションを指定します。
options="--defaults-extra-file=$credentials_file --no-tablespaces --single-transaction"
# バックアップファイルのパターンを指定します。
backup_file_pattern="backup_mysql.*.zip"
# パスワードファイルのパターンを指定します。
password_file_pattern="*restore*.txt"
## 変数ここまで ##

## 処理ここから ##

# 1.アカウントファイルのパーミッションが400かどうかチェックします。
# 400以外は処理そのものを終了します。
permissions=$(stat -c "%a" "$credentials_file")
if [ "$permissions" != "400" ]; then
echo "アカウントファイルのパーミッションは400である必要があります。"
exit 1
fi

# 2.一時的なバックアップディレクトリを作成します。
mkdir "${backup_dir}/${backup_name}"

# 3. mysqldumpを実行してデータベースのバックアップを取ります。
mysqldump $options -h localhost $database_name > "${backup_dir}/${backup_name}/${backup_name}.sql"

# 4. パスワードによる暗号化を実施します。
password=$(openssl rand -base64 12)
cd "${backup_dir}/${backup_name}"
zip -r "${backup_dir}/${zip_file}" -P "$password" .
cd -

# 5. 一時的なバックアップディレクトリを削除します。
rm -rf "${backup_dir}/${backup_name}"

# 6. 解凍パスワードを指定ディレクトリに保存します。
echo $password > $password_file

# 7.パスワードの読み取り権限を600に変更します。
chmod 600 $password_file

# 8. 保持期間より古いバックアップファイルを削除します。
find "$backup_dir" -name "$backup_file_pattern" ! -type f -newermt "${keep_days} days ago" -delete
find "$password_dir" -name "$password_file_pattern" ! -type f -newermt "${keep_days} days ago" -delete

## 処理ここまで ##

作成後、

chmod +x mysql_db_backup.sh

で実行権を付与します。

スクリプトの動かし方

スクリプトの動き

./mysql_db_backup.sh

として実行すると、

  1. 変数で指定したアカウントファイルを読み込み、mysqldumpでバックアップを取ります。
  2. バックアップ作成後、パスワード付きzipファイルに圧縮します。
  3. 圧縮されたバックアップと複合化のためのテキストファイルを変数で指定したディレクトリに格納します。
  4. 変数で指定した期日が過ぎたバックアップファイルと複合化のためのテキストファイルは自動的に削除されます。

バックアップDBの解凍

unzip backup_mysql.yyyymmdd.zip

とすると、パスワードを尋ねられます。

db-restore.yyyymmdd.txtに表示された文字列を入力します。

または、

unzip -P $(cat /path/to/password_file.txt) /path/to/zip_file.zip -d /path/to/output_directory

でファイルを直接引数にして解凍することもできます。

cronの指定

動作を確認したら、

crontab -e -u hoge

でcron編集画面を出し、

0 2 * * * /home/hoge/script/directory/mysql_db_backup.sh

などとして指定すれば、日次のDBバックアップを取得可能です。

Ubuntu 20.04で動いていた Redmine 4.2のデータを Ubuntu 24.04上のRedmine 5.1に移行。

概要

Redmine 4.2 を動かしている Ubuntu 20.04 が2025年4月にEOLを迎えるため、Redmine 5.1 (Ubuntu 24.04)にリプレースをしました。

最初に

  • 「この手順で上手くいった」という筆者のメモ書きです。
  • Rubyのバージョン違いなどで動かないプラグインがいくつかあります。代替手段は別途考慮してください。

動かなかったプラグインとワークアラウンド

redmine_knowledgebase です。

5.xで動くフォークを試したものの、rubyの兼ね合いで動きませんでした。

そのため、記事をこのBookStackに移行させています。

環境

移行前環境

  • Ubuntu 20.04
  • Redmine 4.2
  • Ruby 2.7
  • Apache 2.4
  • MySQL 8.0.39

移行後環境

  • Ubuntu 24.04
  • Redmine 5.1
  • Ruby 3.2
  • Apache 2.4
  • MySQL 8.0.39

さっくりとはならない手順

  1. 【移行先】空のRedmineを構築します。
  2. 【移行元】DBのダンプファイルを作成し、移行先に転送します。
  3. 【移行元】ファイル、メール設定ファイルなどを移行先に転送します。
  4. 【移行先】DBをリストアします。
  5. 【移行先】gemのアップデートを行います。
  6. 【移行先】移行元で稼働していたプラグインを入れていきます。
  7. 【移行先】動作確認を行います。
  8. 【移行元】移行元のredmineを停止します。
  9. 【移行先】必要に応じてDNSの切り替えと移行先のサイト設定を行います。

【移行先】Redmineを構築していきます。

  • こちらのページに従って、移行先のUbuntu 24.04サーバに、Redmineを構築していきます。
  • 構築したてのRedmineのアカウントとパスワードは admin /admin なので、仮パスワードを設定しておきます。

【移行元】DBのダンプファイルを作成して転送します。

  • ディレクトリ移動
cd /hoge && pwd

任意のディレクトリを指定します。

  • ダンプファイル取得
mysqldump -h localhost -u redmine -p --no-tablespaces --single-transaction redmine > redmine_backup.$(date +%Y%m%d).sql

ユーザー名(-uの後)とDB名(>の前)は自分の環境に合わせます。

  • ダンプファイル内容確認
view redmine_backup.$(date +%Y%m%d).sql

SQL文が平文で読めることを確認します。

作成後、任意の安全な方法で移行先に転送します。

【移行元】各種ファイルを転送します。

以下のディレクトリやを任意の安全な方法で転送します。

/path/to/redmine/root/directory配下(移行前のRedmineルートディレクトリ)の

  • files ディレクトリ(添付ファイル一式)
  • config/configuration.yml ファイル(メール設定がある場合)
  • public/thme/配下のテーマディレクトリ

これらのファイルは転送後、移行後のRedmineに対応するディレクトリ/ファイルに、そのまま上書きます。

【移行先】DBのリストアを行います。

  • ダンプファイル格納ディレクトリに移動
cd /hoge && pwd
  • ダンプファイル確認
ls -l redmine_backup.$(date +%Y%m%d).sql

ファイルがあることを確認します。

  • DBリストア
mysql -h localhost -u redmine -p redmine < redmine_backup.$(date +%Y%m%d).sql

このときのパスワードは、「移行先のRedmineのDBユーザのパスワード」です。

  • Webサービス再起動
sudo systemctl restart apache2.service

Webサービス再起動後に

  1. 移行後のRedmineサイトが移行元と同じ見た目であること
  2. ログインできること
  3. プロジェクト一覧が見られること

までは確認しましたが、チケットを確認しようとすると

500 internal server error

が発生し、閲覧も編集もできない事態が発生しました。

【移行先】gemのアップデートを行います。

こちらの、チケット表示でinternal server errorを解消するため、

Has 500 Internal Server Error when upgrade 4.1.1 to 4.2.7, please help.
https://www.redmine.org/boards/2/topics/67435

この手順を追加します。

  • Redmineのルートディレクトリに移動
cd /path/to/redmine/root/directory && pwd

自分の環境に合わせます。

  • gem update
 sudo gem update

しばらく時間がかかります。

  • Webサービス再起動
sudo systemctl restart apache2.service

gemのアップデート後、今度はエラーが出ないことを確認です。

【移行先】各種プラグインを入れていきます。

移行前に動いていたプラグインを確認しながら、プラグインを入れていきます。

DBマイグレーション中にrake abortedが出た場合は、そのプラグインはRedmine/Rubyのバージョンが合わないので、動かないプラグインです。

【移行先】動作環境を行っていきます。

プラグインを入れながら、既存の機能(別のユーザーがログインできるか、アクセス権は合っているか、チケットの作成や更新ができるか)などを見ていきます。

【移行元】稼働していたRedmineサイトを停止します。

移行元のサーバにSSHログインし、

sudo a2dissite redmine.conf

redmine.confは自分の環境に合わせます。

で設定ファイルを無効化後、

  • Webサービス再起動
sudo systemctl restart apache2.service

として、稼働していた移行元のサイトにアクセスできないことを確認します。

【移行先】必要に応じてDNSの切り替えと移行先のサイト設定を行います。

これは、移行元でも同じURL(ドメイン)を使いたい場合の措置です。

  • DNS向き先変更

DNを利用して、ドメインを移行先のIPアドレスへと付け替えます。

各種DNSサービスによって異なるので、そちらを参照してください。

  • apacheバーチャルサイトの設定変更

/etc/apaches/site-available/redmine.conf (自分の環境に合わせます)

の以下の箇所を編集していきます。

<VirtualHost *:80>
servername hoge.example.com
# 今まで動いていたドメイン名

<VirtualHost *:443>
    ServerName hoge.example.com
# 今まで動いていたドメイン名
  • 構文確認
sudo apache2ctl configtest

Syntax OKを確認します。

  • Webサービス再起動
sudo systemctl restart apache2.service

切り替え後

  1. 前と同じドメインで、新しいRedmineサイトにログインできる
  2. (プラグインが廃止していなければ)同じ機能を使える

ことを確認できれば作業完了です。

移行後の処理

  1. DB移行時に利用したダンプファイルを削除します。
  2. 時期を見て旧サーバのデータの削除を行います。

Ubuntu 24.04に設定したMod_Securityでファイルアップロードエラーを退避。

事象

Redmineでクリップボードの画像を貼り付けようとした際、画像のアップロードができませんでした。

そこで、エラーログを確認したところ、以下のメッセージが表示されました。

ModSecurity: Request body no files data length is larger than the configured limit (131072). [hostname "redmine-url"] [uri "/uploads.js"] [unique_id "Zv0wom0FwSak1tXDUgFRLAAAAAw"], referer: https://redmine-url/issues/3

環境

  • Ubuntu 24.04
  • Apache 2.4
  • Mod_Security 2
  • Redmine 5.1

確認したこと

バーチャルサイトのMod_Securityの設定

SecRequestBodyInMemoryLimit 524288000
SecRequestBodyLimit 524288000

と、50MBはアップロードできるようになっています。

Redmineでアップロード可能なファイル容量

管理> 設定 > ファイルの「添付ファイルサイズの上限でも`51200KB`を確認。

Mod_Security Confファイル確認

`cat /etc/modsecurity/modsecurity.conf |grep Limit` の結果、以下を確認しました。

SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyInMemoryLimit 131072
SecRequestBodyLimitAction Reject
SecRequestBodyJsonDepthLimit 512
SecPcreMatchLimit 100000
SecPcreMatchLimitRecursion 100000
SecResponseBodyLimit 524288
SecResponseBodyLimitAction ProcessPartial

原因

`/etc/modsecurity/modsecurity.conf`の設定がバーチャルサイトの設定にオーバーライドしたため、こちらの設定が上書きされた模様です。

これを変更していきます。

対応手順

ファイルバックアップの作成

sudo cp -pi /etc/modsecurity/modsecurity.conf /path/to/backup/directory/modsecurity.conf.$(date +%Y%m%d)

任意のバックアップディレクトリを指定します。

バックアップ作成確認

diff -u /path/to/backup/directory/modsecurity.conf.$(date +%Y%m%d) /etc/modsecurity/modsecurity.conf

バックアップできていること(差分がないこと)を確認します。

sedによる書き換え

sudo sed -i 's/SecRequestBodyNoFilesLimit 131072/SecRequestBodyNoFilesLimit 52428800/; s/SecRequestBodyInMemoryLimit 131072/SecRequestBodyInMemoryLimit 52428800/; s/SecRequestBodyLimit 13107200/SecRequestBodyLimit 52428800/' /etc/modsecurity/modsecurity.conf

ファイル書き換え確認

diff -u /path/to/backup/directory/modsecurity.conf.$(date +%Y%m%d) /etc/modsecurity/modsecurity.conf

以下の差分を確認します。

-SecRequestBodyLimit 13107200
-SecRequestBodyNoFilesLimit 131072
+SecRequestBodyLimit 52428800
+SecRequestBodyNoFilesLimit 52428800
 
 # Store up to 128 KB of request body data in memory. When the multipart
 # parser reaches this limit, it will start using your hard disk for
 # storage. That is slow, but unavoidable.
 #
-SecRequestBodyInMemoryLimit 131072
+SecRequestBodyInMemoryLimit 52428800

設定反映

sudo systemctl restart apache2.service

書き換え後、無事にファイルのアップロードができることを確認しました。

Mod_Securityの連携。(検知した怪しい挙動とtor出口リストをブロックする)

個別に走らせていたスクリプトを1つにまとめました。

前提

  • Apache / Mod_Security導入済み
  • サイトのログローテーションが毎日であること

スクリプト

スクリプトの動き

  1. Tor出口リストを公式からダウンロードします。
  2. このリストからIPアドレスだけを抽出します。
  3. 変数に従ってエラーログからMod_Securityが検知したIPアドレスだけを抽出します。
  4. これらをMod_Securityのnegativelistに入れ、以下のサイトアクセスを丸ごと弾きます。
    • Tor出口リストに登録されているIPアドレス
    • Mod_Securityが検知したIPアドレス
  5. 自分の偽陽性を防ぐため、自分のゲートウェイなどのIPアドレスからのアクセスは除外します。
  6. リストを反映させるためApacheを再起動します。

スクリプト内容

#!/bin/bash
# このスクリプトは、Tor出口ノードリストをダウンロードし、特定のログファイルからIPアドレスを抽出して処理します。
# 使い方: このスクリプトを実行することで、最新のTor出口ノードリストを取得し、ログファイルから抽出したIPアドレスと照合します。
# 結果は指定されたディレクトリに保存され、Apacheが再起動されます。
# cronでの指定例: 毎日午前2時に実行する場合 -> 0 2 * * * /path/to/this_script.sh

# === 変数の定義 ===

# 出口ノードリストのURL
TOR_EXIT_LIST_URL="https://check.torproject.org/exit-addresses"

# ダウンロード先ファイル名
OUTPUT_FILE="/hoge/script/log/node_list/exit_nodes.$(date +%Y%m%d).txt"

# 各サイトのログディレクトリ
log_dirs=("/var/log/bookstack")  # 各サイトのログディレクトリを指定

# エラーログファイル名
log_file="bs_error.log"

# Tor出口ノードリストのディレクトリ
tor_list_dir="/hoge/script/log/sorted_list"  # tor_list.txtのディレクトリを指定

# Tor出口ノードリストのファイル名
tor_list_file="sorted_ip_addresses$(date +%Y%m%d).txt"

# 除外するIPアドレスをファイルで指定
exclude_ips_file="/hoge/script/log/exclude_ips.txt"

# 共通の出力ログディレクトリ
output_log_dir="/hoge/script/log"

# === Tor出口ノードリストのダウンロードと処理 ===

# curlを使用してリストをダウンロード
curl -o "$OUTPUT_FILE" "$TOR_EXIT_LIST_URL" >/dev/null 2>&1

# ダウンロードが成功したかどうかを確認
if [ $? -eq 0 ]; then
echo "Tor出口ノードリストをダウンロードしました。ファイル: $OUTPUT_FILE"
else
echo "ダウンロード中にエラーが発生しました。"
exit 1
fi

# IPアドレスのみを抽出し、ソートして出力
awk '/^ExitAddress/ {print $2}' "$OUTPUT_FILE" | sort -u | tee "$tor_list_dir/$tor_list_file" >/dev/null 2>&1

# === 各サイトのエラーログからIPアドレスを抽出して処理 ===

for log_dir in "${log_dirs[@]}"; do
cd "$log_dir"
awk '/ModSecurity/ && match($0,/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/) { print substr($0, RSTART, RLENGTH) }' "$log_file" | sort -u > "$output_log_dir/suspicious_ip/suspicious_ip.$(date +%Y%m%d)"
done

# 過去のIPアドレスを読み込んで重複を排除し、ファイルに保存
cat "$output_log_dir/suspicious_ip/suspicious_ip."2* | sort -u > "$output_log_dir/suspicious_ip_all.txt"

# 新たにリストに書き起こすと同時に別のログファイルを読み込んで重複を排除し、negativelist.txtに追加
cat "$output_log_dir/suspicious_ip_all.txt" "$tor_list_dir/$tor_list_file" | sort -u > "$output_log_dir/negativelist.txt"

# 除外するIPアドレスをファイルから削除
while IFS= read -r exclude_ip; do
sed -i "/$exclude_ip/d" "$output_log_dir/negativelist.txt"
done < "$exclude_ips_file"

# Apacheを再起動
systemctl restart apache2.service

スクリプトの他に用意するもの

  • exclude_ips.txt
192.168.0.1
nnn.nnn.nnn.nnn

など、エラーログから除外したいIPアドレスのリストを作ります。

  • バーチャルサイトの編集

/etc/apache2/site-available 配下のバーチャルサイトの設定ファイルを以下のように追記します。

    # Mod_Security設定
    SecRuleEngine On

    SecRule REMOTE_ADDR "@pmFromFile negativelist.txt" "phase:1,id:2,deny,msg:'Negativelisted IP address'"
  • スクリプトが生成したファイルへのシンボリックリンク
sudo ln -sf /hoge/script/log/negativelist.txt /etc/apache2/sites-enabled/negativelist.txt
  • cron設定
sudo crontab -e -u root

として、

0 2 * * * /path/to/this_script.sh

等と設定すれば、毎日2時に、その日に検知した怪しい動きをシャットダウンしてくれます。

Linuxサーバの履歴を追いやすくする設定。

初期設定の機会が多くなったので、改めてメモをします。

「いつ、どのような操作をしたか」を追いやすくするために以下の設定を行います。

sudo tee -a /etc/profile.d/history.sh > /dev/null << 'EOF'
export HISTSIZE=50000
export HISTFILESIZE=50000
export HISTTIMEFORMAT='%Y/%m/%d %H:%M:%S '
EOF
  • 全ユーザーでhistoryコマンドの履歴を5万行に引き上げます。
  • コマンド実行した日付を記載します。

設定後、新しいシェルセッションを開始するか、

source /etc/profile.d/history.sh

を実行することで上記が反映されます。

設定後、

history

実行後、

 1964  2024/09/17 15:12:18 cd /mnt/wasabi/
 1965  2024/09/17 15:12:19 ll
 1966  2024/09/17 15:12:24 cd redmine/
 1967  2024/09/17 15:12:24 ll
 1968  2024/09/17 15:12:27 cd files/
 1969  2024/09/17 15:12:27 ll
 1970  2024/09/17 15:12:31 pwd
 1971  2024/09/17 16:27:20 cd 
 1972  2024/09/17 16:27:24 sudo aptitude update
 1973  2024/09/17 16:28:35 sudo aptitude upgrade
 1974  2024/09/17 16:30:23 sudo reboot
 1975  2024/09/17 16:33:05 df -h
 1976  2024/09/17 17:25:47 cd script/
 1977  2024/09/17 17:25:47 ll

のように、日付と時間入りでコマンド操作履歴が表示されます。

BookStackのサーバ移行でハマったこと。

LAMP環境で動くアプリケーションを移行する際、だいたいは

  1. 移行先でWebアプリを作成する
  2. 移行元から移行先へとデータ(画像や添付ファイル)をコピーする
  3. DBをエクスポート→インポートする

の流れで別サーバへと移行が可能です。BookStackも同じような理屈で移行が行えるかを確かめたところ、罠がいくつかありました。

環境

共通環境

  • Apache 2.4
  • MySQL8系

移行元

  • Ubuntu 20.04
  • PHP 8.1

移行先

  • Ubuntu 24.04
  • PHP 8.3

さっくりといかない手順

  1. 【移行先】BookStackを構築します。
  2. オプション【移行元】アカウントのセキュリティ設定を一度解除します。
  3. 【移行元】→【移行先】画像や添付データ一式を転送します。
  4. 【移行元】→【移行先】MySQLのダンプを行い、DBを転送します。
  5. 【移行先】DBをインポートします。
  6. 【移行先】DBマイグレーションを行います。
  7. オプション【移行先】URLの変更処理を行います。
  8. 【移行先】設定の反映を行います。
  9. 【移行先】移行先で確認を行います。

【移行先】BookStackを構築します。

自サイトで恐縮ですが、手順はこちらをそのまま用いています

オプション【移行元】管理アカウントの二要素認証を解除。

これが一番ハマったポイントでした。

マイアカウント > アクセス&セキュリティ > 多要素認証

で、二要素認証をオンにしていると、移行先でログインができませんでした。

なので、移行時の一時的な措置として解除を行います。

【移行元】→【移行先】データの転送

BookStackのルートディレクトリ配下の

/public/uploads/を一式、移行先へと転送して、同じディレクトリ構造に上書きします。SCPやtarで固める塔、任意の方法で転送します。

このとき、アクセス権をWebサービス実行ユーザにしてください。(Ubuntuのデフォルトはwww-data)

【移行元】→【移行先】DBのデータ移行

mysqldump -h localhost -u bookstackuser -p --no-tablespaces --single-transaction bookstack > bookstack_backup.$(date +%Y%m%d).sql

-h DBサーバ名、-u bookstackのDBにアクセスできるユーザー DB名という形です。DBユーザに設定されているパスワードを入力してダンプを取ります。

こうしてできたDBは、任意の(安全で確実な)方法で移行先に転送します。

【移行先】DBのリストア

  • ディレクトリ移動
cd /hoge && pwd

ダンプしたDBファイルが転送されているディレクトリに移動します。

  • DBインポート
mysql -h localhost -u bookstackuser -p bookstack < bookstack_backup.$(date +%Y%m%d).sql

【移行先】DBのマイグレーション

これもハマったポイントでした。

  • BookStackルートディレクトリに移動
/path/to/BookStack/root/directory && pwd

/var/www/html/BookStackなど、移行先の、BookStackがインストールされているディレクトリに移動します。

  • DBマイグレーション
sudo -u www-data php artisan migrate --force

このマイグレーションを行わないと、リストアしたDBを参照してくれませんでした。

オプション【移行先】URL変更処理

サーバのURLを変える場合はここにも罠があります。BookStackの設定ファイル、.env

# Application URL
# This must be the root URL that you want to host BookStack on.
# All URLs in BookStack will be generated using this value
# to ensure URLs generated are consistent and secure.
# If you change this in the future you may need to run a command
# to update stored URLs in the database. Command example:
# php artisan bookstack:update-url https://old.example.com https://new.example.com

という但し書きがありますので、この処理を行います。

  • BookStackルートディレクトリに移動
/path/to/BookStack/root/directory && pwd
  • URLアップデート
sudo -u www-data php artisan bookstack:update-url https://old.example.com https://new.example.com

二回ほど確認されますので、両方とも「yes」で答えます。

DB上書き反映

  • BookStackルートディレクトリに移動
/path/to/BookStack/root/directory && pwd
  • キャッシュクリア
sudo -u www-data php artisan cache:clear
  • Webサービス(apache)再起動
sudo systemctl restart apache2.service
  • Webサービス(apache)再起動確認
systemctl status apache2.service

active(running)を確認します。

サーバ移行確認

  1. ブラウザで移行先のURLにアクセスします。
  2. ログインできることを確認します。
  3. 前のデータ(画像や添付含む)が閲覧できることを確認します。
  4. 記事の作成等が行えることを確認します。
  5. 多要素認証をしている場合は、再設定します。

Mod_Securityで特定のルールを無視する設定(Nextcloudでの偽陽性を排除)

Nextcloudにmod_securityを導入するに当たり、気をつけなければならないのがファイルの閲覧や登録、入力処理中にMod_securityが不審な処理として判断してしまうこと(偽陽性)です。

そこで、

  1. 偽陽性と思われるログの調査
  2. 調査時の補助線引き
  3. 偽陽性になるルールを無視する設定

を行います。

ログ確認

/var/log/nextcloud_error.logから、以下のようなログを見ました。

[Wed Sep 11 16:35:02.048442 2024] [security2:error] [pid 32762:tid 32762] [client aaa.bbb.ccc.ddd:56994] [client aaa.bbb.ccc.ddd] ModSecurity: Warning. Operator GE matched 5 at TX:inbound_anomaly_score. [file "/usr/share/modsecurity-crs/rules/RESPONSE-980-CORRELATION.conf"] [line "92"] [id "980130"] [msg "Inbound Anomaly Score Exceeded (Total Inbound Score: 5 - SQLI=0,XSS=0,RFI=0,LFI=0,RCE=0,PHPI=0,HTTP=0,SESS=0): individual paranoia level scores: 5, 0, 0, 0"] [ver "OWASP_CRS/3.3.5"] [tag "event-correlation"] [hostname "nextcloud.hoge.com"] [uri "/ocs/v2.php/apps/user_status/api/v1/heartbeat"] [unique_id "ZuFIJU_udFaqxqrJvRLaPQAAAAA"]

ここで見たいのは

  • クライアントのIPアドレス
  • どのようなルールIDを
  • どのぐらい検知したか

です。

ログ確認のワンライナー

これらを確認するため、copilotの助けを借りてawkスクリプトを生成します。

awk '/ModSecurity/ {
ip = gensub(/.*\[client ([0-9.]+):.*/, "\\1", "g", $0);
rule_id = gensub(/.*\[id "([0-9]+)"\].*/, "\\1", "g", $0);
print rule_id, ip;
}' /var/log/nextcloud/nextcloud_error.log | sort | uniq -c

これを実行したところ、Mod_Securityがエラーとして検知したログの中から

     36 911100 127.0.0.1
    267 911100 aaa.bbb.ccc.ddd
     65 920420 aaa.bbb.ccc.ddd
     36 949110 127.0.0.1
    267 949110 aaa.bbb.ccc.ddd
     36 980130 127.0.0.1
    267 980130 aaa.bbb.ccc.ddd

という結果を確認しました。127.0.0.1はローカルアドレス、aaa.bbb.ccc.dddも自分がアクセスしてきたIPアドレス。この間、自分がしていたのはNextcloudの設定変更やファイルの閲覧のみです。

Mod_securityが検知したルールIDを「偽陽性」と判断し、処置していきます。

Mod_Securityで特定のルールを検知させない処理

Apacheのバーチャルサイトで設定しているという形です。

設定ファイルの修正

  • ファイルのバックアップ
sudo cp -ci /etc/apache2/sites-available/nextcloud.conf /path/to/backup/directory/nextcloud.conf.$(date +%Y%m%d)

設定ファイルやバックアップディレクトリは自分の環境に合わせます。

  • バックアップ確認
diff -u /path/to/backup/directory/nextcloud.conf.$(date +%Y%m%d) /etc/apache2/sites-available/nextcloud.conf

エラー無く、差分も表示されていなければバックアップは成功です。

  • ファイル修正

/etc/apache2/sites-available/nextcloud.confを以下のように修正していきます。(要root権限)

# Mod_security
## 最初は検知モード

SecRuleEngine DetectionOnly

## 偽陽性と判断したID
SecRuleRemoveById 911100
SecRuleRemoveById 920420
SecRuleRemoveById 949110
SecRuleRemoveById 980130
  • ファイル修正確認
diff -u /path/to/backup/directory/nextcloud.conf.$(date +%Y%m%d) /etc/apache2/sites-available/nextcloud.conf

SecRuleRemoveById IDで、これにマッチするパターンは無視します。

  • 差分例
 ## 最初は検知モード

 SecRuleEngine DetectionOnly
+
+## 偽陽性と判断したID
+SecRuleRemoveById 911100
+SecRuleRemoveById 920420
+SecRuleRemoveById 949110
+SecRuleRemoveById 980130
+
 </VirtualHost>

設定ファイルの設定反映

  • 構文確認
sudo apache2ctl configtest

Syntax OKを確認します。

  • 設定反映
sudo systemctl restart apache2.service
  • Apache稼働確認
systemctl status apache2.service

active(running)を確認します。

動作確認

ターミナルで

tail -f /var/log/nextcloud/nextcloud_error.log

としてエラーログを流します。(エラーログは自分の環境に合わせます)

  • 上記処理を行ったNextcloudにアクセスして操作をしていきます。
  • 処理を行ったIDが検知されないことを確認します。

Ubuntu 20.04で動いていたfirefly-iiiをUbuntu24.04にデータ移行。

Linuxで動く財務管理システムfirefly-iii。

Ubuntu 24.04で動くことを確認したので、20.04からのデータを移行しました。(自分の手順では)

環境

移行前

  • Ubuntu 20.04
  • PHP 8.1
  • firefly-iii 5.7.9

移行先

  • Ubuntu 20.04
  • PHP 8.3
  • firefly-iii 6.1.16

その他はApache、MySQL環境です。

さっくりとした(?)手順

  1. 移行先のfirefly-iiiを構築します。
  2. 移行前のSQLダンプファイルを取得します。
  3. 移行先でダンプファイルをインポートします。
  4. 動作を確認します。

【移行先】firefly-iiiを構築します。

方法はこちらです。

【移行元】DBのダンプファイルを取得します。

  • ディレクトリ移動
cd /hoge && pwd

任意のディレクトリを指定します。

  • ダンプファイル取得
mysqldump -u firelfy -p --no-tablespaces --single-transaction firefly > firefly_$(date +%Y%m%d).sql

-u DBユーザー >の左のfireflyはDB名です。自分の環境に合わせます。

  • パスワードを入力してもファイルが取得できないときは?

パスワードに!などの特殊文字が入っていると羽の肥えるパターンがあります。passwdといったファイルを作成し、以下のように記入します。

[mysqldump]
user=DBユーザー
password=''

※パスワードはシングルクォーテーションで囲みます。

こちらのファイルを作った上で、

mysqldump --defaults-extra-file=passwd --no-tablespaces --single-transaction firefly > firefly_$(date +%Y%m%d).sql

実行します。 このpasswdファイルは作業後に削除します。

  • ファイル取得確認
head -50 firefly_$(date +%Y%m%d).sql

ダンプファイルの内容が見られることを確認します。

このダンプファイルを任意の安全な方法で移行先にコピーします。

【移行先】DBのインポートを行います。

  • ディレクトリ移動
cd /hoge && pwd

ダンプファイルを転送したディレクトリを指定します。

  • DBインポート
mysql -h localhost -u firefly -p firefly < firefly_$(date +%Y%m%d).sql 

ユーザ名やDB名は自分の環境に合わせます。

  • Webサービス(Apache)再起動
sudo systemctl restart apache2.service
  • サービス再起動確認
systemctl status apache2.service

active(running)を確認します。

動作の確認を行います。

移行先のfirefly-iiiをインストールしたドメインにアクセスして

  • 移行元のアカウントで認証できること
  • 移行元のデータが確認できること
  • データの検索や登録ができること

を確認できればOKです。

作業の後処理を行います。

  • ディレクトリ移動
cd /hoge && pwd

ダンプファイルを転送したディレクトリを指定します。

  • ダンプファイル削除
sudo rm firefly_$(date +%Y%m%d).sql 

必要に応じて、移行元サイトの閉鎖を行います。

Ubuntuサーバ、コマンドラインでのパスワード入力時にアスタリスクを表示する。

運用の好みの問題です。

sudo su -

環境

  • Ubuntu 20.04 / 22.04 / 24.04

等でrootに昇格する際、入力されたかを確かめる*を表示するようにして視認性を高めます。

既存ファイルのバックアップ

  • 設定ファイルバックアップ
sudo cp -pi /etc/sudoers /path/to/backup/directory/sudoers.$(date +%Y%m%d)

任意のバックアップディレクトリを指定します。

  • バックアップ確認
sudo diff -u /path/to/backup/directory/sudoers.$(date +%Y%m%d) /etc/sudoers

差分が無ければバックアップは成功です。

ファイル書き換え

  • sedによるファイル書き換え
sudo sed -i 's/^Defaults\s\+env_reset$/Defaults env_reset,pwfeedback/' /etc/sudoers
  • 差分確認
sudo diff -u /path/to/backup/directory/sudoers.$(date +%Y%m%d) /etc/sudoers
-Defaults       env_reset
+Defaults env_reset,pwfeedback

設定反映確認

設定を行ったサーバに対して、新しくSSHセッションを作成します。

sudo su -

でパスワードを入力時に*が表示されれば設定は反映されています。

Page 7 of 26

Powered by WordPress & Theme by Anders Norén