Slide 1

Slide 1 text

DockertestとLocalStackを使って 外部サービスに依存した多要素認証の 動作確認・テストをした話

Slide 2

Slide 2 text

自己紹介 ②本文を書くときに、強調する文字は太字にして、色をつける。 ● 基本的な強調(中性的・ポジティブな意味)はカミナシブルーで表現する。 ※注釈などに利用する小さい文字は文字サイズを相対的に小さく調整し、グレイ色をつける。 ① 基本的な配色は、下記の青系のカラーパレットと白・黒・グレーとする。 ② 特別な強調をしたいときは指定の黄色か赤を使用する。 カミナシブルー サブカラー ベースカラー 文字色など 黄(濃) 黄(淡) ※本スライド最終ページに使用例記載 赤(濃) 赤(淡) ①書体は M PLUSを使用。 【文字】 【色】 ■略歴 2020年にソフトウェアエンジニアのキャリアを開始。決済システムの開 発などを経験し、2023年2月にカミナシに入社。 ■カミナシでの仕事 認証認可システムの設計・開発。 OAuth2.0 や OpenID Connect などの標準仕様に準拠した IdP の開発を 進めています。 ■ひとこと 緊張しました! 株式会社カミナシ 認証認可ユニット ソフトウェアエンジニア 西田 智朗(にしだ ともろう)

Slide 3

Slide 3 text

今回の主役

Slide 4

Slide 4 text

⑤ユーザーが受け取ったワンタイムパスワードを入力することで認 証が完了 ②発行したワンタイムパスワードを AWS DynamoDB に保存 今回の主役 メールを使った多要素認証 ①パスワード認証完了後ワンタイムパスワードを発行 ③DynamoDB Streams をイベントソースに AWS Lambda 関数を 起動 ④Lambda から外部のメール配信サービスにリクエストしてユー ザーにワンタイムパスワードを送信

Slide 5

Slide 5 text

何が課題だったか

Slide 6

Slide 6 text

課題 実装した Lambda 関数が本当にちゃんと動くか分からない… 変更したいけど壊れるかもしれないから触りたくない…

Slide 7

Slide 7 text

⑤ユーザーが受け取ったワンタイムパスワードを入力することで認 証が完了 ②発行したワンタイムパスワードを AWS DynamoDB に保存 課題 メールを使った多要素認証 ①パスワード認証完了後、二要素認証の設定がオンになっている ユーザーに対してワンタイムパスワードを発行 ③DynamoDB Streams をイベントソースに AWS Lambda 関数を 起動 ④Lambda から外部のメール配信サービスにリクエストして、ユー ザーにワンタイムパスワードを送信 ここの動作確認がしたい!

Slide 8

Slide 8 text

検討した方法① 自身の検証用 AWS アカウント上で検証しながら開発する この環境を自身のAWSアカ ウント上に作る 【メリット】 ・実際のAWS環境で動かすため、確実な検証ができる 【デメリット】 ・環境を用意する手間がある →仕様変更などのたびに環境を用意して検証する必要がある

Slide 9

Slide 9 text

検討した方法① Go に閉じた世界で手軽に動作確認できんものか…

Slide 10

Slide 10 text

DynamoDBEvent を受け取る Lambda ハンドラーは以下のような シグネチャになる。テスト関数上で想定される DynamoDBEvent を自前で初期化して実行する。 検討した方法② Event を自前で初期化して Lambda ハンドラー関数を実行する func handlerFunc(event events.DynamoDBEvent) error ここで渡されるイベントを 自前で定義する 【メリット】 ・localstackやらDockertestやらの仕組みを整える必要がない ・テスト自体の実行が早い 【デメリット】 ・テストの実装・修正をするためには、どのようなイベントが発行  されるかを知っている必要がある

Slide 11

Slide 11 text

課題に対して辿りついた結論 Dockertest と LocalStack を使って go test を実行するだけで一通り動くようにする

Slide 12

Slide 12 text

②DynamoDB Streamsに詳しくないとEventを自前で定義するのは難しい →DynamoDB Streamsに詳しくないエンジニアでも開発・修正・変更を行えるようにしたかった 今回の結論に至った理由 ③すでに別のテストでDockertestやLocalStackを利用していた →仕組みを整える手間をある程度無視できた ①Go の機能で動くようにしたかった → go test だけで実行できる手軽さ

Slide 13

Slide 13 text

Dockertest と LocalStack について

Slide 14

Slide 14 text

Dockertest について https://github.com/ory/dockertest When developing applications, it is often necessary to use services that talk to a database system. Unit Testing these services can be cumbersome because mocking database/DBAL is strenuous. Making slight changes to the schema implies rewriting at least some, if not all of the mocks. The same goes for API changes in the DBAL. To avoid this, it is smarter to test these specific services against a real database that is destroyed after testing. Dockertest は Go で書いたプログラムから簡単に Docker コンテナを立ち上げることができるライブラリで、 上記ではデータベースのコンテナについて言及しているが、実際は今回のようにテスト用のLocalStackコンテナを立て たり、自前で定義したDockerfileを元にコンテナを立てることもできる。 DB との通信が発生するサービスを作るときにテスト用のモック DB を作った り管理するのって大変ですよね? Dockertest を使うと任意のデータベースの一時的なコンテナをテスト用に 立てることができるため、 DB 通信部分のテストを書くのが楽になります!

Slide 15

Slide 15 text

