TravisCIが成功したらテストサーバにGithubからクローンしてVAddyを動かす


はじめに

やりたいこと
GithubにPushされたものがTravisCIのテストで成功したら、テストサーバでクローンしてVaddyでセキュリティテストして通知を自動化したい!

TravisCIの設定メインです。

色々と書いていますが、最終的にはVaddyのクライアントを使い公開鍵通信でSSHをする結果になりましたので、他は気にしなくても大丈夫です。参考にしたこちらと全く同じものとなりました。そちらの方が簡潔でわかりやすいです。

gdgdしていますが、色々調べたことを忘れたくなかったのでかきました。

やらなきゃいけないこと

Vaddy
1.テスト設定をする

TravisCI
1.今回テストサーバがTravisのdeployに対応していないため、自分でdeployを設定する
2.テストを走らせる条件を指定できるようにする
3.Vaddyの準備など

その他
1.テスト後の通知など

Vaddy

自動でXSSやSQLインジェクションなどのセキュリティテストをしてくれるもの
アカウントの登録等は、テストが実行できる形(クロールの登録まで)割愛。

テストを実行するには3つやり方がある。
1.ダッシュボードより「Scan」ボタンを押す
2.githubよりクライアントをDLしてコマンドで実行
3.VaddyAPIをコマンドで実行

ダッシュボードよりKEYを発行しておく。

APIを使ってテストをしてみます。下記のようにすると実行できます。変数はTravis上での暗号化を想定しています。

$ curl https://api.vaddy.net/v1/scan -X POST -d "action=start" -d "user=$VADDY_USER" -d "auth_key=$VADDY_AUTH_KEY" -d "fqdn=$VADDY_FQDN"

TravisCI

https://travis-ci.org/
アカウント登録等は割愛。

今回sshする際のパスワードやVaddyAPIのキーなど知られたくない情報がたくさんありますので暗号化していきます。
自前で暗号化しなくてもTravisが用意してくれた暗号で楽々に実現できるのでそちらを使っていきます。

travis encryptコマンドで暗号化して環境変数を定義できますが、今回はコマンドではなくホームページで追加していきます。

1.travisダッシュボードより右上のMore OptionよりSettingを選択。
2.Environment Variables欄のAddを押して環境変数を追加

SSHのサーバとPW、APIKey、テストサーバのVaddy用FQDN、ユーザ名を暗号化して追加しました。
これで環境変数として色々使えるようになります。お手軽ですね。
強いて言うならコマンドで一度に変えれないため、設定を変えたときに面倒くさいかな・・・?

sshpassを使う

コマンドでsshする際にパスワードも一緒に入力できるコマンドです。

.travis.ymlにsshpassをインストールするように追記

addons:
  apt:
    packages:
    - sshpass

実行してみます。
オプションは接続の際にでるAre you sure you want to continue connecting (yes/no)?を防ぐため付けました。

$ sshpass -p XXXXXX(password) ssh -o “StrictHostKeyChecking no” -o “PreferredAuthentications=password” XXXXXX@XXXXXX(SSH先)

エラーにより、詰まったので断念

Warning: Permanently added 'XXXXXX(SSH先)' (ECDSA) to the list of known hosts.
Permission denied, please try again.

.ssh/known_hostsのせいかなとか、.ssh/configのせいかなとか、いろいろ頑張ったのですが、解決できませんでした・・・。オプションで“StrictHostKeyChecking no”にしているが、サーバ側が一般ユーザーのため.ssh/configが変更できないのかな?と考えてます。

多分サーバ側をいじることができる環境なら.ssh/configをいじれば解決できると思いますが、今回はいじれない環境なので、sshpassを諦めsshで接続する方向へ

sshを使う

expectコマンドを使いAre you sure you want to continue connecting (yes/no)?XXXXXX(SSH先)'s password:に対応させます。
expectコマンドについてはこちらを参考にしました。

