{"id":16957847,"url":"https://github.com/ahamez/vault_ecto","last_synced_at":"2026-02-03T00:06:46.201Z","repository":{"id":140512887,"uuid":"417085796","full_name":"ahamez/vault_ecto","owner":"ahamez","description":"Vault + Ecto = 🌈","archived":false,"fork":false,"pushed_at":"2024-05-26T18:24:08.000Z","size":40,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-11T02:03:43.877Z","etag":null,"topics":["credentials-rotation","ecto","elixir","vault"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ahamez.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-14T10:26:37.000Z","updated_at":"2024-05-26T18:24:11.000Z","dependencies_parsed_at":"2024-06-02T16:32:47.838Z","dependency_job_id":null,"html_url":"https://github.com/ahamez/vault_ecto","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ahamez/vault_ecto","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahamez%2Fvault_ecto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahamez%2Fvault_ecto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahamez%2Fvault_ecto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahamez%2Fvault_ecto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ahamez","download_url":"https://codeload.github.com/ahamez/vault_ecto/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahamez%2Fvault_ecto/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261984642,"owners_count":23240302,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["credentials-rotation","ecto","elixir","vault"],"created_at":"2024-10-13T22:20:09.468Z","updated_at":"2026-02-03T00:06:41.182Z","avatar_url":"https://github.com/ahamez.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Automatic reloading of PostgreSQL credentials with Ecto\n\nThis repository is an example of how to automatically reload the credentials with [Ecto](https://github.com/elixir-ecto/ecto) of a [PostgreSQL](https://www.postgresql.org/) instance rotated automatically by [Vault](https://www.vaultproject.io/).\n\nNote that Vault is not strictly required to rotate credentials, as long as an automatic process updates these credentials. However, being the de-facto server to manage secrets, it's useful to see that it's easy to make Ecto and Vault work together.\n\n### Tested with\n\n* Elixir 1.12.3\n* Ecto 3.7.1\n* Vault 1.8.4\n* Postgres 14.1\n\n## How it works\n\n- Vault agent renews credentials automatically and renders them in a file;\n- We use [secrets_watcher](https://hex.pm/packages/secrets_watcher) to detect changes to this file;\n- When a change is detected, we use [`disconnect_all/3`](https://hexdocs.pm/db_connection/2.4.1/DBConnection.html#disconnect_all/3) from [`db_connection`](https://hex.pm/packages/db_connection) to force connections to the database to disconnect (they will automatically reconnect after a backoff);\n- Upon restart, these connections will reconfigure themselves using a MFA given when [configuring the repo](https://github.com/ahamez/vault_ecto/blob/fa88f43c0bdc655e9e69a306b1a78cc930236d9e/config/config.exs#L11).\n\n⚠️ It requires `db_connection` \u003e= 2.4.1, make sure your dependencies are up to date.\n\n## Steps\n\n### Launch server instances\n\n* In a dedicated terminal (will set the root token to `root`), run vault dev server:\n    ```sh\n    vault server -dev -dev-root-token-id root\n    ```\n    ⚠️ in memory only, when the server is shutdown, everything is lost.\n\n* Have a postgres instance running.\n  - On macOS (default user/pass: `postgres`/`postgres`):\n        ```sh\n        brew install postgres\n        brew services start postgresql\n        ```\n  - With docker:\n       ```sh\n       docker run --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:14.1\n       ```\n\n### Create and configure database\n\n* Connect to instance\n    ```sh\n    $ PGPASSWORD=postgres psql -U postgres -p 5432 -h 127.0.0.1\n    ```\n\n* Create database\n    ```sql\n    CREATE DATABASE my_database;\n    ```\n\n* Forbid connection to database by default\n    ```sql\n    REVOKE ALL ON DATABASE my_databse FROM public;\n    ```\n\n* Create role that will be inherited by the accounts generated by Vault\n    ```sql\n    CREATE ROLE base_role NOLOGIN;\n    ```\n\n* Grant permission to connect to database\n    ```sql\n    GRANT CONNECT ON DATABASE my_database TO base_role;\n    ```\n\n* Grant permission to create schemas (not strictly necessary here):\n    ```sql\n    GRANT CREATE ON DATABASE my_database TO base_role;\n    ```\n\n### Configure Vault database engine\n\n* In another terminal, login to vault as root:\n    ```sh\n    export VAULT_ADDR=http://127.0.0.1:8200\n    vault login root\n    ```\n    👉 You can connect to the Vault GUI at [http://127.0.0.1:8200/ui](http://127.0.0.1:8200/ui)\n\n* Enable vault database secret engine:\n    ```sh\n    vault secrets enable database\n    ```\n\n* Configure database plugin for `my_database` database:\n    ```sh\n    vault write database/config/my_database \\\n      plugin_name=postgresql-database-plugin \\\n      allowed_roles=\"vault_ecto\" \\\n      connection_url=\"postgresql://{{username}}:{{password}}@localhost:5432/my_database?sslmode=disable\" \\\n      username=\"postgres\" \\\n      password=\"postgres\"\n    ```\n    ℹ️ `username` and `password` are the  admin postgres instance credentials\n    ℹ️ note the `?sslmode=disable` to connect to the dev instance (which is obviously a bad idea in production!)\n\n* Create role `vault_ecto`:\n    ```sh\n    vault write database/roles/vault_ecto \\\n      db_name=my_database \\\n      creation_statements=\"CREATE ROLE \\\"{{name}}\\\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' IN ROLE base_role;\"\\\n      revocation_statements=\"REASSIGN OWNED BY \\\"{{name}}\\\" TO base_role; DROP USER \\\"{{name}}\\\";\"\\\n      default_ttl=\"60\" \\\n      max_ttl=\"120\"\n    ```\n\n* Create a policy to authorize reading vault_ecto database credentials:\n    ```sh\n    vault policy write read_vault_ecto_creds ./vault/read_vault_ecto_creds_policy.hcl\n    ```\n\n### Configure AppRole for vault agent\n\nℹ️ Vault agent provides many ways to authenticate to vault. However, using an approle is the fastest way for a local setup.\n\n* Enable approle backend:\n    ```sh\n    vault auth enable approle\n    ```\n\n* Create approle:\n    ```sh\n    vault write auth/approle/role/vault-agent\\\n        secret_id_ttl=43200m\\\n        token_num_uses=9999\\\n        token_ttl=43200m\\\n        token_max_ttl=43200m\\\n        secret_id_num_uses=99999\\\n        policies=read_vault_ecto_creds\\\n        token_policies=read_vault_ecto_creds\n    ```\n\n### Launch vault agent\n\n\n* Get agent's approle role-id:\n    ```sh\n    vault read -format=json auth/approle/role/vault-agent/role-id | jq -r '.data.role_id' \u003e ./vault/role_id\n    ```\n\n* Get agent's approle secret-id:\n    ```sh\n    vault write -format=json -f auth/approle/role/vault-agent/secret-id | jq -r '.data.secret_id' \u003e ./vault/secret_id\n    ```\n\n* Launch vault agent:\n    ```sh\n    vault agent -config ./vault/vault_agent_config.hcl\n    ```\n\n### Launch vault_ecto and initialize database\n\n* Launch vault_ecto:\n    ```sh\n    iex -S mix\n    ```\n\n* Create table:\n    ```elixir\n    iex\u003e Ecto.Migrator.with_repo(VaultEcto.Repo, \u0026Ecto.Migrator.run(\u00261, :up, all: true))\n    ```\n\n    ℹ️ This code applies all migrations in `priv/repo/migrations`.\n\n* Seed table with some data:\n    ```elixir\n    iex\u003e Code.eval_file(\"priv/repo/seed.exs\")\n    ```\n\n## Cheatsheet\n\n### `vault_ecto`\n\n* Select query:\n    ```elixir\n    VaultEcto.Person |\u003e Ecto.Query.first() |\u003e VaultEcto.Repo.one()\n    ```\n\n* Insert query:\n    ```elixir\n     %VaultEcto.Person{first_name: \"foo\", last_name: \"bar\", age: 42} |\u003e VaultEcto.Repo.insert()\n    ```\n    or\n    ```elixir\n    VaultEcto.insert()\n    ```\n\n* Long transaction:\n    ```elixir\n    VaultEcto.Repo.transaction(\n      fn -\u003e\n        %VaultEcto.Person{first_name: \"foo\", last_name: \"bar\", age: 42}\n        |\u003e VaultEcto.Repo.insert!()\n\n        :timer.sleep(100_000)\n\n        %VaultEcto.Person{first_name: \"foo\", last_name: \"bar\", age: 42}\n        |\u003e VaultEcto.Repo.insert!()\n      end,\n      timeout: :infinity)\n    ```\n    or\n    ```elixir\n    VaultEcto.long_transaction(100_000)\n    ```\n    👉 Running this long running transaction proves that even if the credentials are rotated during its execution,\n    it will not be interrupted.\n\n### Vault\n\n* To get database credentials:\n    ```sh\n    vault read database/creds/vault_ecto\n    ```\n\n* Login using approle:\n    ```sh\n    TOKEN=$(vault write auth/approle/login role_id=@./vault/role_id secret_id=@./vault/secret_id -format=json | jq -r '.auth.client_token')\n    vault login ${TOKEN}\n    ```\n\n### Postgres\n\n* Connect to database:\n    ```sh\n    psql -U postgres -p 5432 -h 127.0.0.1 -d vault_ecto\n    ```\n\n* List roles\n    ```sh\n    \\du\n    ```\n\n* List tables\n    ```sh\n    \\dt\n    ```\n\n* [List permissions](https://stackoverflow.com/a/40759633/21584)\n    ```sh\n    select * from information_schema.role_table_grants where grantee='YOUR_USER';\n    ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahamez%2Fvault_ecto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fahamez%2Fvault_ecto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahamez%2Fvault_ecto/lists"}