From 64fe2756c2c92614b9181a71cf1ad6d29d863edd Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Tue, 3 Dec 2024 15:11:07 +0200 Subject: [PATCH] Fallback to using unencrypted DB connection when possible (#2084) Fix #1792. --- .changeset/stupid-weeks-look.md | 5 ++ examples/gatekeeper-auth/README.md | 2 +- .../lib/electric/connection/manager.ex | 79 ++++++++++++------- 3 files changed, 57 insertions(+), 29 deletions(-) create mode 100644 .changeset/stupid-weeks-look.md diff --git a/.changeset/stupid-weeks-look.md b/.changeset/stupid-weeks-look.md new file mode 100644 index 0000000000..a400d0f6e3 --- /dev/null +++ b/.changeset/stupid-weeks-look.md @@ -0,0 +1,5 @@ +--- +"@core/sync-service": patch +--- + +Restore the automatic fallback to unencrypted database connections when SSL isn't available. diff --git a/examples/gatekeeper-auth/README.md b/examples/gatekeeper-auth/README.md index ce7ca4d512..73bc1bf7a0 100644 --- a/examples/gatekeeper-auth/README.md +++ b/examples/gatekeeper-auth/README.md @@ -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. diff --git a/packages/sync-service/lib/electric/connection/manager.ex b/packages/sync-service/lib/electric/connection/manager.ex index d369c54103..8c59e52e7e 100644 --- a/packages/sync-service/lib/electric/connection/manager.ex +++ b/packages/sync-service/lib/electric/connection/manager.ex @@ -242,12 +242,16 @@ 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, @@ -255,7 +259,7 @@ defmodule Electric.Connection.Manager do ) 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") @@ -445,32 +449,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 @@ -488,6 +487,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,