Skip to content

Commit d02d8b4

Browse files
mrnovallesjosevalim
authored andcommitted
Add extra_opts to endpoints config (#572)
1 parent 087e8e9 commit d02d8b4

File tree

3 files changed

+68
-16
lines changed

3 files changed

+68
-16
lines changed

Diff for: lib/postgrex.ex

+36-10
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,12 @@ defmodule Postgrex do
7878
7979
* `:hostname` - Server hostname (default: PGHOST env variable, then localhost);
8080
* `:port` - Server port (default: PGPORT env variable, then 5432);
81-
* `:endpoints` - A list of endpoints (host and port pairs); Postgrex will try
82-
each endpoint in order, one by one, until the connection succeeds; The syntax
83-
is `[{host1,port1},{host2,port2},{host3,port3}]`; This option takes precedence
84-
over `:hostname+:port`;
81+
* `:endpoints` - A list of endpoints (host and port pairs, with an optional
82+
extra_opts keyword list);
83+
Postgrex will try each endpoint in order, one by one, until the connection succeeds;
84+
The syntax is `[{host1, port1},{host2, port2},{host3, port3}]` or
85+
`[{host1, port1, extra_opt1: value},{host2, port2, extra_opt2: value}}]`;
86+
This option takes precedence over `:hostname+:port`;
8587
* `:socket_dir` - Connect to PostgreSQL via UNIX sockets in the given directory;
8688
The socket name is derived based on the port. This is the preferred method
8789
for configuring sockets and it takes precedence over the hostname. If you are
@@ -154,21 +156,23 @@ defmodule Postgrex do
154156
155157
iex> {:ok, pid} = Postgrex.start_link(socket_dir: "/tmp", database: "postgres")
156158
{:ok, #PID<0.69.0>}
157-
158-
## SSL client authentication
159159
160-
When connecting to CockroachDB instances running in secure mode it is idiomatic to use
161-
client SSL certificate authentication.
160+
## SSL client authentication
161+
162+
When connecting to CockroachDB instances running in secure mode it is idiomatic to use
163+
client SSL certificate authentication.
162164
163165
An example of Repository configuration:
164166
165167
config :app, App.Repo,
166168
ssl: String.to_existing_atom(System.get_env("DB_SSL_ENABLED", "true")),
167169
ssl_opts: [
168170
verify: :verify_peer,
171+
server_name_indication: System.get_env("DB_HOSTNAME")
169172
cacertfile: System.get_env("DB_CA_CERT_FILE"),
170-
verify_fun: &:ssl_verify_hostname.verify_fun/3
171-
]
173+
customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)],
174+
depth: 3
175+
]
172176
173177
## PgBouncer
174178
@@ -215,6 +219,28 @@ defmodule Postgrex do
215219
(...),
216220
{"test-instance-N.xyz.eu-west-1.rds.amazonaws.com", 5432}
217221
]
222+
223+
### Failover with SSL support
224+
225+
As specified in Erlang [:ssl.connect](https://erlang.org/doc/man/ssl.html#connect-3),
226+
host verification using `:public_key.pkix_verify_hostname_match_fun(:https)`
227+
requires that the ssl_opt `server_name_indication` is set, and for this reason,
228+
the aforementioned `endpoints` list can become a three element tuple as:
229+
230+
endpoints: [
231+
{
232+
"test-instance-1.xyz.eu-west-1.rds.amazonaws.com",
233+
5432,
234+
[ssl: [server_name_indication: String.to_charlist("test-instance-1.xyz.eu-west-1.rds.amazonaws.com")]]
235+
},
236+
(...),
237+
{
238+
"test-instance-2.xyz.eu-west-1.rds.amazonaws.com",
239+
5432,
240+
[ssl: [server_name_indication: String.to_charlist("test-instance-2.xyz.eu-west-1.rds.amazonaws.com")]]
241+
}
242+
]
243+
218244
"""
219245
@spec start_link([start_option]) :: {:ok, pid} | {:error, Postgrex.Error.t() | term}
220246
def start_link(opts) do

Diff for: lib/postgrex/protocol.ex

+11-6
Original file line numberDiff line numberDiff line change
@@ -112,25 +112,28 @@ defmodule Postgrex.Protocol do
112112

113113
case Keyword.fetch(opts, :socket) do
114114
{:ok, file} ->
115-
[{{:local, file}, 0}]
115+
[{{:local, file}, 0, []}]
116116

117117
:error ->
118118
case Keyword.fetch(opts, :socket_dir) do
119119
{:ok, dir} ->
120-
[{{:local, "#{dir}/.s.PGSQL.#{port}"}, 0}]
120+
[{{:local, "#{dir}/.s.PGSQL.#{port}"}, 0, []}]
121121

122122
:error ->
123123
case Keyword.fetch(opts, :endpoints) do
124124
{:ok, endpoints} when is_list(endpoints) ->
125-
Enum.map(endpoints, fn {hostname, port} -> {to_charlist(hostname), port} end)
125+
Enum.map(endpoints, fn
126+
{hostname, port} -> {to_charlist(hostname), port, []}
127+
{hostname, port, extra_opts} -> {to_charlist(hostname), port, extra_opts}
128+
end)
126129

127130
{:ok, _} ->
128131
raise ArgumentError, "expected :endpoints to be a list of tuples"
129132

130133
:error ->
131134
case Keyword.fetch(opts, :hostname) do
132135
{:ok, hostname} ->
133-
[{to_charlist(hostname), port}]
136+
[{to_charlist(hostname), port, []}]
134137

135138
:error ->
136139
raise ArgumentError,
@@ -142,15 +145,17 @@ defmodule Postgrex.Protocol do
142145
end
143146

144147
defp connect_endpoints(
145-
[{host, port} | remaining_endpoints],
148+
[{host, port, extra_opts} | remaining_endpoints],
146149
sock_opts,
147150
timeout,
148151
s,
149152
%{opts: opts, types_mod: types_mod} = status,
150153
previous_errors
151154
) do
152155
types_key = if types_mod, do: {host, port, Keyword.fetch!(opts, :database)}
153-
status = %{status | types_key: types_key}
156+
opts = Config.Reader.merge(opts, extra_opts)
157+
158+
status = %{status | types_key: types_key, opts: opts}
154159

155160
case connect_and_handshake(host, port, sock_opts, timeout, s, status) do
156161
{:ok, _} = ret ->

Diff for: test/login_test.exs

+21
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ defmodule LoginTest do
8282
assert {:ok, %Postgrex.Result{}} = P.query(pid, "SELECT 123", [])
8383
end
8484

85+
@tag :ssl
86+
test "ssl with extra_ssl_opts in endpoints succeeds", context do
87+
opts = [ssl: true, endpoints: [{"localhost", 5555, [ssl: [verify_peer: :none]]}]]
88+
assert {:ok, pid} = P.start_link(opts ++ context[:options])
89+
assert {:ok, %Postgrex.Result{}} = P.query(pid, "SELECT 123", [])
90+
end
91+
92+
@tag :ssl
93+
test "ssl with extra_ssl_opts in endpoints fails due to bad ssl_opt", context do
94+
assert capture_log(fn ->
95+
opts = [ssl: true, endpoints: [{"localhost", 5555, [ssl: [verify_peer: :foobar]]}]]
96+
assert_start_and_killed(opts ++ context[:options])
97+
end)
98+
end
99+
85100
test "env var defaults", context do
86101
assert {:ok, pid} = P.start_link(context[:options])
87102
assert {:ok, %Postgrex.Result{}} = P.query(pid, "SELECT 123", [])
@@ -199,6 +214,12 @@ defmodule LoginTest do
199214
assert {:ok, %Postgrex.Result{}} = P.query(pid, "SELECT 123", [])
200215
end
201216

217+
test "endpoints with extra_opts", context do
218+
opts = [endpoints: [{"localhost", 5555, [ssl: [depth: 3]]}, {"localhost", 5432, [depth: 3]}]]
219+
assert {:ok, pid} = P.start_link(opts ++ context[:options])
220+
assert {:ok, %Postgrex.Result{}} = P.query(pid, "SELECT 123", [])
221+
end
222+
202223
test "server type 'primary' against two primary instances", context do
203224
opts = [endpoints: [{"localhost", 5432}, {"localhost", 5432}], target_server_type: :primary]
204225
assert {:ok, pid} = P.start_link(opts ++ context[:options])

0 commit comments

Comments
 (0)