Cybozu Inside Out | サイボウズエンジニアのブログ

 

Migrating Garoon codebase to PHP 7

(※注 冒頭のみ日本語訳を行いました。本編はベトナムメンバーによる英語の記事になります。)
Xin chào! Cybozu Vietnam で Garoon を開発している Huy Nguyen, Vien Tran, Long Huynh, Dieu Ho, Tai Vu, Anh Nguyen です。ベトナムメンバー初のCybozu Inside Outの投稿です。Garoon で PHP 7 への移行をした経験を記事にしました。

One year ago, Garoon developement team started to migrate the codebase of Cybozu Garoon to PHP 7. Garoon is a product with large codebase, formerly written in PHP 4 and then later migrated to PHP 5. The migration really was a challenge to the team, but it was worth doing because PHP 7 brought a host of improvements. One of the benefits is performance improvements. Benchmark of Garoon using PHP 7 showed a 33 percent of speed improvement compared to PHP 5.6.

And also, active support of PHP 5.6 ended on 19 Jan 2017 according to php.net. Source: http://php.net/supported-versions.php

In this post, I will share things that the Garoon development team met and show you the way we migrated the codebase to PHP 7.

Tools to check incompatible changes

When we upgraded our code from PHP 5 to PHP 7, we had to check our codebase for all the “backward incompatible changes” to make sure our codebase could be successfully interpreted in PHP 7. There are some tools that help you scan your code through and point you out which code needs to be fixed in order to be able to run in PHP 7, among which are Phan and Lint.

Phan

Phan is a static analyzer for PHP. It’s licensed under the MIT License.

Prepare the environment

You have to prepare a CentOS machine, with PHP 7 installed. Then install composer running the code below:

curl \-sS https://getcomposer.org/installer \| php \-\- \--install-dir=/usr/local/bin \--filename=composer

Install Phan and the php-ast extension

First, clone the source code of php-ast:

git clone https://github.com/nikic/php-ast.git

Next, build the php-ast extension and then add it to php. Run each line of the code below in turn:

cd php-ast
phpize
./configure
make install
echo 'extension=ast.so' > /etc/php.ini

Then, install Phan, and run through the code below:

git clone https://github.com/etsy/phan.git
cd phan
composer install
./test
ln \-s /phan/phan /usr/local/bin/phan

Run Phan to check backward incompatible changes

We created a bash file (e.g., testPhan.sh) with the content like the following:

COMMAND="phan \--backward-compatibility-checks \--ignore-undeclared \--quick"
find /repo/source \-type f \-regex '.*\.\(php\)' \-print0 \| xargs \-0 \-n1 \-P 1 \-I % bash \-c "$COMMAND % "

You can change the path to the soure code by changing “/repo/source”. Then run:

sh testPhan.sh

Lint

Lint is a command line option provided by PHP, which is used to check PHP syntax.

On the above CentOS machine, we ran the following command to check our PHP source code with Lint:

find . -name "*.php" -print0 | xargs -0 -n1 -P8 php -l

E_STRICT notices severity changes

E_STRICT notices severity was changed in the PHP 7, and all of the E_STRICT notices were reclassified to other levels.

Situation New level/behaviour
Indexing by a resource E_NOTICE
Abstract static methods Notice removed, triggers no error
“Redefining” a constructor Notice removed, triggers no error
Signature mismatch during inheritance E_WARNING
Same (compatible) property in two used traits Notice removed, triggers no error
Accessing static property non-statically E_NOTICE
Only variables should be assigned by reference E_NOTICE
Only variables should be passed by reference E_NOTICE
Calling non-static methods statically E_DEPRECATED

Some of these changes affected our source code.

Signature mismatch during inheritance

The “Signature mismatch during inheritance” notice usually occurs in the following cases: The first case is a case where the number of the parameters of a function/method is different during inheritance. For example:

<?php
class Foo{
    function method($a){

    }
}
class Bar extends Foo{
    function method(){

    }
}

The above example outputs:

Warning: Declaration of Bar::method() should be compatible with Foo::method($a) in /in/YoDHq on line 11

You can click here to run this example and see the output.

The second is a case where the type of a parameter of a function/method is different during inheritance. For example:

<?php
class Foo{
    function method($a){

    }
}
class Bar extends Foo{
    function method(array $a){

    }
}

The above example outputs:

Warning: Declaration of Bar::method(array $a) should be compatible with Foo::method($a) in /in/fSIbS on line 11

You can click here to run this example and see the output.

We used Phan to check our code and fixed the problems that it reported as PhanSignatureMismatch and PhanSignatureMismatchInternal.

How to fix such issues depends on the logic that we modified in the base class or subclass.

Only variables should be assigned by reference

Example:

<?php
error_reporting(E_ALL);
function foo(){
    return 'string';
}
$temp =& foo();

The above example will output:

Notice: Only variables should be assigned by reference in /in/vXUIZ on line 6

You can click here to run the example and see the output.

In the above example, the ‘&’ operator causes an E_NOTICE saying “Only variables should be assigned by reference”. In case your code is compiled in a PHP 7 environment, an error will be detected, and so you must fix it by removing ‘&’ as follows:

<?php
error_reporting(E_ALL);
function foo(){
    return 'string';
}
$temp = foo();

