ngrok/config/
common.rs

1use std::{
2    collections::HashMap,
3    env,
4    process,
5};
6
7use async_trait::async_trait;
8use once_cell::sync::OnceCell;
9use url::Url;
10
11pub use crate::internals::proto::ProxyProto;
12use crate::{
13    config::policies::Policy,
14    forwarder::Forwarder,
15    internals::proto::{
16        BindExtra,
17        BindOpts,
18        IpRestriction,
19        MutualTls,
20    },
21    session::RpcError,
22    Session,
23    Tunnel,
24};
25
26pub(crate) fn default_forwards_to() -> &'static str {
27    static FORWARDS_TO: OnceCell<String> = OnceCell::new();
28
29    FORWARDS_TO
30        .get_or_init(|| {
31            let hostname = hostname::get()
32                .unwrap_or("<unknown>".into())
33                .to_string_lossy()
34                .into_owned();
35            let exe = env::current_exe()
36                .unwrap_or("<unknown>".into())
37                .to_string_lossy()
38                .into_owned();
39            let pid = process::id();
40            format!("app://{hostname}/{exe}?pid={pid}")
41        })
42        .as_str()
43}
44
45/// Trait representing things that can be built into an ngrok tunnel.
46#[async_trait]
47pub trait TunnelBuilder: From<Session> {
48    /// The ngrok tunnel type that this builder produces.
49    type Tunnel: Tunnel;
50
51    /// Begin listening for new connections on this tunnel.
52    async fn listen(&self) -> Result<Self::Tunnel, RpcError>;
53}
54
55/// Trait representing things that can be built into an ngrok tunnel and then
56/// forwarded to a provided URL.
57#[async_trait]
58pub trait ForwarderBuilder: TunnelBuilder {
59    /// Start listening for new connections on this tunnel and forward all
60    /// connections to the provided URL.
61    ///
62    /// This will also set the `forwards_to` metadata for the tunnel.
63    async fn listen_and_forward(&self, to_url: Url) -> Result<Forwarder<Self::Tunnel>, RpcError>;
64}
65
66macro_rules! impl_builder {
67    ($(#[$m:meta])* $name:ident, $opts:ty, $tun:ident, $edgepoint:tt) => {
68        $(#[$m])*
69        #[derive(Clone)]
70        pub struct $name {
71            options: $opts,
72            // Note: This is only optional for testing purposes.
73            session: Option<Session>,
74        }
75
76        mod __builder_impl {
77            use $crate::forwarder::Forwarder;
78            use $crate::config::common::ForwarderBuilder;
79            use $crate::config::common::TunnelBuilder;
80            use $crate::session::RpcError;
81            use async_trait::async_trait;
82            use url::Url;
83
84            use super::*;
85
86            impl From<Session> for $name {
87                fn from(session: Session) -> Self {
88                    $name {
89                        options: Default::default(),
90                        session: session.into(),
91                    }
92                }
93            }
94
95            #[async_trait]
96            impl TunnelBuilder for $name {
97                type Tunnel = $tun;
98
99                async fn listen(&self) -> Result<$tun, RpcError> {
100                    Ok($tun {
101                        inner: self
102                            .session
103                            .as_ref()
104                            .unwrap()
105                            .start_tunnel(&self.options)
106                            .await?,
107                    })
108                }
109            }
110
111            #[async_trait]
112            impl ForwarderBuilder for $name {
113                async fn listen_and_forward(&self, to_url: Url) -> Result<Forwarder<$tun>, RpcError> {
114                    let mut cfg = self.clone();
115                    cfg.for_forwarding_to(&to_url).await;
116                    let tunnel = cfg.listen().await?;
117                    let info = tunnel.make_info();
118                    $crate::forwarder::forward(tunnel, info, to_url)
119                }
120            }
121        }
122    };
123}
124
125/// Tunnel configuration trait, implemented by our top-level config objects.
126pub(crate) trait TunnelConfig {
127    /// The "forwards to" metadata.
128    ///
129    /// Only for display/informational purposes.
130    fn forwards_to(&self) -> String;
131    /// The L7 protocol the upstream service expects
132    fn forwards_proto(&self) -> String;
133    /// Whether to disable certificate verification for this tunnel.
134    fn verify_upstream_tls(&self) -> bool;
135    /// Internal-only, extra data sent when binding a tunnel.
136    fn extra(&self) -> BindExtra;
137    /// The protocol for this tunnel.
138    fn proto(&self) -> String;
139    /// The middleware and other configuration options for this tunnel.
140    fn opts(&self) -> Option<BindOpts>;
141    /// The labels for this tunnel.
142    fn labels(&self) -> HashMap<String, String>;
143}
144
145// delegate references
146impl<T> TunnelConfig for &T
147where
148    T: TunnelConfig,
149{
150    fn forwards_to(&self) -> String {
151        (**self).forwards_to()
152    }
153
154    fn forwards_proto(&self) -> String {
155        (**self).forwards_proto()
156    }
157    fn verify_upstream_tls(&self) -> bool {
158        (**self).verify_upstream_tls()
159    }
160    fn extra(&self) -> BindExtra {
161        (**self).extra()
162    }
163    fn proto(&self) -> String {
164        (**self).proto()
165    }
166    fn opts(&self) -> Option<BindOpts> {
167        (**self).opts()
168    }
169    fn labels(&self) -> HashMap<String, String> {
170        (**self).labels()
171    }
172}
173
174/// Restrictions placed on the origin of incoming connections to the edge.
175#[derive(Clone, Default)]
176pub(crate) struct CidrRestrictions {
177    /// Rejects connections that do not match the given CIDRs
178    pub(crate) allowed: Vec<String>,
179    /// Rejects connections that match the given CIDRs and allows all other CIDRs.
180    pub(crate) denied: Vec<String>,
181}
182
183impl CidrRestrictions {
184    pub(crate) fn allow(&mut self, cidr: impl Into<String>) {
185        self.allowed.push(cidr.into());
186    }
187    pub(crate) fn deny(&mut self, cidr: impl Into<String>) {
188        self.denied.push(cidr.into());
189    }
190}
191
192// Common
193#[derive(Default, Clone)]
194pub(crate) struct CommonOpts {
195    // Restrictions placed on the origin of incoming connections to the edge.
196    pub(crate) cidr_restrictions: CidrRestrictions,
197    // The version of PROXY protocol to use with this tunnel, zero if not
198    // using.
199    pub(crate) proxy_proto: ProxyProto,
200    // Tunnel-specific opaque metadata. Viewable via the API.
201    pub(crate) metadata: Option<String>,
202    // Tunnel backend metadata. Viewable via the dashboard and API, but has no
203    // bearing on tunnel behavior.
204    pub(crate) forwards_to: Option<String>,
205    // Tunnel L7 app protocol
206    pub(crate) forwards_proto: Option<String>,
207    // Whether to disable certificate verification for this tunnel.
208    verify_upstream_tls: Option<bool>,
209    // DEPRECATED: use traffic_policy instead.
210    pub(crate) policy: Option<Policy>,
211    // Policy that defines rules that should be applied to incoming or outgoing
212    // connections to the edge.
213    pub(crate) traffic_policy: Option<String>,
214    // Allows the endpoint to pool with other endpoints with the same host/port/binding
215    pub(crate) pooling_enabled: Option<bool>,
216}
217
218impl CommonOpts {
219    // Get the proto version of cidr restrictions
220    pub(crate) fn ip_restriction(&self) -> Option<IpRestriction> {
221        (!self.cidr_restrictions.allowed.is_empty() || !self.cidr_restrictions.denied.is_empty())
222            .then_some(self.cidr_restrictions.clone().into())
223    }
224
225    pub(crate) fn for_forwarding_to(&mut self, to_url: &Url) -> &mut Self {
226        self.forwards_to = Some(to_url.as_str().into());
227        self
228    }
229
230    pub(crate) fn set_verify_upstream_tls(&mut self, verify_upstream_tls: bool) {
231        self.verify_upstream_tls = Some(verify_upstream_tls)
232    }
233
234    pub(crate) fn verify_upstream_tls(&self) -> bool {
235        self.verify_upstream_tls.unwrap_or(true)
236    }
237}
238
239// transform into the wire protocol format
240impl From<CidrRestrictions> for IpRestriction {
241    fn from(cr: CidrRestrictions) -> Self {
242        IpRestriction {
243            allow_cidrs: cr.allowed,
244            deny_cidrs: cr.denied,
245        }
246    }
247}
248
249impl From<&[bytes::Bytes]> for MutualTls {
250    fn from(b: &[bytes::Bytes]) -> Self {
251        let mut aggregated = Vec::new();
252        b.iter().for_each(|c| aggregated.extend(c));
253        MutualTls {
254            mutual_tls_ca: aggregated,
255        }
256    }
257}