Skip to main content

mz_sql_server_util/
config.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use anyhow::Context;
11use std::net::SocketAddr;
12
13use mz_ore::future::InTask;
14use mz_repr::CatalogItemId;
15use mz_ssh_util::tunnel::{SshTimeoutConfig, SshTunnelConfig};
16use mz_ssh_util::tunnel_manager::SshTunnelManager;
17#[cfg(any(test, feature = "proptest"))]
18use proptest_derive::Arbitrary;
19use serde::{Deserialize, Serialize};
20
21/// Materialize specific configuration for SQL Server connections.
22///
23/// This wraps a [`tiberius::Config`] so we can configure a tunnel over SSH, AWS
24/// PrivateLink, or various other techniques, via a [`TunnelConfig`]
25#[derive(Clone, Debug)]
26pub struct Config {
27    /// SQL Server specific configuration.
28    pub(crate) inner: tiberius::Config,
29    /// Details of how we'll connect to the upstream SQL Server instance.
30    pub(crate) tunnel: TunnelConfig,
31    /// If all of the I/O for this connection will be done in a separate task.
32    ///
33    /// Note: This is used to prevent accidentally doing I/O in timely threads.
34    pub(crate) in_task: InTask,
35}
36
37impl Config {
38    pub fn new(inner: tiberius::Config, tunnel: TunnelConfig, in_task: InTask) -> Self {
39        Config {
40            inner,
41            tunnel,
42            in_task,
43        }
44    }
45
46    /// Create a new [`Config`] from an ActiveX Data Object.
47    ///
48    /// Generally this is only used in test environments, see [`Config::new`]
49    /// for regular/production use cases.
50    pub fn from_ado_string(s: &str) -> Result<Self, anyhow::Error> {
51        let inner = tiberius::Config::from_ado_string(s).context("tiberius config")?;
52        Ok(Config {
53            inner,
54            tunnel: TunnelConfig::Direct {
55                resolved_addresses: Default::default(),
56            },
57            in_task: InTask::No,
58        })
59    }
60}
61
62/// Configures an optional tunnel for use when connecting to a SQL Server database.
63///
64/// TODO(sql_server2): De-duplicate this with MySQL and Postgres sources.
65#[derive(Debug, Clone)]
66pub enum TunnelConfig {
67    /// Establish a direct TCP connection to the database host.
68    /// If `resolved_addresses` is non-empty, those addresses are used directly;
69    /// otherwise falls back to DNS lookup of the host.
70    Direct {
71        resolved_addresses: Box<[SocketAddr]>,
72    },
73    /// Establish a TCP connection to the database via an SSH tunnel.
74    Ssh {
75        /// Config for opening the SSH tunnel.
76        config: SshTunnelConfig,
77        /// Global manager of SSH tunnels.
78        manager: SshTunnelManager,
79        /// Timeout config for the SSH tunnel.
80        timeout: SshTimeoutConfig,
81        // TODO(sql_server3): Remove these fields by forking the `tiberius`
82        // crate and expose the `get_host` and `get_port` methods.
83        //
84        // See: <https://github.com/MaterializeInc/tiberius/blob/406ad2780d206617bd41689b1b638bddf4538f89/src/client/config.rs#L174-L191>
85        host: String,
86        port: u16,
87    },
88    /// Establish a TCP connection to the database via an AWS PrivateLink service.
89    AwsPrivatelink {
90        /// The ID of the AWS PrivateLink service.
91        connection_id: CatalogItemId,
92        port: u16,
93    },
94}
95
96/// Level of encryption to use with a SQL Server connection.
97///
98/// Mirror of [`tiberius::EncryptionLevel`] but we define our own so we can
99/// implement traits like [`Serialize`].
100#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
101#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
102pub enum EncryptionLevel {
103    /// Do not use encryption at all.
104    None,
105    /// Only use encryption for the login procedure.
106    Login,
107    /// Use encryption for everything, if possible.
108    Preferred,
109    /// Require encryption, failing if not possible.
110    Required,
111}
112
113impl From<tiberius::EncryptionLevel> for EncryptionLevel {
114    fn from(value: tiberius::EncryptionLevel) -> Self {
115        match value {
116            tiberius::EncryptionLevel::NotSupported => EncryptionLevel::None,
117            tiberius::EncryptionLevel::Off => EncryptionLevel::Login,
118            tiberius::EncryptionLevel::On => EncryptionLevel::Preferred,
119            tiberius::EncryptionLevel::Required => EncryptionLevel::Required,
120        }
121    }
122}
123
124impl From<EncryptionLevel> for tiberius::EncryptionLevel {
125    fn from(value: EncryptionLevel) -> Self {
126        match value {
127            EncryptionLevel::None => tiberius::EncryptionLevel::NotSupported,
128            EncryptionLevel::Login => tiberius::EncryptionLevel::Off,
129            EncryptionLevel::Preferred => tiberius::EncryptionLevel::On,
130            EncryptionLevel::Required => tiberius::EncryptionLevel::Required,
131        }
132    }
133}
134
135/// Policy that dictates validation of the SQL-SERVER certificate.
136#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
137#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
138pub enum CertificateValidationPolicy {
139    /// Don't validate the server's certificate; trust all certificates.
140    TrustAll,
141    /// Validate server's certificate using system certificates.
142    VerifySystem,
143    /// Validate server's certifiacte using provided CA certificate.
144    VerifyCA,
145}