From c1f5982b516f6122f351fab993753868ecc0acef Mon Sep 17 00:00:00 2001 From: Kevin Biju <52661649+heavycrystal@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:15:47 +0530 Subject: [PATCH] [clickhouse] allow supplying root CA as input during peer creation (#2048) --- flow/connectors/clickhouse/clickhouse.go | 9 ++++++++- flow/e2e/clickhouse/clickhouse.go | 4 ++-- nexus/analyzer/src/lib.rs | 1 + protos/peers.proto | 1 + ui/app/peers/create/[peerType]/helpers/ch.ts | 19 +++++++++++++++++-- ui/app/peers/create/[peerType]/schema.ts | 6 ++++++ 6 files changed, 35 insertions(+), 5 deletions(-) diff --git a/flow/connectors/clickhouse/clickhouse.go b/flow/connectors/clickhouse/clickhouse.go index d150730b2e..5c7f720996 100644 --- a/flow/connectors/clickhouse/clickhouse.go +++ b/flow/connectors/clickhouse/clickhouse.go @@ -3,6 +3,7 @@ package connclickhouse import ( "context" "crypto/tls" + "crypto/x509" "errors" "fmt" "log/slog" @@ -218,9 +219,15 @@ func Connect(ctx context.Context, config *protos.ClickhouseConfig) (clickhouse.C if err != nil { return nil, fmt.Errorf("failed to parse provided certificate: %w", err) } - // TODO: find a way to modify list of root CAs as well tlsSetting.Certificates = []tls.Certificate{cert} } + if config.RootCa != nil { + caPool := x509.NewCertPool() + if !caPool.AppendCertsFromPEM([]byte(*config.RootCa)) { + return nil, errors.New("failed to parse provided root CA") + } + tlsSetting.RootCAs = caPool + } conn, err := clickhouse.Open(&clickhouse.Options{ Addr: []string{fmt.Sprintf("%s:%d", config.Host, config.Port)}, diff --git a/flow/e2e/clickhouse/clickhouse.go b/flow/e2e/clickhouse/clickhouse.go index a001b1434a..b4b96d74c9 100644 --- a/flow/e2e/clickhouse/clickhouse.go +++ b/flow/e2e/clickhouse/clickhouse.go @@ -10,10 +10,10 @@ import ( "github.com/stretchr/testify/require" "github.com/PeerDB-io/peer-flow/connectors" - "github.com/PeerDB-io/peer-flow/connectors/clickhouse" + connclickhouse "github.com/PeerDB-io/peer-flow/connectors/clickhouse" connpostgres "github.com/PeerDB-io/peer-flow/connectors/postgres" "github.com/PeerDB-io/peer-flow/e2e" - "github.com/PeerDB-io/peer-flow/e2e/s3" + e2e_s3 "github.com/PeerDB-io/peer-flow/e2e/s3" "github.com/PeerDB-io/peer-flow/generated/protos" "github.com/PeerDB-io/peer-flow/model" "github.com/PeerDB-io/peer-flow/model/qvalue" diff --git a/nexus/analyzer/src/lib.rs b/nexus/analyzer/src/lib.rs index 55ec8987c3..49c05a22b4 100644 --- a/nexus/analyzer/src/lib.rs +++ b/nexus/analyzer/src/lib.rs @@ -752,6 +752,7 @@ fn parse_db_options(db_type: DbType, with_options: &[SqlOption]) -> anyhow::Resu endpoint: opts.get("endpoint").map(|s| s.to_string()), certificate: opts.get("certificate").map(|s| s.to_string()), private_key: opts.get("private_key").map(|s| s.to_string()), + root_ca: opts.get("root_ca").map(|s| s.to_string()), }; Config::ClickhouseConfig(clickhouse_config) } diff --git a/protos/peers.proto b/protos/peers.proto index f28823199a..a33cfd5a1d 100644 --- a/protos/peers.proto +++ b/protos/peers.proto @@ -122,6 +122,7 @@ message ClickhouseConfig{ optional string endpoint = 11; optional string certificate = 12 [(peerdb_redacted) = true]; optional string private_key = 13 [(peerdb_redacted) = true]; + optional string root_ca = 14 [(peerdb_redacted) = true]; } message SqlServerConfig { diff --git a/ui/app/peers/create/[peerType]/helpers/ch.ts b/ui/app/peers/create/[peerType]/helpers/ch.ts index d04a954d8f..bd434957d2 100644 --- a/ui/app/peers/create/[peerType]/helpers/ch.ts +++ b/ui/app/peers/create/[peerType]/helpers/ch.ts @@ -95,7 +95,7 @@ export const clickhouseSetting: PeerSetting[] = [ }, type: 'file', optional: true, - tips: 'This is needed only if the user is authenticated via certificate.', + tips: 'This is only needed if the user is authenticated via certificate.', }, { label: 'Private Key', @@ -110,7 +110,22 @@ export const clickhouseSetting: PeerSetting[] = [ }, type: 'file', optional: true, - tips: 'This is needed only if the user is authenticated via certificate.', + tips: 'This is only needed if the user is authenticated via certificate.', + }, + { + label: 'Root Certificate', + stateHandler: (value, setter) => { + if (!value) { + // remove key from state if empty + setter((curr) => { + delete (curr as ClickhouseConfig)['rootCa']; + return curr; + }); + } else setter((curr) => ({ ...curr, rootCa: value as string })); + }, + type: 'file', + optional: true, + tips: 'If not provided, host CA roots will be used.', }, ]; diff --git a/ui/app/peers/create/[peerType]/schema.ts b/ui/app/peers/create/[peerType]/schema.ts index 5b4494ad15..5756c26029 100644 --- a/ui/app/peers/create/[peerType]/schema.ts +++ b/ui/app/peers/create/[peerType]/schema.ts @@ -309,6 +309,12 @@ export const chSchema = (hostDomains: string[]) => }) .optional() .transform((e) => (e === '' ? undefined : e)), + rootCa: z + .string({ + invalid_type_error: 'Root CA must be a string', + }) + .optional() + .transform((e) => (e === '' ? undefined : e)), }); export const kaSchema = z.object({