ShipIt

  •  
  •  
  • Category:

PerlモジュールのCPANへのアップロードはShipItというのが良いらしいので使ってみたのだが、ちょいちょい詰まりどころがあったのでメモ

ShipItのインストールにはcpanminusを使用します。
cpanminusのインストール等はここでは書きません。

ShipItのインストール

$ cpanm ShipIt

ShipItの準備

設定ファイルの作成

設定ファイル.shipitをプロジェクトディレクトリの直下に作成します。
0から書いても良いですが、コマンドで自動生成できます。

$ shipit --write-config

これで以下のようなファイルが生成されます。

# auto-generated shipit config file.
steps = FindVersion, ChangeVersion, CheckChangeLog, DistTest, Commit, Tag, MakeDist
# svn.tagpattern = MyProj-%v
# svn.tagpattern = http://code.example.com/svn/tags/MyProj-%v
# CheckChangeLog.files = ChangeLog, MyProj.CHANGES

stepsがshipitで実行する処理の内容です。左から順番に実行されます。
CPANにアップロードする際は UploadCPAN というステップを書き足します。

ちなみに私の設定は以下です。

steps = FindVersion, ChangeVersion, Commit, Tag, MakeDist, UploadCPAN
git.push_to = origin

それぞれのステップの意味は以下のような感じです。

  • FindVersion: 現在のバージョンを取得、リリースを行うバージョンをユーザーの入力から設定します
  • ChangeVersion: ソースコード内に記載されたバージョンを変更します
  • Commit: 変更をコミットしてGithubにプッシュします
  • Tag: gitのタグ付けを行いGithubにプッシュします
  • MakeDist: モジュールを作成します
  • UploadCPAN: CPANにアップロードします

ShipItの実行①

$ shipit
...
Running step ShipIt::Step::UploadCPAN=HASH(0x14dbf1a90)
Upload to CPAN? [Y/n] Y
Upload failed.

cpan-uploadのインストール

Uploadに失敗したのはアップロードするために必要なcpan-uploadコマンドが入っていないからです。
cpan-uploadは以下でインストールします。
古い記事では"cpan-upload-http"や"App::cpanupload"などをインストールすると書いてあったりしますが、時代は流れ現在は以下のモジュールをインストールするのが良さそうです。

$ cpanm CPAN::Uploader

ShipItの実行②

$ shipit
...
Upload to CPAN? [Y/n] Y
Please provide a value for --user
Upload failed.

PAUSEの設定

これはCPANアップロード先のPAUSEアカウントの設定がされてないからです。
以下の設定を~/.pauseとして保存します。

user <username>
password <password>

ShipItの実行③

$ shipit
...
Upload to CPAN? [Y/n] Y
registering upload with PAUSE web server
POSTing upload for /Users/gucchi/shipit-dist/Devel-PatchPerl-Plugin-Darwin-v0.1.2.tar.gz to https://pause.perl.org/pause/authenquery?ACTION=add_uri
request failed with error code 500
  Message: Can't verify SSL peers without knowing which Certificate Authorities to trust
Upload failed.

Mozilla::CAのインストール

これは信頼できるCAを見つけられなくて失敗したということです。
MacではCAはキーチェーンで管理されていますがPerlからは読み込めてないという感じですね。

これはMozilla::CAをのインストールで解決するようです。

$ cpanm Mozilla::CA

ShipItの実行④

$ shipit
...
Upload to CPAN? [Y/n] Y
registering upload with PAUSE web server
POSTing upload for ...
PAUSE add message sent ok [200]

成功です!

ありがとうございました!

Movable TypeをPSGIで動かす

Movable TypeはCGIプログラムですが、
CGIだとリクエストのたびにPerlを起動してプログラムの動かすため非常に遅いです。

とうわけでもう少し高速化したいと思います。

perlの高速化というとmod_perlやFastCGIが真っ先に思い浮かびますが、今回はもっとナウでヤングなPSGIで動かしたいと思います。

