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

feat: use simple_connection for auth_query #342

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

Conversation

abc3
Copy link
Member

@abc3 abc3 commented May 27, 2024

The PID of the Postgrex connection can be lost when ClientHandler terminates in the middle of getting secrets via auth_query. As a result, we may get zombie connections, which can consume all allowed connections to the tenant's database. With this PR, auth_query will be done with SimpleConnection, where the process crashes automatically if the caller (ClientHandler) goes down.

@abc3 abc3 requested review from chasers and filipecabaco May 27, 2024 15:49
lib/supavisor/helpers.ex Outdated Show resolved Hide resolved
lib/supavisor/helpers.ex Show resolved Hide resolved
@abc3 abc3 requested a review from a team May 28, 2024 13:00
Copy link
Contributor

@josevalim josevalim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! :)

@@ -88,7 +88,11 @@ defmodule Supavisor.Helpers do
@spec get_user_secret(pid(), String.t(), String.t()) :: {:ok, map()} | {:error, String.t()}
def get_user_secret(conn, auth_query, user) do
try do
Postgrex.query!(conn, auth_query, [user])
# sanitize the user input by removing all characters that are not alphanumeric or underscores
user = String.replace(user, ~r/[^a-zA-Z0-9_]/, "")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that instead we should reject user if these characters are present instead of just pretending that these do not exists.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already have a check for invalid characters during connection, but it only checks for slashes. I added this sanitization as an additional guard. It might be worth applying the same rule (a-zA-Z0-9_) during connection as well

not_allowed = ["\"", "\\"]
if String.contains?(user, not_allowed) or String.contains?(db_name, not_allowed) do
reason = "Invalid characters in user or db_name"
Logger.error("ClientHandler: #{inspect(reason)}")
Telem.client_join(:fail, data.id)
HH.send_error(data.sock, "XX000", "Authentication error, reason: #{inspect(reason)}")
{:stop, {:shutdown, :invalid_characters}}

$ psql postgresql://p\"ostgres.tenant:password@localhost:5432/postgres
psql: error: connection to server at "localhost" (::1), port 5432 failed: Connection refused
	Is the server running on that host and accepting TCP/IP connections?
connection to server at "localhost" (127.0.0.1), port 5432 failed: FATAL:  Authentication error, reason: "Invalid characters in user or db_name"

@abc3 abc3 requested a review from a team as a code owner May 29, 2024 13:19
lib/single_connection.ex Outdated Show resolved Hide resolved
Comment on lines +32 to +34
assert Enum.filter(Process.list(), fn pid ->
Process.info(pid)[:dictionary][:auth_host] == db_conf[:hostname]
end) == []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert Enum.filter(Process.list(), fn pid ->
Process.info(pid)[:dictionary][:auth_host] == db_conf[:hostname]
end) == []
assert Enum.all?(Process.list(), fn pid ->
Process.info(pid)[:dictionary][:auth_host] == db_conf[:hostname]
end)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what is worth, using filter for this test has a tiny benefit that it shows the left side with the failed PIDs in exception reports. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. That indeed may be useful.

@@ -88,7 +88,11 @@ defmodule Supavisor.Helpers do
@spec get_user_secret(pid(), String.t(), String.t()) :: {:ok, map()} | {:error, String.t()}
def get_user_secret(conn, auth_query, user) do
try do
Postgrex.query!(conn, auth_query, [user])
# sanitize the user input by removing all characters that are not alphanumeric or underscores
user = String.replace(user, ~r/[^a-zA-Z0-9_]/, "")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that instead we should reject user if these characters are present instead of just pretending that these do not exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants