コンピュータ

Posts filed under コンピュータ

Gitlabを7から8にしてサブディレクトリで動かす

Filed in コンピュータ, 未分類

いつの間にか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を7にした。ついでにサブディレクトリにした。

Filed in コンピュータ

役割ごとにサーバを分けるようにサーバ構成の見直しをした。
その一環で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;
  }

CentOS7でPXEサーバを作ってみる

Filed in コンピュータ

サーバ設定系の記事が続きます。

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ブートを有効にしてね。

CentOS7でソフトウェアRAID1してみる

Filed in コンピュータ

先日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にメールすることにして転送対象にした。。。

ESXi単独でNFSをデータストアとして使う

Filed in コンピュータ

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通信の実装をするという記事を適当に書くのはやめるべき

Filed in ゴミコード, コンピュータ, プログラム

いろんなブログで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~13行目)
  • サーバ名の検証を無条件でOKにしている(17~20行目)

1点目の問題では、証明書が送られてきさえすればそれが偽造されたものであろうと何だろうと通信OKとなってしまう。
2点目の問題では、信頼できる認証局から発行された証明書でありさえすればそれが目的のサーバのものでなくても通信OKとなってしまう。

1点目の偽造証明書にOKを出すのがダメなことは直感的にも分かるでしょう。
では、2点目はなぜダメなのでしょう。

たとえば、こんなケースがある。

上記のような実装をされたウェブブラウザであなたが銀行のサイトに接続したつもりでいます。
しかし、あなたが使っているDNSがレコード書き換えの攻撃を受けており実際には別のサイト(攻撃者が管理するサイト)に繋がってしまいました。
ですが、そのサーバには第三者機関から発行された証明書が設定されていました。
ブラウザは通信相手は期待したサーバであると判断して通信を続行してしまいます。
あなたは銀行のサイトだと思ってIDとパスワードを入力してしまいます。
攻撃者はあなたのIDとパスワードをまんまと手に入れてしまうわけです。

本来は偽のサーバに設定されている証明書のサーバ名とあなたが接続しようとしたサーバ名は異なっているので、接続は拒否されるべきである。ですが上記のコードではこれがなされません。さらに悪いことに上記のコードでは無条件で処理がされてしまうためユーザがそんなことが起こっていることを知る術もない。

では正しく処理するにはどう実装したらよいのだろうか。こうです。

URL url = new URL("https://hogehoge/foo/bar");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
以下略

非常に簡単です。なんのことはない。javaは特に設定を変えない限りデフォルトで正しく動作をするようになっている。
無理やりその動作を歪めてしまう必要はないのである。

自己署名証明書を設定したサーバに対してはこのままで通信をしようとしても通信はできない。
それは当然である。なぜなら自己署名証明書は信用できる証明書ではないからだ。
どうにもならないのかといえばそんなことはない。

自己署名証明書を信用できるという前提でクライアント側のトラストストアにインポートすればよいのだ。
これにより通信は可能になる。
ただ、これはあくまで普通の行為ではないということを認識したうえで行う必要がある。

セキュリティとはかくも面倒くさいことであるが、こういったことが守られてようやく実用レベルの安全性をもったネットワークというのが実現できるのである。守られていてもクラッカーとはイタチごっこであるのに守られていない場合に安全であるなど言えっこないのである。

見ろ!!コードがゴミのようだ!! part3

Filed in ゴミコード, プログラム

さて、、次の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だってできないだろうに。。

プログラマ35歳定年説に物申さない

Filed in プログラム

プログラマ35歳定年説というものがある。
35歳になると能力が衰えて技術習得が追い付かず時代の流れについていけなくなるというものである。

これについては常々違和感を感じていた。
名目上は頭脳労働であるプログラマが35歳程度で働けなくなるなんてあるのだろうかと。
しかし、自分自身がその年齢になってみると確かにいろいろ厳しいと感じることが多くなってきた。

私自身が感じていることをいくつか述べてみたいと思う。

  1. 糞な設計に強烈な違和感を感じるようになった。
  2. 割り込み作業が増えた。
  3. コードを書いてもなぜかあまり評価されない。

それぞれについて少し説明をしたいと思う。

1.糞な設計に強烈な違和感を感じるようになった。
いろいろな経験や勉強を経て、自分自身が最も良いと思っている設計思想が確立されたと思っている。
そのためかダメな設計に対して違和感を感じるようになってきた。

私自信が設計で気をつけることはいろいろあるものの、最も気を配っているのは適切なスコープである。適切なスコープを切るためには適切なクラス設計、コンポーネント設計、アーキテクト設計が必要という比較的上流のレベル、利用する付近で変数宣言をする、関連処理は一箇所に纏めるひいてはメソッドとして切りだせるという下流レベルまで一通り適用できる。

しかしこれに反するものを見たときになんとなく拒否反応が出るようになってきてしまった。こんなことではいけないと思いつつもどうもだめだ。

メソッドの先頭で10も20も変数が定義されているのに、メソッドの中身は実質いくつものブロックに分かれており、それぞれのブロックでは数個の変数しか使わないというコードは比較的よく見かける。
こんなものは変数定義を利用箇所の直前に変えメソッドを分割するべきである。20も変数を覚えつつメソッドの処理を追うなんて人間の脳の処理能力を超えているのである。

昔は頑張って読み解いていたが歳を取るごとにバカバカしさの方を強く感じるようになってきてしまっている。立場的にも指導する立場になり批判的にコードを見ているということが少なからず影響しているのかもしれない。

2.割り込み作業が増えた。
コード書きとは関係のない割り込み作業が非常に増えたと感じている。年齢というより立場の問題ではあるがそのような立場になる時期と年齢は無関係ではないだろう。なにがしの状況はどうなっているのか報告書を書けだの、進捗はどうなのか数字を纏めろ、突如発生した問題の対策会議を開くから集まれだといった作業が10年前に比べてやけに増えた。

正直、現場のプログラマへの配慮が足りないと思う。1時間を6回与えられた6時間と6時間を1回与えられた6時間はプログラマにとって意味が違う。どういうわけかそういうところに配慮してくれる人は少ない。

個人的な意見では作業が中断されるとだいたい30分から1時間くらいの作業内容は失われるくらいに考えて貰いたいのである。なるべく効率よく再開できるように手は打つが効率が悪くなることは避けられない。

3.コードを書いてもなぜかあまり評価されない。
これは開発手法やプロジェクトにも依存する話だと思う。ただ、私が関わったプロジェクトでは管理層がコードを見れない、見ないことが多かった。なので良いコードを書いているか悪いコードを書いているか判断しようがないというのはあったのかもしれない。正直これは良くないと思う。管理者もコードを書けとまでは言わないが見るくらいはすべきだと思う。そうでなくてもサブリーダーなどに誰が綺麗なコードを書いているかくらいは確認すべきだと思う。

比較的先進的な企業ではマネージャ層もある程度積極的にコードを書くそうだがさもありなんという感じである。なぜならコードの善し悪しはお金に直結する。綺麗なコードにはバグが入りにくい。少なくともバグが見つけやすい。修正もしやすい。拡張もしやすい。初見でも理解しやすく手が空いている人を必要な時だけ連れてきて保守させることができるため専属の保守員というのも不要になる。非常に多くのメリットがある。

だがこれらはあまり目に見えるものではない。いや、目には見えているはずである。だが何故そうなのかが考えられて評価に結び付くことが少ない。評価されるべきメンバー、あるいはふざけるなというコードを書いたメンバーはその時にはすでにプロジェクトから外れているということも多い。
そういったこともあり、バグ数の多寡は評価されてもコードの善し悪しが評価されることは少ない。そのせいなのか良いコードを書く人もそうでない人もひとくくりにされてしまってコードを書く行為への評価が高くなく、コードを書くのは給料の低い人となっている感がある。そのため給料が上がってくる=>コードを書かなくなってくる=>書けなくなるということがあるように思われる。

私の経験上、汚いコードを書く人が多いプロジェクトは属人的になっていることが多く、人員の入れ替えが難しい。新たな人を投入してもコードの理解が難しいため戦力になるまでに長い時間を要すが、管理者はそれが分かっていないから人を増やした分だけ進捗が上がると思っている。その分を古参のメンバーが頑張ってカバーしようとするが、時間が足りないため小手先のコードが増えてますますコードが汚くなっていくという悪循環となる。こうなるともうおしまいだ。

繰り返し言うがコードの善し悪しはお金に直結する。高給取りがコードを書いていても何もおかしいことは無いはずだ。彼・彼女の書いたコードは綺麗なのでお金がかからなくていいね~という会話がされるようなプロジェクトに参加したいものである。

さて、いろいろ書いてきたがそういうわけで年齢によりある種の壁にぶつかるということはあるのかもしれないと考えるようになってきた。

だがまあ仕事でコードを書くことは減ったとしても、技術的に付いていけるかどうかということについては最終的には個人の意識の問題だ。仕事はどうあれ生涯プログラマであると言えるように常に技術だけは磨いていきたいと思っている。

GitLab+Jenkinsで超ハマった

Filed in コンピュータ

自宅にGitLabとJenkinsを構築したんですが、超ハマりました。
ググってもなかなかそのものズバリの解が見つからずかなり悩んだので
同じ問題で躓いた誰かの一助になれば幸いだと思いここに記録をのこしておきます。

