それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

php で 自己署名CAを使って証明書certificateを作成する例。

sample for generate certificate

php で certificate 作成する例。

証明書作成の手順

  • 発行人の秘密鍵の作成
  • 発行人の証明書の作成(自己署名・署名リクエスト)
  • 被発行人の秘密鍵の作成
  • 被発行人の証明書の作成(自己署名・署名リクエスト)
  • 発行人が署名リクエストに署名する。

詳しくは、サンプルコードを参照

phpでの秘密鍵の作成

$key = openssl_pkey_new();
openssl_pkey_export($key,$pkey_pem);

自己署名証明書の作成

$dn = ['C' => 'JP', 'ST' => 'Kyoto', 'L' => 'Kyoto City', 'O' => 'alice'];
$csr = openssl_csr_new( $dn, $key, $conf );
$cert = openssl_csr_sign( $csr, null, $key, 365, $conf );

署名リクエストへ署名

$opt = [
  'config'          => $path,
  'x509_extensions' => 'usr_cert',
];
$cert = openssl_csr_sign( $userCSR, $issuerCrt, $issuerPkey, $days=30, $opt, time() );

設定の作成

$conf = [
'config'          => $path,
'req_extensions'  => 'v3_req',
'x509_extensions' => 'v3_req',
];

注意点

php関数openssl_csr_sign() は openssl x509 -reqコマンド相当である。

openssl ca コマンドとは無関係である。

openssl_csr_signを見てみると、 openssl_csr_sign は openssl の X509_sign( int X509_sign(X509 *x, EVP_PKEY *pkey, const EVP_MD *md); ) を用いていることがわかる。

また、openssl_csr_signopenssl_csr_new が受け入れるオプションはかなり制限されているとわかる。

subjectAltName の取り扱い。

上記の注意点に留意すると、subjectAltNameは署名時・リクエスト作成時、それぞれ設定が必要がある。 openssl CAでよくある例の、openssl ca .. copy_extensions=copyのようなコピーは不可能である。

このあたりは不自由であるが、CA機能未使用のため仕方ないものとして受け入れるしかない

リクエスト作成時のSAN指定サンプルは見つかるが、一方でX509_signのSAN指定サンプルは簡単に見つからないので注意したい。

php でのopenssl.cnf

php -i | grep ssl で得られるopenssl.cnf が標準的に使われている。

openssl.cnf の構造

個人的な見解であるが、次のようになっている。

[name]
my_name = my_reference
[my_reference]
my_list = @my_list
[my_list]
item = value
item = value

どのセクションが使われるのかはopensslのどの機能を使うかによる。署名リクエスト(req)のときは[req]セクションが使われる。認証局(CA)のときは、[ca]セクションが使われる。

リクエスト作成時のcnf

リクエスト作成時には [req]が使われる。

distinguished_name = req_distinguished_nameの設定により、[req_distinguished_name]を参照することを明示している #req_extensions=v3_reqコメントアウトしているが、phpではreq_extensionsセクション名はphpから指定するため。openssl コマンドでも同様に、オプションで渡すことが可能。

[ req ]
distinguished_name = req_distinguished_name
# req_extensions = v3_req
[ req_distinguished_name ]

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = DNS:mydomain.tld, DNS:seconddomain.tld
nsComment = "Generated Certificate by plain php"

署名時のopenssl.cnf

署名時には、X509.extensionusr_certという名前で指定して、SubjectAltNameを登録している。

phpではx509_extensions=usr_certとセクション名を関数から指定可能である。openssl コマンドも同様のオプションを渡すことが可能である。

[ usr_cert ]
basicConstraints=CA:FALSE
nsComment = "[php] OpenSSL Generated Certificate"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
subjectAltName=DNS: replace.me

openssl のCA機能であれば、SubjectAltNameをcopy_extensionでまるごとコピーが可能であるが、openssl x509 req のときはコピー不可だった。明示する必要があった。

市販品のUSB-Wifi ドングルを使って5GHzのAPをつくる

USB-Wifi ドングルを使って5GHzのAPをつくる

USBのWifiドングルを使ってアクセス・ポイントを構成できないのかとずっと疑問に思っていた。色々と調べているとMediatekのチップでLinuxからAPになれることがわかった。

また、5Ghzでアクセス・ポイントを構成するには、一部のカーネル機能を使わないといけないので、OpenWRTやKaliのようなWifi制御を扱えるOSを選ぶ必要がある。とくに5Ghzは、航空無線とのバッティングがあるの細かい部分でややこしい。

今回選んだのは次のUSB Wifi である。NetGear, Inc. A6210が、5GHZのAPになれるし、11acでつながるし速度も十分にでるし、日本のAmazonで手軽に買える。2020年に購入し放置していた。その当時はx86のドライバしかWRTで提供がなかったのでアップデートを待っていた。

今回の構成は、Raspi-UbuntuにLXDでOpenWRTを起動し、USBドライブをパススルーする予定だった。

USBデバイスをパススルーしなくても、Raspi自体がNetGear, Inc. A6210のmt76x2u ドライバをロードしてくれたので、ネットワークデバイスとしてパススルーして試すことにした。

lxd の準備

