春は若者 夏は年寄 秋は幼子 冬は現役

出典:総務省統計局
エンジニアです。管理職にはつきたくありません。
このブログはWordPressで動いている。で、WordPressのバージョンアップを久しぶりに行ったところ、ヘルスチェックなるものにPHPが古いから新しくしろというメッセージが出ていた。
ああそうですか、と思い調べたらバージョン5.6を使っておりサポートも終了していた。そりゃダメねと軽い気持ちでバージョンアップをしてからWordPressにアクセスすると「致命的なエラー」などといままで見たことのないメッセージだけが画面に表示されてコンテンツが全く見られない。
再起動しても回復はしない。いつもならスナップショットを取ってから作業するのだが、今回に限ってサボってしまったせいで結構焦ってしまった。
いや、こんな時こそ落ち着かねばと小一時間ほど放置してからログを調べた。
問題点1.
/var/log/httpd/error.logには「PHP Fatal error:Array and string offset access syntax with curly braces is no longer supported」と記録されていた。
Syntax エラーだと。。そんな馬鹿な。。内容は書いてある通りだが、調べてみるとPHPの配列アクセスに{}が使えなくなったとのこと。使っているWordPressのプラグインに{}を使った配列アクセスがあったようだった。幸いエラーが発生した1か所だけ修正するとこのエラーは消えた。
問題点2.
これだけで問題は終わらなかった。
次なるエラーは「PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function~」
調べてみるとWordPressのバージョンアップで置き換わった関数の引数が変わったようで、使っているテーマから渡されている引数の数が少なくてエラーになっているということであった。こんなの直すのしんどいよ。。ということで同じテーマの最新版がGitHubで配布されていたので入手してディレクトリごと置き換えた。これでこの問題は解決して投稿記事を見られる状態にはなった。
問題点3.
だが、それでも問題は終わらなかった。管理用サイトにアクセスするとやはり致命的なエラーが起きた。これは管理用サイトで使っていたプラグインがエラーを起こしていた。Search Regexというプラグインだ。どうやらPHPで静的に宣言されていない関数を静的関数のように呼ぶとエラーになるように厳格にチェックされるようになったようだ。こちらは問題2と違って最新版でも修正されていないという情報を見たのでプラグインそのものを削除して対応した。
こうして何だかんだで復活したのでついでに書き込みしてみた次第であったが、普段PHPを使っていないのでこんなに変更があったのを知らなかった。PHPのシステムを保守している人たちには衝撃だったんだろうなあ。
と書き終えたところで、左上のCount Per Dayの見出しと数字が逆になっていることに気づいた。そのうち直したい。
P.S.
直しましたよっと。でもなんかスタイルシートが読めてない。これまたそのうち直したい。
# pwd
/var/www/html/blog/wp-content/plugins/count-per-day
# diff counter.php.bk counter.php
1446a1447
> echo __($instance[$k.'_name']).':';
1454c1455
< echo '</span>'.__($instance[$k.'_name']).':</li>';
---
> echo '</span></li>';
修正前
if ( ($k == 'show' && is_singular()) || $k != 'show' )
{
$f = str_replace( $this->funcs, $this->cpd_funcs, $k );
echo '<li class="cpd-l">';
echo '<span id="cpd_number_'.$k.'" class="cpd-r">';
// parameters only for special functions
if ( $f == 'getUserPerDay' )
echo $count_per_day->getUserPerDay($count_per_day->options['dashboard_last_days']);
else if ( $f == 'show' )
echo $count_per_day->show('', '', false, false);
else
echo call_user_func( array(&$count_per_day, $f) );
echo '</span>'.__($instance[$k.'_name']).':</li>';
}
修正後
if ( ($k == 'show' && is_singular()) || $k != 'show' )
{
$f = str_replace( $this->funcs, $this->cpd_funcs, $k );
echo '<li class="cpd-l">';
echo '<span id="cpd_number_'.$k.'" class="cpd-r">';
echo __($instance[$k.'_name']).':';
// parameters only for special functions
if ( $f == 'getUserPerDay' )
echo $count_per_day->getUserPerDay($count_per_day->options['dashboard_last_days']);
else if ( $f == 'show' )
echo $count_per_day->show('', '', false, false);
else
echo call_user_func( array(&$count_per_day, $f) );
echo '</span></li>';
}
いつの間にかGitlabの8が出ていた。
いくつか機能も増えていて良さそうなのでGitlab7からバージョンアップすることにした。
バージョンは8.8.5。
バージョンアップ自体は公式の通りにやれば問題なくできる。
まずyum用のリポジトリができてるそうなのでリポジトリを追加する。
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | bash
そしてインストール
yum install gitlab-ce
これでおしまい。どんどん便利になるな。
サブディレクトリで動かすのもかなり楽にできるようになっている。
設定を変更する。
/etc/gitlab/gitlab.rb external_url http://server:port を external_url http://server:port/gitlab にする。
これで設定ファイルの編集はおしまい。これまた非常に楽。
あとは以下で再構成する。
gitlab-ctl reconfigure
chefが頑張ってくれていろいろなファイルを更新してくれる。
確認出来た範囲で以下が更新されていた。
/var/opt/gitlab/gitlab-rails/etc/gitlab.yml /var/opt/gitlab/gitlab-rails/etc/unicorn.rb /var/opt/gitlab/gitlab-shell/config.yml /var/opt/gitlab/nginx/conf/girlab-http.conf /var/opt/gitlab/gitlab-rails/etc/relative_url.rb
特に最後のファイルは新たに作られていた。
以前はapplication.rbに書かれていた内容がここに書かれていた。
あとは再起動すればよい。
gitlab-ctl restart
もう待ってる時間を除けば1分でできるね。
役割ごとにサーバを分けるようにサーバ構成の見直しをした。
その一環でGitLabを専用サーバに立て直すことにした。
以前はV6を使っていたがせっかくなのでV7にすることにした。
物凄くインストールが楽になっていた。ありがとう。
ただデフォルトだとルートで動いてしまう。つまり以下でアクセスすることになる。
http://サーバ/
うちのサーバ構成ではnginxをリバースプロキシにしてURLを見てサーバの振り分けをしてるのでこれでは少し具合がわるい。
http://サーバ/gitlab
にURLを変更することにする。
まずはインストール。以下のとおりにやればOKだ。
https://about.gitlab.com/downloads/
インストール時のバージョンは7.2.2だったので適宜読み替えで。
$ curl -O http://downloads-packages.s3.amazonaws.com/centos-7.0.1406/gitlab-7.2.2_omnibus-1.el7.x86_64.rpm $ yum install openssh-server $ systemctl enable sshd $ systemctl start sshd $ yum install postfix $ systemctl enable postfix $ systemctl start postfix $ rpm -i gitlab-7.2.2_omnibus.1-1.el7.x86_64.rpm
$ vi /etc/gitlab/gitlab.rb external_url 'http://サーバ' <= なぜかhttp://をつけてくれてないので付ける
$ gitlab-ctl reconfigure $ firewall-cmd --permanent --add-service=http $ systemctl reload firewalld
通常ならこれでインストールと起動は完了。非常に楽だ。V6の時に何時間もかけたのは何だったのだろう。。
http://サーバ でアクセスするとGitLabのログイン画面が表示されるだろう。
さて、このままだとURLがルートのままなので/gitlabに変更する。
/opt/gitlab/embedded/service/gitlab-rails/config/application.rb
に以下ようなやり方が書いてある。これに従えばOKだ。
$ vi /opt/gitlab/embedded/service/gitlab-rails/config/application.rb 冒頭略 # Relative url support # Uncomment and customize the last line to run in a non-root path # WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this. # Note that following settings need to be changed for this to work. # 1) In your application.rb file: config.relative_url_root = "/gitlab" # 2) In your gitlab.yml file: relative_url_root: /gitlab # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" # 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab" # 5) In lib/support/nginx/gitlab : do not use asset gzipping, remove block starting with "location ~ ^/(assets)/" # # To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production # config.relative_url_root = "/gitlab" <= 1)はここのことだからコメントを外す
2) In your gitlab.yml file: relative_url_root: /gitlab
$ vi /var/opt/gitlab/gitlab-rails/etc/gitlab.yml 冒頭略 relative_url_root: /gitlab <=ここ
3) In your unicorn.rb: ENV[‘RAILS_RELATIVE_URL_ROOT’] = “/gitlab”
$ vi /var/opt/gitlab/gitlab-rails/etc/unicorn.rb 最後に追加 ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
4) In ../gitlab-shell/config.yml: gitlab_url: “http://127.0.0.1/gitlab”
$ vi /var/opt/gitlab/gitlab-shell/config.yml gitlab_rul: "http:/127.0.0.1:8080/gitlab"
5) In lib/support/nginx/gitlab : do not use asset gzipping, remove block starting with “location ~ ^/(assets)/”
はやらなくてもよいようだ。
サービスを再起動
$ gitlab-ctl stop $ gitlab-ctl start
さて、これで設定は終わり。
ブラウザでhttp://サーバ/gitlabに繋いでみよう。ログイン画面が表示されるはず。
初期ユーザは以下なので、これでログインする。
Username: root Password: 5iveL!fe
新しいパスワードの設定を求められるので設定すると、ログイン画面に戻されるので新しいパスワードでもう一度ログインする。
ログイン後にユーザ名もrootから変更しておくといいだろう。
以下おまけ。
最新版では直っているようだが7.2.2の時は/assets下が/gitlab下にならないようである。
そのせいでうちのようにリバースプロキシで/gitlabのアクセスだけをgitlabサーバに振り分けしているとassets下が正しく振り分けられずにアイコンが正しく表示されない。
コードを直してしまうというのもあるが面倒なので以下で対処する。
/assets下もgitlabのサーバに振り分けする。
nginxの場合は以下のようにする。
location /assets { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Server $host; proxy_set_header Host $host; proxy_pass http://<gitlabサーバ>/assets; }
サーバ設定系の記事が続きます。
NFSサーバを立ててESXiから使っている訳ですがRAIDを組むためにHDDを追加したことは先の記事のとおり。
拡張性のあまりないPCを使ったので空きベイが足りなかった。
このとき何も考えていなかったのでCD-ROMドライブを外して5インチベイにHDDを2つ挿してしまいました。
そうです。OSをインストールしようとしたときになって「あっ!起動できない」と気づいたわけです。
いったんHDDを外すという手もありますが、せっかくなのでPXEサーバをたててネットワークインストールをすることにしました。
ということでメモ。
始める前にインストールイメージをどっかから用意しておく。
今回はCDを用意した。
必要なものをインストール
$ yum install tftp-server xinetd dhcp syslinux httpd
xinetdの設定をしてtftpを有効にする
/etc/xinetd.d/tftp service tftp { socket_type = dgram protocol = udp wait = yes user = root server = /usr/sbin/in.tftpd server_args = -s -v /var/lib/tftpboot <= -vつけ。/var/log/messageにログが出るよ。 disable = no <= yesから変える。 per_source = 11 cps = 100 2 flags = IPv4 }
xinetdを再起動
$ systemctl restart xinetd
DHCPサーバを用意する。
抜粋。
/etc/dhcp/dhcpd.conf option domain-name "dracula.mydns.jp"; <= ドメイン option domain-name-servers 192.168.1.1; <= DNS subnet 192.168.1.0 netmask 255.255.255.0 { filename "/pxeboot/pxelinux.0"; <= PXEの設定。 range 192.168.1.250 192.168.1.254; <= 割り当てに使うIP option routers 192.168.1.1; default-lease-time 600; max-lease-time 7200; }
サービス再起動
$ systemctl restart dhcpd
ここから本番。
ブートローダsyslinuxを準備する。
cp -p /usr/share/syslinux/pxelinux.0 /var/lib/tftpboot/pxeboot/ cp -p /usr/share/syslinux/menu.c32 /var/lib/tftpboot/pxeboot/
webサーバの公開ディレクトリに用意したCDをマウント。
webサーバのディレクトリにするのはそのままインストールに使うから。
$ mkdir /var/www/html/centos7 $ mount /dev/cdrom /var/www/html/centos7
インストーラのイメージファイルをPXEブートディレクトリにコピー
$ cp -p /var/www/html/centos7/iso/isolinux/initrd.img /var/lib/tftpboot/pxeboot $ cp -p /var/www/html/centos7/iso/isolinux/vmlinuz /var/lib/tftpboot/pxeboot
PXEブートの設定ファイル置き場を作成
$ mkdir /var/lib/tftpboot/pxeboot/pxelinux.cfg
PXEブートの設定ファイルを作成
/var/lib/tftpboot/pxeboot/pxelinux.cfg/default default menu.c32 label 1 menu label ^1) install centos 7 from pxeserver kernel vmlinuz append load initrd=initrd.img inst.repo=http://<pxeサーバ>/centos7/ <= CDマウントして公開したたもの。
inst.repoはhttp://ftp.riken.jp/Linux/centos/7/os/x86_64/など外部を使ってもいいと思う。
それならhttpdいらない。
ただし遅いと思うのでそれは覚悟。
最後にポートを開ける
$ firewall-cmd --permanent --add-port=67/udp <= Boostrap Protocol Server $ firewall-cmd --permanent --add-port=68/udp <= Bootstrap Protocol Client (BOOTP) $ firewall-cmd --permanent --add-port=69/udp <= Trivial File Transfer Protocol (TFTP) $ firewall-cmd --reload
これで準備は完了である。
あとはクライアントを同じセグメントに接続して起動すればいいのだ。
BIOSとかでPXEブートを有効にしてね。
先日NFSを構築してESXiからマウントするという記事を書いた。
実はNFSはソフトウェアRAID1を組んでいる。
たまにしかやららないので備忘。
あらかじめCentOSを入れてあったPCに後からHDDを2つ増設した。
この2つでRAIDを組むことにする。
NFSで公開する領域だけRAIDにしてます。
まずは必要なものインストール
$ yum install mdadm
追加したHDDのデバイスファイルを確認する
$ fdisk -l Disk /dev/sda: 500.1 GB, 500107862016 bytes, 976773168 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト Disk label type: dos ディスク識別子: 0x00045e9d デバイス ブート 始点 終点 ブロック Id システム /dev/sda1 * 2048 1026047 512000 83 Linux /dev/sda2 1026048 976773119 487873536 8e Linux LVM Disk /dev/sdb: 2000.4 GB, 2000398934016 bytes, 3907029168 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 4096 bytes I/O サイズ (最小 / 推奨): 4096 バイト / 4096 バイト Disk label type: dos ディスク識別子: 0x3f51115e Disk /dev/sdc: 2000.4 GB, 2000398934016 bytes, 3907029168 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 4096 bytes I/O サイズ (最小 / 推奨): 4096 バイト / 4096 バイト Disk label type: dos ディスク識別子: 0x17246bea
こんな感じで出力されると思う。まあ/dev/sdb, /dev/sdcのようだ。
それぞれにパーティションを切る。といっても全部で1パーティションにしてしまう。
/dev/sdb,/dev/sdc両方で以下をする。
若干省略気味で。
$ fdisk /dev/sdb コマンド : n <= NEW コマンドアクション e 拡張 p 基本領域(1-4) p <= 基本にする 領域番号(1-4): 1 <= デフォルトで 先頭シリンダ(1-xxx、default 1):1 <= デフォルトで 終点シリンダ(1-xxx default xxx):xxx <= デフォルトで コマンド: t <= パーティションID変更 select partition:1 <=1つなので1 16進コード: fd <= Linux raid autoのコード コマンド: w <=書き込んで終了
RAIDアレイの作成
$ mdadm --create /dev/md0 --level=1 --raid-device=2 /dev/sd[bc]
ごにょごにょ裏でアレイの構築が始まる
進捗の確認は以下でできる。
$ cat /proc/mdstat
設定ファイルは作ってくれないので作る。
先にARRAY情報を調べておく
$ mdadm --detail --scan ARRAY /dev/md0 metadata=1.2 name=nfs.dracula.mydns.jp:0 UUID=f98afbd0:563e89b4:8b3ae7ed:21a3790b
で、設定
/etc/mdadm.conf DEVICE /dev/sdb /dev/sdc ARRAY /dev/md0 metadata=1.2 name=nfs.dracula.mydns.jp:0 UUID=f98afbd0:563e89b4:8b3ae7ed:21a3790b
ARRAYにはあらかじめ調べたARRAY情報を書く。リダイレクトで追記するとよい。
ファイルシステムを作る。ext4でいいでしょう。
$ mkfs -t ext4 /dev/md0
オートマウントの設定
/etc/fstab /dev/md0 /マウントポイント ext4 defaults 1 2
マウントする
$ mount -a
片方のHDDが壊れても動いてしまうのでmdadmによる監視設定を入れる。
echo MAILADDR 送信先メールアドレス >> /etc/mdadm.conf
監視機能の実行と自動起動
$ systemctl start mdmonitor $ systemctl enable mdmonitor
通知のテスト
mdadm --monitor --test --oneshot --scan
ソフトバンクに送ろうとしたら拒否られた。ポートが違うとかなとかの情報が見つかったが
面倒なのでISPにメールすることにして転送対象にした。。。
VMware Workstationを使ってサーバを立てていたのだが仮想サーバが増えてきたのでESXi(当然無償版)の専用サーバを立ててそこに集約することにした。ただストレージ容量の多いマシンは確保できなかったので長らくお休みになられていたPCにNFSサーバとして復帰してもってESXiからマウントすることにした。
NFSサーバはCentOS7を使って構築した。
ただ、CentOSはデフォルトではNFSv4であり、ESXiはNFSv3までしかサポートしていないのでなかなか接続成功までが大変であった。
まずインストール
$ yum -y nfs-utils rpcbind
次にidmapdの設定をする
/etc/idmapd.conf [General] Domain = 自ドメイン [Mapping] Nobody-User = nfsnobody Nobody-Group = nfsnobody 他デフォルトのまま
NFSv3では動的なポートをいくつか使うのでファイアウォールと相性が悪い。
まずはそれを固定してしまう。
/etc/sysconfig/nfsに設定を入れる。MOUNTD_NFS_V3はいるのか分からんけど。。
RPCRQUOTADOPTS="--port ポート番号" LOCKD_TCPPORT=ポート番号 LOCKD_UDPPORT=ポート番号 STATDARG="--outgoing-port ポート番号 --port ポート番号" MOUNTD_NFS_V3="yes"
各サービスを立ち上げる、かつサーバ起動時に有効にする。
$ systemctl start nfs-idmap nfs-mountd nfs-server nfs-lock rpcbind $ systemctl enable nfs-idmap nfs-mountd nfs-server nfs-lock rpcbind
ポートが固定できたか確認する。
$ rpcinfo -p program vers proto port service 100000 4 tcp 111 portmapper 100000 3 tcp 111 portmapper 100000 2 tcp 111 portmapper 100000 4 udp 111 portmapper 100000 3 udp 111 portmapper 100000 2 udp 111 portmapper 100005 2 udp 20048 mountd 100024 1 udp 2050 status 100024 1 tcp 2050 status 100005 3 udp 20048 mountd 100003 3 tcp 2049 nfs 100003 4 tcp 2049 nfs 100227 3 tcp 2049 nfs_acl 100003 3 udp 2049 nfs 100003 4 udp 2049 nfs 100227 3 udp 2049 nfs_acl 100021 1 udp 32769 nlockmgr 100021 3 udp 32769 nlockmgr 100021 4 udp 32769 nlockmgr 100021 1 tcp 32803 nlockmgr 100021 3 tcp 32803 nlockmgr 100021 4 tcp 32803 nlockmgr 100011 1 udp 2052 rquotad 100011 2 udp 2052 rquotad 100011 1 tcp 2052 rquotad 100011 2 tcp 2052 rquotad
こんな感じで出ると思う。
portmapper, mountd, status, nfs, nlockmgrr, quotadが予定どおりのポート番号ならOK。
次にファイアウォールを設定して固定したポートを開放する。
ポート番号指定で開けてもいいけど、せっかくなのでサービスを定義して開けてみる
/lib/firewalld/servicesに使えるサービスを定義したxmlがある。
nfs, mountd, rpc-bindは定義されているのでそれ以外を定義する。
rquotd, nlockmgr, statdを追加する。
既存のサービスのファイルをコピーして中身を書きかえるのが吉。
shortやdescriptionは好きなものにすればよい。
rquotd.xml <?xml version="1.0" encoding="utf-8"?> <service> <short>rquotad</short> <description>define rquotad</description> <port protocol="tcp" port="ポート番号"/> <port protocol="udp" port="ポート番号"/> </service>
nlockmgr.xml <?xml version="1.0" encoding="utf-8"?> <service> <short>nlockmgr</short> <description>define nlockmgr</description> <port protocol="tcp" port="ポート番号"/> <port protocol="udp" port="ポート番号"/> </service>
statdはoutgoing分も纏めて定義してしまう。
statd.xml <?xml version="1.0" encoding="utf-8"?> <service> <short>statd</short> <description>define statd</description> <port protocol="tcp" port="ポート番号(outgoing)"/> <port protocol="udp" port="ポート番号(outgoing)"/> <port protocol="tcp" port="ポート番号"/> <port protocol="udp" port="ポート番号"/> </service>
これらと元々定義されているサービスを使ってfirewalldの設定に入れてポートを解放する。
$ firewall-cmd --permanent --add-service=mountd $ firewall-cmd --permanent --add-service=rpc-bind $ firewall-cmd --permanent --add-service=nfs $ firewall-cmd --permanent --add-service=rquotad $ firewall-cmd --permanent --add-service=nlockmgr $ firewall-cmd --permanent --add-service=statd $ firewall-cmd --reload
反映されたかを確認
$ firewall-cmd --list-all public (default, active) interfaces: enp3s0 sources: services: mountd nfs nlockmgr rpc-bind rquotad ssh statd ports: masquerade: no forward-ports: icmp-blocks: rich rules:
余計なサービスが表示されるなら以下で消しておく。
$ firewall-cmd --permanent --remove-servie=サービス名
これでポートの開放のOK。
後はNFSサーバのexportsを設定。
/exportsを192.168.1.0のネットワークに公開する例
/etc/exports /exports 192.168.1.0/255.255.255.0(rw,sync)
exportsを反映させて確認する。
$ exportfs -ar $ exportfs -v /exports 192.168.1.0/255.255.255.0(rwwdelay,root_squash,no_subtree_check,sec=sys,rw,secure,root_squash,no_all_squash)
最後にSELINUXのポリシーを変えて読み書きを許可する。
$ setsebool -P nfs_export_all_rw on
ここまででNFSサーバは構築できた。
このNFSをESXiからマウントしてデータストアとして使う。
だがここで問題が起こった。。
vSphere Clientを使ってESXiにログインし、データストアの追加を行った。
マウントは無事できた。
しかし、数分経つとNSFが切断されてしまう。数分経つと復旧したりしなかったりで非常に不安定となる。
ESXi5.5 Update1 でそういうバグがあるとの報告があったがESX5.1を使っても改善しなかった。
だが、どうやらvSphere Clientを立ち上げなおした直後は接続できていてしばらく経つと切断するという傾向が見えてきた。そこでESXiにログインしてCLIで操作することにした。
マウントは以下
$ esxcli storage nfs add -H NFSサーバ -s /exports -v データストア名
マウントは無事できた。
状態確認
$ esxcli storage nfs list Volume Name Host Share Accessible Mounted Read-Only Hardware Acceleration ----------- ----------- ----------------------- ---------- ------- --------- --------------------- データストア名 NFSサーバ /exports true true false Not Supported
問題ないようだ。
しばらく待ってもAccessibleはtrueのままである。
ここでvSphere Clientを立ち上げてESXiに接続してみる。
するととたんにAccessibleがfalseに切り替わった。
やはりvSphere Clientをつなぐとダメなようだ。
しかたがないのでVMware WorkstationをESXiに繋いでしのぐことにしよう。。
# 2014/10/05 追記
接続が不安定な問題は基本的なミスであった。
クライアントとして使っていたマシンのIPアドレスがNFSサーバとかぶってしまっていた。
ホント恥ずかしいくらい初歩的なミス。
VMwareのバグを疑ってごめんなさい。むしろ一時的にでも良く繋がったなというくらい。
いろんなブログでjavaを使ったHTTPSクライアントの実装について書かれている。
だが適当な記事が多すぎて困る。RFCを読んでいるのか疑問に思うことがほとんどだ。というかRFCの存在を知らないのかもしれない。
このブログではリンクは基本的に張らないことにしているが特別に張っておこう。
こちらはIPAが提供してくれているHTTP オーバー TLSを規定したRFC2818の和訳である。
http://www.ipa.go.jp/security/rfc/RFC2818JA.html#31
こちらはRFC9110の日本語訳である。(RFC9110はRFC2818の修正版)
https://tex2e.github.io/rfc-translater/html/rfc9110.html
javaはセキュリティライブラリをJSSEとしてコアパッケージと分離することでセキュリティ機能をサードパーティによるカスタマイズが可能となるような思想で設計されている。そのためSSL/TLSについても柔軟なカスタマイズが可能である。
逆に言うとしっかりと理解していないとセキュリティホールを作りこんでしまうことになる。世のブログはこれに該当してしまう記事が異常に多い。さらに悪いのはそれが正解であるかのように広がってしまっていることだ。
エンジニアの中にもそういったブログの記事が正解だと思っている人が思いのほか多いという事実があっては笑ってもいられない。
よく紹介されているHTTPSクライアントの実装はこうである。
TrustManager[] tm = { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] xc,String type) { } public void checkServerTrusted(X509Certificate[] xc,String type) { } } }; SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, tm, new SecureRandom()); URL url = new URL("https://hogehoge/foo/bar"); HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); conn.setDefaultHostnameVerifier(new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) {return true;} } ); 以下略まあ通信できるでしょう。しかしこれはやるべきことを全くやっていません。
このコードには簡単に以下の2つの問題がある。
1点目の問題では、証明書が送られてきさえすればそれが偽造されたものであろうと何だろうと通信OKとなってしまう。
2点目の問題では、信頼できる認証局から発行された証明書でありさえすればそれが目的のサーバのものでなくても通信OKとなってしまう。
1点目の偽造証明書にOKを出すのがダメなことは直感的にも分かるでしょう。
では、2点目はなぜダメなのでしょう。
たとえば、こんなケースがある。
上記のような実装をされたウェブブラウザであなたが銀行のサイトに接続したつもりでいます。
しかし、あなたが使っているDNSがレコード書き換えの攻撃を受けており実際には別のサイト(攻撃者が管理するサイト)に繋がってしまいました。
ですが、そのサーバには第三者機関から発行された証明書が設定されていました。
ブラウザは通信相手は期待したサーバであると判断して通信を続行してしまいます。
あなたは銀行のサイトだと思ってIDとパスワードを入力してしまいます。
攻撃者はあなたのIDとパスワードをまんまと手に入れてしまうわけです。
本来は偽のサーバに設定されている証明書のサーバ名とあなたが接続しようとしたサーバ名は異なっているので、接続は拒否されるべきである。ですが上記のコードではこれがなされません。さらに悪いことに上記のコードでは無条件で処理がされてしまうためユーザがそんなことが起こっていることを知る術もない。
では正しく処理するにはどう実装したらよいのだろうか。こうです。
URL url = new URL("https://hogehoge/foo/bar"); HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); 以下略非常に簡単です。なんのことはない。javaは特に設定を変えない限りデフォルトで正しく動作をするようになっている。
無理やりその動作を歪めてしまう必要はないのである。自己署名証明書を設定したサーバに対してはこのままで通信をしようとしても通信はできない。
それは当然である。なぜなら自己署名証明書は信用できる証明書ではないからだ。
どうにもならないのかといえばそんなことはない。自己署名証明書を信用できるという前提でクライアント側のトラストストアにインポートすればよいのだ。
これにより通信は可能になる。
ただ、これはあくまで普通の行為ではないということを認識したうえで行う必要がある。セキュリティとはかくも面倒くさいことであるが、こういったことが守られてようやく実用レベルの安全性をもったネットワークというのが実現できるのである。守られていてもクラッカーとはイタチごっこであるのに守られていない場合に安全であるなど言えっこないのである。
郵便局の不在連絡票が入っていた。
いつもは電話で再配達の申し込みをするのだが気まぐれにウェブページでしてみようとしたところ
あまりにも不親切で面倒になり断念した。
理由を以下に述べたいと思う。
1.連絡票に記載されている名称と入力欄の名称が異なって対応が分かりづらい。
2.郵便物の種類の選択欄で「ゆうパケット」は「ゆうパック」なのか「その他」なのかよく分からん。
3.郵便物の種類の選択欄で追跡番号を入力したときの欄と問い合わせ番号を入力したときのどちらかを選べば良いと思うが
両方選べてしまう上に一度選ぶと解除できない。
もうこの時点で分かりにくさ爆発。早々にネットで申し込むのはあきらめた。
用語をを統一するなんて基本だろう。。
その後QRコードでアクセスして申し込みをした。
同じ郵便局のシステムとは思えないくらいほとんど入力不要で良くできていた。
QRコードの読みとりがすぐできる機器があるのなら電話よりも簡単なくらいである。
それだけにあのウェブページの出来の悪さが際立って仕方がない。
どこに発注したのだろう。。利権が絡んでるところに発注したのかなあ。
さて、、次のjavaのコードを見て貰いましょう。
mainでほぼ同じ処理を2回しています。
違いは呼び出しているメソッドがtest1,test2と違うだけ。
そしてtest1,test2の違いはメソッド内の最初の行だけです。
結果を受け取った後に戻り値を更新しています。
その後、fooXの値を2行ずつ出力していますが何が出力されるか分かりますか?
public class Test { public static void main(String[] args) { Test test = new Test(); Bar bar1 = new Bar(100, "abc"); Foo foo1 = new Foo(bar1); Bar ret1 = test.test1(foo1); ret1.i = 10000; ret1.s = "ABC"; System.out.println(foo1.bar.i); //1回目処理の出力1 System.out.println(foo1.bar.s); //1回目処理の出力2 Bar bar2 = new Bar(100, "abc"); Foo foo2 = new Foo(bar2); Bar ret2 = test.test2(foo2); ret2.i = 10000; ret2.s = "ABC"; System.out.println(foo2.bar.i); //2回目処理の出力1 System.out.println(foo2.bar.s); //2回目処理の出力2 } public Bar test1(Foo foo) { Bar bar = new Bar(foo.bar.i, foo.bar.s); // ここでいろんな処理 return bar; } public Bar test2(Foo foo) { Bar bar = foo.bar; // ここでいろんな処理 return bar; } } class Foo { Bar bar; public Foo(Bar bar) { this.bar = bar; } } class Bar { int i; String s; public Bar(int i, String s) { this.i = i; this.s = s; } }
答えはこうです。
100 abc 10000 ABC
コードのどこを見てもfooXの更新なんてないですね。でも値は変わります。
test1とtest2で違いが出る理由が分からなかった人はもう少し頑張りましょう。
そして分かった人もtest2のようなメソッドは書かないようにしましょう。
test2メソッドを書いた人はjavaを理解していないか、
保守を全く考えていないか、
小さいプログラムしか書いたことが無いかの三択でしょうか。。
エンハンスやバグ修正などでfooXの更新場所を探さないといけなくなったらどうするんでしょうか。。
test2のようなのがあると機械的に探すのがほぼ不可能です。
こんなじゃTDDだってできないだろうに。。