ブロックリスト自動生成その3(素人のサーバー管理)

自動生成プログラムの機能はオプション‘-h’を付けると確認することが出来る。この辺は普通のコマンドラインプログラムっぽく作ってみました。

root@xxx:~# blockset.py -h
usage: blockset.py [-h] [-n] [-f] [-d] [-u] [-o MASK,COUNT] [-c COUNT] [-a ADDRESS] [logfile]

blockset.py collection of unauthorized access list and restart firewall(ufw)

positional arguments:
  logfile               input log file default:/var/log/mail.log

optional arguments:
  -h, --help            show this help message and exit
  -n, --none            Check only mode.
  -f, --force           bloclist force update, unless new record.
  -d, --debug           debug print mode
  -u, --update          blocklist update and restart firewall(ufw)
  -o MASK,COUNT, --orderck MASK,COUNT
                        ipset order ckeck
  -c COUNT, --count COUNT
                        abuse count
  -a ADDRESS, --address ADDRESS
                        individually address entry to blacklist
root@xxx:~#

  • -h(–help)
    ヘルプの表示、引数やコマンドラインオプション説明
  • -n(–none)
    ログファイルの検査のみで何もしない
  • -f(–foce)
    ログの検査で不正アクセスが無かった場合でも、ファイヤーウォール設定ファイルへのブロックリスト追加を続行する(ネットマスクによるリスト整理時に使用する)
  • -u(–upadte)
    ファイヤーウォール設定ファイルの更新とプロセスリスタートを行う
  • -o MASK,COUNT (–oderck MASK,COUNT)
    ブロックリストを再検査し、指定したマスクビットにてグループ化して、一定数以上のものをリストアップする。-fオプションで該当アドレスをマスクビットでまとめてファイヤーウォール設定ファイルを更新する、ファイヤーウォールの再起動には-uオプションを併用する
  • -c N(–count N)
    検査対象ログにて不正アクセスがN回以上あった場合にリストアップする
  • -a ADDRESS(–address ADDRESS)
    IPアドレスを直接指定して登録する、ログ検査と併用する仕様上、便宜的に100回不正アクセスがあったことにしている
続きを読む…

ブロックリスト自動生成その2(素人のサーバー管理)

手動起動によるメンテナンス機能をまとめると(順不同)

  1. 指定した(規定の)ログファイルを検索して不正アクセスのパターンにマッチしたIPアドレスを収集する
  2. 規定回数(例:5回)以上の不正アクセスが確認できたIPアドレスをリスト化
  3. 現行ブロックリスト(ufwの場合:/etc/ufw/before.rules)より現行のブロックリストを読み出し、新しく検出したIPアドレスリストを重複しないようにマージ(基本的には重複しないはず)
  4. オプション機能としてブロックリストの整理ーアクセスの多いIPアドレスのノードを指定幅ネットマスクでまとめる
  5. 新しいブロックリストを更新し、ファイヤーウォールを再起動 ー オプション:基本的には確認のみ

ipv4adrsetクラス

1のIPアドレスのパターンマッチは普通に正規表現と比較だけですむが、ブロックリストにはネットマスクでグループ化した形式(xxx.yyy.zzz.0/24)などに指定アドレスが含まれるかどうかの判定も必要になる。おそらく便利なライブラリがあるとは思うが、ここは勉強を兼ねてクラスを作成した