However, since in our code ‘&’ was used in so many places and it would take us so much time to fix them all, I would like to introduce a tool which runs on CentOS to solve this problem. First of all, in order to write E_NOTICE messages to a php_error.log, you can do the following steps:

1. Add the following script to the code of your web application and then run it:

file_put_contents('/tmp/php_error.log', implode(',', [$errno, $[errstr], $errfile, $errline]) . PHP_EOL, FILE_APPEND);

2. The “php_error.log” will look like the following:

12 2048,Only variables should be assigned by reference,/var/www/project/code/xxx1.php,12
12 2048,Only variables should be assigned by reference,/var/www/project/code/xxx2.php,121
12 2048,Only variables should be assigned by reference,/var/www/project/code/xxx3.php,26
5 2048,Only variables should be assigned by reference,/var/www/project/code/xxx.php,194

The tool will run the following steps:

  1. Find the latest folder created with the prefix “ system-private-* ” under tmp.
  2. Grep the string “assigned by reference” in the php_error.log file, which exists inside tmp, and then output the file name and the line number to a temporary result.
  3. Find “=&”, “= &” and “= & ” and replace them with “=” following the result from the step 2.
#!/bin/bash

systemd_private_name=$(ls -t1F /tmp/ | grep systemd-private-* | head -n1)
php_error_path="/tmp/${systemd_private_name}tmp/php_error.log"

if [ ! -f $php_error_path ]; then
    echo "File is not found"
    exit;
fi


grep "assigned by reference"  $php_error_path  | sort | uniq -c | while IFS=',' read -ra line ; do
    sed -ri ${line[3]}'s/(\$.+)(\s+=\s*&\s*)(.+())/\1 = \3/g'  ${line[2]}
done

Only variables should be passed by reference

In PHP 7, using a function argument could return an error when the function argument was passed by reference. This issue can occur if you use a function of a PHP framework or a method you added.

To fix this issue we declared a variable to store the value of a function, which had been directly passed at a position called. What follows is an example:

Example: Strict Standards will be thrown out if you put exploded array in array_pop:

<?php
   $fruit = array_pop(explode(",", "Orange,Apple,strawberry"));
   echo $fruit;

Fixed code:

<?php
   $fruits = explode(",", "Orange,Apple,strawberry");
   $fruit = array_pop($fruits);
   echo $fruit;

Note that in this example you must assign a variable to the function explode and then pass the reference to the variable to array_pop to avoid a Strict Standard warning.

The methods in PHP framework

Example:

<?php
   $file_name = "abc.txt";
   $file_extension = end(explode('.', $file_name));

The above example will output:

Notice: Only variables should be passed by reference in /in/Cv65t on line 3

You can click here to run the example and see the output.

Fixed code:

<?php
   $tmp = explode('.', $file_name);
   $file_extension = end($tmp);

We used Phan to check our code and fixed the problems which were reported as PhanTypeNonVarPassByRef. However, Phan only retrieved methods in the PHP framework: as for the methods we added, Phan could not detect them as PhanTypeNonVarPassByRef. The example below is to explain this point.

The method you added

Example:

<?php
function getArray() {
    return [1, 2, 3];
}

function squareArray(array &$a) {
    foreach ($a as &$v) {
        $v **= 2;
    }
}

// Generates a warning in PHP 7.
squareArray((getArray()));

The above example will output:

Notice: Only variables should be passed by reference in in /in/HYONN on line 14

You can click here to run the example and see the output.

To detect a method you define in your project, you can search for it with regular expression using an editor (e.g, notepad++).

function.*\&\s+\$.*\)$

The above example will output:

E:\yourproject\comment_util.php (3 hits)
    Line 30:     function getFollowByUser( & $user, & $article )
    Line 61:     function _setNameRowSet( & $rowset, $inverse = FALSE )
    Line 119:     function _row2object( & $row )

After you list your reference methods from the search result, you must manually modify function calls.

Calling non-static methods statically

Static calls to methods that are not declared static are deprecated in PHP 7 and may be removed in the future.

We have an example:

<?php
class foo {
    function bar() {
        echo 'I am not static!';
    }
}

foo::bar();

The above example will output:

Deprecated: Non-static method foo::bar() should not be called statically in - on line 8
I am not static!

We used Phan to check our code and fixed the problems that were reported as PhanStaticCallToNonStatic. The easiest way to fix such a problem is change all non-static functions to static functions.

PHP 4 constructor

PHP 4 constructors are methods that have the same name as the class they are defined in.

PHP 7 will emit E_DEPRECATED whenever a PHP 4 constructor is defined. When the method name matches the class name, the class is not in a namespace, and a PHP 5 constructor (__construct) is not present then an E_DEPRECATED will be emitted.

Example:

<?php

class Filter {
    function Filter() {

    }
}

new Filter();

Output:

Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; Filter has a deprecated constructor in /in/IDpi7 on line 3

You can click here to run the above example and see the output.

You should use PHP 5 constructor like the below

<?php

class Filter {
    function __construct() {

    }
}

new Filter();

If your project is large, manually fixing the constructors will take a lot of time. There’s a useful tool called PHP-CS-Fixer that can help you automatically fix them. The PHP-CS-Fixer tool helped us fix over 500 files in our Garoon project.

