GoでSecrets Managerに保存したSlackトークンを使ってSlackへメッセージ送る

Secrets ManagerからGolangのSDKを使い保存した値を取ってくる動きを確認しました。
2020.09.26

Secrets Managerに用があったのでAWS SDK for Goを使い少し触れてみました。

  • AWS Secrets Managerは資格情報などの機密情報を一元的に保存、取得、アクセス制限、更新、監査、モニタリングできるサービスです。
  • GoのSDKを使いSecrets Managerに保存した情報を取得して利用してみます。

お題

Slackにテストメッセージを送信する簡単なプログラムを作ります。Slackにメッセージを送信するために必要なSlackのトークン情報を事前にSecrets Managerへ保存します。プログラム実行時にSecrets Managerからトークン情報を取得してSlackへメッセージを送信します。

成果物

プログラムを実行するとメッセージが届く!それだけです!!

Secrets Managerの設定

事前準備: トークンを作成しておきます。

Slack API: Applications | Slack

シークレットキーは任意で設定します。ここで設定したシークレットキーは後々JSONをパースする際に使います。値には必要なSlackのトークンと面白みのないサンプルになりそうなのでチャンネルIDもついでに入れました。

シークレットの名前は任意で設定します。SDKで呼び出すときにここの名前を使います。

Goのサンプルコードが表示されました。エラーの処理が少し長くて端折って短くしました。

やってみた

Secrets Managerから保存したトークン情報とチャンネルIDを取得し、Slackへテストメッセージを送ります。

  • AWS SDK for Goを使ってSecrets Managerに保存した値を取得します。
  • Slackへのテストメッセージ送信はslack-go/slackのサンプルを流用します。

後半の説明はSecrets Managerの値を取得した際の備忘録です。

環境

> go version
go version go1.15.2 darwin/amd64

コード

main.go

package main

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/secretsmanager"
	"github.com/slack-go/slack"
)

// REGION ...
const REGION = "ap-northeast-1"

func main() {
	// The name you saved in Secrets Manager
	secretName := "SlackInfo"

	// Fetch a value from the Secrets Manager
	secretChannelID, secretSlackToken, err := getSecret(secretName) // メモ5-1
	if err != nil {
		log.Fatal(err)
	}

	// Send to Slack channel
	api := slack.New(secretSlackToken) // メモ5-2
	sendMessage := "Test Message"
	channelID, timestamp, err := api.PostMessage(secretChannelID, slack.MsgOptionText(sendMessage, false)) // メモ5-3
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Message successfully sent to channel %s at %s", channelID, timestamp)
}

func getSecret(sec string) (string, string, error) {
	// Create a Secrets Manager client
	svc := secretsmanager.New(session.New(),
		aws.NewConfig().WithRegion(REGION))
	input := &secretsmanager.GetSecretValueInput{
		SecretId:     aws.String(sec),
		VersionStage: aws.String("AWSCURRENT"), // VersionStage defaults to AWSCURRENT if unspecified
	}

	result, err := svc.GetSecretValue(input)
	if err != nil {
		return "", "", err
	}
	fmt.Printf("%T型\n%[1]v\n", result) // メモ1

	secretString := aws.StringValue(result.SecretString)
	fmt.Printf("%T型\n%[1]v\n", secretString) // メモ2

	res := make(map[string]interface{})
	if err := json.Unmarshal([]byte(secretString), &res); err != nil {
		return "", "", err
	}
	fmt.Printf("%T型\n%[1]v\n", res) // メモ3

	return res["channel_id"].(string), res["slack_token"].(string), nil // メモ4
}

メモ1の出力結果より

GetSecretValueでSecrets Managerから取得した結果、GetSecretValueOutputのポインター型で返ってきました。欲しい値はSecretsStringの部分です。

*secretsmanager.GetSecretValueOutput型
{
  ARN: "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:SlackInfo-r5Aq3u",
  CreatedDate: 2020-07-24 01:34:41 +0000 UTC,
  Name: "SlackInfo",
  SecretString: "{\"channel_id\":\"xxx\",\"slack_token\":\"nnn\"}",
  VersionId: "790573b5-d0a0-40a2-8aa8-577dbc757bba",
  VersionStages: ["AWSCURRENT"]
}

メモ2の出力結果より

最初はsecretString := *result.SecretStringでデリファレンスすればよいかと思いました。しかし、aws.StringValue()で変換するほうがお行儀のよいような感じがしたのでこちらを採用。JSON形式でSecrets Managerに保存した値を取り出せました。

string型
{"channel_id":"xxx","slack_token":"nnn"}

aws - Amazon Web Services - Go SDK

メモ3の出力結果より

interface型のmapで受け取りました。メモ4でmapから値を取り出します。

map[string]interface {}型
map[channel_id:xxx slack_token:nnn]

メモ4

mapのkeyにSecrets Managerで保存したSecret keyの名前が必要になります。returnで返すときにstring型へ変換しています。後々、Slack送信処理時にslack.Newや、PostMessageでTokenとChannel IDを渡す際、string型で必要になりました。

	return res["channel_id"].(string), res["slack_token"].(string), nil // メモ4

メモ5

secretChannelIDsecretSlackTokenの変数にSecrets Managerで保存した値を入れることができました。あとはSlackを扱うライブラリにトークン情報と、チャネルIDを渡しているだけです。

おわりに

本職ではない上に勉強中の身ですので至らぬ点も多いかもしれません、ご了承ください。
コード書く時間よりSDKのドキュメント読むのに時間かかったのでどなたかの参考になりましたら幸いです。
最後までお読みいただきありがとうございました。

以上、コンサル部の大村@網走でした。

参考

secretsmanager - Amazon Web Services - Go SDK
Goで JSONをパースしたい時のやつ. はじめに | by taka | Medium