LoginSignup
6

More than 3 years have passed since last update.

Slash Commandsで自分が使用している端末のグローバルIPは取得できるのか【API Gateway + Lambda(Go)】

Posted at

はじめに

こんにちは。先月AWS ChatbotからRDSを操作していて、とても簡単にSlackから実行できて感動したので、第2弾です!今回はSlackから自分のグローバルIPを取得できるのかを、試してみました!

結論

残念ながら、Slackから自分のグローバルIPは取得できませんでした。

Slackで自分のグローバルIPを取得するべく、Slash CommandsというSlackのapiと、AWSのAPI Gateway、Lambdaを使って試みたので、その過程について備忘録です。

方法検討

自分のグローバルIPを取得して、それを引数としてLambdaを実行したかったので、AWSを使う方針で進めていきます。

調査したところ、SlackからLambdaを実行する方法を2つ見つけました。

  • API Gatewayを使う方法(Slash Commandsの作成)
  • AWS Chatbotを使う方法

API Gatewayはアクセス元のグローバルIPを取得できるというのを知ったので、今回はAPI Gatewayを使う方法(Slash Commandsの作成)で実装することにしました!

構成図

構成は下図の通りです。
ちなみに、Slash commandsは3000ms以内に応答を返す必要があるので、処理時間が長いものに関しては先にSlackに応答を返す必要があるみたいです。今回はアクセス元IPを返すだけなので、こちらの構成で実装しました。
参考:https://api.slack.com/interactivity/slash-commands

Slash_commands.png

やってみる

Lambda関数の作成

今回はチャンネルを識別し、指定チャンネルから実行されていれば、アクセス元のグローバルIPを返すというLambda関数を作りました。

チャンネルを識別する意図としては、Slash Commandsは、ワークスペース内の全チャンネル、全DMで使えるもので、チャンネルを指定することができません。今回作成するSlash CommandsはグローバルIPを返すだけなので、どこで実行できても問題はないのですが、実際には指定したプライベートチャンネルのメンバーのみしか実行できないようにしたかったので、実行チャンネルを識別し、実行権限があるのかを判断する処理を入れています。

IAMロール

特別必要な権限はないので、基本的なLambdaアクセス権限をアタッチしたロールを使用します。

Slackから送られるデータと構造

Slackから送られるデータと構造は下記の通りです。
※ Valueは削除しています。本来はそれぞれ値が入っています。
※ 後述のAPI Gatewayのマッピングテンプレートを使用した場合の構造です。

{
  "querystring":{
    "api_app_id":"",
    "channel_id":"",
    "channel_name":"",
    "command":"",
    "response_url":"",
    "team_domain":"",
    "team_id":"",
    "text":"",
    "token":"",
    "trigger_id":"",
    "user_id":"",
    "user_name":""
  },
  "source_ip":""
}

ソースコード


package main

import (
    "github.com/aws/aws-lambda-go/lambda"
    "os"
)

type MyEvent struct {
    Querystring Querystring `json:"querystring"`
    SourceIp string `json:"source_ip"`
}

type Querystring struct {
    ChannelId string `json:"channel_id"`
}

/**************************
    処理実行
**************************/
func run(event MyEvent) (interface{}, error) {
    // os.Getenv()でLambdaの環境変数を取得
    if event.Querystring.ChannelId == os.Getenv("channelId") {
        return event.SourceIp, nil
    }
    res := "実行権限がありません。"
    return res, nil
}

/**************************
    メイン
**************************/
func main() {
    lambda.Start(run)
}

API Gatewayの作成

API Gatewayの作成を行います。
POSTメソッドを作成し、先ほど作成したLambda関数を紐付けます。

そして、マッピングテンプレートの設定をしていきます。
[統合リクエスト]をクリックします。

スクリーンショット 2020-09-06 23.39.41.png

最下部にあるマッピングテンプレートを開き、リクエスト本文のパススルーはテンプレートが定義されていない場合 (推奨)を選択します。そして、[マッピングテンプレートの追加]をクリックします。

スクリーンショット 2020-09-06 23.45.21.png

[マッピングテンプレートの追加]を押したら出てくるテキストボックスにapplication/x-www-form-urlencodedを入力、保存します。

テンプレートの入力フォームが出てくるので、下記のマッピングテンプレートを貼り付け、[保存]をクリックします。

スクリーンショット 2020-09-06 23.43.58.png

【マッピングテンプレート】

#set($rawAPIData = $input.path('$'))
## escape any quotes
#set($rawAPIData = $rawAPIData.replace('"', '\"'))

## first we get the number of "&" in the string, this tells us if there is more than one key value pair
#set($countAmpersands = $rawAPIData.length() - $rawAPIData.replace("&", "").length())

## if there are no "&" at all then we have only one key value pair.
## we append an ampersand to the string so that we can tokenise it the same way as multiple kv pairs.
## the "empty" kv pair to the right of the ampersand will be ignored anyway.
#if ($countAmpersands == 0)
 #set($rawPostData = $rawAPIData + "&")
#end

## now we tokenise using the ampersand(s)
#set($tokenisedAmpersand = $rawAPIData.split("&"))

## we set up a variable to hold the valid key value pairs
#set($tokenisedEquals = [])

