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

Prod deploy #183

Closed
wants to merge 9 commits into from
Closed
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
6 changes: 3 additions & 3 deletions .github/workflows/prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
packages: write
id-token: write
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Setup elixir
id: beam
uses: erlef/setup-beam@v1
Expand All @@ -24,7 +24,7 @@ jobs:
elixir-version: 1.14.5 # Define the elixir version [required]
version-type: strict
- name: Cache Mix
uses: actions/cache@v1
uses: actions/cache@v3
with:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
Expand All @@ -37,7 +37,7 @@ jobs:
- name: Create tarball
run: cd _build/prod/rel/ && tar -czvf ${{ secrets.TARBALL_REGIONS_PROD }}_supavisor_v$(cat ../../../VERSION)_$(date "+%s").tar.gz supavisor
- name: configure aws credentials - production
uses: aws-actions/configure-aws-credentials@v1
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.PROD_AWS_ROLE }}
aws-region: "us-east-1"
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/stage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
packages: write
id-token: write
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Setup elixir
id: beam
uses: erlef/setup-beam@v1
Expand All @@ -24,7 +24,7 @@ jobs:
elixir-version: 1.14.5 # Define the elixir version [required]
version-type: strict
- name: Cache Mix
uses: actions/cache@v1
uses: actions/cache@v3
with:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
Expand All @@ -37,7 +37,7 @@ jobs:
- name: Create tarball
run: cd _build/prod/rel/ && tar -czvf ${{ secrets.TARBALL_REGIONS_STAGE }}_supavisor_v$(cat ../../../VERSION)_$(date "+%s").tar.gz supavisor
- name: configure aws credentials - staging
uses: aws-actions/configure-aws-credentials@v1
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.DEV_AWS_ROLE }}
aws-region: "us-east-1"
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/staging_linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Setup elixir
id: beam
uses: erlef/setup-beam@v1
with:
otp-version: 25.x # Define the OTP version [required]
elixir-version: 1.14.x # Define the elixir version [required]
- name: Cache Mix
uses: actions/cache@v1
uses: actions/cache@v3
with:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
Expand All @@ -37,7 +37,7 @@ jobs:
- name: Credo checks
run: mix credo --strict --mute-exit-status
- name: Retrieve PLT Cache
uses: actions/cache@v1
uses: actions/cache@v3
id: plt-cache
with:
path: priv/plts
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/version_updated.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
name: Bump version
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Verify Versions Updated
uses: tj-actions/changed-files@v35
id: verify_changed_files
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.9.6
0.9.13
5 changes: 4 additions & 1 deletion lib/supavisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ defmodule Supavisor do
default_pool_size: def_pool_size,
default_max_clients: def_max_clients,
client_idle_timeout: client_idle_timeout,
default_pool_strategy: default_pool_strategy,
users: [
%{
db_user: db_user,
Expand Down Expand Up @@ -201,6 +202,7 @@ defmodule Supavisor do
upstream_verify: tenant_record.upstream_verify,
upstream_tls_ca: H.upstream_cert(tenant_record.upstream_tls_ca),
require_user: tenant_record.require_user,
method: method,
secrets: secrets
}

Expand All @@ -213,7 +215,8 @@ defmodule Supavisor do
mode: mode,
default_parameter_status: ps,
max_clients: max_clients,
client_idle_timeout: client_idle_timeout
client_idle_timeout: client_idle_timeout,
default_pool_strategy: default_pool_strategy
}

