kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Lambda + Apex 事例紹介 ~ 全ては AWS コスト削減のために ~

今日は社内の別プロダクトと合同勉強会があって,Lambda + Apex 便利だよ!という発表をしてきた.

タイトルは「Lambda + Apex 事例紹介 ~ 全ては AWS コスト削減のために ~」で,発表内容としては,大きく3点にフォーカスした.

  • CloudWatch Events + Lambda + CircleCI + Apex で Lambda をサーバレスに実行 & デプロイする話
  • Apex で Golang with Lambda を動かす仕組みとは
  • AWS 便利ツールの紹介

メインは Lambda + Apex の話で,前に書いた記事をベースにした.

kakakakakku.hatenablog.com

発表資料

speakerdeck.com

Golang with Lambda

Apex の珍しい点を挙げるとすると「Lambda で Golang を動かせる」ことだと思う.今回実際に試してみて,理解できた挙動をまとめた.詳しくは発表資料に書いたけど,Node.js から Golang バイナリを動かす子プロセスを生成して,プロセス間通信をすることで実現しているところまで確認することができた.

github.com

github.com

AWS 便利ツール

最後に AWS 便利ツールの紹介をした.awslogs は非常に便利!

github.com

github.com

github.com

github.com

gapps を使って Google Apps Script を継続的にデプロイする

最近 GAS (Google Apps Script) を書く機会があって,Google 関連のリソースに簡単にアクセスできて便利だなー!と感じる反面,直接 js を書いて実行する開発プロセスに違和感を感じていた.普通に GitHub でバージョン管理して,継続的にデプロイする方法を探していたところ,専用の公式ツール gapps を知って,実際に使ってみたら便利だったので,紹介する.

developers-jp.googleblog.com

gapps (node-google-apps-script)

gapps を使うと GAS を CLI でデプロイできるようになる.あまり難しいところはなく,README の通りに進めていけば使えるようになる.ザッと手順を残しておく.

github.com

gapps をインストールする

npm で簡単に使えるようになる.

$ npm install -g node-google-apps-script
$ gapps version
1.1.5

Google API Console でプロジェクトを作成して認証をする

事前に以下の設定が必要なため Google API Console でプロジェクトを作成する.今回は “gas-dmm” という名前にした.

  • “Google Drive API” を有効にする
  • OAuth 認証情報を取得する

console.developers.google.com

“Google Drive API” は「有効にする」を選択しておく.

f:id:kakku22:20170226030029p:plain

次に OAuth 認証情報を取得したら JSON をローカルにダウンロードする.

f:id:kakku22:20170226030256p:plain

ダウンロードした JSON を引数に gapps auth コマンドを実行すると URL が表示されるため,アクセスすると自動的に認証が完了する.