sudo snap install lxd
sudo lxc init 
sudo lxc launch images:openwrt/22.03 wrt-01

wrt の準備

## ネットワーク疎通確認を兼ねてbashを入れる。
sudo lxc exec wrt-01 -- opkg update
sudo lxc exec wrt-01 -- opkg install bash 

ネットワークデバイスをパススルー

Raspi(LXDホスト)でwlan1として見えている A6210を、LXDゲスト(wrt)にwlan0として譲渡する。

lxc config device add wrt-01 wifi nic nictype=physical parent=wlan1 name=wlan0
lxc config device remove wrt-01 wifi  # 削除できることを確認。
lxc config device add wrt-01 wifi nic nictype=physical parent=wlan1 name=wlan0

ついでの作業用にeth0もMacVLANでパススルー

lxc config device add wrt-01 myvlan nic nictype=macvlan parent=eth0

作業用eth0 にDHCPからLANアドレスを割り当てておく

ip link set eth1 up
udhcpc -i eth1

wifi 関連のモジュールを入れる。

ネットワークメニューに config/wireless を出すために必要

sudo lxc exec wrt-01 -- opkg install wpad-wolfssl

lxdでストレージに余裕があるため、フルパッケージ版wpad-wolfsslを選んだ。

もしWRTだけでやるなら

LXDを経由せずに、WRTをベアメタルで使うのであれば、ドライバをインストールしないと動かないと思う。 適当にドライバを入れる。A6210はmt76x02なので、mt76関連を入れておけばいいはず。細かい一致を調べるのはまた今度。

opkg find *mt76* | cut -d ' ' -f 1  | xargs opkg install
opkg install wpad-wolfssl

今回は実験だけのつもりなので、適当に関連モジュールをインストールした。 正確にやるには、kmod-mt76x02-usb関連を入れておけばいいと思う。

OpenWRT の22.03 からサポートされている。以前はX86のDevにしかなかったのだが、いまはarmやmipsにも配布されている感謝しかない。

動作チェック

再起動をして、ネットワークデバイスがロードされる。

bash-5.2# iw dev
phy#1
        Interface wlan0
                ifindex 15
                wdev 0x100000001
                addr 9c:c9:eb:21:fa:53
                type managed
                txpower 18.00 dBm
                multicast TXQ:
                        qsz-byt qsz-pkt flows   drops   marks   overlmt hashcol tx-bytes        tx-packets
                        0       0       0       0       0       0       0       0               0

再起動を何度か繰り返したり、デバイスのパススルーを追加削除を何度か繰り返してたら認識した。LXD経由だとちょっと不安定。

LXDのホスト側でWifiバイスを完全に開放した状態で、再起動すると起動した。

lxd stop wrt-01

## 無線デバイスを解放
rfkill unblock all

lxd start wrt-01

メニューに出てきた

出てきた!

Wrt on LXD でもWirelessメニューを出せた。ちょっと感動した。時代は進歩した。ソフトウェアのサポートが充実している。

APとして設定して接続

iperf3で負荷を与えてみる。

iperf3 かけててたら XHCI が死んだ。

bash: /sbin/service: /bin/sh: bad interpreter: I/O error
bash-5.2#
exit
Error: Command not executable
takuya@raspi-ubuntu:~$ sudo ps
-bash: /usr/bin/sudo: Input/output error

io error でUSBまるごと死んだ。

これは、USBドライバが、Raspiまるごと死んだということだろう。

つかったRaspi4は、USBーHDDでusb3.0を使っていて、USBのWifiアダプタもUSB3.0でつないでいるので、バスパワー不足になったのだろうか。USB通信の通信量が多すぎたのだろうか、USB3.0がまるごとダウンしてしまった。SDカードでやれば動くかもね。USB−HDDのbtrfs が死んだ。USB3に指すと巻き込みで死ぬ。

usb2.0usb3.0で分けて試す。

2.0/3.0のポートで分けて試すことにした。LXDに渡したWifiドングルでも安定して動作する。

USB3.0のとき、5GHzのAPを使うと、450Mbps前後の速度が出たが、USB2.0であれば、同じく5GHzのAP設定でも、150Mbpsが限界だった。USB自体の速度限界までは使い切れないようだ。3.0だと不安定だったが2.0だと比較的安定した。LXDを経由して通信量やSSL関連でRaspi4には荷が重いのかもしれない。

SDカードなら動くかもしれない。

SDカードをつかってUSBを使わずにWifiドングルがUSB3.0を占拠すれば、もっと安定して動くかもしれない、そのうち試したい。

今回試したUSB-Wifi

NetGear, Inc. A6210

Linuxルータが5GHzでAPになるのは航空無線DSP関連があるので常用は憚られる。ちゃんと国の設定を行っていれば問題にはならないんだけど。。。

X86マシンであればPCI-EのデバイスでもっとかんたんにAP作れる。今回はRaspi4を前提にしたのでUSBで試したが、いまのWRTであれば問題なくAPを作れることがわかった。これさえあれば、次々とAPを購入したり、WRTファームを焼きかえなくてもいい。USBデバイスで5GHzのWifi-APを作れるとわかった。GNUの自由が手に入ることがわかった。

参考資料

curl って telnet 出来たのか。

curl って telnet 出来たのか。

