From 8b2cdc41c0ea075127f61b144b6482e724dc6f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Wed, 12 Jun 2024 12:45:22 +0200 Subject: [PATCH] Support SSL for Postgres/MySQL and accept a custom CA certificates file (#73) --- lib/assets/connection_cell/main.js | 29 +++++++++++- lib/kino_db/connection_cell.ex | 63 +++++++++++++++++++-------- mix.exs | 4 +- mix.lock | 4 +- test/kino_db/connection_cell_test.exs | 18 +++++++- 5 files changed, 95 insertions(+), 23 deletions(-) diff --git a/lib/assets/connection_cell/main.js b/lib/assets/connection_cell/main.js index 24e983b..56744c1 100644 --- a/lib/assets/connection_cell/main.js +++ b/lib/assets/connection_cell/main.js @@ -381,12 +381,28 @@ export function init(ctx, info) { :grow :required /> + +
+ +
+
+ +
attrs["port"] || default_port, "use_ipv6" => Map.get(attrs, "use_ipv6", false), "use_ssl" => Map.get(attrs, "use_ssl", false), + "cacertfile" => attrs["cacertfile"] || "", "username" => attrs["username"] || "", "password" => password, "use_password_secret" => Map.has_key?(attrs, "password_secret") || password == "", @@ -143,13 +144,15 @@ defmodule KinoDB.ConnectionCell do "sqlserver" -> if fields["use_password_secret"], - do: ~w|database hostname port use_ipv6 username password_secret use_ssl instance|, - else: ~w|database hostname port use_ipv6 username password use_ssl instance| + do: + ~w|database hostname port use_ipv6 username password_secret use_ssl cacertfile instance|, + else: + ~w|database hostname port use_ipv6 username password use_ssl cacertfile instance| type when type in ["postgres", "mysql"] -> if fields["use_password_secret"], - do: ~w|database hostname port use_ipv6 use_ssl username password_secret|, - else: ~w|database hostname port use_ipv6 use_ssl username password| + do: ~w|database hostname port use_ipv6 use_ssl cacertfile username password_secret|, + else: ~w|database hostname port use_ipv6 use_ssl cacertfile username password| end Map.take(fields, @default_keys ++ connection_keys) @@ -251,7 +254,7 @@ defmodule KinoDB.ConnectionCell do defp to_quoted(%{"type" => "postgres"} = attrs) do quote do - opts = unquote(shared_options(attrs)) + opts = unquote(shared_options(attrs) ++ postgres_and_mysql_options(attrs)) {:ok, unquote(quoted_var(attrs["variable"]))} = Kino.start_child({Postgrex, opts}) end @@ -259,7 +262,7 @@ defmodule KinoDB.ConnectionCell do defp to_quoted(%{"type" => "mysql"} = attrs) do quote do - opts = unquote(shared_options(attrs)) + opts = unquote(shared_options(attrs) ++ postgres_and_mysql_options(attrs)) {:ok, unquote(quoted_var(attrs["variable"]))} = Kino.start_child({MyXQL, opts}) end @@ -362,13 +365,6 @@ defmodule KinoDB.ConnectionCell do database: attrs["database"] ] - opts = - if attrs["use_ssl"] do - opts ++ [ssl: attrs["use_ssl"]] - else - opts - end - if attrs["use_ipv6"] do opts ++ [socket_options: [:inet6]] else @@ -376,13 +372,46 @@ defmodule KinoDB.ConnectionCell do end end + defp postgres_and_mysql_options(attrs) do + if attrs["use_ssl"] do + cacertfile = attrs["cacertfile"] + + ssl_opts = + if cacertfile && cacertfile != "" do + [cacertfile: cacertfile] + else + [cacerts: quote(do: :public_key.cacerts_get())] + end + + [ssl: ssl_opts] + else + [] + end + end + defp sqlserver_options(attrs) do + opts = + if attrs["use_ssl"] do + cacertfile = attrs["cacertfile"] + + ssl_opts = + if cacertfile && cacertfile != "" do + [cacertfile: cacertfile] + else + [cacerts: quote(do: :public_key.cacerts_get())] + end + + [ssl: true, ssl_opts: ssl_opts] + else + [] + end + instance = attrs["instance"] if instance && instance != "" do - [instance: instance] + opts ++ [instance: instance] else - [] + opts end end @@ -413,13 +442,13 @@ defmodule KinoDB.ConnectionCell do defp missing_dep(%{"type" => "postgres"}) do unless Code.ensure_loaded?(Postgrex) do - ~s/{:postgrex, "~> 0.17"}/ + ~s/{:postgrex, "~> 0.18"}/ end end defp missing_dep(%{"type" => "mysql"}) do unless Code.ensure_loaded?(MyXQL) do - ~s/{:myxql, "~> 0.6"}/ + ~s/{:myxql, "~> 0.7"}/ end end diff --git a/mix.exs b/mix.exs index 8ac2560..82d8432 100644 --- a/mix.exs +++ b/mix.exs @@ -31,8 +31,8 @@ defmodule KinoDB.MixProject do {:adbc, "~> 0.2", optional: true}, {:db_connection, "~> 2.4.2 or ~> 2.5", optional: true}, {:exqlite, "~> 0.11", optional: true}, - {:myxql, "~> 0.6.2 or ~> 0.7", optional: true}, - {:postgrex, "~> 0.17.3 or ~> 0.18 or ~> 1.0", optional: true}, + {:myxql, "~> 0.7", optional: true}, + {:postgrex, "~> 0.18 or ~> 1.0", optional: true}, {:tds, "~> 2.3.4 or ~> 2.4", optional: true}, # Those dependecies are new, so we use stricter versions diff --git a/mix.lock b/mix.lock index 914d296..7b0b5ff 100644 --- a/mix.lock +++ b/mix.lock @@ -23,11 +23,11 @@ "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, - "myxql": {:hex, :myxql, "0.6.3", "3d77683a09f1227abb8b73d66b275262235c5cae68182f0cfa5897d72a03700e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.4", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "af9eb517ddaced5c5c28e8749015493757fd4413f2cfccea449c466d405d9f51"}, + "myxql": {:hex, :myxql, "0.7.0", "3382f139b0b0da977a8fc33c8cded125e20df2e400f8d7b7e674fa62a7e077dd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.4", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "40e4b7ad4973c8b895e86a3de04ff7a79c2cf72b9f2bddef7717afb4ab36d8c0"}, "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, - "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, + "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, "req": {:hex, :req, "0.3.11", "462315e50db6c6e1f61c45e8c0b267b0d22b6bd1f28444c136908dfdca8d515a", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.9", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0e4b331627fedcf90b29aa8064cd5a95619ef6134d5ab13919b6e1c4d7cccd4b"}, "req_athena": {:hex, :req_athena, "0.1.4", "8e9e125c265e19982af415d4f2e11ee4bfd411ce6887835675082ebe6fa2cf58", [:mix], [{:aws_signature, "~> 0.3.0", [hex: :aws_signature, repo: "hexpm", optional: false]}, {:req, "~> 0.3.5", [hex: :req, repo: "hexpm", optional: false]}, {:table, "~> 0.1.1", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "ba241610a30c9c82db0eb9be87ae5e795d60bbed251ff70c4836e9964827ea5f"}, "req_bigquery": {:hex, :req_bigquery, "0.1.2", "46272169724867561335bd54052daf2e43e5673ece3630a1f5ec42aeb5d98257", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:goth, "~> 1.3.0", [hex: :goth, repo: "hexpm", optional: false]}, {:req, "~> 0.3.5", [hex: :req, repo: "hexpm", optional: false]}, {:table, "~> 0.1.1", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "b1dbac8d5cecaaf7e85217119c035aededcde8f7bc0f64d324b16e15e326c8ea"}, diff --git a/test/kino_db/connection_cell_test.exs b/test/kino_db/connection_cell_test.exs index 8e0da59..e093331 100644 --- a/test/kino_db/connection_cell_test.exs +++ b/test/kino_db/connection_cell_test.exs @@ -14,6 +14,7 @@ defmodule KinoDB.ConnectionCellTest do "port" => 4444, "use_ipv6" => false, "use_ssl" => false, + "cacertfile" => "", "username" => "admin", "password" => "pass", "use_password_secret" => false, @@ -97,7 +98,22 @@ defmodule KinoDB.ConnectionCellTest do username: "admin", password: "pass", database: "default", - ssl: true + ssl: [cacerts: :public_key.cacerts_get()] + ] + + {:ok, db} = Kino.start_child({Postgrex, opts})\ + ''' + + attrs = Map.merge(@attrs, %{"use_ssl" => true, "cacertfile" => "/path/to/cacertfile"}) + + assert ConnectionCell.to_source(attrs) === ~s''' + opts = [ + hostname: "localhost", + port: 4444, + username: "admin", + password: "pass", + database: "default", + ssl: [cacertfile: "/path/to/cacertfile"] ] {:ok, db} = Kino.start_child({Postgrex, opts})\