PSGIとはPerl Web Server Gateway Interfaceの略で、WebサーバーとWebアプリケーションを繋ぐインターフェイスになります。
PythonのWSGI、RubyのRackをインスパイアしたものみたいです。

というわけでPSGIで高速化してみました。

今回はApacheをリバースプロキシとし、PSGIサーバーはStarmanを使用する。



MTの設定

mt-config.cgiにPIDを保存するファイルを設定する
以下を追記(別にパーミッションさえちゃんとしていればパスはどこでも良い)

PIDFilePath /var/run/mt/mt.pid



Starman


インストール

$ cpanm Task::Plack
$ cpanm XMLRPC::Transport::HTTP::Plack

Task::PlackをインストールすればStarman他関連モジュールがインストールされるとのこと。


systemctlの設定

[Unit]
Description=starman for mt
After=syslog.target
After=network.target
After=mysql.service
[Service]
User=apache
Group=apache
Restart=always
SyslogIdentifier=starman
Environment=MT_HOME=/var/www/cgi-bin/mt
Environment=MT_CONFIG=${MT_HOME}/mt-config.cgi
ExecStart=/bin/sh -c "/usr/local/perl5/perls/perl-5.30.0/bin/starman ${MT_HOME}/mt.psgi --access-log /var/log/starman/access_log --error-log /var/log/starman/error_log --pid `perl -ne 'next if /^#/; print $1 if /^\s*PIDFilePath\s+(.*)/i' ${MT_CONFIG}`"
ExecReload=/bin/kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target

Unitセクションには依存の設定等を書く
movable typeはmysqlを使用するのでmysql起動後に起動するようにする

Serviceセクションにはサービスの起動設定等を書く
今回はExecStartにstarmanの起動コマンドを書いた

あとは以下のようにして自動的に起動するようにしておけば安心

$ sudo systemctl enable starman



Apacheの設定

安全にsslでアクセスするようにしたので443のVirtualHostで以下を設定した

<VirtualHost _default_:443>
...
<IfModule proxy_module>
ProxyPass /cgi-bin/mt/ http://localhost:5000/cgi-bin/mt/
ProxyPassReverse /cgi-bin/mt/ http://localhost:5000/cgi-bin/mt/
</IfModule>
...
</VirtualHost>



これで完了!

apacheとstarmanを起動すれば無事CGI版より早くなったMTを味わえるはず。

Docker on MacのVolumeマウント先

  •  
  •  

Dockerのvolumeはどこにマウントされているか?と言うお話

$ docker volume inspect 
[
    {
        "CreatedAt": "2020-08-19T14:55:42Z",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.project": "volume",
            "com.docker.compose.version": "1.26.2",
            "com.docker.compose.volume": "vol"
        },
        "Mountpoint": "/var/lib/docker/volumes/volume_vol/_data",
        "Name": "volume_vol",
        "Options": null,
        "Scope": "local"
    }
]

これで分かるじゃないか。/var/lib/docker/volumes/volume_vol/_dataだろ。と思った方は大間違い。

$ ls /var/lib/docker/volumes/volume_vol/_data
ls: /var/lib/docker/volumes/volume_vol/_data: No such file or directory

こういうオチが待っている。。。

ない。。。

googleさんに聞いてみると

「Docker for MacのDocker EngineはVM上で動いているので、このMountpointはMac上のPathではなくVM上のPathである」

ということらしい。

なのでscreenでVMに入ってみると良いらしい

$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty
Can not exec '~/Library/Containers/com.docker.docker/Data/vms/0/tty': Permission denied

。。。

$ ls ~/Library/Containers/com.docker.docker/Data/vms/0/tty
----------  1 user  staff  12  7 30 00:00 /Users/gucchi/Library/Containers/com.docker.docker/Data/vms/0/tty

というわけで、Permissionを変えたりsudoしてみたりしたがダメ


こんな時はnsenterを使えってVMに入るらしい。

$ docker run -it --privileged --pid=host alpine nsenter -t 1 -m -u -n -I sh
# ls /var/lib/docker/volumes/volume_vol/_data
hello.txt

あった!

しかし毎度毎度Docker for Macの特殊な点に悩まされるなぁ。。。