curl telnet://www.google.com:80

GET / HTTP/1.1
Host: www.google.com

telnet コマンドをインストールしなくても、curlさえあればtelnetができる。

最近はtelnetコマンドをインストールしなくちゃいけないのだが、インストールしなくても使えうのが良い。

https の443 ポートにも接続できる。(できるがTLSがうまくいかないことが多い。)

curl -v  telnet://mixi.jp:443
openssl s_client -connect mixi.jp:443

TLSがあるのなら、openssl s_client を使う方がいい。

参考資料

curl で telnet を代替する - 理系学生日記

lxd のストレージ・プールbtrfsに透過圧縮(zstd)を有効にする。

btrfs で透過圧縮ができる。

btrfs はCowで透過圧縮をサポートしている。書き込み量が少なければSSDの寿命にも優しいだろう。

lxd のストレージにbtrfs を採用したのだが、透過圧縮はオフになっている。どうせなら圧縮しておこう。

現在の設定の確認

現在の設定を確認する。ストレージ一覧をだして、設定を確認

lxc storage list
lxc storage show default
lxc exec my-host --  mount  | grep btrfs

設定を確認したところ、圧縮はされてなかったのでデフォルトでは圧縮されないようです。

透過圧縮を有効にする

lxc storage set default btrfs.mount_options compress=zstd

圧縮するにはアルゴリズムを選べる。zlib, lzo, zstd から選べる。zstdでは互換性を失うが処理時間が速いので使い勝手が損なわれないようだ。圧縮率を優先するならlzo がいいのかな

lzo を使う場合

lxc storage set default btrfs.mount_options compress=lzo

設定が保存されたか確認する。

lxc storage get default btrfs.mount_options

lxd を再起動する。再起動したら反映された。

snap restart lxd

マウントオプションが変わったか確認する。

lxc exec docker --  mount  | grep btrfs

変わっていた。

/dev/vg/lxd on / type btrfs (rw,relatime,compress=zstd:3,ssd,space_cache,subvolid=259,subvol=/containers/my-host)

これでしばらく使ってみよう。

tar から名前を指定して取り出す。

tar から指定ファイルの取り出し

tar xvf archive.tar home/takuya/.config

名前の指定

tar xvf archive.tar なまえ

フォルダを指定すると、カレントディレクトリにフォルダが作成されて取り出される。

名前の取り出し

名前の一覧を見るには、

tar tv archive.tar 
lsar archive.tar 

コマンドで取り出せる。

p7zip やunarコマンドでも同じことができる。

使ってみて気づいたポイント

大きいTARファイルでは時間はかかる。

tar は、すべてをスキャンしないとファイルの存在が不明なので全ファイルをスキャンする。そういう点から20GBのtarを展開するのがめんどくさくて指定したファイルだけを取り出そうとしたが、スキャン時間はそれなりにかかった。書き込み時間は省略できるので全部展開するよりずいぶん早い。

そのため、少し時間がかかる。

高速化する方法は「SSD上で作業を行うこと」くらいだろうか。

mirakurun の b25テストが古いのかもしれないので、最新版のビルドに変えた。

mirakurun(docker)に同梱されている arib-b25-stream-test が古い

どれくらい古いのかというと、6年前にnpm に出されたままだ。

しかもnpmのくせに、C言語をmake install するだけのパッケージである。npmにあがってるので、TSを期待して探してたら完全にmake install だけでした。

しかたないので、dockerをビルドして使うことにした

mirakurun のdocker-compose.ymlの先頭を見ると、次のようになっていて、ビルドがしやすくなっている。

cat docker-compose.yml
version: "3.7"
services:
  mirakurun:
    build:
      context: ./
      dockerfile: docker/Dockerfile
    image: chinachu/mirakurun:latest
    container_name: mirakurun
mkdir docker
touch docker/Dockerfile
touch docker/install.sh

サクッと dockerfileを書いて

FROM chinachu/mirakurun:latest

COPY docker/pcsc.tgz  /app
COPY docker/pcsc-install.sh /app
##
RUN apt-get update && \
  apt-get upgrade -y && \
  apt-get -y install git cmake pkg-config libpcsclite-dev


RUN /app/pcsc-install.sh

ビルド時に必要なライブラリを追加する

#!/usr/bin/env bash


tar zxvf pcsc.tgz
cd pcsc
make

cp libpcsclite.so.1.0.0 /usr/lib/arm-linux-gnueabihf/libpcsckai.so
sed '/Libs:/s|lpcsclite|lpcsckai|' \
   /usr/lib/arm-linux-gnueabihf/pkgconfig/libpcsclite.pc  \
   >/usr/lib/arm-linux-gnueabihf/pkgconfig/libpcsckai.pc


cd /opt/node_modules/arib-b25-stream-test/src
sed -i 's|libpcsclite|libpcsckai|'  Makefile
make all
make install

cd /app
git clone --depth 1  https://github.com/tsukumijima/libaribb25.git
cd libaribb25
mkdir build
cd build
cmake -DWITH_PCSC_PACKAGE=NO -DWITH_PCSC_LIBRARY=pcsckai ..
make install
cp /usr/local/bin/arib-b25-stream-test /opt/bin/arib-b25-stream-test
cd /app