I will show you how to use the tool to fix PHP 4 constructors. The below code runs with PHP-CS-Fixer 2.2.1.

1. First we need to create a configuration for PHP-CS-Fixer

.php_cs
<?php

$source_dir = 'c:\repo\projectX\source';
$finder = PhpCsFixer\Finder::create()
    ->in($source_dir)
    ->files()->name('*.csp')->name('*.php')
;

return PhpCsFixer\Config::create()
    ->setRiskyAllowed(true)
    ->setRules(array(
        'no_php4_constructor' => true
    ))
    ->setFinder($finder)
;

Note: Our Garoon project uses “.csp” as the file extension of PHP source files, which is why we add “.csp” to the configuration of PHP-CS-Fixer.

2. Run PHP-CS-Fixer

php php-cs-fixer.phar fix --config=.php_cs --verbose

For details about this tool, see: https://github.com/FriendsOfPHP/PHP-CS-Fixer

Internal functions Changes

There are some internal functions that were changed in PHP 7, and the substr() function is one of them.

For example:

<?php
$foo = substr("foo",3);
if($foo !== FALSE){
    echo "PHP 7\n";
    echo gettype($foo); //string
}
else{
    echo "PHP 5\n";
    echo gettype($foo); //boolean
}

The type of the variable $foo in the above example will be a boolean in PHP 5 and a string in PHP 7.

You can click here to run the above example and see the output.

To fix this issue, we checked the length of a string using the strlen function.

<?php
$foo = substr("foo",3);
if(strlen($foo) == 0){
    echo "Run on both PHP 5 and PHP 7\n";
    echo gettype($foo); //boolean on PHP 5 and string on PHP 7
}

You can click here to run the above example and see the output.

Conclusion

In the Garoon project, performance was improved and the codebase became more readable after the migration from PHP 5.6 to PHP 7: for example, the performance of the instance of Garoon we are using in Cybozu Inc. was shown to have been improved by 33 percent. We separated the migration into multiple parts and carried them out one by one. This way helped us cover possible issues while the migration was done in a short period of time.

 

デザイングループのお仕事、アクセシビリティへの取り組み──Cybozu Meetup #5 レポート

こんにちは、コネクト支援チームの風穴(かざあな)です。

6月20日に開催した「Cybozu Meetup #5 デザイン/アクセシビリティ」についてレポートします。

f:id:cybozuinsideout:20170812213426j:plain

Cybozu Meetupとは?

「Cybozu Meetup」も、早いもので、今回で5回目(東京開催として)となりました。

「Cybozu Meetup」は、サイボウズのエンジニアとカジュアルに交流する場として企画、開催しているシリーズです。会場はサイボウズのオフィスなので、社内の雰囲気や社員の様子を、実際に肌で感じて頂ける機会でもあります。

東京オフィスは毎月1回、大阪オフィスは3カ月に1回のペースで開催しています。毎回テーマを決めていて、これまでは、以下のようなラインアップで開催してきました。

そんな時代がサイボウズにもありました

第5回のテーマは、「デザイン/アクセシビリティ」ということで、まずは、サイボウズのデザイングループの活動について。

「デザイングループって、アイコンとかボタンとか作る部署でしょ」と思われていた時代が、サイボウズにもありました(笑)。そこから脱却し、「上流」から関わって活動することで、デザイングループの存在感が日々、高まってきています。

f:id:cybozuinsideout:20170812213523j:plain

f:id:cybozuinsideout:20170812213550j:plain

続いて、これも近年、社内で急速に動きが広まってきたアクセシビリティについて。

アクセシビリティの考え方をコツコツと社内で広め、遂には、正式に会社の活動として取り組むまでに持っていった小林が、サイボウズにおけるアクセシビリティ対応について紹介しました。

f:id:cybozuinsideout:20170812213629j:plain

質問&交流タイム

発表の後の質問タイムでは、「デザインプロトタイピングのスコープと周期は?」「アクセシビリティをやろうと思ってから実際に製品に反映されるまで、どれぐらいかかった?」「アクセシビリティに取り組むなら、どのタイミングでやるべき?」といった質問が出て、それぞれの議論もかなり盛り上がりました。

今回は、デザイン、アクセシビリティというテーマに直接関係している参加者が多かったせいか、その後の交流タイムは、いつも以上に突っ込んだ話で盛り上がりました。

f:id:cybozuinsideout:20170812213657j:plain

f:id:cybozuinsideout:20170812213809j:plain

最後は、恒例の集合写真!

f:id:cybozuinsideout:20170814151012j:plain

今後も、東京オフィスでは、毎月開催を予定しています。ご興味ある方は、connpassグループ「Cybozu Inside Out」をフォローして、情報をチェック頂ければ。

「サイボウズのこんな話を聞いてみたい!」というご要望も、随時、受け付けています。お気軽にお寄せください。

ではまた。

 

x86/x64における小数から整数への丸め処理命令の変遷

こんにちは、サイボウズ・ラボの光成です。

今回は小数を整数に丸める処理に関して、x86/x64における命令がどのように変わってきたかを紹介します。

C++における小数から整数への変換ルール

まずC++における浮動小数点数型(float, double)を整数型(int, int64_tなど)に丸めるルールをおさらいしましょう。 floating-integral conversionsによるとその変換では小数点部分を取り除きます。 つまり1.5, 2.3, -2.9をintにキャストするとそれぞれ1, 2, -2になります。