class ipv4adrset:
    def __init__(self):
        self.ips = []
        self.msk = 32
    def __init__(self, line = None):
        self.set_line(line)

    def set_line(self, line):
        if line is None:
            self.ips = []
            self.msk = 32
        else:
            res = re.match('^([0-9\.]+)\/([0-9]+)$', line)
            (ip, msk0) = (res.group(1), res.group(2)) if res else (line, 32)
            self.ips = list(map(lambda n: int(n), ip.split('.')))
            self.msk = int(msk0)

    def set_mask(self, mask):
        if self.msk < mask:
            return self
        msks = list(map(lambda n: int('1'*n+'0'*(8-n), 2), [8 if (mask - i*8) >= 8 else (mask - i*8) if (mask - i*8) > 0 else 0 for i in range(4)]))
        rip = list(np.array(self.ips) & np.array(msks))
        ret = ipv4adrset()
        ret.ips = rip
        ret.msk = mask
        return ret

    def __eq__(self, other):
        smsks = list(map(lambda n: int('1'*n+'0'*(8-n), 2), [ 8 if (self.msk - i*8) >= 8 else (self.msk - i*8) if (self.msk - i*8) > 0 else 0 for i in range(4)]))
        omsks = list(map(lambda n: int('1'*n+'0'*(8-n), 2), [ 8 if (other.msk - i*8) >= 8 else (other.msk - i*8) if (other.msk - i*8) > 0 else 0 for i in range(4)]))
        sip = list(np.array(self.ips) & np.array(smsks) & np.array(omsks))
        oip = list(np.array(other.ips) & np.array(smsks) & np.array(omsks))
        return sip == oip

    def __str__(self):
        return '.'.join(map(lambda i: str(i), self.ips)) + ('/' + str(self.msk) if self.msk < 32 else '')

  • メンバ変数self.ipsは4要素整数の配列でアドレスを示しており、self.mskはネットマスクピット数でデフォルトは全マスクで32
  • メゾッドset_lineはIPアドレスのマスク有り無しの文字列を読み込みオブジェクトの値をセットする
  • メゾッドset_maskはマスクピット幅をセットする。マスク外のビットはゼロにする
  • メゾッド__eq__は比較関数だが、ネットマスク幅の短い方で比較する。指定したアドレスがあるネットマスクに含まれるかどうかを判別出来る
  • メゾッド__str__は文字列変換関数

改めて見てみるとリスト内包表記で無理に一行で書こうとしている感じで可読性が非常に悪いですね。。。

とくに1文メチャクチャ長いリスト内包表記があるので、覚書きついでに解説

続きを読む…

Django と Letsencrypt

まろ茶ネットの公開WEBサーバーをSSL化するに当たってLeysencryptを導入しましたが、現在作成中のポータルサイトで採用しているDjango WEBアプリでは -webrootによるサイト登録がうまく行かない問題がありました。(現在はテスト用仮想サーバにて動作確認中)

certbot-auto で登録するサイトのwebroot(DocumentRoot?)に一時的な静的ファイルを作成して、登録URLからアクセスできるかどうかを確認しているようですが、DjangoではDocumentRootに静的ファイルを作成しても、WEBアプリのエンジンによりURLからアクセスできないようになっている感じ。

そこで、urls.py などを細工して、Letsencryptの静的ファイルにアクセスできるように誘導する必要がありました。

<project>/settings.py

# for Letsencrypt certification 
CERT_ROOT = os.path.join(BASE_DIR, '.well-known') 
CERT_URL = '/.well-known'

注).well-known はcertbot-autoが作成する静的ファイル

<project>/urls.py

urlpatterns = [ 
    path('admin/', admin.site.urls),
    path('', include('top.urls')), 
    path('tinymce/', include('tinymce.urls')),
] 
urlpatterns += static(settings.CERT_URL, document_root=settings.CERT_ROOT)

urlpatterns はリストに直接入れても良いような気がするが、上記のようにしないとうまく行きませんでした。

これでDjangoアプリサイトをLetsencryptに登録出来るようになりました。

下記サイトを参考にしました(と言いますか、まんまの情報です)

CentOS8にpythonモジュールmysqlclinetインストール問題

サーバーOSをCentOS8に変更した機会に、メインのポータルサイトをXOOPS環境から変更を検討中。

個人サイトのポータルなのでそんなに豊富なコンテンツはないが、かといって静的なHTMLサイトではつまらないと言うことで、Python環境のWEBアプリフレームワークDjangoの導入・勉強で進めている。

参考サイト
https://docs.djangoproject.com/ja/3.0/intro/tutorial01/

デフォルトのSQLite3からWordPressと同じMySQLに変更
https://qiita.com/SE_AmericanFootball/items/e222604943c500801746

しかし、CentOS8では MariaDBがメインになっているため、うまく mysqlclientがインストールできない。

そこで

$ dnf remove mariadb-devel
$ dnf install mysql-devel

とすることで

$ python pip install mysqlclient

が通るようになり

$ python manage.py migrate

などあとはSQLiteと同じ手続きで進めるようになった。