echo end

これで、docker版のmirakurun でも、b25やrecdvb関連のコマンドで動作チェックが可能になった。ずいぶん昔に買って放置しているUSBのチューナーや自作のアンテナケーブルが多くあり、ちゃんと動くか片っ端からテストしたいので手軽にDocker環境が作れるのは嬉しい。

chinachuまるごと入れても良かったのだが、chinachu のpm2エラーログが溢れまくってraspiのSDカードもUSBメモリも破壊されたので、使いたくありません。

2023-07-27追加

pcscd が動かなかったので、docker file を見直した(apt upgrade を追加)

puppeteer でキャッシュフォルダのエラー

puppeteer で自動実行してたら、ずっとエラーになるのでおかしいなと思って調べた。

自分の環境では動くけどサーバーで自動実行したら動かない。

 throw new Error(`Could not find Chromium (rev. ${this.puppeteer.browserRevision}). This can occur if either\n` +
                          ^

Error: Could not find Chromium (rev. 1083080). This can occur if either
 1. you did not perform an installation before running the script (e.g. `npm install`) or
 2. your cache path is incorrectly configured (which is: /root/.cache/puppeteer).

理由は、キャッシュフォルダだった。

/root/.cache/puppeteer

キャッシュフォルダは、uid から拾わずに、env から拾っている。そういえば、sudo して実行してたわ。

対応策

export HOME=/home/takuya
sudo -u takuya node bin/my-puppeteer.js

rclone でSFTPに接続できない。

rclone で sftpに接続しようとしたらエラーになる。

NewFs: couldn't connect SSH: ssh: handshake failed: ssh: unable to authenticate,

原因がわからずパニックになるが、sshはRSA鍵をデフォルトで無効化したことを思い出した。多分これだろうと予想を立てて対応

sshd のログを見てみる

Mar 19 22:22:05 docker sshd[507223]: userauth_pubkey: key type ssh-rsa not in PubkeyAcceptedAlgorithms [preauth]

ssh-rsa not in PubkeyAcceptedAlgorithms なので、公開鍵のタイプ(アルゴリズム)が異なるのでエラーって感じですね、

サーバー側に設定を追加

/etc/ssh/sshd_config

PubkeyAcceptedAlgorithms +ssh-rsa
sudo service sshd reload

つながった。

考えられる原因。

rclone がSSHプロトコルRSAを使っている。rcloneはSFTPをRSAで行おうとしている。rcloneが古いのかもしれない。最新版のビルドでは対応済みっぽいですね。

sshdの仕様変更が思わぬ波及効果を生み出している。

致命的な問題が出ない限りRSA鍵の許可を追加でも問題は起きないと思う。

apt-cacher をnginxの後ろに置こうとしてハマる

apt-cacher を443 でnginxの後ろに置こうとした。

動かない。

ログを見た。

10.10.1.240 - - [17/Mar/2023:17:45:20 +0900] "CONNECT esm.ubuntu.com:443 HTTP/1.1" 400 166 "-" "-"
10.10.1.240 - - [17/Mar/2023:17:45:20 +0900] "CONNECT esm.ubuntu.com:443 HTTP/1.1" 400 166 "-" "-"
10.10.1.240 - - [17/Mar/2023:17:45:20 +0900] "CONNECT download.docker.com:443 HTTP/1.1" 400 166 "-" "-"
10.10.1.240 - - [17/Mar/2023:17:45:21 +0900] "CONNECT esm.ubuntu.com:443 HTTP/1.1" 400 166 "-" "-"
10.10.1.240 - - [17/Mar/2023:17:45:21 +0900] "CONNECT esm.ubuntu.com:443 HTTP/1.1" 400 166 "-" "-"
10.10.1.240 - - [17/Mar/2023:17:45:22 +0900] "CONNECT download.docker.com:443 HTTP/1.1" 400 166 "-" "-"
10.10.1.240 - - [17/Mar/2023:17:45:23 +0900] "CONNECT esm.ubuntu.com:443 HTTP/1.1" 400 166 "-" "-"
10.10.1.240 - - [17/Mar/2023:17:45:23 +0900] "CONNECT esm.ubuntu.com:443 HTTP/1.1" 400 166 "-" "-"
10.10.1.240 - - [17/Mar/2023:17:45:26 +0900] "CONNECT download.docker.com:443 HTTP/1.1" 400 166 "-" "-"
10.10.1.240 - - [17/Mar/2023:17:45:27 +0900] "CONNECT esm.ubuntu.com:443 HTTP/1.1" 400 166 "-" "-"
10.10.1.240 - - [17/Mar/2023:17:45:27 +0900] "CONNECT esm.ubuntu.com:443 HTTP/1.1" 400 166 "-" "-"

CONNECTじゃん。nginxでは対応できない。

そうだよね。冷静に考えたらリバプロではく、フォワード・プロキシになるので、CONNECTが必要だよね。CONNECTなど素のnginxで動くはずもないので諦める方がいい。

nginx で Connect できなくても stream で流せば良いんでしょ?

nginxをプロキシで使ってるとついつい次のような発想になりがち

nginxでTCPさばきました

stream で流すって単なるNAT(SNAT+DNAT)じゃん?

それ、iptablesのDNATでやったほうが良いよ。

nginxだと、SNATと同等になるので接続ログの管理がめんどくさいいと思います。

素のTCPしかしゃべらないプロトコルを強引にSSL化するSSLアクセラレータとしてTCPをSTREMする場合は、次のようになるので、この場合はiptablesよりnginxに理由があるのですが、streamでTCPを捌くだけの記事を読んでいてiptablesがめんどくさいんだろうなわかる、とおもいつつもnginxでやる意味・・・とモヤッとした。

stream{
  upstream apt-cacher {
    server my_service:8000;
  }
  map $target $name{
    example.tld apt-cacher
    default 127.0.0.1:443
  }

  server {
    listen  127.0.0.2:443 ssl;
    proxy_pass $name;
    ssl_preread on;
    ssl_certificate     /var//certs/nginx/cert.pem;
    ssl_certificate_key /var//certs/nginx/key.pem;

    proxy_connect_timeout 1s;
    proxy_timeout 3s;
  }
}

上記のように平文通信の間にnginxを挟み込んで、TLS化するのはありだと思った。

stream mapを使ってALPNホスト名ごとに接続先を変えるのは便利だと思う。

apt-cacher で試したが、httpsのaptではうまく動かなかった。

参考資料

https://stackoverflow.com/questions/34741571/nginx-tcp-forwarding-based-on-hostname

ubuntuのaptで入れたsslhで細かい設定を行う。

ubuntu の apt で入れた sslh が微妙だった件。

ubuntu の apt で入れた sslh にいくつか問題があった。どのようにめんどくさいかというと、設定の追加がほぼ不可能なのだ。

/etc/init.d/sslh/ をベースにしていて、 init.dが/etc/default/sslhsourceして起動するが、init.d は systemd 管理下に置かれたので、 sourceをせずにsystemdに変数を設定として文字列を拾われる。

このため/etc/default/sslh/bin/shを使うsource前提にならず、systemd経由の起動されるため変数展開が反映されない。このため起動設定をbin/shで記述ができなくなってる

さらに、sslhは forking で起動するのに、foreground が前提でtype simple に固定されている。そのためpkillで一時的に止めることもかなわない。ユニットの編集は必須である。

さらに、inetd を基本線にしているのか、設定をsystemdに書くのか、init.dでやるのかdefaultsに書くのかと判断に迷う。恐ろしく設定がめんどくさい。

apt で導入される systemdユニットを捨てて書き直すか、自分でカスタマイズするしかない。

systemd ユニットを上書きする

 sudo systemctl edit sslh.service

次のように上書きした。

# /etc/systemd/system/sslh.service.d/override.conf
## 2023-03-17 /etc/defaultsに変数が使えないので代替案
[Service]
ExecStartPre=/usr/bin/bash /etc/sslh/sslh-start-pre.sh
ExecStartPre=/usr/bin/bash /etc/sslh/transparent-iptables.sh
KillMode=control-group
PIDFile=/run/sslh/sslh.pid
KillMode=control-group
KillSignal=SIGTERM
Type=simple

起動前にシェルスクリプトを実行して /etc/default/sslhを動的に書き換えることにした。

/etc/sslh/sslh-start-pre.sh

シェルスクリプトで動的に書き換えるために、テンプレートを使い変数を展開し、/etc/defaults を書き換えるようにした。

#!/usr/bin/env bash
#DAEMON_OPTS="--user sslh --listen <change-me>:443 --ssh 127.0.0.1:22 --ssl 127.0.0.1:443 --pidfile /var/run/sslh/sslh.pid"


ADDR=$( ip -o  addr show | grep eth | grep -oP '192.168.[0-9]{1,3}.[0-9]{1,3}(?=/24)' )
OPTS="--user sslh --listen $ADDR:443 --ssh 127.0.0.1:22 --tls 127.0.0.1:443 --pidfile /run/sslh/sslh.pid"
OPTS="-F/etc/sslh/sslh.conf --listen $ADDR:443"


DAEMON_OPTS=$OPTS

echo "# Default options for sslh initscript
# sourced by /etc/init.d/sslh

# binary to use: forked (sslh) or single-thread (sslh-select) version
# systemd users: don't forget to modify /lib/systemd/system/sslh.service
DAEMON=/usr/sbin/sslh


# auto generate by takuya
DAEMON_OPTS=$OPTS


" > /etc/default/sslh

リッスンするIPアドレスとポートをDHCPの配布状況に応じて書き換える。

/etc/sslhに設置した設定ファイルを使うようにする。

透過プロキシ用iptables

#!/usr/bin/env bash
## sslhでtransparent を使うには、必要な設定
##

IFACE=$( ip  addr show | grep eth | grep -P '192.168.2.[0-9]{1,3}(?=/24)' | grep -oP 'eth[0-9]' )

function add(){
  sysctl -w net.ipv4.conf.default.route_localnet=1
  sysctl -w net.ipv4.conf.all.route_localnet=1

  # DROP martian packets as they would have been if route_localnet was zero
  # Note: packets not leaving the server aren't affected by this, thus sslh will still work
  iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
  iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP

  # Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
  iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f

  # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
  iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f

  # Configure routing for those marked packets
  ip rule add fwmark 0x1 lookup 100
  ip route add local 0.0.0.0/0 dev lo table 100
}

function down(){

  iptables -t raw -D PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
  iptables -t mangle -D POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
  iptables -t nat -D OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
  iptables -t mangle -D OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
  ip rule del fwmark 0x1 lookup 100
  ip route del local 0.0.0.0/0 dev lo table 100

}

function main(){
  case $1 in
    add)
      add;
      ;;
    del)
      down;
      ;;
  esac

}