Dockertest について postgres, err := pool.RunWithOptions(&dockertest.RunOptions{ Repository: "postgres", Tag: "11", Env: []string{ "POSTGRES_USER=test", "POSTGRES_PASSWORD=test", "listen_addresses = '*'", }, }, func(config *docker.HostConfig) { // set AutoRemove to true so that stopped container goes away by itself config.AutoRemove = true config.RestartPolicy = docker.RestartPolicy{ Name: "no", } }) テストを実行する前にこのように記述するだけで一時的なPostgreSQLのコンテナを起動することができる。

Slide 16

Slide 16 text

LocalStack について https://github.com/localstack/localstack LocalStack is a cloud service emulator that runs in a single container on your laptop or in your CI environment. With LocalStack, you can run your AWS applications or Lambdas entirely on your local machine without connecting to a remote cloud provider! Whether you are testing complex CDK applications or Terraform configurations, or just beginning to learn about AWS services, LocalStack helps speed up and simplify your testing and development workflow. LocalStackは、あなたのラップトップやCI環境上で単一のコンテナで動作す るクラウドサービス(AWS)エミュレータです。LocalStackを使えば、 Terraform構築のテストやAWSのサービスを使った複雑なアプリケーション のテストがローカルマシン上で実行できます。 AWS CLI/SDK, Terraform などの適用先を LocalStack コンテナにすることで、 LocalStack 上にリソースを用意するこ とができる。

Slide 17

Slide 17 text

DockertestでLocalStackコンテナを起動するサンプル func UpLocalStackWithDockerTest() (func(), string) { pool, err := dockertest.NewPool("") pool.MaxWait = 5 * time.Second if err != nil { log.Panicf("Could not connect to docker: %v", err) } err = pool.Client.Ping() if err != nil { log.Panicf("Could not connect to docker: %v", err) } runOptions := &dockertest.RunOptions{ Repository: "localstack/localstack", Tag: "latest", Name: "localstack-for-email-otp-test", Env: []string{ "SERVICES=sns,sqs,dynamodb,lambda", "DEFAULT_REGION=ap-northeast-1", "DEBUG=1", }, // ホストマシンのdocker daemonを使ってLambdaコンテナを立てるため // ホストマシンのdocker daemonとソケット通信できるようにする Mounts: []string{"/var/run/docker.sock:/var/run/docker.sock"}, } resource, err := pool.RunWithOptions(runOptions, func(config *docker.HostConfig) { config.AutoRemove = true config.RestartPolicy = docker.RestartPolicy{ Name: "no", } }, ) if err != nil { log.Panicf("Could not start resource: %v", err) } // コンテナ側の4566ポートに対応するホスト側のホストとポートを取得 // ここで取得したホストとポートを使ってLocalStackのAPIにアクセスする hostAndPort := resource.GetHostPort("4566/tcp") healthcheckFunc := func() error { resp, err := http.Get(fmt.Sprintf("http://%s/health", hostAndPort)) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("status code is not 200: %d", resp.StatusCode) } return nil } if err := pool.Retry(healthcheckFunc); err != nil { pool.Purge(resource) log.Panicf("Could not success healthcheck function: %v", err) } close := func() { if err := pool.Purge(resource); err != nil { log.Panicf("Could not purge resource: %v", err) } } return close, hostAndPort }

Slide 18

Slide 18 text

テスト方法の紹介

Slide 19

Slide 19 text

②メール配信サービスは「ワンタイムパスワードの受け取り」と「受け取ったワンタイムパスワードの返却」ができるだ  けのモックサーバーを建てる テストの方針と流れ 【方針】 ①アプリケーションのテストコードを実行すると LocalStack 上に必要な環境が構築され、  ワンタイムパスワードの発行からメール配信リクエスト用のLambda関数まで動くようにする ②テストパッケージの初期化時にterraform-exec(https://github.com/hashicorp/terraform-exec)を使ってDynamoDB  やLambda関数の構築を行う 【流れ】 ①テストパッケージの初期化時にDockertestを使って「LocalStackコンテナ」と「メール配信サービスのモックサー   バーコンテナ」を立ち上げる ③テスト上でワンタイムパスワードの発行、DynamoDBへの保存を行う処理を実行する ④テスト上で「メール配信サービスのモックサーバーコンテナ」の「受け取ったワンタイムパスワードの返却」エンドポ  イントを叩き、取得したコードと生成したコードを比較する

Slide 20

Slide 20 text

テストの流れ(初期化フェーズ)

Slide 21

Slide 21 text

テストの流れ(個別のテストケース)

Slide 22

Slide 22 text

テストの流れ(終了フェーズ)

Slide 23

Slide 23 text

結果 これで「手軽」にテストを動かしつつ、 一定の安心感を持って Lambda 関数や DynamoDB テーブルなどを触れるようになった!

Slide 24

Slide 24 text

まとめ

Slide 25

Slide 25 text

まとめ ・Dockertest に LocalStack を組み合わせると、  go test だけで AWS をエミュレートしたテストが実行できる ・Go でテストを書く際は Dockertest を使うと、テスト用の一時的なコンテナを  簡単に立ち上げられる Go の機能だけでテスト関数を簡単に実行できるように工夫したことで、テストの手軽さが向上し、結果的に手戻りを 減らすことができた。 また、今回紹介したテストの仕組みを作る前は漠然と「ちょっと大変そうかも」と思っていたが、元から Dockertest や LocalStack を利用していたこともあり実際は3~4時間ぐらいで実装することができた。 最初に数時間投資するだけで自分のような AWS 初心者でも快適に Lambda のコードを触れるようになった。 【感想】 【ポイント】

Slide 26

Slide 26 text

ソフトウェアエンジニア募集中! We’re hiring!! カミナシはソフトウェアエンジニアを募集しています。