after_success:
  - expect -c "
    spawn ssh $TEST_SERVER
    expect \"Are you sure you want to continue connecting (yes/no)?\"
    send \"yes\n\"
    expect \":\"
    send \"$SSH_KEY\n\"
    expect \"%\"
    send \"git pull XXXXXX(github)\n\"
    expect \"%\"
    send \"exit\n\"
    interact
    "

こんな感じになりました。
yes/no?が聞かれたらyes、password:を聞かれたらSSH_KEYを入れます。ログインしたらgit pullしてexitです。ですが、戻り値の出し方がわからなかったり、成功してるか失敗しているかもわからなかった...断念...

ssh-askpassを使う

stdinからpasswordを読み込ませるらしい。
参考はこちら

できる未来が見えなかったので、パスワードを諦め、鍵で接続してみる。

公開鍵を使う

ローカルでキーを作り、Travis上で

# 鍵を作る
$ ssh-keygen -f deploy_key

# 秘密鍵を暗号化する為のベースとなる文字列を作成
$ cat /dev/urandom | head -c 10000 | openssl sha1
XXX

# 秘密鍵を暗号化
$ openssl aes-256-cbc -k "XXX" -in deploy_key -out deploy_key.enc -a

# deploy_key.enc を、レポジトリ直下に配置
# deploy_key.pub を、テストサーバーの ~/.ssh/authorized_keys に追記

# Travis 用のコマンドをインストール
$ sudo gem install travis

# 復号化用の文字列を作成 (.travis.ymlに自動的に記述される)
# ここはコマンドではなくダッシュボードで追加してもok
$ travis encrypt -r XXXX(github) "SERVER_KEY=XXX" -a

# .travis.yml 編集(最終的に、テストサーバー上で、script.sh 実行される)
- openssl aes-256-cbc -k "$SERVER_KEY" -in deploy_key.enc -d -a -out deploy.key
- cp deploy.key ~/.ssh/
- chmod 600 ~/.ssh/deploy.key
- ssh -i ~/.ssh/deploy.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $TEST_SERVER 'bash -s' < script.sh

script.sh

git pull origin dev-4
touch vaddy_test.txt

公開鍵暗号通信で楽に接続できました。今までの努力が....w
あとは.shの中に処理を書けば色々なことができます。

条件分岐

テストの数だけ毎回Vaddyテストが動いたり、プルリクのたびにテストサーバが汚されたりすると困るので、masterブランチがpushされたときだけ、sshしてテストを動かすようにします。

  - if [ "$TRAVIS_PHP_VERSION" == "5.6" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "master" ]; then
    openssl aes-256-cbc -k "$SERVER_KEY" -in deploy_key.enc -d -a -out deploy.key;
    cp deploy.key ~/.ssh/;
    chmod 600 ~/.ssh/deploy.key;
    ssh -i ~/.ssh/deploy.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $TEST_SERVER 'bash -s' < script.sh;
    fi;

自動取得

apiを使っているのですが、終了判定が用意されていないため自分で作成する必要があります。
apiの返り値でスキャンidが帰ってくるのでスキャンidを元に結果をリクエストし続けます。

$ curl ($api) | echo $?
0

とすることで curlの出力結果を表示してみたい..のですが、結果は0
idを取得できないと結果がとってこれません。

