カテゴリー: PC Page 20 of 50

BookStackサイトの後処理。

幸いなことに、既に構築しているサービスと連携させることができました。

それを更に発展させます。

概要

BookStackにあるヘッダーを用いて以下を行います。

  1. フォント変更
  2. Matomoトラッキングシステムの追加

設定方法

BookStackに管理者権限でログインします。

フォント変更

設定>カスタマイズ>カスタムheadタグに進みます。

以下のコードを入れます。

  • フォントは適宜、指定してください。
  • 著作権などの問題から、フリーのフォントを強く推奨します
<style>
  body {
    --font-body: 'TakaoPGothic', Regular;
    --font-heading: 'TakaoPGothic', sans-serif;
    --font-code: 'TakaoPGothic', monospace;
  }
</style>

Matomoトラッキングシステムのコード追加

※この操作は、別にMatomoトラッキングシステムを運用していることが前提です。※

上記の</stile>に続けて、以下のようなコードを入れます。

<!-- Matomo -->
<script>

<!-- Matomoシステムで提示されたトラッキングコードを貼り付け -->

</script>
<!-- End Matomo Code -->

設定後の確認

以下を確認します。

  1. BookStackでの表示が指定したフォントであること。
  2. Matomoトラッキングシステムで、サイトの閲覧記録を見ることができること。

これからの課題

日本語フォントは表示されましたが、PDFがうまくエクスポートできません。(htmlやMarkdown形式のエクスポートは可能)

これを修正しつつ、コンテンツを増やしていきます。

AWS LightsailにBookStack構築。

BookStackの構築がうまくいき、「これは使えそうだ」と思ったので、AWS Ligtsail上に構築しました。

やったこと

サイト構築

上述した通りです。

ログローテーション設定

https://barrel.reisalin.com/books/bookstack/page/bookstack

/etc/logrotate.d/bookstackに