main $@

透過プロキシのスクリプトを、nftablesで書き直した例

#!/bin/sh

function sslh_transparent_add_nft(){
  ## router ip
  LAN_IP=$(ip a s br-lan | grep -oP '(?<=inet )[\d\.]+(?=/)')
  LAN_NET=$(ip a s br-lan | grep -oP '(?<=inet ).+(?= brd)')

  ## up
    nft add table sslh_tproxy
    nft add chain sslh_tproxy sslh_mark
    nft add  rule sslh_tproxy sslh_mark mark set 0x1 accept
    nft add chain sslh_tproxy prerouting { type filter hook prerouting priority mangle\; }
    nft add  rule sslh_tproxy prerouting meta l4proto tcp socket transparent 1 jump sslh_mark
    nft add chain sslh_tproxy output { type route hook output priority mangle\; }
    nft add  rule sslh_tproxy output oifname "eth0" meta l4proto tcp tcp sport 443 jump sslh_mark
    ## sslh process output rule
  ip rule add fwmark 0x1 lookup 100
  ip route add local 0.0.0.0/0 dev lo table 100
  ## NAT loop back
    nft add chain sslh_tproxy postrouting { type nat hook postrouting priority srcnat\;}
  nft add  rule sslh_tproxy postrouting ip saddr $LAN_NET ip daddr { 192.168.2.5, 192.168.2.21 } tcp dport 443 snat to $LAN_IP

}