X509v3 Subject Key Identifier

X509証明書のv3拡張にはSubject Key Identifier (SKI)というものがある。

このSKIはそのままでX509証明書に含まれる公開鍵の識別子である。

$ openssl x509 -text -noout -in server.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 13130459247485585328 (0xb638c31e101f07b0)
    Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN=root ca
        Validity
            Not Before: Aug  4 14:53:26 2020 GMT
            Not After : Aug  4 14:53:26 2021 GMT
        Subject: CN=server
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:18:76:24:20:7e:90:d5:8e:a9:76:3d:1b:0f:56:
                    01:f1:4e:6f:7f:43:71:2c:3f:86:15:45:08:03:2e:
                    d4:2d:d1:12:c9:f1:9e:4f:a6:03:dc:6b:50:ab:a7:
                    c0:4a:62:cc:0f:64:7b:00:fe:9a:d7:84:89:5b:35:
                    e4:e8:c2:50:32
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:localhost
            X509v3 Subject Key Identifier:
                5B:77:97:D1:B9:03:E2:C4:31:F6:84:3F:E6:70:51:BB:86:DD:DD:87
    Signature Algorithm: ecdsa-with-SHA256
         30:46:02:21:00:e0:39:08:21:72:e8:8c:0c:65:5f:e4:34:ed:
         04:bb:69:80:75:e8:d8:05:23:33:e3:6c:27:5d:49:50:a4:45:
         72:02:21:00:af:e8:84:50:95:fc:36:6f:01:13:03:90:f2:f0:
         21:20:cc:0d:42:70:98:c4:af:6a:78:5d:ef:7f:cf:d8:d3:a8

このSKIはopensslコマンドを使用する場合、証明書署名時に以下のような設定のextfileをオプションで与えると書き込まれる

subjectKeyIdentifier = hash

そしてこのhash、ただ公開鍵のハッシュを求めるのかと思ったら違った。。。

OpenSSLの説明(x509v3_config)を見ると2つの設定があるらしい。

  • hashが設定されていたらRFC5258に従って計算したハッシュ値が入る
  • 他の設定(違う文字列?)なら16進の文字列が入る(非推奨)

とのこと

そしてRFC5258には公開鍵からSKIを求める方法は一般的に2つあると書いてある

  • 1つは公開鍵のBIT STRING(excluding the tag, length and number of unused bits)のSHA1ハッシュ
  • もう1つは4bitのタイプフィールドとそれに続く公開鍵のBIT STRINGのSHA1ハッシュの最下位60bit

らしいが、

あくまで一般的な方法の例なので、ユニークな識別子を生成できるなら他の方法でも良い。

ここでopensslのhashという設定は前者になる模様

そんでもってここで言うBIT STRINGとは公開鍵ファイルからタグや長さ等のメタ情報を除いたもの。

これはopensslのasn1parseで求めることができる。

  1. まずパースして構造をチェック
    $ openssl asn1parse -in pub.key
        0:d=0  hl=2 l=  89 cons: SEQUENCE
        2:d=1  hl=2 l=  19 cons: SEQUENCE
        4:d=2  hl=2 l=   7 prim: OBJECT            :id-ecPublicKey
       13:d=2  hl=2 l=   8 prim: OBJECT            :prime256v1
       23:d=1  hl=2 l=  66 prim: BIT STRING
    ここでは23がBIT STRINGのオフセットである(オフセットは鍵のアルゴリズムによって変わる)
  2. BIT STRINGのオフセットを設定してパース
    $ openssl asn1parse -in pub.key -strparse 23 -noout -out pub.bin


これでBIT STRINGが求められたのであとはハッシュ(SHA1)の計算をするだけ

$ openssl sha1 pub.bin
SHA1(pub.bin)= 5b7797d1b903e2c431f6843fe67051bb86dddd87


↓ 証明書のSKI(再掲)

X509v3 Subject Key Identifier:
                5B:77:97:D1:B9:03:E2:C4:31:F6:84:3F:E6:70:51:BB:86:DD:DD:87