なお整数型に入りきらないときの挙動は未定義です。

4種類の丸め規則

x86の浮動小数点数を扱うFPUは丸め処理のモードを4種類持ちます。 これはIEEE標準754の丸めモードの規則に従ったものです。

  • 最近接丸め(round to nearest(even) : RN)
  • 切り捨て(round down toward -∞ : RD)
  • 切り上げ(round up toward +∞ : RU)
  • 0への丸め(round toward zero(truncate) : RZ)

f:id:cybozuinsideout:20170814151516p:plain

それぞれの処理を図にしてみました。矢印で示される区間内の値が矢印の先の整数に丸められます。黒丸●はその点を含み、白丸○は含まないことを意味します。

切り捨ては負の無限大方向に切り捨てる、切り上げは正の無限大方向に切り上げます。 それぞれfloor()関数、ceil()関数に相当します。 0への丸めは0に向かう方向に切り捨てます。doubleからintにキャストするときのルールはこれに相当します。

最近接丸めは若干馴染みが無いかもしれません。 通常よく使われる四捨五入では1.5, 2.5, 3.5は全て2, 3, 4と切り上げます。 しかし、この方法だと多数の値を処理したときに全体として正の方向に大きくなる可能性があります。 そこで端数が0.5のときは偶数に向かう方向に処理します。つまり0.5は0に、1.5は2に、2.5は2になります。 こうすると多数のランダムな値に対しては切り上げと切り下げが均等になります。

丸めモードの中に四捨五入はありません。 これはxが0以上なら0.5を足し、xが負なら0.5を引いて0へ丸めればよいからです。

コードとしては

double round(double x)
{
    double c = (x >= 0) ? 0.5 : -0.5;
    return (double)(int)(x + c);
}

で実現できます。

昔のx86における整数型への変換方法

FPUの丸めモードは、通常最近接丸めに設定されています。 そうするとdoubleをintにキャストするにはモードを変更しなくてはなりません。 もしかしたらプログラム実行中にどこかでモードが変更されているかもしれないので、

  1. 現在の丸めモードを取得して保持する
  2. 丸めモードをRZに変更する
  3. fist命令でdouble→intキャストを行う
  4. 丸めモードを1.で取得したモードに戻す

という処理が必要になります。 FPUのモードを変更するのはなかなかコストが大きく、多数の小数を整数に変換する際にはそこがボトルネックになることもありました。 この状況はベクトル演算命令が追加されたSSE, SSE2でも変わりませんでした(cvtsd2si ~2004年)。

0への丸め命令の追加

そこでIntelはSSE3で0への丸め専用命令(fisttp, cvttsd2siなど)を追加します(2004年)。 これはFPU/SSEの制御レジスタに関わらず常に0への丸めを行う命令です。 そのためdoubleからintへのキャストを丸めモードを変更せずに実現可能となり、処理性能が向上しました。

切り捨て、切り上げ対応命令の追加

intへのキャストは高速に処理できるようになっていましたが、floor()やceil()関数の実装では従来のintへのキャスト同様、丸めモードを変更するか、あるいは符号を考慮しつつcvttsd2siをうまく使う必要がありました。 そこで丸めモードを自由に設定できるroundsdなどの命令がSSE4.1で追加されました(2007年)。

roundsd  xmm1, xmm2/mem, imm8
vroundsd xmm1, xmm2, xmm3/mem, imm8

vroundsdはAVXで追加された命令です(2011年)。 imm8で丸めモードを指定できます。

たとえば前述のround関数は次のように実装できます。 浮動小数点数の符号は最上位ビットにあるので条件分岐を使わずに実装しています。

// xmm0にxが入っているとする
vandpd   xmm1, xmm0, ptr [const1] // xの符号ビットを取り出す
vorpd    xmm1, xmm1, ptr [const2] // xmm1 = (x >= 0) ? 0.5 : -0.5
vaddsd   xmm0, xmm0, xmm1         // xmm0 += xmm1
vroundsd xmm0, xmm0, 3            // truncate

const1: // 最上位ビットが符号ビット
  dd 0x00000000
  dd 0x80000000
  dd 0, 0
const2: // double(0.5)のビットパターン
  dd 0x00000000
  dd 0x3fe00000
  dd 0, 0

ただfloorやceilは整数が欲しいときに使うことが多いのにroundsd命令は結果が浮動小数点数型なので, 更にもう一度cvtsd2si命令を使って整数レジスタに変換しなければなりません。冗長な感じがします。

AVX-512命令

2016年IntelはAVX-512対応CPUを発表しました。 従来の256ビットSIMD命令だったAVX-2を2倍に拡張することで512ビットSIMD命令が使えるようになりました。 同時にほとんどの演算命令に個別に丸めモードを指定できるようになっています。

従来のvcvtsd2siも拡張されています。したがってこれ1命令でdoubleからintへの切り上げ、切り捨て、0への丸めが可能になりました。

アセンブリ言語では

vcvtsd2si eax, xmm0, {rn-sae}

のように指定します。", {rn-sae}“のカンマや丸括弧が気持ち悪い(というかパースしにくい)ですがこのように指定します。saeはSuppress All Exceptionsの略で小数演算例外を抑制します。