function sslh_transparent_del_nft(){

  ## down
  ip rule del fwmark 0x1 lookup 100
  ip route del local 0.0.0.0/0 dev lo table 100
  if nft list tables | grep -wq 'table ip sslh_tproxy' ; then
    nft delete table sslh_tproxy
  fi

}

function sslh_transparent_start(){
  sslh_transparent_del_nft;
  sslh_transparent_add_nft;
}
function sslh_transparent_stop(){
  sslh_transparent_del_nft;
}

function main(){
  #  sslh_transparent_start;
  case $1 in
    "add")
      echo transparent iptables,route added.
      sslh_transparent_start;
      ;;
    "del")
      echo transparent iptables,route removed.
      sslh_transparent_stop;
      ;;
    *)
      echo "usage"
      echo "     " $0 ' del'
      echo "     " $0 ' add'
  esac
}

if [[ $0 =~ 'transparent-sslh.sh' ]] ; then
  # execute する場合
  main $*
fi

/etc/sslh/sslh.conf

リッスンするアドレスとポートは、systemdから起動時のオプションで与えるため設定では省略

systemdはforegroundは常にTrueを想定するのでtrueにした。

foreground: true;
inetd: false;
numeric:false;
transparent: true;
timeout: 2;
user: "sslh";
pidfile: "/var/run/sslh.pid";
chroot: "/etc/sslh";


listen:()

protocols:
(
  { name: "ssh"; service: "ssh"; host: "localhost"; port: "22";
    keepalive: true; fork: true; tfo_ok: true },
  { name: "tls"; host: "localhost"; port: "443";  tfo_ok: true }
);

つながった

curl で動作確認して、疎通を確認した。

これで無事にsslhを使って接続を仕分けられる。

sslhのメリット/ nginxの設定がIP非依存に

現在では、nginxの前段にsslhを入れている。

nginxの設定では、次のように、listenを記述している。

listen 127.0.0.1:443 ssl http2;

このように書けるので、nginxの設定ファイルを使い回せるようになった。環境依存の固定IPアドレスがnginxの設定ファイルから消えて、nginxの設定が環境非依存になりコピペがしやすくクラスタリングも楽になった。CD/CIファイルの記述も減った。

初期接続開始のTCPセッション・TLSセッション時間が極僅かだけ遅くなったが許容範囲だった。

2023-06-27

iptablesで書くと優先度の管理が面倒になったし、openwrtではiptables記述が警告になったので、思い切ってnft(nf_tables)に書き直した。

プロセス管理でプロセスグループをkillする

プロセスグループをKILLしたい。

プロセスからfork したプロセスから forkしたプロセス、子プロセス・孫プロセスを除去したい。

プロセスグループの確認

ps -ejH で PGIDとSIDを確認する

ps -ejH | 
  PID  PGID   SID TTY          TIME CMD
19666 19666 19666 ?        00:00:00   ssh
19668 19666 19666 ?        00:00:00     ssh

まともなプログラムを書いているなら、ForkしたときにPGIDを設定して子プロセスとして生成しているはず。

プロセスの親子関係や、プロセスグループ、プロセスの祖先と子孫がたどれるようにプロセスは生成される(ようにプログラムを書いているはず、もし辿れないなら意図的に別プロセスで起動しようとして失敗しているかもしれない。)