ちょっと表現が違いますが、見比べてみると全く同じSHA1の160bitのハッシュ値ですねヽ(・∀・)ノ ワチョーイ♪

DenyHostsでSSHブルートフォースアタックに対抗 CentOS

たまたま/var/log/secureを除いたら怪しいログが!

sshd[4113]: Invalid user shashi from 114.141.167.190 port 57982
sshd[4113]: input_userauth_request: invalid user shashi [preauth]
sshd[4113]: pam_unix(sshd:auth): check pass; user unknown
sshd[4113]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=114.141.167.190
sshd[4113]: Failed password for invalid user shashi from 114.141.167.190 port 57982 ssh2
sshd[4113]: Received disconnect from 114.141.167.190 port 57982:11: Bye Bye [preauth]
sshd[4113]: Disconnected from 114.141.167.190 port 57982 [preauth]
sshd[5949]: Invalid user joerg from 181.49.246.20 port 33874
sshd[5949]: input_userauth_request: invalid user joerg [preauth]
sshd[5949]: pam_unix(sshd:auth): check pass; user unknown
sshd[5949]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=181.49.246.20
sshd[5949]: Failed password for invalid user joerg from 181.49.246.20 port 33874 ssh2
sshd[5949]: Received disconnect from 181.49.246.20 port 33874:11: Bye Bye [preauth]
sshd[5949]: Disconnected from 181.49.246.20 port 33874 [preauth]
sshd[18062]: Invalid user admin from 106.241.33.158 port 23663
sshd[18062]: input_userauth_request: invalid user admin [preauth]
sshd[18062]: pam_unix(sshd:auth): check pass; user unknown
sshd[18062]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=106.241.33.158
sshd[18062]: Failed password for invalid user admin from 106.241.33.158 port 23663 ssh2
sshd[18062]: Received disconnect from 106.241.33.158 port 23663:11: Bye Bye [preauth]
sshd[18062]: Disconnected from 106.241.33.158 port 23663 [preauth]

明らかな攻撃ですね。

何も対策してなかったので対策しないといけないですね。

調べると色々あるようです。
日本以外からのアクセスを拒否するとか、手動で拒否するIPを追加するとか。。。

まあエンジニアなので自動化します!

自動化するソフトとしてはDenyHosts, Fail2banといったところが有名なようです。

DenyHostsはSSH用で拒否するIPをhosts.deny(拒否IPリスト)に追加していくという単純なもの。

Fail2banはiptableベースでSSH以外にも対応しているらしい。

今回はDenyHostsで対応することに。

昔はepelのリポジトリにあったようですが、現在は削除されている模様。

というわけでgithubのリポジトリにrpmがあるのでgithubからインストールします。(2020-07現在の最新版は3.1.2)
https://github.com/denyhosts/denyhosts/releases

$ yum install https://github.com/denyhosts/denyhosts/releases/download/v3.1/DenyHosts-3.1.2-1.noarch.rpm
$ $ rpm -ql DenyHosts
/etc/denyhosts.conf
/usr/local/bin/daemon-control-dist
/usr/local/bin/denyhosts.py
/usr/local/lib/python2.7/dist-packages/DenyHosts-3.1.2.egg-info
/usr/local/lib/python2.7/dist-packages/DenyHosts/__init__.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/__init__.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/__init__.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/allowedhosts.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/allowedhosts.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/allowedhosts.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/constants.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/constants.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/constants.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/counter.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/counter.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/counter.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/daemon.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/daemon.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/daemon.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/deny_hosts.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/deny_hosts.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/deny_hosts.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/denyfileutil.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/denyfileutil.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/denyfileutil.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/filetracker.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/filetracker.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/filetracker.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/lockfile.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/lockfile.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/lockfile.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/loginattempt.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/loginattempt.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/loginattempt.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/plugin.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/plugin.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/plugin.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/prefs.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/prefs.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/prefs.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/purgecounter.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/purgecounter.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/purgecounter.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/python_version.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/python_version.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/python_version.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/regex.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/regex.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/regex.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/report.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/report.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/report.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/restricted.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/restricted.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/restricted.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/sync.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/sync.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/sync.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/util.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/util.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/util.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/version.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/version.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/version.pyo
/usr/share/man/man8
/usr/share/man/man8/denyhosts.8.gz