対応するintrinsic命令は _mm_cvt_roundsd_i64(__m128d, int r)です。

ただ手元で実験した限り、NASM 2.14rc0でアセンブルすると{rn-sae}を無視したコード生成をするようです(バグ? 報告してみました)。 intrinsic関数についてはVisual Studio 2017は非対応で、gcc-7.1やclang-4でコンパイルエラーになりました(もしかしたら私の使い方が悪いのかもしれません→2017/8/16 最後の段に加筆)。

拙作のXbyakはAVX-512に対応しておりたとえばvcvtsd2si(eax, xmm0 | T_rn_sae);と記述できます(サンプルコード round.cpp)。 AVX-512対応CPUを持っていない人でもIntel Software Development Emulatorを使えば正しく動いていることを確認できます。

sde -- round.exe

まとめ

x86/x64における浮動小数点数から整数への丸め方法について従来の方法から(2017年における)最新のプロセッサ向けの方法について紹介しました。 地味な下回りの命令ではありますが、今後対応したツールも増えてくるでしょう。

intrinsic関数での使い方

_mm_cvt_roundsd_i64(__m128d, int r)のrに指定する丸めモードはRN, RD, RU, RZに対応する0~3の整数ではなく、それと8(SAE)とのorをとる必要がありました。_mm_round_pdなら0~3を指定できるので私はそれと同じだと思い込んでいました。

int x = _mm_cvt_roundsd_i64(m1, 0); // error: incorrect rounding operand
int y = _mm_cvt_roundsd_i64(m2, 0 | 8); // ok

0, 1, 2, 3, 8はヘッダでそれぞれ

#define _MM_FROUND_TO_NEAREST_INT 0
#define _MM_FROUND_TO_NEG_INF     1
#define _MM_FROUND_TO_POS_INF     2
#define _MM_FROUND_TO_ZERO        3
#define _MM_FROUND_NO_EXC         8

と定義されているのでこれらのマクロを利用してもよいでしょう。

 

Hardening 1010 Cash Flow に参加しました

こんにちは。東京品質保証部 Cy-PSIRT の長友です。主に弊社製品の脆弱性の調査・検証をしています。
今回は、2017年6月23日から24日にかけて沖縄県宜野湾市で開催された、Hardening 1010 Cash Flow に事業支援本部の石渡と私で参加しましたので、報告します。
Hardening Project

Hardening Project とは

Hardening Project とは、セキュリティの競技の一つです。
チーム対抗で、脆弱性のあるECサイトの堅牢化技術を総合的に競い合います。セキュリティや運用に関する技術だけでなく、ビジネスを継続するためのスキルも問われます。
弊社もスポンサーとして協賛しています。

参加のきっかけ

私は学生時代から CTF (Capture The Flag) をやっていました。CTF 自体、非常に面白い競技なのですが、ほかにもセキュリティに関する面白い競技はないものか、と思っていたところ、Hardening Project の存在を知り、興味を持ちました。
まだまだ経験の浅いエンジニアなので、うまくいくかどうか自信はありませんでしたが、ひとまず「当たって砕けろ」の精神で参加申し込みをしました。(CTF だって、最初から自信をもって参加できていたわけではありませんから)
選考があるということで、参加できるかどうか不安でしたが、なんとか参加の権利を得ました。

参加決定後