プロセスをkillする

親のプロセスIDがわかるなら、pkillを使うのが早い

pkill -P 親のPID

killコマンドで負数を指定する(pkillがない場合など)

kill -- -1234

プロセスを識別せずに全部消したい

commandの文字列にマッチさせてすべて除去したいのであれば killall が使える。

killall でコマンドを全部消す。

killall /usr/bin/ssh

複数起動していて残すべきものがない場合は、すべて消せる。そのときは killall で手っ取り早い。

また、pkillでもできる。

pkill /usr/bin/ssh

プロセスを消す場合には起動時に覚えておく。

pidファイルを作って保存しておけば、PIDファイルを見てグループを除去できる。

普段使わないから、なかなか覚えられないんですよねぇ。

参考資料

openWrtのFirewallにホットプラグする。

OpenWrtはホットプラグで「〇〇したとき」に任意のスクリプトを実行できる。

interface(iface)で、接続時に〇〇するとかできる。

Firewallが追加されたときに、なにかしたいと思ったが、公式サイトに記述がない。

イベントを調べた。

mkdir /etc/hotplug.d/firewall
touch /etc/hotplug.d/firewall/sample.sh
chmod +x /etc/hotplug.d/firewall/sample.sh

/etc/hotplug.d/firewall/sample.sh

## 実行時に変数を保存して調べる。
echo '-----------' > /tmp/firewal.$ZONE
env >> /tmp/firewal.$ZONE

環境変数でファイルを調べる。 Firewallの追加はゾーン単位で起動され非同期(ノン・シーケンシャル / 並列)で行われる。

イベントの識別。

変数は次のようになっていて、必要なイベントを拾うことができる。

環境変数
ZONE FWゾーン wan
USER 実行ユーザ root
ACTION 手順種別 add
SHLVL シェルの深さ 1
HOTPLUG_TYPE 種別 firewall
LOGNAME ログ名 root
DEVICENAME バイス名? 空白
PATH 実行PATH /usr/sbin:/usr/bin:/sbin:/bin
INTERFACE インターフェース vpn_net
PWD 実行DIR /
DEVICE バイス eth1

サンプル

case と if を使って必要なイベント時にだけ実行されるようにする。

#!/bin/bash


function my_firewall(){
    case $1  in
      add)
        my_iptables add
        ;;
      remove)
        my_iptables add
        ;;
    esac
}


TARGET_ZONE=wan
TARGET_NAME=pppoe_isp
TARGET_HOTPLUG=fiewall


function main(){
  if [[ ! $HOTPLUG_TYPE == $TARGET_HOTPLUG ]]; then
    return 0;
  fi
  if [[ $ZONE == $TARGET_ZONE && $DEVICE==$TARGET_NAME  ]];then
    my_firewall $ACTION
  fi
}

main;

ホットプラグで便利

ホットプラグでファイアウォール使うと、スクリプトを使ってiptablesを使いやすい。LuCIに書くと複雑になりがちな条件をまとめて書くことができて便利だ。

参考資料

https://openwrt.org/docs/guide-user/base-system/hotplug

curlが404でif文を判定する。exit code を httpステータスに従わせる。

curlは 接続に成功したら exit code == 0 である。

接続に成功したら、404・500でも 結果は 0 になる。

curl -s  http://example.tld ; echo $?

httpステータスコードを検証する

接続に成功し、HTTP取得に成功したときだけ何かをしたい。

curl --fail -s  http://example.tld && echo Success

稼働チェックをするときに重要です。

ファイルを空にするlinuxコマンド

ファイルを空っぽにしたい。

dhcp leases とか log とかファイルを空っぽにしたい事があると思います。

rm && touch

単純に考えると、rm して touch ですが。パーミッションが初期化されるので面倒です。

rm fileA && touch fileA

空文字を書き出す。(リダイレクト)

/dev/null から空っぽを取り出せばいいのです。

cat /dev/null  > /tmp/dhcp.leases

この他にも、リダイレクトを応用すると次のような方法があります。

: > file
echo -n > file
print '' > file

bash の コロン(:)は 何もしない(true)です。なので、: > a は次のようにもかけます。
また falseも結果としておなじになるのでファイルを空っぽにすることができます。

: > file 
true > file
false> file

ファイルを空にするにはリダイレクトが便利なようです。

空っぽへ編集する。

sed を用いて、空ファイルへ編集すればいいとわかります。

sed 'd' -i a

sed で全行(指定省略)にたいして、d (delete)を発行します。

同等のことはvi/ex/ed コマンドでもできるかもしれませんが試してません。

vim なら ex モードを使って書くことも可能ですが、現実的ではありませんでした。

vim -e +'norm dG' +'wq'  fileA

空っぽのファイルをコピーする

/dev/null はいつでも空っぽなので cp コマンドでもできます。

cp /dev/null file

dd する

/dev/null はいつでも空っぽなので dd コマンドでもできます。

dd if=/dev/null of=/tmp/echo bs=1 count=0

truncate コマンド

この目的(ファイルを指定サイズにする)ためには、truncate コマンドが便利です。

truncate -s0 file 

ただし、busyboxなどの極限環境ではtruncateは使えないことがあると思います。

