ElixirでSI開発入門 #4 本番パスワードを環境変数に持たせる

（この記事は、「[Elixir or Phoenix Advent Calendar 2017](https://qiita.com/advent-calendar/2017/elixir-or-phoenix "Elixir or Phoenix Advent Calendar 2017")」の19日目です）

昨日は、@piacere さんの[「Excelから関数型言語マスター4回目：Webに外部APIデータ表示」](https://qiita.com/piacere/items/4c212615a4eb699dd109)でした

# はじめに

Elixirで実際にプロダクト開発した経験からサンプルコードを交えて解説する本連載
今回もDB周りではありますが、configに関するお話です。

今までの連載で実装したサンプルでは、DBのアクセス情報はPostgreSQLのデフォルトをそのまま使っていましたが、本番環境ではユーザー/パスワードなどの接続情報はセキュリティ観点から何かしらの秘匿をする必要があります。
※どういった秘匿方法をとるかは、システムに求められる要件や、本番環境の構成、保守チームの運用ルールによるところがあるので一概には言えません。

今回はPostgreSQLへの接続設定を例に、環境変数に設定することでGitなどのリポジトリに残らない設定方法を紹介します。
※開発者と運用者が分離された体制において、秘匿情報をコード管理上から排除することで開発者に対して秘匿する目的の場合に有効

本連載の記事はこちら
|> [ElixirでSI開発入門 #1 Ectoで悲観的ロック](https://qiita.com/tuchiro/items/7b04b0661cfa4a7af179)
|> [ElixirでSI開発入門 #2 Ectoで楽観的ロック](https://qiita.com/tuchiro/items/8949cc43ba90f4e68bf3)
|> [ElixirでSI開発入門 #3 主キーが"id "じゃない既存DBへの接続](https://qiita.com/tuchiro/items/a4808baacea9a07d2409)
|> ElixirでSI開発入門 #4 本番パスワードを環境変数に持たせる


:stars::stars::stars::stars::stars:<b>　お知らせ　</b>:stars::stars::stars::stars::stars:
[<b>「fukuoka.ex#11：DB／データサイエンスにコネクトするElixir」</b>](https://fukuokaex.connpass.com/event/87241)を6/22（金）19時に開催します
私も本連載で出してないSI開発ネタを出す予定ですので、
Elixirでプロダクト開発最前線に興味ある方はぜひぜひご応募ください！

[![image.png](https://connpass-tokyo.s3.amazonaws.com/thumbs/cb/13/cb130a03343bc310b07244c308b940db.png)](https://fukuokaex.connpass.com/event/87241)

# System.get_env()で環境変数を取得する

Phoenixの configは、それ自体が[Mix.Config](https://hexdocs.pm/mix/1.0.5/Mix.Config.html)を実装したElixirプログラムですので、内部でモジュールを呼び出すことが可能です。

[System.get_env()](https://hexdocs.pm/elixir/1.6.1/System.html#get_env/0)を使えば、設定を実行環境の環境変数から取得することができます。

# 実装の前提

今回は以下の実装を例に考えます

- PhoenixでUserモデルを実装する
- DBはデフォルトのPostgreSQLを選択する
- 本番環境設定ではDB設定情報を環境変数から取得する

また、以下の環境で実装しました

- Elixir     v1.6.1
- Phoenix v1.3.2
- Ecto      v2.2.10
- PostgreSQL v10.2

# 開発手順　
## プロジェクト〜モデルの作成

PhoenixプロジェクトとDBを作成

```
> mix phx.new config_env_var --no-brunch
> cd config_env_var
> mix ecto.create
```

モデルを作成

```
> mix phx.gen.html Accounts User users name mail
```

ルーティングを追加

```Ruby:lib/config_env_var_web/router.ex
defmodule ConfigEnvVarWeb.Router do
  use ConfigEnvVarWeb, :router

  〜　中略　〜

  scope "/", ConfigEnvVarWeb do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
    resources "/users", UserController # <-- ルーティングを追加
  end

  〜　中略　〜

end
```

## configの内容を確認する

Phoenixの設定ファイルを改めて見てみると環境ごとの設定は{環境名}.exsというネーミングの別のファイルを参照している。

```Ruby:config/config.exs
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
use Mix.Config

〜　中略　〜

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs" # <-- 環境固有の設定は{環境名}.exsファイルに記載されている
```
開発環境の場合、DBの接続情報もそのまま定義されている。

```Ruby:config/dev.exs
use Mix.Config

〜　中略　〜

# Configure your database
config :config_env_var, ConfigEnvVar.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "postgres",  # <-- テスト系では秘匿しないで良い
  password: "postgres",  # <-- テスト系では秘匿しないで良い
  database: "config_env_var_dev",
  hostname: "localhost",
  pool_size: 10

```

一方、本番環境の設定では、さらにprod.secret.exsという別の設定ファイルを参照するようになっている。

```Ruby:config/prod.exs
use Mix.Config

〜　中略　〜

# Finally import the config/prod.secret.exs
# which should be versioned separately.
import_config "prod.secret.exs"  # <-- 環境固有となる設定の大部分はprod.secret.exsに記載するようになっている

```
prod.secret.exsは.gitignoreファイルにあらかじめ定義されているので、バージョン管理の対象とならない（ここがみそ）

```Ruby:.gitignore

〜　中略　〜

/config/*.secret.exs
```

ただし、この仕組みに則ると、サーバー毎にprod.secret.exsを git以外の経路で配布、管理する必要がある。
で、あればもうファイルじゃなくてもいいのではないかと・・・

## 本番環境設定を変更する

prod.exsを修正して、DBの接続情報を環境変数から取得するようにする

```Ruby:config/prod.exs
use Mix.Config

〜　中略　〜

# エンドポイントの設定は今回対象外なのでdevのものを移植して動くようにする

#config :config_env_var, ConfigEnvVarWeb.Endpoint,
#  load_from_system_env: true,
#  url: [host: "example.com", port: 80],
#  cache_static_manifest: "priv/static/cache_manifest.json"
config :config_env_var, ConfigEnvVarWeb.Endpoint,
  http: [port: 4000],
  debug_errors: true,
  code_reloader: false, # <-- デフォルトのmix.exsではphoenix_live_reloadがonly: :devなのでOFFにする
  check_origin: false,
  watchers: []

〜　中略　〜

# Finally import the config/prod.secret.exs
# which should be versioned separately.
# import_config "prod.secret.exs" <--コメントアウトする

# 以下を追記
config :config_env_var, ConfigEnvVar.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: System.get_env("EX_DB_USER"), # <--秘匿情報を環境変数から取得
  password: System.get_env("EX_DB_PASS"), # <--秘匿情報を環境変数から取得
  database: System.get_env("EX_DB_NAME"), # <--秘匿情報を環境変数から取得
  hostname: System.get_env("EX_DB_HOST"), # <--秘匿情報を環境変数から取得
  pool_size: 10


```

# 本番環境設定で動かしてみる

環境変数を設定して確認する

```
> export EX_DB_USER=prod_user
> export EX_DB_PASS=prodpass
> export EX_DB_NAME=config_env_var_prod
> export EX_DB_HOST=localhost
> env | grep EX_
EX_DB_USER=prod_user
EX_DB_PASS=prodpass
EX_DB_NAME=config_env_var_prod
EX_DB_HOST=localhost
```

Phoenixサーバーを本番設定で起動する。

```
> MIX_ENV=prod mix ecto.create
> MIX_ENV=prod mix ecto.migrate
> MIX_ENV=prod mix phx.server
```

以下のURLにアクセスし、適当にユーザーを登録してみる。

http://localhost:4000/users
<img src="https://qiita-image-store.s3.amazonaws.com/0/243163/01983abf-a1fd-8738-7101-409620a6828d.png" width=50% >


本番用のDBにアクセスして登録結果を確認してみる。

```
> psql -h localhost -d config_env_var_prod -U postgres
config_env_var_prod=# select * from users ;
 id |  name  |             mail             |        inserted_at         |        updated_at         
----+--------+------------------------------+----------------------------+---------------------------
  1 | 土ロー | tcr.yoshimura@karabiner.tech | 2018-05-11 10:12:19.152122 | 2018-05-11 10:12:19.15584
(1 row)
```
# まとめ

- 環境変数を取得するにはSystem.get_env()
- 本番設定でMixコマンドを実行するときには先頭にMIX_ENV=prodをつける
- 秘匿の方法や必要なレベルはプロジェクトによって異なるので注意

いかがだったでしょうか。
これで本番リリースへ向けて一歩前進したのではないでしょうか？

明日は、@twinbee さんの「[Elixirから簡単にRustが呼べるRustler#1 準備編](https://qiita.com/twinbee/items/aabc11d0d667800fc0bb)」です。