コードで行うMySQLのアカウント管理

インフラストラクチャー部の菅原(@sgwr_dts)です。

インフラストラクチャー部のメンバーはオペレーションのため強力な権限のMySQLアカウントを使用していますが、サービス開発をするエンジニアも業務のためにサービスのDBの参照・更新権限を持ったアカウントが必要になることがあります。

セキュリティやオペレーションミスのことを考えると、すべてのエンジニアのアカウントをスーパーユーザーにするわけにはいかないため、都度適切な権限を付与していますが、手動での作業は地味に手間がかかります。

そこでクックパッドではMySQLのアカウント情報をコード化し、リポジトリで管理するようにしています。

gratanによるコード化

MySQLのアカウント管理はgratanという自作のツールを使って行っています。 gratanを使うとMySQLのアカウントをRubyのDSLで記述することができるようになります。

require 'other/grantfile' # 他の権限定義ファイルを読み込む

user "scott", ["127.0.0.1", "%"] do
  on "*.*" do
    grant "USAGE"
  end

  # test DBへの権限付与は2014/10/08まで
  on "test.*", expired: '2014/10/08' do
    grant "SELECT"
    grant "INSERT"
  end

 # test2 DBのresipe_*テーブルに対して権限を付与
  on /^test2\.recipe_/ do
    grant "SELECT"
    grant "INSERT"
  end
end

上記のDSLをMySQLに適用すると、たとえば以下のようなログが出力されます。

$ bundle exec rake apply[db_foo]
[WARN] User `scott@%`: Object `test.*` has expired (dry-run)
[WARN] User `scott@127.0.0.1`: Object `test.*` has expired (dry-run)
REVOKE SELECT ON `test`.`*` FROM 'scott'@'%'
REVOKE INSERT ON `test`.`*` FROM 'scott'@'%'
REVOKE SELECT ON `test`.`*` FROM 'scott'@'127.0.0.1'
REVOKE INSERT ON `test`.`*` FROM 'scott'@'127.0.0.1'
GRANT SELECT ON `test2`.`recipe_photos` TO 'scott'@'%'
GRANT SELECT ON `test2`.`recipe_photos` TO 'scott'@'127.0.0.1'
FLUSH PRIVILEGES

gratanは冪統制を保証しているので、MySQLの権限がすでに定義ファイル通りである場合には、なにも変更を行いません。

$ bundle exec rake apply[db_foo]
No change

また、expiredを記述すると、指定した日付以降にDSLを適用した場合に、期限の切れた権限をREVOKEするようになります。このため、現在の運用では定期的にDSLの適用を行い、期限が切れた権限がDBに残らないようにしています。

このような定義ファイルを各エンジニアごとに作成し、以下のようなディレクトリ構成でGitに保存しています。

repo
├── Gemfile
├── Rakefile
├── db_bar
│   ├── Grantfile
│   ├── alice.grant
│   └── bob.grant
└── db_foo
    ├── Grantfile
    ├── scott.grant
    └── tiger.grant

権限付与のワークフロー

エンジニアへの権限の付与は次のようなワークフローで行われます。

  1. エンジニアがリポジトリをFork
  2. 必要な権限を追加した修正をPull Request
  3. Pull Requestをレビューして問題がなければマージ
  4. マージしたリポジトリのアカウント情報をMySQLに適用

何がうれしいのか

オペレーションのしやすさ

権限付与の作業はrakeタスクで自動化されているので作業者はrakeコマンドを実行するだけでよく、オペレーションミスを防ぐことができます。

アカウントの見通しの良さ

すべてのエンジニアのMySQLアカウントはテキストファイルとしてGitに保存されているため、誰が、どのDBに対して、どのような権限を持っているかがすぐに把握できます。そのため不必要なアカウントがずっと残ってしまうような問題を防ぐことができます。

履歴が残る

Gitで管理しているので、いつ誰にどのDBの権限を与えたかがすべて残ります。またどのような理由にで権限が必要になったかについても、Pull Requestの形でレビューとともに履歴が残ります。

おわりに

今のところMySQLへの適用作業はインフラエンジニアが手動でコマンドを実行しているのですが、Pull Requestがマージされたタイミングで自動的に適用するのもそれほど難しいことではないため、できれば完全に自動化したいと考えています。

インフラストラクチャー部では、刺身タンポポを撲滅できるエンジニアを募集しています。

インフラエンジニア | クックパッド株式会社 採用情報

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/