$ gapps auth xxx.json
Please visit the following url in your browser (you'll only have to do this once): https://accounts.google.com/o/oauth2/auth?xxx
Successfully Authenticated with Google Drive!

具体的には以下のメッセージが表示される.

You’ve been authenticated with Google Drive! You may close this page.

GAS を用意する

事前に GAS を用意しておく必要がある.GAS の URL に入ってる ID をコピーして gapps init コマンドの引数にすると,自動的に gapps.config.json が生成される.この gapps.config.json は秘匿情報のため,必ず .gitignore に含めること!

$ gapps init xxx

f:id:kakku22:20170226031020p:plain

コードを書いてデプロイする

最後にコードを書いてデプロイする.gapps upload コマンドで簡単にデプロイができる.

$ gapps upload

「DMM 英会話」のスケジュールを Google Calendar に同期する

今年から使ってる「DMM 英会話」で,1点残念なところを挙げるとすると「Google Calendar にスケジュールを同期できない」ところだと思う.個人的にスケジュールを振り返るために,今までは手動で登録していたけど,この作業を自動化したいと思った.

「DMM 英会話」の予約完了メールをトリガーに Google Calendar にスケジュール登録する GAS は既に公開されていて,非常に参考になった.個人的なユースケースとして,スケジュール名に「DMM 英会話」と「先生」の情報が入っていれば十分だったので,少し処理を減らしたりした.

yoonchulkoh.hatenablog.com

コードを雑に push しておいた.

github.com

フロー図

gapps のことも含めると,ザッとこんなフロー図になる.

f:id:kakku22:20170224234002p:plain

GAS を定期的に実行するにはトリガーを設定する必要がある.簡単!

f:id:kakku22:20170226032415p:plain

まとめ

  • gapps を使うと GAS の管理をアプリケーション開発のベストプラクティスに乗せることができる
  • 「DMM 英会話」のスケジュールを自動的に Google Calendar に同期できる

f:id:kakku22:20170226033953p:plain

Aurora のスロークエリログを Fluentd 経由で Amazon Elasticsearch Service に転送する

Aurora でスロークエリログを出力して,Fluentd 経由で Amazon Elasticsearch Service に転送する構成を作った.MySQL on EC2 のときは,サーバ内で直接スロークエリログを確認していて非常に面倒だったので(単なる運用上の考慮不足),運用面で大きく改善できるし,アプリケーションエンジニアも自由に Kibana で確認できるため,パフォーマンス改善が捗ると思う.非常に良い.

アーキテクチャ図

f:id:kakku22:20170223135353p:plain

Aurora インスタンスパラメータ

デフォルトのインスタンスパラメータではスロークエリログは有効になっていないため,パラメータを変更する必要がある.今回はスロークエリログを有効にするだけではなく,デフォルトで10秒になっている閾値を5秒に変更した.

パラメータ名 Before After
slow_query_log <engine-default> 1
long_query_time <engine-default> 5

どちらも「適用タイプ」は「dynamic」なので,Aurora の再起動は必要なく,自動的に反映される.反映されたかどうかは,実際に SQL で確認することができる.

mysql> SHOW VARIABLES LIKE 'slow_query_log';
+----------------+-------+
| Variable_name  | Value |
+----------------+-------+
| slow_query_log | ON    |
+----------------+-------+
1 row in set (0.00 sec)

mysql> SHOW VARIABLES LIKE 'long_query_time';
+-----------------+----------+
| Variable_name   | Value    |
+-----------------+----------+
| long_query_time | 5.000000 |
+-----------------+----------+
1 row in set (0.00 sec)

Aurora のスロークエリはデフォルトでテーブルに書き込まれる

MySQL on EC2 と違って,Aurora ではスロークエリログがデフォルトでテーブルに書き込まれる.もしファイルに書き込む場合は,log_output パラメータを変更する必要がある.今回はテーブルのまま運用することにした.

mysql> SHOW VARIABLES LIKE 'log_output';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_output    | TABLE |
+---------------+-------+
1 row in set (0.00 sec)

スロークエリログは mysql.slow_log というテーブルに書き込まれるが,Aurora 側で用意されているストアドプロシージャ mysql.rds_rotate_slow_log を実行すると mysql.slow_log_backup にローテートされる.よって,例えば mysql.slow_log_backup を2回連続で実行すると,スロークエリログは完全に消すことができる.後述する Fluentd Plugin でも mysql.rds_rotate_slow_log を実行して,mysql.slow_log_backup からスロークエリログを取得する実装になっている.簡単に動作確認をすると,以下のようになる.

--
-- スロークエリログなし
--

mysql> SELECT * FROM mysql.slow_log\G
Empty set (0.00 sec)

mysql> SELECT * FROM mysql.slow_log_backup\G
Empty set (0.00 sec)

--
-- スロークエリログを発行する
--

mysql> SELECT SLEEP(10);
+-----------+
| SLEEP(10) |
+-----------+
|         0 |
+-----------+
1 row in set (10.01 sec)

--
-- スロークエリログが `mysql.slow_log` に登録された
--

mysql> SELECT * FROM mysql.slow_log\G
*************************** 1. row ***************************
    start_time: 2017-02-23 13:06:28
     user_host: xxx[xxx] @  [1.2.3.4]
    query_time: 00:00:10
     lock_time: 00:00:00
     rows_sent: 1
 rows_examined: 0
            db:
last_insert_id: 0
     insert_id: 0
     server_id: 0
      sql_text: SELECT SLEEP(10)
     thread_id: 42
1 row in set (0.00 sec)

mysql> SELECT * FROM mysql.slow_log_backup\G
Empty set (0.01 sec)

--
-- ストアドプロシージャを実行してローテートする
--

mysql> CALL mysql.rds_rotate_slow_log;
Query OK, 0 rows affected (0.02 sec)

--
-- スロークエリログが `mysql.slow_log_backup` に移動された
--

mysql> SELECT * FROM mysql.slow_log\G
Empty set (0.00 sec)

mysql> SELECT * FROM mysql.slow_log_backup\G
*************************** 1. row ***************************
    start_time: 2017-02-23 13:06:28
     user_host: xxx[xxx] @  [1.2.3.4]
    query_time: 00:00:10
     lock_time: 00:00:00
     rows_sent: 1
 rows_examined: 0
            db:
last_insert_id: 0
     insert_id: 0
     server_id: 0
      sql_text: SELECT SLEEP(10)
     thread_id: 42
1 row in set (0.00 sec)

Aurora のログ関連の仕様とストアドプロシージャの仕様はドキュメントに書いてある.

docs.aws.amazon.com

docs.aws.amazon.com

SELECT SLEEP(10)

MySQL には標準で SLEEP 関数が付属していて,意図的にスロークエリを再現することができる.案外知らない人も多そうだし,僕も今回検証をしていて知ったので,一応載せておく.

fluent-plugin-rds-slowlog

今回スロークエリログを Fluentd 経由で転送するために,以下のプラグインを使った.

github.com

td-agent.conf は以下のように書いた.ポイントを箇条書きにしておく.

  • Aurora Writer と Aurora Reader のスロークエリログを転送するため source を2個用意した
  • interval はデフォルトの10秒だと短すぎるため,300秒に変更した
<source>
    @type rds_slowlog
    tag rds_slowlog.writer
    host example1.ap-northeast-1.rds.amazonaws.com
    username xxx
    password yyy
    interval 300
</source>

<source>
    @type rds_slowlog
    tag rds_slowlog.reader
    host example2.ap-northeast-1.rds.amazonaws.com
    username xxx
    password yyy
    interval 300
</source>

<match rds_slowlog.*>
    ...
</match>

また,接続するユーザーも専用に用意した.必要なのは参照権限とプロシージャ実行権限なので,以下のように SELECTEXECUTE だけを GRANT した.

mysql> GRANT SELECT, EXECUTE ON *.* TO 'fluentd'@'1.2.3.4' IDENTIFIED BY 'xxx';

今日 2/23 にリリースされた fluent-plugin-rds-slowlog v0.0.8 を使おう!

今までずっと公開されていた最新バージョンは v0.0.7 で,実はこれだと MySQL の再接続でエラーになる問題があり,既に master では修正が取り込まれていた.しかし,Gem がリリースされていなかったため,v0.0.7 だと source を2個用意したことによって,コネクションがバッティングする場合があり,そのときに以下のエラーが出てしまっていた.

2017-02-22 15:35:12 +0900 [error]: Lost connection to MySQL server during query
  2017-02-22 15:35:12 +0900 [error]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/mysql2-0.3.21/lib/mysql2/client.rb:80:in `_query'
  2017-02-22 15:35:12 +0900 [error]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/mysql2-0.3.21/lib/mysql2/client.rb:80:in `block in query'
  2017-02-22 15:35:12 +0900 [error]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/mysql2-0.3.21/lib/mysql2/client.rb:79:in `handle_interrupt'
  2017-02-22 15:35:12 +0900 [error]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/mysql2-0.3.21/lib/mysql2/client.rb:79:in `query'
  2017-02-22 15:35:12 +0900 [error]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/fluent-plugin-rds-slowlog-0.0.7/lib/fluent/plugin/in_rds_slowlog.rb:69:in `output'
  2017-02-22 15:35:12 +0900 [error]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/fluent-plugin-rds-slowlog-0.0.7/lib/fluent/plugin/in_rds_slowlog.rb:92:in `call'
  2017-02-22 15:35:12 +0900 [error]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/fluent-plugin-rds-slowlog-0.0.7/lib/fluent/plugin/in_rds_slowlog.rb:92:in `on_timer'
  2017-02-22 15:35:12 +0900 [error]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/cool.io-1.4.2/lib/cool.io/loop.rb:88:in `run_once'
  2017-02-22 15:35:12 +0900 [error]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/cool.io-1.4.2/lib/cool.io/loop.rb:88:in `run'
  2017-02-22 15:35:12 +0900 [error]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/fluent-plugin-rds-slowlog-0.0.7/lib/fluent/plugin/in_rds_slowlog.rb:62:in `watch'
2017-02-22 15:45:22 +0900 [error]: MySQL server has gone away
  2017-02-22 15:45:22 +0900 [error]: suppressed same stacktrace
2017-02-22 15:55:32 +0900 [error]: closed MySQL connection
  2017-02-22 15:55:32 +0900 [error]: suppressed same stacktrace

エラーの詳細は Issue にも書いた.

github.com

Issue を書いた後に,直接コードを書き換えて master の状態にして検証をしてみたら,自動的に再接続が行われることによって問題は解決した.よって,メンテナ側に頼んで v0.0.8 を今日 2/23 にリリースしてもらった.

@winebarrel さん,助かりました!!!

github.com

ちなみに v0.0.8 だと,上記エラーが出た後に,以下のログが追加で出るようになっている.引き続きスタックトレース自体は出てしまうため,捨てる方法があるなら捨てたいなぁ…という感じ.どうしたら良いんだろう.

2017-02-23 12:03:28 +0900 [info]: fluent-plugin-rds-slowlog: try to reconnect to RDS

Amazon Elasticsearch Service + Kibana で可視化する

Elasticsearch 側は特に難しいことはなく,テンプレートを用意して,Kibana で可視化した.

{
    "template": "rds_slowlog-*",
    "mappings": {
        "rds_slowlog": {
            "properties": {
                "user_host": {
                    "type": "string",
                    "index": "not_analyzed"
                },
                "sql_text": {
                    "type": "string",
                    "index": "not_analyzed"
                }
            }
        }
    }
}

Elasticsearch のインデックスは統一しているけど,@log_name で Writer と Reader の識別ができるため,以下のようなクエリを書くとデータを分割できる.

@log_name: rds_slowlog.reader
@log_name: rds_slowlog.writer

スロークエリログを可視化できて便利!

f:id:kakku22:20170223135408p:plain

検証してるときに Fluentd → Amazon Elasticsearch Service の転送でエラーになってて,困ってたら @yoshi_ken さんに Twitter でリプを頂いて,デバッグ方法などを教えてもらった.結果的に aggregator の時刻がズレてて AWS API のコールがエラーになっていることが原因だった.NTP 入って無くて残念な感じだった.アドバイスありがとうございました!

まとめ

  • Aurora のスロークエリを Fluentd 経由で Amazon Elasticsearch Service に転送した
  • Kibana でスロークエリを可視化できると,アプリケーションエンジニアのパフォーマンス改善が捗る
  • fluent-plugin-rds-slowlogv0.0.8 を使おう

CloudWatch Events + Lambda + CircleCI + Apex で Lambda をサーバレスに実行 & デプロイする

先週,プロダクトで開発合宿を企画して実施してきた.今回のテーマは「開発効率/運用効率の改善」だったので,僕はインフラチームとして先輩と一緒に開発に取り組んだ.お題は「AWS コスト削減」にした.開発合宿で開発したツールで学んだことを簡単にまとめておこうと思う.

Time-based approach

AWS Well-Architected Framework の「コスト最適化」に「タイムベースアプローチ」と呼ばれるベストプラクティスがある.そんな難しいものではなく,単純に開発系インスタンスを夜間や週末に止めることでコストを最適化できるし,クラウドネイティブな設計にもできるというものだ.意外と開発系インスタンスが週末も起動されているチームも多いのではないだろうか?

Time-based approach Examples of a time-based approach include following the sun, turning off Development and Test instances over the weekend, following quarterly or annual schedules (e.g., Black Friday).

今回は「AWS コスト削減」という課題に対して「タイムベースアプローチをサーバレスアーキテクチャで実現する」という手法で解決することをゴールに決めた(実際にはもう少し他の施策も含めてコスト削減をした).

アーキテクチャ図

今回は以下のようなアーキテクチャで実現した.ポイントは大きく3点ある.

  • スケジューリング実行を CloudWatch Events で行った
    • Jenkins を使いたくなかった
  • インスタンスの起動停止を Lambda で行った
    • 専用のインスタンスを立てたくなかった
  • Lambda Function のデプロイを CircleCI + Apex で行った
    • Jenkins を使いたくなかった

f:id:kakku22:20170217220344p:plain

CloudWatch Events

CloudWatch Events を使うと,cron 形式のスケジューリング設定をして Lambda を起動することができる.注意点は2点で「cron 式が AWS 独自なこと」と「UTC 設定なこと」と言える.

例えば「日本時間で “火水木金土” の 00:00」と設定する場合,CloudWatch Events の cron 式は以下のようになる.

0 15 ? * MON-FRI *

  • UTC 表記なので 15:00 になる
  • 曜日指定だと1日戻るため “月火水木金” を指定する必要がある
  • 日(3番目)と月(4番目)の両方に * を指定することができず,片方は ? にする必要がある
  • 6番目に「製造年」を設定する必要がある(必要ないから * にした)

特に ? の部分が意味不明で,設定にハマった.ドキュメントを読んだらちゃんと書いてあったけど,なんなんだろう.「製造年」もよくわからなかった.

cron 式の Day-of-month フィールドおよび Day-of-week フィールドに同じ 値を指定することはできません。 一方のフィールドに値を指定すると、もう一方のフィールドで ? (疑問符) を使用する必要があります。

docs.aws.amazon.com

ちなみに CloudWatch Events から Lambda を実行するときに

  • { "action": "start" }
  • { "action": "stop" }

というイベントを発火させて,Lambda 側でハンドリングするように実装した.

Lambda

あまり難しいことはしてなく,以下の記事などを参考にして,Node で API を叩いてインスタンスを起動したり,停止したりした.

唯一面倒だったのは Zabbix の考慮で,インスタンスを落とすとアラートが鳴ってしまうため,Zabbix のメンテナンスを API 経由で ON/OFF できるようにする必要があった.ここは先輩に書いてもらった.

dev.classmethod.jp

あとは Lambda Function に割り当てる IAM ロールで,以下のように必要最低限に抑えた.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ec2:Describe*",
                "ec2:StartInstances",
                "ec2:StopInstances",
                "logs:*"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

Apex

今回 Lambda のデプロイもサーバレスにしたかったため,Apex を使った.Apex は Lambda のデプロイに特化していることもあって非常にシンプルで,Serverless のように複雑ではないところが良かった.また Lambda 以外の AWS リソースを操作したい場合は,Terraform を使うということで,無理に手広くサポートしていないところも逆に好印象だった.

github.com

ドキュメントも非常に充実していて,特に困ることは無かった.主に以下のコマンドを使った.

  • apex deploy
  • apex invoke
  • apex logs
  • apex metrics

apex.run

ドキュメントにも載っているが,コマンド補完もすることができるので,便利だった.また Apex に必要最低限な IAM ポリシーもドキュメントに書いてあるため,すぐに使うことができた.

_apex()  {
  COMPREPLY=()
  local cur="${COMP_WORDS[COMP_CWORD]}"
  local opts="$(apex autocomplete -- ${COMP_WORDS[@]:1})"
  COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
  return 0
}

complete -F _apex apex

Apex で1点だけ残念だったのは,CloudWatch Events の設定は Terraform で行う必要があることだった.方針として正しいとは思うけど,CloudWatch Events だけでも良いから Apex で登録できると良いのになとは思った.今回は CloudWatch Events のためだけに Terraform を使う気にはならなかったため,手動で設定して Lambda と紐付けた.

github.com

CircleCI

雑に書いた.ポイントは master にマージしたときに apex deploy をするところ.これで CircleCI から Lambda のデプロイができるようになった.CircleCI にクレデンシャルを登録することを忘れずに!

machine:
  timezone: Asia/Tokyo

dependencies:
  post:
    - curl https://raw.githubusercontent.com/apex/apex/master/install.sh | sudo sh
    - apex version

deployment:
  master:
    branch: master
    commands:
      - apex deploy

まとめ

  • AWS Well-Architected Framework を見るとクラウドネイティブなベストプラクティスが学べる
  • CloudWatch Events + Lambda + CircleCI + Apex で Lambda をサーバレスに実行 & デプロイできるようにした
  • 開発合宿は最高に燃える!

関連記事

kakakakakku.hatenablog.com

アクセスキーのコミットを抑止できて安全便利な awslabs/git-secrets

GitHub で awslabs のリポジトリを眺めてたら git-secrets という便利なツール(シェルで実装されてる)を発見した.

どんなものかを簡単に説明すると,アクセスキーなどを誤ってコミットすることを Git の hooks を使って未然に防ぐツールで,誤って GitHub に push してしまったために,AWS を不正利用されてしまった,みたいな事故もたまに聞くし,そういうのを防ぐことができる.非常に良かったので,一部のリポジトリに git-secrets を設定した.

github.com

インストール

make install でも良いけど,Mac なら brew が使える.

$ brew install git-secrets

インストールすると git secrets コマンドが使えるようになった.

$ git secrets
usage: git secrets --scan [-r|--recursive] [--cached] [--no-index] [--untracked] [<files>...]
   or: git secrets --scan-history
   or: git secrets --install [-f|--force] [<target-directory>]
   or: git secrets --list [--global]
   or: git secrets --add [-a|--allowed] [-l|--literal] [--global] <pattern>
   or: git secrets --add-provider [--global] <command> [arguments...]
   or: git secrets --register-aws [--global]
   or: git secrets --aws-provider [<credentials-file>]

    --scan                Scans <files> for prohibited patterns
    --scan-history        Scans repo for prohibited patterns
    --install             Installs git hooks for Git repository or Git template directory
    --list                Lists secret patterns
    --add                 Adds a prohibited or allowed pattern, ensuring to de-dupe with existing patterns
    --add-provider        Adds a secret provider that when called outputs secret patterns on new lines
    --aws-provider        Secret provider that outputs credentials found in an ini file
    --register-aws        Adds common AWS patterns to the git config and scans for ~/.aws/credentials
    -r, --recursive       --scan scans directories recursively
    --cached              --scan scans searches blobs registered in the index file
    --no-index            --scan searches files in the current directory that is not managed by Git
    --untracked           In addition to searching in the tracked files in the working tree, --scan also in untracked files
    -f, --force           --install overwrites hooks if the hook already exists
    -l, --literal         --add and --add-allowed patterns are escaped so that they are literal
    -a, --allowed         --add adds an allowed pattern instead of a prohibited pattern
    --global              Uses the --global git config

ちなみに,コマンド自体は /usr/local/bin/git-secrets にある.

$ which git-secrets
/usr/local/bin/git-secrets

セットアップ

git-secrets のセットアップはリポジトリ単位に行う.適当なリポジトリのルートディレクトリに移動して git secrets --install を実行すると,3種類の hooks が登録される.

$ git secrets --install
✓ Installed commit-msg hook to .git/hooks/commit-msg
✓ Installed pre-commit hook to .git/hooks/pre-commit
✓ Installed prepare-commit-msg hook to .git/hooks/prepare-commit-msg

ただし,既に hooks を使っている場合,エラーになる.この場合は --force オプションを付けて強制的に上書きする必要がある.

$ git secrets --install
.git/hooks/commit-msg already exists. Use -f to force

残念ながらマージする機能はないため,一度 hooks を退避した後に上書きして,戻す必要がありそう.

$ git secrets --install --force
✓ Installed commit-msg hook to .git/hooks/commit-msg
✓ Installed pre-commit hook to .git/hooks/pre-commit
✓ Installed prepare-commit-msg hook to .git/hooks/prepare-commit-msg

AWS アクセスキーのコミットを防ぐ場合

AWS 関連のリポジトリの場合は,デフォルトで用意されている git secrets --register-aws を使う.そうすると,アクセスキー/アカウントを対象にコミットを防ぐことができる.

$ git secrets --register-aws

--list オプションで,正規表現などの設定値を確認することができる.secrets.allowed に入ってる値は example AWS keys と README.md に書いてあった.

$ git secrets --list
secrets.providers git secrets --aws-provider
secrets.patterns [A-Z0-9]{20}
secrets.patterns ("|')?(AWS|aws|Aws)?_?(SECRET|secret|Secret)?_?(ACCESS|access|Access)?_?(KEY|key|Key)("|')?\s*(:|=>|=)\s*("|')?[A-Za-z0-9/\+=]{40}("|')?
secrets.patterns ("|')?(AWS|aws|Aws)?_?(ACCOUNT|account|Account)_?(ID|id|Id)?("|')?\s*(:|=>|=)\s*("|')?[0-9]{4}\-?[0-9]{4}\-?[0-9]{4}("|')?
secrets.allowed AKIAIOSFODNN7EXAMPLE
secrets.allowed wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

--list オプションは git config を参照しているので,直接 git config を叩いても,同じ情報が確認できる.同じく secrets.allowed に入ってる値は example AWS keys だから問題なし.

$ git config -l | grep secrets
secrets.providers=git secrets --aws-provider
secrets.patterns=[A-Z0-9]{20}
secrets.patterns=("|')?(AWS|aws|Aws)?_?(SECRET|secret|Secret)?_?(ACCESS|access|Access)?_?(KEY|key|Key)("|')?\s*(:|=>|=)\s*("|')?[A-Za-z0-9/\+=]{40}("|')?
secrets.patterns=("|')?(AWS|aws|Aws)?_?(ACCOUNT|account|Account)_?(ID|id|Id)?("|')?\s*(:|=>|=)\s*("|')?[0-9]{4}\-?[0-9]{4}\-?[0-9]{4}("|')?
secrets.allowed=AKIAIOSFODNN7EXAMPLE
secrets.allowed=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

実際にアクセスキーをコミットしてみる

正規表現に合致する ABCDEFGHIJKLMN123456 というアクセスキー(サンプル)を README.md に書いてコミットしようとすると,以下のエラーが出てコミットできないようになった.

$ git commit -m'Added access key'
README.md:50:ABCDEFGHIJKLMN123456

[ERROR] Matched one or more prohibited patterns

Possible mitigations:
- Mark false positives as allowed using: git config --add secrets.allowed ...
- Mark false positives as allowed by adding regular expressions to .gitallowed at repository's root directory
- List your configured patterns: git config --get-all secrets.patterns
- List your configured allowed patterns: git config --get-all secrets.allowed
- List your configured allowed patterns in .gitallowed at repository's root directory
- Use --no-verify if this is a one-time false positive

今回は AWS の例を試したが,--add オプションを使えば,任意の正規表現も登録できるため,他の SaaS などを使ってる場合などにも簡単に対応できる.

スキャン機能

--scan オプションを使うと,リポジトリを対象に既にコミットされていないかをスキャンすることができる.

$ git secrets --scan

さらにこのオプションでは git secrets --aws-provider で取得した ~/.aws/credentials の値を対象にチェックもしてくれるため,気が利いている.

まとめ

git-secrets は,地味な感じもあるけど,非常に便利なツールだった.AWS としても,誤ってコミットされてしまって事故が起きてしまうことは望んでないだろうし,もっと普及すると良さそう.git-secrets をまだ使ってない人はすぐ使いましょう!