これでインストール完了。

本体は/usr/local/bin/denyhosts.pyというpythonスクリプトです。

いちいち起動するのはアホなんでsystemdに登録します。

そこで使用するのが/usr/local/bin/daemon-control-dist

こちらを/etc/init.d以下にコピーします

$ cp /usr/local/bin/daemon-control-dist /etc/init.d/denyhosts

ちょこっと中身も編集します。

###############################################
# Edit these to suit your configuration #
###############################################
DENYHOSTS_BIN = "/usr/sbin/denyhosts.py"
DENYHOSTS_LOCK = "/run/denyhosts.pid"
DENYHOSTS_CFG = "/etc/denyhosts.conf"
PYTHON_BIN = "/usr/bin/python3"

ここのDENYHOSTS_BINが異なるので実際のパスに変更

python3はデフォでは入ってないのでPYTHON_BINをpythonに

編集した結果が以下

###############################################
#    Edit these to suit your configuration    #
###############################################
DENYHOSTS_BIN = "/usr/local/bin/denyhosts.py"
DENYHOSTS_LOCK = "/run/denyhosts.pid"
DENYHOSTS_CFG = "/etc/denyhosts.conf"
PYTHON_BIN = "/usr/bin/python"

これでOK.

余談ですが、実はこのコードの下にOS判定のコードがあるんですが、いけてなくてうまくCentOSだと認識してくれません。
しかも、判定されても/usr/bin/denyhosts.pyに設定されるので使えない。。。

これで使えると思いきや、実はさっきyumでインストールしたDenyHostsのライブラリにパスが通ってない。。。
pythonのモジュール検索パスに含まれていないところにインストールされています。。。

というわけで、検索パス上にリンクを作ります(もちろんパスを追加してもOK)

$ ln -s /usr/local/lib/python2.7/dist-packages/DenyHosts /usr/lib/python2.7/site-packages/DenyHosts


そして、DenyHostsで使用しているpythonライブラリをインストールします。
基本的にはipaddrだけだと思います。

$ pip install ipaddr




そして最後に肝心のdenyhostsの設定です。
/etc/denyhosts.confを編集します

デフォルトでは監視するログファイルが/var/log/auth.logになっているので/var/log/secureに変更します

SECURE_LOG = /var/log/secure

これでsystemdでdenyhostsを起動すれば動くはず。

$ sudo systemctl start denyhosts


ちゃんと動いていれば、しばらく攻撃を受け続けた後に/etc/hosts.denyにIPが追加されているはず。

我が家では1日で約300件追加されてました。。。(それでもまだ攻撃を受けている。。。)

ちなみに主な設定項目の意味はこちら

SECURE_LOG 監視するsshdのログファイル
HOST_DENY アクセス拒否リスト
PURGE_DENY アクセス拒否リストにから削除する経過時間 空の場合永遠
BLOCK_SERVICE アクセス拒否リストを使用して拒否するサービス
DENY_THRESHOLD_INVALID アクセス拒否リストに入れる存在しないユーザーでのログイン試行回数
DENY_THRESHOLD_VALID アクセス拒否リストに入れるログイン失敗回数
DENY_THRESHOLD_ROOT アクセス拒否リストに入れるrootでのログイン試行回数
IPTABLES iptablesのパス iptablesを使って拒否する場合
DAEMON_SLEEP
DAEMON_PURGE アクセス拒否の解除を判断する間隔 PURGE_DENYが空の場合は無意味
SYNC_SERVER アクセス拒否リストを同期するサーバー 同期機能を使用する場合
SYNC_INTERVAL アクセス拒否リストの同期間隔
SYNC_UPLOAD アクセス拒否リストをサーバーに送るかどうか
SYNC_DOWNLOAD アクセス拒否リストをサーバーから取ってくるかどうか

というわけでグッドラック!