/var/log/bookstack/*.log {
        daily
        missingok
        ifempty
        copytruncate
        rotate 10
        compress
        create 0640 www-data www-data
}

を作成しました。

Mod_Security設定

既に動いているので使わない手はありません。

https://barrel.reisalin.com/books/bookstack/page/bookstackmod-security

で連携させました。

ロゴ/バナー設定

既に「BarrelGazer」というサイト名をつけたので、それっぽいロゴやバナーをAIに描写してもらいました。

使ってみての感想

Scrapboxのように階層で区別できる上に「本棚」というイメージがお気に入りです。

しかも、描写が速いのが特徴。あとはMarkdownの自動補完があれば言うことなしですが、そこはローカルで動かしているGrowi環境との連携です。

検証:BookStackサイト構築。

Redmineのプラグイン、knowledgebaseのようなWiki編集/公開システム「BookStack」を検証機に入れてみます。

https://www.bookstackapp.com/

前提

こちらが既に動いています。

  • Ubuntu 20.04
  • Apache 2.4系
  • MySQL 8系
  • PHP 8.1系

また、このサイト用のドメインを有しており、ドメインに即した証明書も発行済みです。

さっくりとした手順

  1. composerをインストールします。
  2. DBを作成します。
  3. BookStackをダウンロードします。
  4. BookStackの設定を行います。
  5. Apache設定ファイルを用意します。
  6. Webサービス再起動後、設定を反映します。

Composerインストール

  • インストール
cd /hoge
# 任意の作業ディレクトリに移動します
sudo php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
sudo php composer-setup.php
sudo php -r "unlink('composer-setup.php');"
sudo php composer.phar install --no-dev --optimize-autoloader
  • バージョン確認
composer --version
# バージョンが表示されることを確認します。

DBを作成します。

mysql -u root -p
CREATE DATABASE bookstack;
CREATE USER 'bookstackuser'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON bookstack.* TO 'bookstackuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

DB名/パスワードはポリシーに応じて適切なものを指定します。

BookStackの配置

  • プログラム配置
cd /home/www-data
# パーティションの都合上、/home/www-dataに置いています。
# 環境に合わせて適切なWebサービス公開ディレクトリを指定してください。

sudo git clone https://github.com/BookStackApp/BookStack.git --branch release --single-branch 
sudo chown -R www-data:www-data BookStack
cd BookStack
  • 設定ファイル編集
sudo cp -pi .env.example .env

教義・信仰に沿ったエディタで以下を編集します。

APP_URL=https://hoge.example.com
# 公開用URLを指定します

# Database details
DB_HOST=localhost
DB_DATABASE=bookstack
DB_USERNAME=bookstackuser
DB_PASSWORD=password
# DB名、パスワードなどは先ほど作成したものです。
  • マイグレート
sudo php artisan key:generate
sudo php artisan migrate --force
sudo php artisan db:seed --force

Web公開用のファイルを作成します。

  • 作成するファイル(要管理者権限)
/etc/apache2/sites-available/bookstack.conf
  • 作成内容
<VirtualHost *:80>
    servername hoge.example.com
    # ドメイン名を指定します
    RewriteEngine On
        RewriteCond %{HTTPS} off
        RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# HTTPアクセスを強制的にHTTPSにリダイレクトします
</VirtualHost>

<VirtualHost *:443>
    ServerName hoge.example.com
    # ドメイン名を指定します
    CustomLog /var/log/bookstack/bs_access.log combined
    ErrorLog /var/log/bookstack/bs_error.log
    DocumentRoot /home/www-data/BookStack/public
    # 自身の環境に合わせます
    <Directory /home/www-data/BookStack/public>
    # 自身の環境に合わせます
        AllowOverride All
        Require all granted
        Options +FollowSymLinks
        RewriteEngine On
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule ^ index.php [L]
    </Directory>

#SSL設定
  SSLEngine on
    Protocols h2 http/1.1
  # SSLを有効化します

SSLCertificateFile /etc/certs/example.com.crt
# SSL証明書を指定します
SSLCertificateKeyFile /etc/private/example.com.key
# 秘密鍵を指定します

# SSLCACertificateFile /etc/certs/example.com.CA.crt
# 中間証明書が発行元から別ファイルで提供されている場合は、この直上をコメントアウトして中間証明書を指定します

#セキュリティヘッダー付与

    Header always set Strict-Transport-Security "max-age=63072000"
    Header set X-Content-Type-Options "nosniff"
    Header always append X-Frame-Options "SAMEORIGIN"
    Header set X-XSS-Protection "1; mode=block"

</VirtualHost>

SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2
SSLCipherSuite          ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:EC6-GCM-SHA384
SSLHonorCipherOrder     off
SSLSessionTickets       off

SSLUseStapling On
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"

設定を反映させます。

  • 設定ファイル追加
sudo a2ensite bookstack.conf
sudo apache2ctl configtest
# Syntax OKを確認します
  • Webサービス再起動
sudo systemctl restart apache2.service

インストール確認

https://設定したドメイン

でアクセスします。

以下のログイン画面が出れば成功です。

  • admin@admin.com
  • password

が初期パスワードです。ログイン後、直ちにアカウント情報を設定してください。

今後

  • 現在運用しているサイトへのインストール
  • セキュリティ強化
  • 画像保存ディレクトリの設定

等を行っていきます。

Bingによるシェルスクリプト。(UFW遮断)

概要

先だってChatGPTが書いた

  1. IPアドレスを尋ねる
  2. IPアドレスそのままか、/24ごとブロックするか、/16ごとブロックするかを0~2で質問(空白エンターは0)
  3. 実行前に内容を確認する。yなら続行、nなら中止
  4. 1~2に沿ってufw deny処理を行う
  5. 「ルールを付け加えました。他のIPブロックしますか?」の分岐を出す。yなら1に戻る。nなら次の処理
  6. ufw reloadを行う
  7. 現在のブロック状況を出しますか? yならufw status numberedを表示。nなら終了メッセージを出す

このスクリプト、Bingが書くとどうなるかを試してみました。

Bingの出力結果

  • bing_ufw_deny.sh
#!/bin/bash

# ループフラグを設定
loop=true

# ループ開始
while $loop; do
    # IPアドレスを尋ねる
    echo "ブロックしたいIPアドレスを入力してください。"
    read ip

    # IPアドレスの形式をチェック
    if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
        # IPアドレスそのままか、/24ごとブロックするか、/16ごとブロックするかを0~2で質問
        # 空エンターは0
        echo "IPアドレスをそのままブロックする場合は0、/24ごとブロックする場合は1、/16ごとブロックする場合は2を入力してください。"
        echo "何も入力しない場合は0とみなします。"
        read option

        # オプションに応じてIPアドレスを変換
        case $option in
            1)
                # /24ごとブロックする場合
                ip=${ip%.*}.0/24
                ;;
            2)
                # /16ごとブロックする場合
                ip=${ip%.*.*}.0.0/16
                ;;
            *)
                # その他の場合はIPアドレスそのまま
                ;;
        esac

        # 実行前に内容を確認する。yなら続行、nなら中止
        echo "ufw deny from $ip to any を実行します。よろしいですか? (y/n)"
        read confirm

        if [[ $confirm == [yY] ]]; then
            # yならufw deny処理を行う
            sudo ufw deny from $ip to any
            echo "ルールを付け加えました。"
        else
            # nなら中止メッセージを出す
            echo "処理を中止しました。"
        fi

        # 「ルールを付け加えました。他のIPブロックしますか?」の分岐を出す。yなら1に戻る。nなら次の処理
        echo "他のIPをブロックしますか? (y/n)"
        read loop

        if [[ $loop == [yY] ]]; then
            # yならループフラグをtrueにして1に戻る
            loop=true
        else
            # nならループフラグをfalseにしてループを抜ける
            loop=false
        fi

    else
        # IPアドレスの形式が不正な場合はエラーメッセージを出す
        echo "正しいIPアドレスを入力してください。"
    fi

done

# ufw reloadを行う
echo "ufw reload を実行します。"
sudo ufw reload

# 現在のブロック状況を出しますか? yならufw status numberedを表示。nなら終了メッセージを出す
echo "現在のブロック状況を表示しますか? (y/n)"
read status

if [[ $status == [yY] ]]; then
    # yならufw status numberedを表示する
    sudo ufw status numbered
else
    # nなら終了メッセージを出す
    echo "スクリプトが終了しました。"
fi

体感として

Bingはネットワークアドレスの切り出しが素直です。

  • Bingの場合
        # オプションに応じてIPアドレスを変換
        case $option in
            1)
                # /24ごとブロックする場合
                ip=${ip%.*}.0/24
                ;;
            2)
                # /16ごとブロックする場合
                ip=${ip%.*.*}.0.0/16
                ;;
            *)
                # その他の場合はIPアドレスそのまま
                ;;
        esac
  • ChatGPTの場合
    # ネットワークアドレスに変換
    if [[ -n "$block_cidr" ]]; then
        if [[ $block_option == "1" ]]; then
            # /24で区切る場合
            network_address=$(echo $ip_address | cut -d'.' -f1-3)
            # CIDR表記の生成 (/24)
            cidr_notation="$network_address.0$block_cidr"
        elif [[ $block_option == "2" ]]; then
            # /16で区切る場合
            network_address=$(echo $ip_address | cut -d'.' -f1-2)
            # CIDR表記の生成 (/16)
            cidr_notation="$network_address.0.0$block_cidr"
        fi
    else
        cidr_notation="$ip_address"
    fi

と、シンプルになっています。

蛇足ですが、ChatGPTはこの処理にたどり着くまで5回ぐらいの錯誤を繰り返しました。

なので、このケースで言えばBingはよりユーザーの意図をくみ取ったと考えられます。

こちらの選択肢もあると分かったという収穫。

ChatGPTによるシェルスクリプト。(対話式のUFWブロック)

概要

不正アクセス対策として役立つUFW。これによる不審なIPアドレスの遮断をもっと効率的に行うようにしてみました。

このスクリプトを書い(てもらっ)た背景

攻撃者は複数のIPアドレスから同時に不正アクセスをしてくるパターンが多々あり、

ufw deny from xxx.yyy.1.1
ufw deny from aaa.bbb.0.2

とやるより、

ufw deny from xxx.yyy.1.0/24
ufw deny from aaa.bbb.0.0/16

と、ネットワークアドレスごと遮断した方が効率的だからです。

前提

  • 既にUFWが稼働していること。
  • サービス指定ではなく、あらゆるアクセスを拒絶するという合意が当事者で取れていること。

ChatGPTへの質問と回答

提示した要件

  1. IPアドレスを尋ねる
  2. IPアドレスそのままか、/24ごとブロックするか、/16ごとブロックするかを0~2で質問(空エンターは0)
  3. 実行前に内容を確認する。yなら続行、nなら中止
  4. 1~2に沿ってufw deny処理を行う
  5. 「ルールを付け加えました。他のIPブロックしますか?」の分岐を出す。yなら1に戻る。nなら次の処理
  6. ufw reloadを行う
  7. 現在のブロック状況を出しますか? yならufw status numberedを表示。nなら終了メッセージを出す

出てきたスクリプト

  • ufw_deny.sh
#!/bin/bash

# ユーザーからIPアドレスとブロック範囲を入力してもらい、
# UFWを使用して指定した範囲のIPアドレスをブロックするスクリプトです。

while true; do
    # IPアドレスを尋ねる
    read -p "ブロックしたいIPアドレスを入力してください: " ip_address

    # ブロック範囲を尋ねる
    read -p "IPアドレスをそのままブロックする場合は0、/24ごとにブロックする場合は1、/16ごとにブロックする場合は2を入力してください(空エンターは0): " block_option

    # 入力が空の場合はデフォルトで0(IPアドレスそのまま)とする
    block_option=${block_option:-0}

    # ブロック範囲を計算
    case $block_option in
        1) block_cidr="/24" ;;
        2) block_cidr="/16" ;;
        *) block_cidr="" ;;
    esac

    # ネットワークアドレスに変換
    if [[ -n "$block_cidr" ]]; then
        if [[ $block_option == "1" ]]; then
            # /24で区切る場合
            network_address=$(echo $ip_address | cut -d'.' -f1-3)
            cidr_notation="$network_address.0$block_cidr"
        elif [[ $block_option == "2" ]]; then
            # /16で区切る場合
            network_address=$(echo $ip_address | cut -d'.' -f1-2)
            cidr_notation="$network_address.0.0$block_cidr"
        fi
    else
        cidr_notation="$ip_address"
    fi

    # 設定内容を確認する
    echo "以下の内容でブロックを行います:"
    echo "IPアドレス: $cidr_notation"

    read -p "よろしいですか?(y/n): " confirm

    # ユーザーが処理を続行しない場合、スクリプトを終了する
    if [[ $confirm != "y" && $confirm != "Y" ]]; then
        echo "処理を中止しました。"
        exit 0
    fi

    # ブロックルールをufwに追加
    sudo ufw deny from "$cidr_notation"

    # エラーがあった場合はエラーメッセージを表示して終了
    if [[ $? -ne 0 ]]; then
        echo "ERROR: 不正なソースアドレス"
        exit 1
    fi

    echo "ルールを付け加えました。"

    # 他のIPをブロックするか尋ねる
    read -p "他のIPをブロックしますか?(y/n): " continue_blocking
    if [[ $continue_blocking != "y" && $continue_blocking != "Y" ]]; then
        break
    fi
done

# ufwを再読み込みする
sudo ufw reload

# ブロック状況を確認するか尋ねる
read -p "現在のブロック状況を確認しますか?(y/n): " check_status
if [[ $check_status == "y" || $check_status == "Y" ]]; then
    sudo ufw status numbered
fi

# 終了メッセージを出す
echo "IPブロックを終了しました。"

このスクリプトを

chmod +x ufw_deny.sh

とすれば作成は完了です。

スクリプトの動き

./ufw_deny.sh
ブロックしたいIPアドレスを入力してください:
# IPアドレスを入力します

IPアドレスをそのままブロックする場合は0、/24ごとにブロックする場合は1、/16ごとにブロックする場合は2を入力してください(空エンター
は0): 
# IPアドレスのレンジを
# IPアドレスそのまま:0
# /24(255.255.255.0)で区切る:1
# /16(255.255.0.0)で区切る:2

IPアドレス:
よろしいですか? (y/n):
# 先ほどのIPを表示します。ネットワークアドレスの場合は、/24 /16で区切って表示します。
# 内容を確認します。
# y を入力後、ufwの処理が走ります。一般ユーザーの場合はsudoパスワードが訊かれます。

ルールを追加しました
ルールを付け加えました。
# ufw deny from (入力したIP/NWアドレス)を実行します。

他のIPをブロックしますか?(y/n): 
# 続行するかを訊きます。yの場合はIPアドレスから始まります。

ファイアウォールを再読込しました
現在のブロック状況を確認しますか?(y/n):
# yの場合は sudo ufw status numberedを実行します。

と、

  • IPアドレス→ネットワークアドレスへの変換
  • UFW DENYに追加
  • UFWルールの読込
  • 設定後のルール表示

まで一元管理してくれます。

IPアドレスをネットワークアドレスにした上での洗い出し。

この記事の続きです。

シェルスクリプトによって、スペースで区切られただけのIPアドレスをリスト化することに成功したので、

192.168.1.2
192.168.1.3
172.28.1.3
172.28.1.5
...

といったIPアドレスのリストを

2, 192.168.1.0/24
2, 172.28.1.0/24

というように、/24で区切ったアドレス帯と件数を表示するスクリプトをchatGPTに作成いただきました。

スクリプト内容

  • network-address-count.sh
#!/bin/bash

# ファイルのパスを引数として受け取る
file_path=$1

# ファイルが存在するかを確認する
if [ ! -f "$file_path" ]; then
    echo "指定されたファイルが見つかりません。"
    exit 1
fi

# ネットワークアドレスごとにIP数をカウントして表示
awk -F. '{print $1"."$2"."$3".0"}' $file_path | sort | uniq -c | while read count address; do
    echo "$count, $address/24"
done

あとは実行権を付与します。

スクリプトの動き

./network-address-count.sh /path/to/log/file
# 上述のリンク先でのログファイルを指定します。

結果

2, 71.6.146.0/24
1, 71.6.158.0/24
1, 71.6.199.0/24
11, 71.6.232.0/24

のように、攻撃をしてきたIPアドレスをネットワークアドレスごとにカウントすることができました。

これは、今後の防御手段をしる手段として有効です。

Fail2banが弾いたIPアドレスの洗い出し。

はじめに

不審なアクセスを弾いているfail2ban。

sudo fail2ban-client status sshd

と実行することでBANしたIPのリストが出てきます。

ただ、それは

aaa.bbb.ccc.ddd AAA.BBB.CCC.DDD

のようにスペース区切りとなっていて読むのが大変。

そこで、IPアドレスを一覧表示して可読性を高めるようにします。

前提

環境

  • Ubuntu 20.0.4
  • fail2ban 0.11.1

sshdの設定

  • /etc/fail2ban/jail.local
(抜粋)
[sshd]
enabled=true
filter=sshd
mode=normal
port=22
protocol=tcp
logpath=/var/log/auth.log
maxretry=3
bantime=-1
# ログインが3回失敗したIPアドレスを猶予期間無しに永久追放します
ignoreip = 127.0.0.0/8 ::1 #その他、除外IPを必要に応じて

enabled=true filter=sshd mode=normal port=22 protocol=tcp logpath=/var/log/auth.log maxretry=3 bantime=-1 # ログインが3回失敗したIPアドレスを猶予期間無しに永久追放します ignoreip = 127.0.0.0/8 ::1 #その他、除外IPを必要に応じて

さっくりとした手順

  1. fail2banが弾いているIPをログファイルに出力するためのスクリプトを作成します。
  2. 出力したファイルのディレクトリを作成します。
  3. 動作を確認します。
  4. cronで自動実行されるようにします。
  5. 出力したログファイルをローテーションする設定を追加します。

スクリプト作成

※教義・信仰に沿ったエディタで作成してください。※

  • fail2ban-ssh-sort.sh
#!/bin/bash

# ログの格納場所を指定
log_directory="/path/to/log/directory"

# fail2ban-client status sshdを実行し、IPアドレスを取得
ip_addresses=$(fail2ban-client status sshd | awk 'BEGIN {RS="[ \\t]+"} {if ($1 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) print $1}')

# IPアドレスを一行ずつ表示してソートし、csvファイルに出力
echo "$ip_addresses" | sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4 > "$log_directory/sorted_ips.csv"
  • 実行権付与
chmod +x fail2ban-ssh-sort.sh

ログ出力ディレクトリを作成

sudo mkdir /var/log/fail2ban-ips
# 上記のスクリプトで指定したディレクトリを指定します。

動作確認

  • スクリプト実行※要管理者権限※
sudo bash fail2ban-ssh-sort.sh
  • ファイル出力確認
cat /var/log/fail2ban-ips/sorted_ips.csv
# IPアドレスの出力がされていれば成功です。

cron自動実行

  • cron編集
sudo crontab -e -u root
  • 編集内容
0 3 * * * /path/to/directory/fail2ban-ssh-sort.sh
# スクリプトを格納したディレクトリを絶対パスで指定します
# この例では毎日午前3時に実行します

ログローテーション設定

※管理者権限で、教義・信仰に沿ったエディタで作成してください。※

  • /etc/logrotate.d/fail2ban-ssh-sort
/var/log/fail2ban-ips/sorted_ips.csv {
# スクリプトで指定したログ格納ディレクトリ/ログファイルを指定
    daily
    rotate 3
    compress
    missingok
    notifempty
}

ローテーション設定確認

sudo logrotate -d /etc/logrotate.d/fail2ban-ssh-sort

エラーがなければ成功です。

これで、加工しやすいCSVファイルで不審なログインのアクセス元を確認することが可能になりました。

ChatGPTによるRubyスクリプト(対話型のSSL証明書の有効期限確認)

概要

昨日の「SSL証明書の有効期限確認を確認するスクリプト」は、確認するURLをスクリプト内にべた書きしているため、利便性に欠けます。

そこで、もっと使いやすく改良してもらいました。

ChatGPTに実装してもらった内容

  • URLを対話型で入力する
  • 存在しないURLやアクセスできないサイト(ローカルNW内など)を入力した場合はタイムアウトする

の2点。

スクリプト内容

  • qa_ssl_checker.rb
require 'openssl'
require 'socket'
require 'date'
require 'uri'
require 'timeout'

# ユーザーからURLを対話的に受け取る
def get_user_input
  print "チェックしたいサイトのURLを入力してください(https://example.comのような形式): "
  gets.chomp
end

# 変数で指定したURLに接続して証明書の有効期限を取得するメソッド
def get_certificate_expiry_date(url)
  uri = URI.parse(url)
  hostname = uri.host
  ssl_socket = nil
  tcp_client = nil

  begin
    # タイムアウトを5秒に設定してSSL接続を確立
    Timeout.timeout(5) do
      tcp_client = TCPSocket.new(hostname, 443)
      ssl_context = OpenSSL::SSL::SSLContext.new
      ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_client, ssl_context)
      ssl_socket.hostname = hostname
      ssl_socket.connect

      # 証明書の有効期限を取得
      cert = ssl_socket.peer_cert
      expiration_date = DateTime.parse(cert.not_after.to_s)
      days_remaining = (expiration_date - DateTime.now).to_i

      return expiration_date, days_remaining
    end
  rescue Timeout::Error
    return nil, "サーバーへの接続がタイムアウトしました。"
  rescue => e
    return nil, e.to_s
  ensure
    ssl_socket&.close
    tcp_client&.close
  end
end

# メイン処理
def main
  url = get_user_input
  expiration_date, days_remaining = get_certificate_expiry_date(url)

  if expiration_date
    formatted_date = expiration_date.strftime("%Y/%m/%d")
    puts "サイト #{url} の有効期限は #{formatted_date} です。残り #{days_remaining} 日です。"
  else
    puts "証明書の取得に失敗しました: #{days_remaining}"
  end
end

# メイン処理を呼び出し
main

スクリプト実行結果

bash qa_ssl_checker.rb
チェックしたいサイトのURLを入力してください(https://example.comのような形式): https://news.bbc.co.uk
サイト https://news.bbc.co.uk の有効期限は 2024/03/04 です。残り 139 日です。

これによって、ブラウザのマウス操作より確実にSSL証明書の有効期限を確認することができるようになりました。

update-mot.dに仕込むスクリプト(SSL証明書の有効期限確認)

概要

2023年10月現在、自分のサイトの証明書はLet's Encryptのワイルドカード証明書を利用しています。
性質上、有効期限が90日のため「いつ頃が有効期限か」を確認するためのスクリプトをChatGPTに書いてもらいました。

スクリプトが動く要件

  • サーバがインターネットに接続されていること。
  • Rubyがインストールされていること。
  • Opensslがインストールされていること。

指示した内容

  1. 変数で指定したURLにopensslで接続
  2. 証明書の有効期限を読み取る
  3. 「サイト ○○ の有効期限はyyyy/mm/dd です。残り○日です」と表示する

返ってきたスクリプト

  • ssl_cecker.rb
  • URLは必要に応じて変更してください。
require 'openssl'
require 'socket'
require 'date'
require 'uri'  # URIモジュールを追加

# 変数で指定したURL
url = "https://hoge.example.com"

# 変数で指定したURLに接続して証明書の有効期限を取得するメソッド
def get_certificate_expiry_date(url)
  uri = URI.parse(url)
  hostname = uri.host

  begin
    # SSL接続を確立
    tcp_client = TCPSocket.new(hostname, 443)
    ssl_context = OpenSSL::SSL::SSLContext.new
    ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_client, ssl_context)
    ssl_socket.hostname = hostname
    ssl_socket.connect

    # 証明書の有効期限を取得
    cert = ssl_socket.peer_cert
    expiration_date = DateTime.parse(cert.not_after.to_s)
    days_remaining = (expiration_date - DateTime.now).to_i

    return expiration_date, days_remaining
  rescue => e
    return nil, e.to_s
  ensure
    ssl_socket&.close
    tcp_client&.close
  end
end

# メイン処理
def main(url)
  expiration_date, days_remaining = get_certificate_expiry_date(url)

  if expiration_date
    formatted_date = expiration_date.strftime("%Y/%m/%d")
    puts "サイト #{url} の有効期限は #{formatted_date} です。残り #{days_remaining} 日です。"
  else
    puts "証明書の取得に失敗しました: #{days_remaining}"
  end
end

# メイン処理を呼び出し
main(url)

スクリプト実行例

ruby ssl_checker.rb

サイト https://hoge.example.com の有効期限は 2024/01/12 です。残り 88 日です。

motodへの登録

これはサーバ管理の度に確認したいので、motdに以下のスクリプトを管理者権限で配置します。

  • ファイル名
/etc/update-motd.d/99-custom-motd
  • スクリプト内容
#!/bin/bash
ruby /path/to/directory/ssl_cecker.rb
  • 実行権付与
sudo chmod +x /etc/update-motd.d/99-custom-motd

motd登録確認

任意のターミナルクライアントでSSH接続後、

サイト https://hoge.example.com の有効期限は 2024/01/12 です。残り 88 日です。

と表示されればうまくいっています。

備考

  • 今回のChatGPTへの伺いはほぼ一発でした。
  • 対話型やテキストを読み込むなども機を見て実装させたいです。

Ansible検証:簡単なplaybookとコマンドのエイリアス

概要

検証の続き。

  • Ansibleサーバのインストール
  • Ansibleサーバにクライアントを設定

まで完了したので、Playbookを書いてみます。

Playbookサンプル

単純に、Webサーバ(apache2)を再起動するためのymlを書きます。

  • WebRestart.yml
---
- name: Restart Apache2 Service
  hosts: clients
  become: yes  
  tasks:
    - name: Restart Apache2
      service:
        name: apache2
        state: restarted

Playbook実行失敗

  • Playbook実行
ansible-playbook WebRestart.yml
  • 実行結果抜粋
fatal: [IP]: FAILED! => {"ansible_facts": {}, "changed": false, "failed_modules": {"setup": {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "failed": true, "module_stderr": "Shared connection to IP closed.\r\n", "module_stdout": "sudo: パスワードが必要です\r\n", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1, "warnings": ["Platform linux on host IP is using the discovered Python interpreter at /us
  • サービス再起動のためには管理者権限が必要
  • 管理者権限への昇格のためのパスワードが書かれていない

ため、この結果となりました。

回避策

  • Ansibleのクライアントにrootログインする
  • sudoの昇格時にパスワードを不要とする

の2つはセキュリティ的によろしくありません。

  • Ansible Vaultで暗号化

は、暗号化→複合化の手間を伴います。そこで、単純に

ansible-playbook --ask-become-pass WebRestart.yml 

とすることで、Playbook実行時にリモートホストのSudoパスワードを尋ねられるようになり、上記Playbookも正常に実行できました。

回避策の更に回避策

効率化を図るとはいえ、Playbook実行時に

ansible-playbook --ask-become-pass 

とオプションをつけるのは面倒です。更に回避策を考えます。

Case1. セッション時のみ有効なエイリアスをつける

alias ansible-playbook='ansible-playbook --ask-become-pass'

を作業前に実行。これで、

ansible-playbook

と実行するだけで

ansible-playbook --ask-become-pass

が得られます。この方法は一回限り有効な手段。サーバの設定を余り変えたくないときに有効な手段です。

Case2. .bashrcにエイリアスを付与する。

echo "alias ansible-playbook='ansible-playbook --ask-become-pass'" | tee -a ~/.bashrc

source ~/.bashrc

Case1を永続的に付与する手法です。運用者での合意が取られているなら、メンテナンス的にも楽だと言うことでこの方法を採ります。

Page 20 of 50

Powered by WordPress & Theme by Anders Norén