LoginSignup
13
7

More than 5 years have passed since last update.

bashスクリプトやPHPを実行できるslack botを作る

Last updated at Posted at 2017-12-20

この記事はaratana Advent Calendar 2017 21日目の記事です

こんにちは、インデントは全角スペース派1の岡元です。
前日は@aratana_tamutomoさんの「Vim一年生による、Vimプラグイン作成指南」というエントリーでした。
Vimプラグインってあんな感じで作るんですね!

今回は、bashスクリプトやPHPコードを実行するslack botを作ったのでそれについて書こうと思います。
(botのソースはこちらです)

botについて

botに対してメンションしてメッセージを送ると、メッセージをbashスクリプトとして実行してくれます。
また、メッセージに-phpのプレフィックスをつけるとPHPのコードとして実行してくれます。

shellbot.gif

スクリプト自体はdockerコンテナ内で実行するのでrm -rf /などファイルを削除する系のコマンドは実行してもおそらく大丈夫ですが、
:(){ :|:& };:などのリソースを食いつぶす系のスクリプトはコンテナ内だけでなくホストにも影響があるかも知れないので実行するときは気をつけたほうがいいかもしれません。

botを作る

botのフレームワークはpythonのslackbotを使います。
プログラムの内容は、受け取ったメッセージをsubprocessでrunするだけです。
メッセージはクオートが全角になっていたりurlエンコードされていたりするのでrunする前に整形します。

plugins/shell.py
from slackbot.bot import default_reply
import subprocess
from plugins.utils.Modifier import Modifier


@default_reply
def shell(message):
    modifier = Modifier()
    text = modifier.modify(message.body['text'])
    print(text)
    res = subprocess.run(text, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, executable="/bin/bash")
    message.send(res.stdout.decode())

また、これをdockerで実行させるためにDockerfileを書きます。
ファイルはdocker hubのpythonのページにあるサンプルとほぼ同じものです。

(サンプルのDockerfileについて、はじめは何でCOPYを二回に分けてるんだろうと思いましたが、
COPYを一回で纏めてしてその後にRUN pip installするとファイルを編集するたびにキャッシュが無効になるからだったんですね。)

PHPコードを評価するサービスを作る

先ほど作成したpythonのイメージとは別にPHPコードを実行するイメージを作成します。

プログラムの内容は単純にhttpリクエストで渡ってきたコードを直接evalする感じです。危ないですね。

php-evaluate-server/public/index.php
if (isset($_REQUEST['eval'])) {
    var_dump(eval($_REQUEST['eval']));
}

実際には、エラー時の出力をいい感じにしたり、入力のreturnや行末の;を省略しても実行されるようにしたいのでpsyshを参考に実装します。

以下はDockerfileです。
multi-stage buildsを用いることで、PHPの依存ファイルのインストールが簡単に行えます。

php-evaluate-server/Dockerfile
FROM composer:1.5.5 AS build

COPY composer.json composer.lock /app/

RUN composer install --no-dev

FROM php:7.1.12-apache-jessie

COPY --from=build /app/vendor /var/www/vendor/

COPY . /var/www/

RUN ln -s /var/www/public/* /var/www/html/

また、slackからメッセージを受け取りPHPコードを評価するサービスを呼び出す処理を先程のpythonイメージに追加します。

plugins/php/main.py
import re
from slackbot.bot import respond_to
from plugins.php.Client import Client as PhpClient
from plugins.utils.Modifier import Modifier


@respond_to('^-php (.*)', re.IGNORECASE)
def main(message, php_message):
    modifier = Modifier()
    client = PhpClient()
    php_code = modifier.modify(php_message)
    message.send(client.eval(php_code))

docker-composeする

これまででbotを実行するpythonイメージとPHPコードを評価するphpのイメージを作成したので、docker-composeを使いこの2つを動かしてみます。

docker-compose.yml
version: '3'
services:
  shellbot:
    build:
      context: .
    networks:
      - bot
    environment:
      - SLACKBOT_API_TOKEN=${SLACKBOT_API_TOKEN}

  php-evaluate-server:
    build:
      context: ./php-evaluate-server
    networks:
      - bot

networks:
  bot:

docker-composeファイルで環境変数の値を指定する際は${...}のように書くことで現在のシェルの変数を入れることができるみたいです。
また、デフォルトの値は.envファイルを見てくれるみたいです。(詳しくはこちら

.env
SLACKBOT_API_TOKEN=xoxb-12345XXX

ここではslackbotの実行に必要なSLACKBOT_API_TOKENという変数を設定しています。
SLACKBOT_API_TOKENにはslackのcustom integrationsでBotsを追加することで取得できるAPI Tokenを設定します。

今作成したdocker-composeファイルを使いbotを動かすには以下のコマンドを実行します。

$ docker-compose up --build

rm -rf /されたら、、swarmのhealthcheckを使う

rm -rf /などを実行されると/bin/bashなどのファイルが削除されてbotが動かなくなってしまうのでhealthcheckでbotが動ける状態にあるか監視します。
dockerのswarmモードを用いるとstatusがunhealthyになった際にコンテナを立ち上げなおしてくれます。

swarmにサービスをデプロイする際にdocker stack deployコマンドを用いるのですが、
その際に使用するdocker-stackファイルを以下のように作成します。

docker-stack.yml
version: '3'
services:
  shellbot:
    image: mokamoto12/slack-shellbot:0.2
    networks:
      - bot
    env_file: .env
    depends_on:
      - php-evaluate-server
    healthcheck:
      test: type ls

  php-evaluate-server:
    image: mokamoto12/php-evaluate-server:latest
    networks:
      - bot
    healthcheck:
      test: type ls

networks:
  bot:

先程のdocker-composeファイルと違うのはbuildが消えimageになっているのと、environmentが消えenv_fileになり、healthcheckの項目が増えています。

buildではなくimageを使う

swarmを用いる際には複数のマシンでコンテナを立ち上げることになるため、それぞれのマシンからアクセスできるdocker registryが必要になるみたいです。
そのため、buildの項目は使うことはできず、imageでregistryにあるイメージを指定します。

environmentで.envファイルによる値の指定ができない

docker-composeファイルでは${...}のように記述することで.envで指定した変数の値を用いることができていましたが、試しにdocker stack deployしてみると値がうまく設定されていませんでした。(よくリファレンスを見ると.envは使えないことが書いてあるんですね)
代わりにenv_fileで環境変数の設定がされているファイルの指定を行います。

healthcheck

healthcheckのtestで記述した内容はコンテナ内で定期的に実行され、その終了ステータスからコンテナがhealthyな状態かunhealthyな状態かを判断してくれます。
また、先程も書きましたがdocker stack deployした時にはコンテナがunhealthyになると勝手に新しくコンテナを立ち上げてくれます。

今回はtype lsを実行し/bin/lsのファイルが削除されていればbotが動かない状態としています。

botを実行する

上で作成したdocker-stackファイルを用いbotを動かします。
以下のコマンドを実行します

$ docker stack deploy -c docker-stack.yml bot

まとめ

docker swarmを用いることでrm -rf /を実行されても復活するbotを作成することができました。

また、今回始めてdocker swarmを使ったのですがdocker-composeで使っていたdocker-compose.ymlをそのまま使うことができず一部試行錯誤する感じでした、
リファレンスを読むだけではわからないことも多く実際に触ってみることで色々理解できることもあったので良かったです。

明日は@okazono_erikaさんの「初対面の人に会う時のドキドキのついて」です。
ドキドキしますね!


  1. 冗談です 

13
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
7