DynamicSupervisor.start_child(
Expand Down
79 changes: 58 additions & 21 deletions lib/supavisor/client_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ defmodule Supavisor.ClientHandler do
case decode_startup_packet(bin) do
{:ok, hello} ->
Logger.debug("Client startup message: #{inspect(hello)}")
{user, external_id} = parse_user_info(hello.payload["user"])
{user, external_id} = parse_user_info(hello.payload)
Logger.metadata(project: external_id, user: user, mode: data.mode)
{:keep_state, data, {:next_event, :internal, {:hello, {user, external_id}}}}

Expand All @@ -119,7 +119,7 @@ defmodule Supavisor.ClientHandler do
def handle_event(:internal, {:hello, {user, external_id}}, :exchange, %{sock: sock} = data) do
sni_hostname = try_get_sni(sock)

case Tenants.get_user(user, external_id, sni_hostname) do
case Tenants.get_user_cache(user, external_id, sni_hostname) do
{:ok, info} ->
id = Supavisor.id(info.tenant.external_id, user, data.mode, info.user.mode_type)
Registry.register(Supavisor.Registry.TenantClients, id, [])
Expand Down Expand Up @@ -157,8 +157,15 @@ defmodule Supavisor.ClientHandler do

case handle_exchange(sock, {method, secrets}) do
{:error, reason} ->
Logger.error("Exchange error: #{inspect(reason)}")
msg = Server.exchange_message(:final, "e=#{reason}")
Logger.error("Exchange error: #{inspect(reason)} when method #{inspect(method)}")

msg =
if method == :auth_query_md5 do
Server.error_message("XX000", reason)
else
Server.exchange_message(:final, "e=#{reason}")
end

sock_send(sock, msg)

{:stop, :normal, data}
Expand Down Expand Up @@ -393,15 +400,19 @@ defmodule Supavisor.ClientHandler do

## Internal functions

@spec parse_user_info(String.t()) :: {String.t() | nil, String.t()}
def parse_user_info(username) do
case :binary.matches(username, ".") do
@spec parse_user_info(map) :: {String.t() | nil, String.t()}
def parse_user_info(%{"user" => user, "options" => %{"reference" => ref}}) do
{user, ref}
end

def parse_user_info(%{"user" => user}) do
case :binary.matches(user, ".") do
[] ->
{username, nil}
{user, nil}

matches ->
{pos, _} = List.last(matches)
{name, "." <> external_id} = String.split_at(username, pos)
{pos, 1} = List.last(matches)
<<name::size(pos)-binary, ?., external_id::binary>> = user
{name, external_id}
end
end
Expand Down Expand Up @@ -433,7 +444,10 @@ defmodule Supavisor.ClientHandler do
map =
fields
|> Enum.chunk_every(2)
|> Enum.map(fn [k, v] -> {k, v} end)
|> Enum.map(fn
["options" = k, v] -> {k, URI.decode_query(v)}
[k, v] -> {k, v}
end)
|> Map.new()

# We only do light validation on the fields in the payload. The only field we use at the
Expand All @@ -447,8 +461,25 @@ defmodule Supavisor.ClientHandler do
end

@spec handle_exchange(S.sock(), {atom(), fun()}) :: {:ok, binary() | nil} | {:error, String.t()}
def handle_exchange({_, socket} = sock, {:auth_query_md5 = method, secrets}) do
salt = :crypto.strong_rand_bytes(4)
:ok = sock_send(sock, Server.md5_request(salt))

with {:ok,
%{
tag: :password_message,
payload: {:md5, client_md5}
}, _} <- receive_next(socket, "Timeout while waiting for the md5 exchange"),
{:ok, key} <- authenticate_exchange(method, client_md5, secrets.().secret, salt) do
{:ok, key}
else
{:error, message} -> {:error, message}
other -> {:error, "Unexpected message #{inspect(other)}"}
end
end

def handle_exchange({_, socket} = sock, {method, secrets}) do
:ok = sock_send(sock, Server.auth_request())
:ok = sock_send(sock, Server.scram_request())

with {:ok,
%{
Expand All @@ -473,8 +504,7 @@ defmodule Supavisor.ClientHandler do

defp receive_next(socket, timeout_message) do
receive do
{_proto, ^socket, bin} ->
Server.decode_pkt(bin)
{_proto, ^socket, bin} -> Server.decode_pkt(bin)
after
15_000 -> {:error, timeout_message}
end
Expand All @@ -495,7 +525,7 @@ defmodule Supavisor.ClientHandler do
end

defp authenticate_exchange(:auth_query, secrets, signatures, p) do
client_key = :crypto.exor(p |> Base.decode64!(), signatures.client)
client_key = :crypto.exor(Base.decode64!(p), signatures.client)

if H.hash(client_key) == secrets.().stored_key do
{:ok, client_key}
Expand All @@ -504,6 +534,14 @@ defmodule Supavisor.ClientHandler do
end
end

defp authenticate_exchange(:auth_query_md5, client_hash, server_hash, salt) do
if "md5" <> H.md5([server_hash, salt]) == client_hash do
{:ok, nil}
else
{:error, "Wrong password"}
end
end

@spec db_checkout(:on_connect | :on_query, map()) :: pid() | nil
defp db_checkout(_, %{mode: :session, db_pid: db_pid}) when is_pid(db_pid) do
db_pid
Expand Down Expand Up @@ -579,11 +617,8 @@ defmodule Supavisor.ClientHandler do
case Cachex.fetch(Supavisor.Cache, cache_key, fn _key ->
{:commit, {:cached, get_secrets(info, db_user)}, ttl: 15_000}
end) do
{_, {:cached, value}} ->
value

{_, {:cached, value}, _} ->
value
{_, {:cached, value}} -> value
{_, {:cached, value}, _} -> value
end
end

Expand All @@ -594,6 +629,7 @@ defmodule Supavisor.ClientHandler do
[
{:verify, :verify_peer},
{:cacerts, [H.upstream_cert(tenant.upstream_tls_ca)]},
{:server_name_indication, String.to_charlist(tenant.db_host)},
{:customize_hostname_check, [{:match_fun, fn _, _ -> true end}]}
]
end
Expand All @@ -615,7 +651,8 @@ defmodule Supavisor.ClientHandler do
resp =
case H.get_user_secret(conn, tenant.auth_query, db_user) do
{:ok, secret} ->
{:ok, {:auth_query, fn -> Map.put(secret, :alias, user.db_user_alias) end}}
t = if secret.digest == :md5, do: :auth_query_md5, else: :auth_query
{:ok, {t, fn -> Map.put(secret, :alias, user.db_user_alias) end}}

{:error, reason} ->
{:error, reason}
Expand Down
20 changes: 11 additions & 9 deletions lib/supavisor/db_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -179,17 +179,18 @@ defmodule Supavisor.DbHandler do
acc

%{payload: {:authentication_md5_password, salt}}, {ps, _} ->
password = data.auth.password
user = data.auth.user
Logger.debug("dec_pkt, #{inspect(dec_pkt, pretty: true)}")

digest = [password.(), user] |> :erlang.md5() |> Base.encode16(case: :lower)
digest = [digest, salt] |> :erlang.md5() |> Base.encode16(case: :lower)
payload = ["md5", digest, 0]
digest =
if data.auth.method == :password do
H.md5([data.auth.password.(), data.auth.user])
else
data.auth.secrets.().secret
end

payload = ["md5", H.md5([digest, salt]), 0]
bin = [?p, <<IO.iodata_length(payload) + 4::signed-32>>, payload]

:ok = sock_send(data.sock, bin)

{ps, :authentication_md5}

%{tag: :error_response, payload: error}, _ ->
Expand Down Expand Up @@ -224,7 +225,7 @@ defmodule Supavisor.DbHandler do

def handle_event(:internal, :check_buffer, :idle, %{buffer: buff} = data) do
if buff != [] do
Logger.warning("Buffer is not empty, try to send #{IO.iodata_length(buff)} bytes")
Logger.debug("Buffer is not empty, try to send #{IO.iodata_length(buff)} bytes")
buff = Enum.reverse(buff)
:ok = sock_send(data.sock, buff)
end
Expand All @@ -251,7 +252,7 @@ defmodule Supavisor.DbHandler do
end

def handle_event({:call, {pid, _} = from}, {:db_call, bin}, state, %{buffer: buff} = data) do
Logger.warning(
Logger.debug(
"state #{state} <-- <-- bin #{inspect(byte_size(bin))} bytes, caller: #{inspect(pid)}"
)

Expand Down Expand Up @@ -328,6 +329,7 @@ defmodule Supavisor.DbHandler do
[
verify: :verify_peer,
cacerts: [auth.upstream_tls_ca],
server_name_indication: auth.host,
customize_hostname_check: [{:match_fun, fn _, _ -> true end}]
]

Expand Down
12 changes: 12 additions & 0 deletions lib/supavisor/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ defmodule Supavisor.Helpers do
[
{:verify, :verify_peer},
{:cacerts, [upstream_cert(params["upstream_tls_ca"])]},
{:server_name_indication, String.to_charlist(params["db_host"])},
{:customize_hostname_check, [{:match_fun, fn _, _ -> true end}]}
]
end
Expand Down Expand Up @@ -124,6 +125,10 @@ defmodule Supavisor.Helpers do
end
end

def parse_secret("md5" <> secret, user) do
{:ok, %{digest: :md5, secret: secret, user: user}}
end

def parse_postgres_secret(_), do: {:error, "Digest not supported"}

## Internal functions
Expand Down Expand Up @@ -303,4 +308,11 @@ defmodule Supavisor.Helpers do
def parse_server_first(message, nonce) do
:pgo_scram.parse_server_first(message, nonce) |> Map.new()
end

@spec md5([String.t()]) :: String.t()
def md5(strings) do
strings
|> :erlang.md5()
|> Base.encode16(case: :lower)
end
end
Loading