alias を使う

truncate がない場合などに、aliasや関数を使うと便利かもしれない。

function trancate(){
  dd if=/dev/null of=$1 bs=$2 count=1
}

参考資料

vimをパイプにする - 余白の書きなぐり

Let'Encrypt発行証明書を使って中間CAを作るとどうなるのか。

Let'Encrypt発行の証明書を使って中間CAを作るとどうなるのか。

Let'Encrypt発行の証明書は、次の用途に使える。

openssl x509 -purpose -noout   < cert.pem
Certificate purposes:
SSL client : Yes
SSL client CA : No
SSL server : Yes
SSL server CA : No
Netscape SSL server : Yes
Netscape SSL server CA : No
S/MIME signing : No
S/MIME signing CA : No
S/MIME encryption : No
S/MIME encryption CA : No
CRL signing : No
CRL signing CA : No
Any Purpose : Yes
Any Purpose CA : Yes
OCSP helper : Yes
OCSP helper CA : No
Time Stamp signing : No
Time Stamp signing CA : No

openssl x509 -purpose で見る限りxxx CA no が並んでいる。Any Purpose CA : Yes がYESなので、ワンちゃん使えるかも?みたいな気持ちになって、試してみようと思う。*1

認証局を作る。

前回に中間CAを作ったりして証明書を作る手順は理解できたので、Let'Encryptの鍵と証明書で試してみることができる。

認証局ディレクトリを整える。

mkdir MyCA
cd MyCA
mkdir ./newcerts
mkdir ./requests
touch index.txt
echo 00 > serial
echo 00 > crlnumber

認証局用の設定を作る

cat /etc/ssl/openssl.cnf \
 | sed -n '/\[ ca \]/,$p' \
 | sed  '/\[ tsa/,$d' \
 | sed -e '/^keyUsage = nonRepudiation/s|^|# |' \
 | sed -e '/keyUsage = cRLSign/s|^# ||' \
 | sed -e 's|365|3600|'  \
 | sed -e 's|\./demoCA|\.|'  \
 > myCA.openssl.cnf

Let'Encryptから鍵と証明書を取り出す。

sudo cp /etc/letsencrypt/live/${DOMAIN}/privkey.pem ./
sudo cp /etc/letsencrypt/live/${DOMAIN}/cert.pem ./
sudo cp /etc/letsencrypt/live/${DOMAIN}/chain.pem ./
sudo chown takuya:takuya privkey.pem
sudo chown takuya:takuya cert.pem
sudo chown takuya:takuya chain.pem

証明書を発行する

任意の公開鍵を作り署名リクエストを作る。

openssl genrsa > server.priv.key
openssl req -new \
  -subj "/C=JP/ST=Kyoto/L=Kyoto City/O=acid/CN=raspi3.lan" \
  -key server.priv.key \
  > server.csr

Let'Encryptの秘密鍵で任意の証明書に署名する。

openssl ca -policy policy_anything \
   -in server.csr \
   -keyfile privkey.pem \
   -cert  cert.pem \
   -config myCA.openssl.cnf

署名して出来た証明書を取り出す。

openssl x509 -text < newcerts/00.pem
openssl x509 < newcerts/00.pem > server.crt.pem

検証のために証明書チェーンを作る。

cat cert.pem chain.pem  > full.pem

検証する。

openssl verify -verbose -CAfile full.pem server.crt.pem

検証結果。

CN = xxxx.mydomain.com
error 24 at 1 depth lookup: invalid CA certificate
C = US, O = Let's Encrypt, CN = R3
error 25 at 2 depth lookup: path length constraint exceeded
O = Digital Signature Trust Co., CN = DST Root CA X3
error 10 at 4 depth lookup: certificate has expired
CN = xxxx.mydomain.com
error 32 at 1 depth lookup: key usage does not include certificate signing
error server.crt.pem: verification failed

こうなる。

key usage does not include certificate signing

利用不可の証明書が出来た

予想通りの結果でした。証明書を作成する手前でRejectされるかと思ったが、証明書の発行は出来ている。証明書を通用させようとして検証するとエラーになる。なるほどね。

使えないとわかった。

証明書自体は作れるが、証明書を利用時に検証されて、エラーになる。

SAN値ルーターのIPとか入れてローカルアドレスHTTPS化できたら嬉しかったんだけど。無理なようです。残念。

参考資料

https://serverfault.com/questions/111625/openssl-x509-purpose-flag-any-purpose-what-is-this

The Any Purpose : Yes and Any Purpose CA : Yes lines from the openssl x509 -purpose are special. It is openssl specific and represents what the certificate will be validated for when used with ancient software versions that do not check for extensions.

certificate — OpenSSL x509目的フラグ「任意の目的」これは何ですか?

'「任意の目的」設定は、何でも通過させ、チェックをまったく実行しないものです。もともとは、他に選択肢がなく、結果に耐えられる場合に壊れた証明書を使用する方法としてそこに置かれていました...それ以来、「任意の目的」が設定されている場合でも、コードでCAチェックが必須になっています。したがって、実際にその証明書をCAとして使用しようとすると、拒否されます。

*1:といってもAny Purposeは歴史的経緯で残ってるだけで使えるものではないのだけれど