GitLabのバージョンは6.4.2です。Gitoliteは使わずにGitLab Shellの1.7.9を使っています。
jenkinsのバージョンは今回あまり重要ではなかったので適当に。
構築の手順などはググれば出てくるので省略しますがGitLabとjenkinsを同じサーバにインストールしています。
一通り構築が終わってプロジェクトをGitLabに登録してクライアントからソースのコミットやプッシュができるのを確認し終えてjenkinsでビルドをしようとしたとこから。

秘密鍵を使ってSSHによる接続をするための設定をしました。
伏字ですけどこんな感じ。
jenkins

GitLabには公開鍵を登録してあります。

ここまでやってJenkinsでビルドを実行するとこんなエラーが。

Fetching changes from the remote Git repository
Fetching upstream changes from xxx@サーバ:ユーザ/プロジェクト.git
using GIT_SSH to set credentials 
FATAL: Failed to fetch from xxx@サーバ:ユーザ/プロジェクト.git
hudson.plugins.git.GitException: Failed to fetch from xxx@サーバ:ユーザ/プロジェクト.git
	at hudson.plugins.git.GitSCM.fetchFrom(GitSCM.java:623)
	at hudson.plugins.git.GitSCM.retrieveChanges(GitSCM.java:855)
	at hudson.plugins.git.GitSCM.checkout(GitSCM.java:880)
	at hudson.model.AbstractProject.checkout(AbstractProject.java:1411)
	at hudson.model.AbstractBuild$AbstractBuildExecution.defaultCheckout(AbstractBuild.java:651)
	at jenkins.scm.SCMCheckoutStrategy.checkout(SCMCheckoutStrategy.java:88)
	at hudson.model.AbstractBuild$AbstractBuildExecution.run(AbstractBuild.java:560)
	at hudson.model.Run.execute(Run.java:1670)
	at hudson.model.FreeStyleBuild.run(FreeStyleBuild.java:46)
	at hudson.model.ResourceController.execute(ResourceController.java:88)
	at hudson.model.Executor.run(Executor.java:231)
Caused by: hudson.plugins.git.GitException: Command "git fetch --tags --progress 
xxx@サーバ:ユーザ/プロジェクト.git +refs/heads/*:refs/remotes/origin/*" 
returned status code 128:
stdout: 
stderr: Access denied.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

それで19行目に出ているステータスコード128でいろいろ調べてみたんだが、
これが原因がいろいろありすぎてなかなか分からない。

SSHKeyを作りなおして再登録したり、ユーザのアクセス権を見直したり、restoreconを打ってみたりとうんうん数時間あれやこれやと試行錯誤。大体においてこういうときは何をやっても上手くいかないもんで一向に解決しないのでちょっとゲームでもして気分転換をしたところはっとひらめいた。

結論としてはgitlab-shellの設定がダメだった。

# GitLab user. git by default
user: git

# Url to gitlab instance. Used for api calls. Should end with a slash.
gitlab_url: "http://127.0.0.1:80/"
# GitLab user. git by default
user: git

# Url to gitlab instance. Used for api calls. Should end with a slash.
gitlab_url: "http://127.0.0.1:8080/"

何のことはない。リバースプロキシから8080番に飛ばすためにポート番号を変えていたのにGitLab Shellの対応をしてなかったということでした。別PCをクライアントにしてしか使ってなかったのでなかなか気づけませんでした。

しかしもうちょっとエラーメッセージが分かり易くできそうなものですけどね。

デジタルの脳を持つ人々

Filed in プログラム

1つ目のジョブが成功なら2つ目のジョブを実行するという処理を
何の疑問も持たずにこんなコードで書く人達がいる。

public enum Result {
	SUCCESS,
	FAILED
}

public class TaskExecuter {
  public void execute(Job job) {
     job.firstTask();

     if( job.getResult() != Result.FAILED ) {
        job.secondTask();
     }
  }
}

これはダメなコードです。さて、何がダメでしょうか。

エンハンスによりEnumがこうなったらどうでしょう。

public enum Result {
	SUCCESS,
	FAILED,
	SUSPEND
}

そう、もう期待通りには動きません。
前の処理が終わっていないのに次の処理を実行してしまいます。
なぜこんなコードを書いてしまうのでしょうか。

それは「失敗でなければ成功」という思い込みですね。
失敗でなければ成功などとは考えてはいけません。
「失敗でなければ失敗していない」以上の何物でもないのです。

私の経験上、思い込みというのはバグの発生源としてかなりの勢力を持っています。
また、バグが見つけられない理由としても一大勢力を築いています。

そんなわけでこう書くべきなのです。

if( job.getResult() == Result.SUCCESS ) {
   job.nextTask();
}

成功したとき以外には動いてもらっては困るのですから成功したら実行と素直に書けばよろしいのです。

同じようにこれはダメです。

if( job.getResult != Result.SUCCESS ) {
  throw new SomeException("errorだよ");
}

答えを曖昧にする日本人なのになぜかプログラムでは中間状態を無視してしまうような思考をするのは不思議ですね。