Skip to content

Commit e5ea4d4

Browse files
committed
better password handling, each topology process has an own password safe process now.
1 parent 02021a4 commit e5ea4d4

File tree

8 files changed

+60
-23
lines changed

8 files changed

+60
-23
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# An alternative Mongodb driver for Elixir
22
[![Build Status](https://travis-ci.org/zookzook/elixir-mongodb-driver.svg?branch=master)](https://travis-ci.org/zookzook/elixir-mongodb-driver)
3+
[![Coverage Status](https://coveralls.io/repos/github/zookzook/elixir-mongodb-driver/badge.svg?branch=master)](https://coveralls.io/github/zookzook/elixir-mongodb-driver?branch=master)
34
[![Hex.pm](https://img.shields.io/hexpm/v/mongodb_driver.svg)](https://hex.pm/packages/mongodb_driver)
45
[![Hex.pm](https://img.shields.io/hexpm/dt/mongodb_driver.svg)](https://hex.pm/packages/mongodb_driver)
56
[![Hex.pm](https://img.shields.io/hexpm/dw/mongodb_driver.svg)](https://hex.pm/packages/mongodb_driver)

lib/mongo/app.ex

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ defmodule Mongo.App do
77
children = [
88
worker(Mongo.IdServer, []),
99
worker(Mongo.PBKDF2Cache, []),
10-
worker(Mongo.PasswordSafe, []),
1110
worker(:gen_event, [local: Mongo.Events])
1211
]
1312

lib/mongo/auth.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@ defmodule Mongo.Auth do
3131

3232
defp setup(opts) do
3333
username = opts[:username]
34-
password = PasswordSafe.get_pasword()
34+
pw_safe = opts[:pw_safe]
35+
password = PasswordSafe.get_pasword(pw_safe)
3536
auth = opts[:auth] || []
3637

3738
auth =
3839
Enum.map(auth, fn opts ->
3940
username = opts[:username]
40-
password = PasswordSafe.get_pasword()
41+
password = PasswordSafe.get_pasword(pw_safe)
4142
{username, password}
4243
end)
4344

lib/mongo/password_safe.ex

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,53 @@
11
defmodule Mongo.PasswordSafe do
22
@moduledoc """
33
The password safe stores the password while parsing the url and/or the options to avoid it from logging while the sasl logger is activated.
4+
5+
The password is encrypted before storing in the GenServer's state. It will be encrypted before returning. This should help, that the password
6+
is not stored as plain text in the memory.
47
"""
58

69
@me __MODULE__
710

811
use GenServer
912

10-
def start_link(_ \\ nil) do
11-
GenServer.start_link(__MODULE__, [], name: @me)
13+
def new() do
14+
GenServer.start_link(@me, [])
1215
end
1316

14-
def set_password(password) do
15-
GenServer.cast(@me, {:set, password})
17+
def set_password(pid, password) do
18+
GenServer.cast(pid, {:set, password})
1619
end
1720

18-
def get_pasword() do
19-
GenServer.call(@me, :get)
21+
def get_pasword(nil), do: nil
22+
def get_pasword(pid) do
23+
GenServer.call(pid, :get)
2024
end
2125

2226
def init([]) do
23-
{:ok, nil}
27+
{:ok, %{key: generate_key(), pw: nil}}
28+
end
29+
30+
def handle_cast({:set, password}, %{key: key} = data) do
31+
{:noreply, %{data | pw: (password |> encrypt(key))}}
32+
end
33+
34+
def handle_call(:get, _from, %{key: key, pw: password} = data) do
35+
{:reply, password |> decrypt(key), data}
36+
end
37+
38+
defp encrypt(plaintext, key) do
39+
iv = :crypto.strong_rand_bytes(16) # create random Initialisation Vector
40+
ciphertext = :crypto.crypto_one_time(:aes_256_ctr, key, iv, plaintext, true)
41+
iv <> ciphertext # "return" iv & ciphertext
2442
end
2543

26-
def handle_cast({:set, password}, data) do
27-
{:noreply, password}
44+
defp decrypt(ciphertext, key) do
45+
<<iv::binary-16, ciphertext::binary>> = ciphertext
46+
:crypto.crypto_one_time(:aes_256_ctr, key, iv, ciphertext, false)
2847
end
2948

30-
def handle_call(:get, _from, password) do
31-
{:reply, password, password}
49+
defp generate_key() do
50+
:crypto.strong_rand_bytes(32)
3251
end
3352

3453
end

lib/mongo/topology.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ defmodule Mongo.Topology do
121121
end
122122

123123
def terminate(_reason, state) do
124+
case state.opts[:pw_safe] do
125+
nil -> nil
126+
pid -> GenServer.stop(pid)
127+
end
124128
Enum.each(state.connection_pools, fn {_address, pid} -> GenServer.stop(pid) end)
125129
Enum.each(state.monitors, fn {_address, pid} -> GenServer.stop(pid) end)
126130
:ok = Mongo.Events.notify(%TopologyClosedEvent{

lib/mongo/url_parser.ex

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,13 @@ defmodule Mongo.UrlParser do
164164
case Keyword.get(opts, :password) do
165165
nil -> opts
166166
value ->
167-
with :ok <- Mongo.PasswordSafe.set_password(value) do
168-
Keyword.put(opts, :password, "*****")
167+
168+
## start GenServer and put id
169+
with {:ok, pid} <- Mongo.PasswordSafe.new(),
170+
:ok <- Mongo.PasswordSafe.set_password(pid, value) do
171+
opts
172+
|> Keyword.put(:password, "*****")
173+
|> Keyword.put(:pw_safe, pid)
169174
end
170175
end
171176
end

test/mongo/password_safe_test.exs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,26 @@ defmodule Mongo.PasswordSafeTest do
55
alias Mongo.UrlParser
66
alias Mongo.PasswordSafe
77

8+
test "encrypted password" do
9+
10+
pw = "my-secret-password"
11+
{:ok, pid} = PasswordSafe.new()
12+
PasswordSafe.set_password(pid, pw)
13+
%{key: _key, pw: enc_pw} = :sys.get_state(pid)
14+
assert enc_pw != pw
15+
assert pw == PasswordSafe.get_pasword(pid)
16+
17+
end
18+
819
#
920
# When the sasl logger is activated like `--logger-sasl-reports true` then the supervisor reports all parameters when it starts a process. So, the password should not
1021
# used in the options
1122
#
12-
describe "parse_url and hide the password in options" do
13-
test "encoded password" do
23+
test "encoded password" do
1424
url = "mongodb://myDBReader:D1fficultP%[email protected]:27017/admin"
1525
opts = UrlParser.parse_url([url: url])
16-
1726
assert "*****" == Keyword.get(opts, :password)
18-
assert "D1fficultP@ssw0rd" == PasswordSafe.get_pasword()
27+
assert "D1fficultP@ssw0rd" == PasswordSafe.get_pasword(Keyword.get(opts, :pw_safe))
1928
end
20-
end
2129

2230
end

test/mongo/url_parser_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ defmodule Mongo.UrlParserTest do
1313
url =
1414
"mongodb://user:[email protected]:27017,seed2.domain.com:27017,seed3.domain.com:27017/db_name?ssl=true&replicaSet=set-name&authSource=admin&maxPoolSize=5"
1515

16-
assert UrlParser.parse_url(url: url) == [
16+
assert UrlParser.parse_url(url: url) |> Keyword.drop([:pw_safe]) == [
1717
password: "*****",
1818
username: "user",
1919
database: "db_name",
@@ -47,7 +47,7 @@ defmodule Mongo.UrlParserTest do
4747
end
4848

4949
test "url srv with user" do
50-
assert UrlParser.parse_url(url: "mongodb+srv://user:[email protected]") ==
50+
assert UrlParser.parse_url(url: "mongodb+srv://user:[email protected]") |> Keyword.drop([:pw_safe]) ==
5151
[
5252
password: "*****",
5353
username: "user",

0 commit comments

Comments
 (0)