石渡と私は、偶然にもサイボウズ社員が 2 人、同じチーム (Team #5 WannaEnjoy) として競技に参加することになりました。世間を騒がせた WannaCry にも負けず、Hardening を Enjoy するぞ! という願いを込めてこんなチーム名になりました。
まずはお互いの自己紹介をしたり、リーダーを決めたりした後、それぞれがどのようなスキルを持っているかの確認をしました。すると、それぞれ持つスキルが分散しており、バランスの良いチームだということが分かりました。ほっとしたことをよく覚えています。 その後、スキルマップに沿って、サーバ全体の堅牢化を担当する「堅牢化チーム」、与えられたサーバの運用・監視を担当する「運用チーム」、EC サイトの在庫管理や、チームの外の人や組織とのやり取りを担当する「ビジネス系チーム」に分かれ、それぞれに準備を進めて行きました。
私は運用チームだったので、当日の運用で必要になりそうなスクリプトを書いたり、触ったことのないミドルウェアを実際に触ったりして、当日に備えました。

Hardening 1010 Cash Flow 当日

1 日目は実際にビジネスとシステムの堅牢化作業を行う「Hardening Day」、2 日目は Hardening Day の振り返りを行う「Softening Day」が行われました。

Hardening Day 前日

前日の夕方に沖縄に到着しました。沖縄は梅雨明け直後でとても蒸し暑く、クーラーでちょうどいい温度になっている部屋に慣れ切った私はここで 3 日間過ごせるのだろうか、と若干の不安を覚えました。
ともあれ、Hardening Project に備えることにします。
ちょうど打ち合わせ会場に向かい始めたころ、運営から今回の競技で使う環境の情報や、競技ルールについての資料がメールで送られてきました。 資料をみて、私はたいへん驚き、焦りました。なぜなら、護るべきサーバがとても多いうえに、それぞれのサーバに入っているアプリケーションの大半が古かったり、脆弱性がたくさん報告されていたりするものばかりだったからです。さらに、インターネット回線を使ってアップデートすることができないと分かったので、これはどうしたものかなあ、と考え込んでしまいました。
打ち合わせ中は、沖縄のおいしい食事を味わいながら、資料を読み合わせしたり、議論をしたりしました。お刺身とそうめんチャンプルーがとてもおいしかったです。(今後参加される皆さん、次の日は本番なので、お酒は控えめにしましょうね。) ゴーヤチャンプルー 打ち合わせが終わり、ホテルに戻った後 NVD や JVN iPedia でどのような脆弱性があるのか調べたり、利用されているミドルウェアの設定を調べたりしてから寝ました。(こちらも、次回以降参加される方はきちんと寝ましょう。睡眠不足はインシデントレスポンスの敵です。)

Hardening Day

戦いの日にふさわしい、爽やかな朝でした。
インシデントが起きた、まずいぞ! と訴えるという動画から競技は始まります。
最初は比較的穏やかだったので、パスワードを変えたり、与えられた環境にどのようなミドルウェアが入っているか調査したりしていました。しかし、いざインシデントが起きるとかなりうろたえました。頭が真っ白になって何をすればいいのか落ち着いて考えることができませんでした。
また、初期対応ばかりに追われ、根本的な対応を忘れてしまい、同じ脆弱性を突かれるというイベントが発生しました。
会場 Hardening Project にはマーケットプレイスと呼ばれる、セキュリティ系の製品やサービス、人的リソース等を調達できる仕組みが存在します。今振り返ってみると、その仕組みをうまく使いこなすのが重要だと思いました。今回 WannaEnjoy では 仮想環境上にあるすべての Windows 端末にセキュリティ対策ソフトを導入することを先に決めていました。そのおかげで、早い段階で Windows 環境に仕込まれたマルウェアを検知し、隔離することに成功しました。
マルウェアが隔離されて一安心、さすがマーケットプレイスの製品だ、と思って胸をなでおろしていたら、近隣のチームから「ランサムウェアだ!」という声が上がって、ぞっとしました。もし後回しにしていたら……と思うと、今でもぞっとします。
また、今回 Windows 環境に導入したソフトウェアをご提供いただいた会社の方から、導入のアドバイスをいただいたり、運用中のサービスに見つかった脆弱性のご連絡を頂戴しました。頂いたご指摘の中には、冷静に見ていれば気づけそうなものもあったので、第三者の目線はとても大切と痛感しました。Softening Day のプレゼンでもお伝えしましたが、本当に感謝が尽きません。

そんなこんなで、とにかく大慌てしていたら、いつの間にか10時間過ぎていました。人間の感覚というものは不思議なものです。

腹が減っては戦ができぬ、ということで、昼食においしいハンバーガーをいただきました。また、おやつの時間にはおいしいコーヒーとお菓子をいただきました。どれもとてもおいしかったのですが、対応で焦りすぎてじっくり味わう余裕がありませんでした。今度沖縄に行くことがあれば、もう一度食べたいです。 昼食のハンバーガー

Softening Day

Softening Dayでは、チームごとの振り返りの発表、Hardening Project で利用する環境の作成や当日の攻撃などを担当したチーム (kuromame6) からの解説などがありました。苦しめられていた脆弱性が単純なもので、落ち着いて考えればわかったかもしれない、と気付いたときの悔しさはすごかったです。
そして、なんと WannaEnjoy のチーム発表を担当することになりました。資料が完成したのが当日の朝でしたので、朝慌てて出来上がった資料を確認したり、発表原稿を作ろうとしてあきらめたりしていました。Hardening Day は終わったはずなのに冷や汗が止まりませんでした。
当日の発表はこちらの動画からご覧いただけます。
その後の結果発表で、株式会社ラック様よりスポンサー賞をいただきました。チームのバランスをご評価いただきました。本当にありがとうございました。なお、表彰式の様子はこちらからご覧いただけます。

今回の会場は海のすぐそばでした。せっかく沖縄に来たので沖縄らしいことをしたくなり、Softening Day のお昼休憩を使って少しだけ海に遊びに行きました。とてもきれいな海でした。潮風がとても気持ちよかったです。 沖縄の海

今後 Hardening Project に参加するみなさんへ

たくさん反省したいことはありますが、今後参加される皆さんにお伝えしておきたい、私の過ちをここに記しておきます。

慌てない、焦らない

慌ててはいけません。
慌てると、普段から使っているはずの簡単なコマンドの使い方が分からなくなりますし、机の上の物は落としますし、お手洗いに向かっている間にこけそうになりますし、本当に何もいいことがありません。(すべて本当に起きたことです……)
何か大きなことをする前は深呼吸して、落ち着いて行動しましょう。「本番環境を触るのだから当たり前では?」と思うかもしれませんが、その「当たり前」ができなくなるのが Hardening Project の恐ろしい(面白い?)ところです。

サーバの構成を把握するのはとても重要

たくさんのサーバの運用を任されます。たくさんあるので、たまに監視や設定が漏れます。そうやって対応が漏れたサーバでインシデントが起こります。
ですから、サーバの構成はきちんと把握しておきましょう。そして、きちんとすべてのサーバ(せめて、死守しなければならないサーバだけでも)をまんべんなく保守・監視しましょう。可能であれば、サーバの監視ツールや、構成管理ツールを導入すると、人間の手でやるより抜け漏れが少なくなるのでよいかもしれません。

「ほうれんそう」はとても重要

社会人の基本としてよく教えられる「報告」「連絡」「相談」は、Hardening Project 競技中も非常に大切です。たとえば、インシデントが起きたらチームに報告したり、ちょっとした設定を変えた後にはその旨を連絡したり、ちょっと悩んだらすぐ誰かに相談したり。これらはすべて普段の業務では「当たり前」と思われることですが、様々なイベントが同時多発的に起きる競技中はうっかり忘れてしまいがちです。そして、うっかり忘れていた「ほうれんそう」事項が原因で障害が起きる可能性が十分にあります。ですから、競技中は普段以上に「ほうれんそう」を大事にしていきましょう。
また、チーム全体で「ほうれんそう」がしやすい雰囲気のチームを作るように心がけるとよいでしょう。具体的には、どんなことでも相談しやすい雰囲気のチームを目指すとよいと思います。

非エンジニアメンバーの有効活用

チームメンバーの石渡からのメッセージも掲載します。エンジニアではないけれど Hardening に興味がある皆さん、必見です。

おまけで非エンジニアメンバーからのつぶやきです。
初期設定をやっている時間帯など、何もできない(と思って止まってしまう)時間帯が結構あったのですが、もっとできることがあったはずなので、非エンジニアメンバーがいるチームは、事前資料を入手したときに非エンジニアメンバーにやってもらいたい作業の洗い出しをできるだけ具体的にしておくと良いと思います。
例えば、webサイトをユーザー視点で定期的に見ていくとか、全てのメールを確認する(ための環境構築?)をして、とにかく返信するとか、初期から何も考えずに広告購入をするとか、そのメンバーのスキル等に合わせて当日の環境で対応してもらえそうなもの(でやっておくとエンジニアメンバーが助かるもの)を事前に洗い出して、とにかく動いてもらえるようにする、ということをしておくと、非エンジニアメンバーがいることがプラスに働くと思います。
また、技術系以外のところ(特に法務的なところ)は、思い切った行動(経営者への意見メールなど、運営サイドの予想を超えるもの)が評価されたりするようなので、非エンジニアで参加された方は、広い意味での「会社の堅牢化」のために必要なものは、思いついたら積極的に行動してみるのが良いと思います。

まとめ

Hardening Project の会場はまさに「精神と時の部屋」でした。10 時間でECサービスを Hardening することで、堅牢化の技術や心構えを一気に叩きこまれました。また、これからさらに技術を深めていくために、自分に足りないことを知ることもできました。それに、初対面の人といかにうまくやるか、考えるきっかけにもなりました。
Hardening は、コンピュータセキュリティインシデントにかかわる可能性のあるすべての人に薦めたい競技です。精神的にも肉体的にもきついことは間違いないですが、参加する前と参加した後では物の見え方が変わるかもしれません。私は変わりました。
最後になりましたが、Hardening Project 実行委員会の皆様、このような素敵な競技を開催してくださり、本当にありがとうございました。

 

RでDatadogのデータを取得するパッケージをCRANで公開しました

ドーモ、SREチームの湯谷(@yutannihilation)です。ニンジャスレイヤー7周年おめでとうございます。

Wantedlyの募集ページにも画面が映りこんでいますが、SREチームではDatadogも使っています。

Datadogはグラフ描画も高機能で便利ですが、やはり慣れ親しんだRでグラフが描きたい。誰しもそんな気持ちになることもありますよね。ということで、datadogrというパッケージをつくりました。

https://cran.r-project.org/package=datadogr

インストール

datadogrはCRANにリリースされているのでinstall.packages()でインストールできます。

install.packages("datadogr")

使い方

認証

datadogrパッケージを使うにはまず、DATADOG_API_KEYという環境変数にAPIキーを設定します。これは、.Renvironに書いておくか、k9_auth()でインタラクティブに設定するという方法があります。

library(datadogr)

k9_auth()

上のコードを実行するとポップアップが出てきます。ここにAPIキーを入力するとDATADOG_API_KEY環境変数に設定してくれます。

f:id:cybozuinsideout:20170725084905p:plain

ちなみに、datadogrパッケージの関数にはすべてk9_というプレフィックスがつきますが、K9というのはDoctor WhoというBBCのSFドラマに出てくる機械の犬です。「Datadog」という言葉から私はこれを連想しました。

実は、もともとパッケージ名もK9にしていたんですが、パッケージの審査で「分かりにくくね??」(意訳)というツッコミを受けたので無難なパッケージ名にフォールバックしました。うう…

メトリクス名の一覧を取得

k9_list_metrics()を使うと、メトリクス名の一覧を取得できます。デフォルトだと直近1時間に値のあるメトリクス名が返ってきます。例えば、IO関連のメトリクスは以下のようなものです。

library(tidyverse)

k9_list_metrics() %>%
  stringr::str_subset("^system.io")
#> This API is rate-limited and you have 98 requests left (reset after 2555 seconds)
#> 
#>  [1] "system.io.avg_q_sz"  "system.io.avg_rq_sz" "system.io.await"     
#>  [4] "system.io.r_await"   "system.io.r_s"       "system.io.rkb_s"
#>  [7] "system.io.rrqm_s"    "system.io.svctm"     "system.io.util"
#> [10] "system.io.w_await"   "system.io.w_s"       "system.io.wkb_s"
#> [13] "system.io.wrqm_s" 

メトリクスを取得

k9_get_metrics()を使うとメトリクスを取得できます。

Datadogからメトリクスを取り出すには独自のクエリの記法があります(参考:Graphing Primer using JSON)。基本的には、

メトリクス名{スコープ} by {グループ}

という形式になります。

メトリクス名は上で取得したsystem.io.r_sのようなものです。スコープは、メトリクスを絞り込むためのもので、role:dbのようなタグを指定します。グループはメトリクスをどのように分けるかを指定するもので、たとえばホストごとのメトリクスが見たい場合はhostを指定します。

例として、roleタグがdbかつgroupタグがtest1のホストのsystem.io.r_sを、各ホストのデバイス単位で見たい、という場合を考えてみます。クエリは以下のようになります。

system.io.r_s {role:db, group:test1} by {host, device}

これをk9_get_metrics()のクエリ引数に指定すると、欲しいメトリクスが取得できます。デフォルトだと直近1日分のメトリクスが取得できます。

d <- k9_get_metrics(
  query = "system.io.r_s {role:db, group:test1} by {host, device}"
)

結果はデータフレーム形式になっています。タイムスタンプと値、それにスコープやグループに指定した属性値が入っています。

glimpse(d)
Observations: 26,588
Variables: 11
$ timestamp    <dttm> 2017-07-23 22:30:00, 2017-07-23 22:35:00, 2017-07-23 22:40:00, 2017-07-23 22:45:00, 2017-07-23 ...
$ value        <dbl> 0.00000000, 0.00000000, 0.00000000, 0.00000000, 0.00000000, 0.00000000, 0.00000000, 0.00000000, ...
$ metric       <chr> "system.io.r_s", "system.io.r_s", "system.io.r_s", "system.io.r_s", "system.io.r_s", "system.io....
$ display_name <chr> "system.io.r_s", "system.io.r_s", "system.io.r_s", "system.io.r_s", "system.io.r_s", "system.io....
$ query_index  <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
$ interval     <int> 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 3...
$ device       <chr> "sdb", "sdb", "sdb", "sdb", "sdb", "sdb", "sdb", "sdb", "sdb", "sdb", "sdb", "sdb", "sdb", "sdb"...
$ group        <chr> "test1", "test1", "test1", "test1", "test1", "test1", "test1", "test1", "test1", "test1", "test1...
$ host         <chr> "db-34", "db-34", "db-34", "db-34", "db-34", "db-34", "db-34", "db-34", "db-34", "db-34", "db-34...
$ role         <chr> "db", "db", "db", "db", "db", "db", "db", "db", "db", "db", "db", "db", "db", "db", "db", "db", ...
$ expression   <chr> "system.io.r_s{device:sdb,group:test1,host:db-34,role:db}", "system.io.r_s{device:sdb,group:test...

このクエリを文字列として組み立てるのは面倒なので、k9_get_metrics()にはquery引数の代わりにmetricscopeby引数を指定することもできます。以下のコードは上と同じリクエストを送ります。

k9_get_metrics(
  metric = "system.io.r_s",
  scope = list(role = "db", group = "test1"),
  by = c("host", "device")
)

他にも、fromto引数でメトリクス取得の期間を変えたり、.split_request引数でリクエストを日ごとに分割するかどうかを選べたりします。詳しくはドキュメント(?k9_get_metrics)をご参照ください。

グラフを描く

このデータを使って、最近はやりのjoyplot*1を描いてみましょう。ggjoyパッケージを使います。

library(ggjoy)

# ホスト名を並べ替え
x <- d %>%
  mutate(host = factor(host, levels = naturalsort::naturalsort(unique(.$host))))

ggplot(x, aes(value, host)) +
  geom_joy(fill = "tomato", colour = "white", alpha = 0.8) +
  facet_wrap(~ device) +
  scale_x_sqrt(limits = c(NA, 100)) +
  theme_minimal() +
  labs(title = "system.io.r_sの分布",
       subtitle = "role:db, group:test1")

f:id:cybozuinsideout:20170725084937p:plain

ちなみに、scale_x_log10()(対数スケール)ではなくscale_x_sqrt()になっているのは、メトリクスにゼロが多くてlogだとエラーになるからです。 ゼロが多いのは当然で、よく使われるデータが正しくメモリに載っていれば読み取りのIOは発生しません。 逆に言うと、メトリクスが横長に這いつくばっているホストがいくつかいますが、これは読み取りIOが発生しているのでチューニングの余地があるという兆候です。

Datadogも便利なんですけど、こんな感じで多数のホストのグラフをひたすら並べていく、みたいなときはやっぱりRがやりやすいですね。

最後に

何かあれば、GitHubレポジトリのIssuesTwitterでお知らせいただけると助かります。

SREチームではDatadogをバリバリ使い倒せる人、私ならRのグラフにも勝てる!という猛者を募集しています。募集要項はこちらです。

*1:Joy Divisionのアルバム「Unknown Pleasures」のジャケットに似ていることから名付けられたグラフ。確率密度曲線を所狭しと並べていく。