##スキャン実行
$ RES=$(curl -Ss https://api.vaddy.net/v1/scan -X POST -d "action=start" -d "user=$VADDY_USER" -d "auth_key=$VADDY_AUTH_KEY" -d "fqdn=$VADDY_FQDN")
echo "$RES"

##結果取得
$ RES=$(curl -G -d "user=$VADDY_USER&auth_key=$VADDY_AUTH_KEY&fqdn=$VADDY_FQDN&scan_id=$VADDY_ID" https://api.vaddy.net/v1/scan/result)
echo "$RES"

変数に代入して結果を取得してみました。とりあえず表示はできました。
表示されたものを利用するためには一旦中間ファイルに書き出したりする必要があるみたいです。

あとはこれをbashでwhileして10分起きくらいにおわったか確認したらいいしたらいいのかな?

・・ここでVaddyのクライアントツールの方がコマンド実行後、進行度がわかり、終了されたら結果がかえってくることが判明。
APIを捨ててクライアントを使おうかとおもいます。

APIからCLIツールへ

Travis上で下記のコマンドでインストールし実行します。travisはlinuxなのでlinux-64bitを実行
仕様書

git clone https://github.com/vaddy/go-vaddy.git
cd go-vaddy/bin/vaddy-linux-64bit $VADDY_AUTH_KEY $VADDY_USER $VADDY_FQDN

最初はshに書いてたのですが、正常に動作しなかったため、travisのbefore_scriptに git cloneを加えました。

実行すると

No output has been received in the last 10m0s

となり、30分くらいかかるテストの10分ほどでエラーがでて強制終了されてしまいます。
進捗が出力されてるはずなのになー・・と思いながら、エラーがでないように。

travisが10分間出力がない場合強制終了するみたい。
travis側で時間を変更するコマンドが用意されていました。
参考こちら

#30分までタイムアウトしない
travis_wait 30 XXX(コマンド)

しかしこちらもエラー・・・

Terminated              travis_jigger $! $timeout $cmd

このエラーが解消できなかったため、別の方法を模索

とりあえず何かアウトプットすればいいので、何分おきかにechoで適当に表示させてみます。
参考2こちら
しかしうまくいかず、リアルタイムで何か表示させれないかと色々調べました。

とりあえず今の状態は

#travis
./vaddy.sh | ./slack.sh

とパイプで通知を実装しようとしていたのですが、これではログが標準出力されないらしく、

./vaddy.sh

としたら出力される感じ。
どうにか変数に結果をいれることができないと何もできない!

またまたぐぐったり人にきいたり。すると、teeコマンドで結果を保存できることが判明!

teeはログを標準出力しつつ、ファイルにも出力してくれるコマンドなので、今の状況にぴったりです!

./vaddy.sh | tee result.txt
$msg = 'cat result.txt'
echo "$msg"

これで解決しました!

何度もリクエストさせる

TravisCIでは並列でテストが進むことがあるが、Vaddyは並列のテストにはエラーの原因になるため、対応していません。なので、テストリクエストが失敗してもテストリクエストが成功するまで、リクエストし続ける処理を書かなければいけません。

さっき用意した変数を使い、正規表現で判断します。
vaddy.shに、[[ ]]を使う正規表現の判別を追加してみます。

msg=`cat result.txt`

if [[ $msg =~ "Scan has already been running" ]]; then
    while :
    do
      echo "テスト中のため5分後にもう一度テストリクエストします。"
      sleep 300
      go-vaddy/bin/vaddy-linux-64bit $VADDY_AUTH_KEY $VADDY_USER $VADDY_FQDN | tee result.txt
      msg=`cat result.txt`
      if [[ ! $msg =~ "Scan has already been running" ]]; then
        break
      fi
    done
fi

すごく汚い気がしますが、とりあえず動くように。しかし、ローカルでは成功するのですが、どうもtravis上では[[ ]]が使えない様子・・・なぜ?

#!/bin/sh
から下記に変更
#!/bin/bash

どうやらshではだめみたいです。

とりあえず動くようになったやつ

travis.yml

after_success:
    #Vaddyテスト
  - cd ../secure_test/
  - if [ "$TRAVIS_PHP_VERSION" == "5.6" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "dev-4" ]; then
    ./vaddy.sh;
    fi;

vaddy.sh

#!/bin/bash

git clone https://github.com/vaddy/go-vaddy.git;

while :
do
    timeout 5 go-vaddy/bin/vaddy-linux-64bit $VADDY_AUTH_KEY $VADDY_USER $VADDY_FQDN | tee result.txt
    msg=`cat result.txt`

    if [[ $msg =~ "Scan has already been running" ]]; then
          echo "既に他のテスト実行中のため5分後にもう一度テストリクエストします。"
          sleep 300
    fi

    if [[ ! $msg =~ "Scan has already been running" ]]; then
        break
    fi

done

openssl aes-256-cbc -k "$SERVER_KEY" -in deploy_key.enc -d -a -out deploy.key
cp deploy.key ~/.ssh/
chmod 600 ~/.ssh/deploy.key
ssh -i ~/.ssh/deploy.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $TEST_SERVER 'bash -s' < script.sh

並列でテストしているときのために、一度テストを動かして、動けばデプロイしてもう一度動かす、という風にしたいと思い書きましたが、これではテスト結果がとってこれず、timeoutコマンドで強制終了したらもうテストをコマンドではキャンセルする術がないです。困りました。
ここでAPIだけがスキャンIDをとってこれ中止や結果取得ができることに気づきます!
前述のAPIで詰まっていた出力問題はもう解決しているので、このコードを書き直しました。

#確認
while :
do
    curl -Ss https://api.vaddy.net/v1/scan -X POST -d "action=start" -d "user=$VADDY_USER" -d "auth_key=$VADDY_AUTH_KEY" -d "fqdn=$VADDY_FQDN" | tee result.txt
    msg=`cat result.txt`
    if [[ $msg =~ "Scan has already been running" ]]; then
          echo "既に他のテスト実行中のため5分後にもう一度テストリクエストします。"
          sleep 300
    fi
    if [[ $msg =~ scan_id\":\"(.*)\" ]]; then
        SCAN_ID=${BASH_REMATCH[1]}
        curl -Ss https://api.vaddy.net/v1/scan -X POST -d "action=cancel" -d "user=$VADDY_USER" -d "auth_key=$VADDY_AUTH_KEY" -d "scan_id=$SCAN_ID" -d "fqdn=$VADDY_FQDN"
        break
    fi
done

# デプロイ
openssl aes-256-cbc -k "$SERVER_KEY" -in deploy_key.enc -d -a -out deploy.key
cp deploy.key ~/.ssh/
chmod 600 ~/.ssh/deploy.key
ssh -i ~/.ssh/deploy.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $TEST_SERVER 'bash -s' < deploy.sh

#Vaddy実行
curl -Ss https://api.vaddy.net/v1/scan -X POST -d "action=start" -d "user=$VADDY_USER" -d "auth_key=$VADDY_AUTH_KEY" -d "fqdn=$VADDY_FQDN" | tee result.txt
msg=`cat result.txt`
echo "$msg"
if [[ $msg =~ scan_id\":\"(.*)\" ]]; then
    SCAN_ID=${BASH_REMATCH[1]}
fi;

#結果取得
while :
do
    echo "$SCAN_ID"
    curl -G -d "user=$VADDY_USER&auth_key=$VADDY_AUTH_KEY&fqdn=$VADDY_FQDN&scan_id=$SCAN_ID" https://api.vaddy.net/v1/scan/result | tee result.txt
    msg=`cat result.txt`
    if [[ $msg =~ status\":\"scanning ]]; then
        echo "テストが完了していないため、5分後にもう一度確認します。"
        sleep 300
    fi
    if [[ ! $msg =~ status\":\"scanning ]]; then
        break;
    fi
done

あとはこのresult.txtの中身をslack通知するだけ!
長かったができてよかった!

あとがき。

scanidがとってこれなかったり、キャンセルが強制終了したらできなくなったり。まだまだ増えてほしい機能が多いですね。shとかsshとかコマンドとか、色々勉強できました。

--追記(2017/08/25)
https://github.com/vaddy/WebAPI-document/commit/fac22ae0750e1f4885cc481a25092968911be0c2
APIが追加されたので、実行可能かどうか状況を確認するための実行リクエストを出しているwhile部分を

while :
do
    curl -G -d "user=$VADDY_USER&auth_key=$VADDY_AUTH_KEY&fqdn=$VADDY_FQDN" https://api.vaddy.net/v1/scan/runcheck | tee result.txt
    msg=`cat result.txt`
    if [[ $msg =~ {\"running_process\":0} ]]; then
        break
    fi
    if [[ $msg =~ {\"running_process\":.*} ]]; then
          echo "既に他のテスト実行中のため5分後にもう一度テストリクエストします。"
          sleep 300
    fi
done

に変更しました。これにより実行が2度行われないのでキャンセルする必要もなくなりました。