## now we set up a loop to find the valid key value pairs, which must contain only one "="
#foreach( $kvPair in $tokenisedAmpersand )
 #set($countEquals = $kvPair.length() - $kvPair.replace("=", "").length())
 #if ($countEquals == 1)
  #set($kvTokenised = $kvPair.split("="))
  #if ($kvTokenised[0].length() > 0)
   ## we found a valid key value pair. add it to the list.
   #set($devNull = $tokenisedEquals.add($kvPair))
  #end
 #end
#end

{
"querystring" : {
#foreach( $kvPair in $tokenisedEquals )
  ## finally we output the JSON for this pair and append a comma if this isn't the last pair
  #set($kvTokenised = $kvPair.split("="))
  #if($kvTokenised.size() == 2 && $kvTokenised[1].length() > 0)
    #set($kvValue = $kvTokenised[1])
  #else
    #set($kvValue = "")
  #end
  #if( $foreach.hasNext )
    #set($itemDelimiter = ",")
  #else
    #set($itemDelimiter = "")
  #end
 "$kvTokenised[0]" : "$kvValue"$itemDelimiter
#end
},
"source_ip" : "$context.identity.sourceIp"
}

最後にリソースのデプロイを行い、発行されたURLをコピーしておきます。

スクリーンショット 2020-09-07 0.17.36.png

Slash Commandsの作成

Slack appの作成

Slack apiからSlack appを作成していきます。

[Create New App]をクリックするとAppの作成画面に遷移します。

スクリーンショット 2020-09-05 13.26.37.png

ここで、Slack Appの名前Appを実行したいワークスペースを選択します。
そして、[Create App]をクリックするとSlack Appが完成します。

スクリーンショット 2020-09-05 13.27.43.png

Slash commandsの作成

Appの設定ページに移動して、Slash commandsを作成します。

左側のメニューから[Slash commands]を選択し、[Create New Commands]をクリックします。

スクリーンショット 2020-09-05 13.28.36.png

次に、Slackから送信するコマンド名前述のAPI Gatewayで取得したリクエストURL説明文の3つを入力します。今回は、/get_ipというコマンド名を設定しました。

スクリーンショット 2020-09-05 13.44.48.png

今回は引数を入力しないのでヒントは入れませんでしたが、引数など入力時の補足がある場合はUsage Hintに入力しておきます。Escape channels, users, and links sent to your appにチェックを入れることで、引数にユーザ名を入れる時に、ユーザ名だけではなくユニークなユーザIDも送ることができ、アカウントを識別できるそうです。今回はアカウントの識別は不要なので、チェックは入れませんでした。

それぞれ入力完了後、[Save]をクリックすると、Slash commandsが完成します。

Botの名前設定

このままAppのインストールに進むと、(App名)にはインストールするボットユーザーがありません。というエラーが出て、Appをインストールできなかったので、Appの設定ページから、Botを作成します。

ちなみに、App名が小文字英数字の場合は、自動でBotが設定されるみたいので、初期値が入っていればこのフローは不要です。わたしはIP取得マンというApp名にしていたためボットユーザが作られなかったのだと思われます。

左側のメニューから[App Home]を選択し、Your App's Presence in Slackの[Edit]をクリックします。

スクリーンショット 2020-09-06 18.19.39.png

Botの表示名ユーザ名を入力して[Add]をクリックすると、Botの名前が設定されます。

スクリーンショット 2020-09-06 18.19.51.png

Appのインストール

いよいよAppのインストールです。

左側のメニューから[Install App]を選択し、[Install App to Workspace]をクリックします。

スクリーンショット 2020-09-06 18.20.06.png

作成したAppが指定したワークスペースにアクセスするのを許可すると、グローバルIPを取得するコマンドの完成です。

実行結果

実行方法

今回作成したコマンド/get_ipをメッセージとして送信します。

スクリーンショット 2020-09-13 19.22.55.png

指定チャンネルから実行した場合

指定チャンネルから実行した場合のレスポンスは次の通りです。

スクリーンショット 2020-09-06 19.13.55.png

IPアドレスが返ってきました!!!

ただ、自分のグローバルIPを調べてみると・・・違う。。
これはSlackのグローバルIPアドレスですね。。。

残念ながら、自分のグローバルIPアドレスを取得することはできませんでした。

指定外のチャンネルから実行した場合

ちなみに、指定外のチャンネルから実行した場合のレスポンスは次の通りです。

スクリーンショット 2020-09-06 19.13.45.png

これで、実行チャンネルを識別し、指定チャンネル外からは実行できないことが確認できました!

おわりに

結果、SlackからグローバルIPを取得することはできませんでしたが、SlackのIPは取得することができました。ただ、実行する度に違うIPアドレスが返ってくることも確認できました。今回とは関係ないですが、Slackは複数のグローバルIPアドレスを持っていることがわかりました。

前回に引き続き、SlackからLambdaを実行してみましたが、AWS Chatbotと比較してSlash commandsの良いところは個人DMで使えるというところかなと感じました。反対に、Slash commandsの場合は処理時間が3000msを越えるものは、まず応答を返すようにしないといけないところは少し面倒かな、、と思いました。

今回は、残念な結果に終わってしまったので、また違う方法を考えます。SlackからグローバルIPを取得するいい方法があるよっていう方いらっしゃいましたら、教えていただけると嬉しいです!

参考

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
6