Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fallback to using unencrypted DB connection when possible #2084

Merged
merged 3 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/stupid-weeks-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@core/sync-service": patch
---

Restore the automatic fallback to unencrypted database connections when SSL isn't available.
2 changes: 1 addition & 1 deletion examples/gatekeeper-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ $ curl -sv --header "Authorization: Bearer ${AUTH_TOKEN}" \
Note that we got an empty response when successfully proxied through to Electric above because there are no `items` in the database. If you like, you can create some, e.g. using `psql`:

```console
$ psql "postgresql://postgres:password@localhost:54321/electric?sslmode=disable"
$ psql "postgresql://postgres:password@localhost:54321/electric"
psql (16.4)
Type "help" for help.

Expand Down
79 changes: 51 additions & 28 deletions packages/sync-service/lib/electric/connection/manager.ex
Original file line number Diff line number Diff line change
Expand Up @@ -242,20 +242,24 @@ defmodule Electric.Connection.Manager do

@impl true
def handle_continue(:start_lock_connection, %State{lock_connection_pid: nil} = state) do
case Electric.Postgres.LockConnection.start_link(
connection_opts: state.connection_opts,
connection_manager: self(),
lock_name: Keyword.fetch!(state.replication_opts, :slot_name)
) do
{:ok, lock_connection_pid} ->
opts = [
connection_opts: state.connection_opts,
connection_manager: self(),
lock_name: Keyword.fetch!(state.replication_opts, :slot_name)
]

case start_lock_connection(opts) do
{:ok, pid, connection_opts} ->
state = %{state | lock_connection_pid: pid, connection_opts: connection_opts}

Electric.StackSupervisor.dispatch_stack_event(
state.stack_events_registry,
state.stack_id,
:waiting_for_connection_lock
)

Process.send_after(self(), :log_lock_connection_status, @lock_status_logging_interval)
{:noreply, %{state | lock_connection_pid: lock_connection_pid}}
{:noreply, state}

{:error, reason} ->
handle_connection_error(reason, state, "lock_connection")
Expand Down Expand Up @@ -435,32 +439,27 @@ defmodule Electric.Connection.Manager do
}}
end

defp start_replication_client(opts) do
case Electric.Postgres.ReplicationClient.start_link(opts) do
defp start_lock_connection(opts) do
case Electric.Postgres.LockConnection.start_link(opts) do
{:ok, pid} ->
{:ok, pid, Keyword.fetch!(opts, :connection_opts)}

{:error, %Postgrex.Error{message: "ssl not available"}} = error ->
sslmode = get_in(opts, [:connection_opts, :sslmode])
{:ok, pid, opts[:connection_opts]}

if sslmode == :require do
error
else
if not is_nil(sslmode) do
# Only log a warning when there's an explicit sslmode parameter in the database
# config, meaning the user has requested a certain sslmode.
Logger.warning(
"Failed to connect to the database using SSL. Trying again, using an unencrypted connection."
)
end

opts
|> Keyword.update!(:connection_opts, &Keyword.put(&1, :ssl, false))
|> start_replication_client()
error ->
with {:ok, opts} <- maybe_fallback_to_no_ssl(error, opts) do
start_lock_connection(opts)
end
end
end

defp start_replication_client(opts) do
case Electric.Postgres.ReplicationClient.start_link(opts) do
{:ok, pid} ->
{:ok, pid, opts[:connection_opts]}

error ->
error
with {:ok, opts} <- maybe_fallback_to_no_ssl(error, opts) do
start_replication_client(opts)
end
end
end

Expand All @@ -478,6 +477,30 @@ defmodule Electric.Connection.Manager do
)
end

defp maybe_fallback_to_no_ssl(
{:error, %Postgrex.Error{message: "ssl not available"}} = error,
opts
) do
sslmode = get_in(opts, [:connection_opts, :sslmode])

if sslmode == :require do
error
else
if not is_nil(sslmode) do
# Only log a warning when there's an explicit sslmode parameter in the database
# config, meaning the user has requested a certain sslmode.
Logger.warning(
"Failed to connect to the database using SSL. Trying again, using an unencrypted connection."
)
end

opts = Keyword.update!(opts, :connection_opts, &Keyword.put(&1, :ssl, false))
{:ok, opts}
end
end

defp maybe_fallback_to_no_ssl(error, _opts), do: error

defp handle_connection_error(
{:shutdown, {:failed_to_start_child, Electric.Postgres.ReplicationClient, error}},
state,
Expand Down
Loading