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 Apach
910//! Console Impersonation HTTP endpoint.
1112use std::sync::Arc;
1314use axum::Extension;
15use axum::body::Body;
16use axum::http::{Request, StatusCode};
17use axum::response::{IntoResponse, Response};
18use http::HeaderValue;
19use http::header::HOST;
20use hyper::Uri;
21use hyper_tls::HttpsConnector;
22use hyper_util::client::legacy::Client;
23use hyper_util::client::legacy::connect::HttpConnector;
24use hyper_util::rt::TokioExecutor;
2526pub(crate) struct ConsoleProxyConfig {
27/// Hyper http client, supports https.
28client: Client<HttpsConnector<HttpConnector>, Body>,
2930/// URL of upstream console to proxy to (e.g. <https://console.materialize.com>).
31url: String,
3233/// Route this is being served from (e.g. /internal-console).
34route_prefix: String,
35}
3637impl ConsoleProxyConfig {
38pub(crate) fn new(proxy_url: Option<String>, route_prefix: String) -> Self {
39let mut url = proxy_url.unwrap_or_else(|| "https://console.materialize.com".to_string());
40if let Some(new) = url.strip_suffix('/') {
41 url = new.to_string();
42 }
43Self {
44 client: Client::builder(TokioExecutor::new()).build(HttpsConnector::new()),
45 url,
46 route_prefix,
47 }
48 }
49}
5051/// The User Impersonation feature uses a Teleport proxy in front of the
52/// Internal HTTP Server, however Teleport has issues with CORS that prevent
53/// making requests to that Teleport-proxied app from our production console URLs.
54/// To avoid CORS and serve the Console from the same host as the Teleport app,
55/// this route proxies the upstream Console to handle requests for
56/// HTML, JS, and CSS static files.
57pub(crate) async fn handle_internal_console(
58 console_config: Extension<Arc<ConsoleProxyConfig>>,
59mut req: Request<Body>,
60) -> Result<Response, StatusCode> {
61let path = req.uri().path();
62let mut path_query = req
63 .uri()
64 .path_and_query()
65 .map(|v| v.as_str())
66 .unwrap_or(path);
67if let Some(stripped_path_query) = path_query.strip_prefix(&console_config.route_prefix) {
68 path_query = stripped_path_query;
69 }
7071let uri = Uri::try_from(format!("{}{}", &console_config.url, path_query)).unwrap();
72let host = uri.host().unwrap().to_string();
73// Preserve the request, but update the URI to point upstream.
74*req.uri_mut() = uri;
7576// If vercel sees the request being served from a different host it tries to redirect to it's own.
77req.headers_mut()
78 .insert(HOST, HeaderValue::from_str(&host).unwrap());
7980// Call this request against the upstream, return response directly.
81Ok(console_config
82 .client
83 .request(req)
84 .await
85.map_err(|err| {
86tracing::warn!("Error retrieving console url: {}", err);
87 StatusCode::BAD_REQUEST
88 })?
89.into_response())
90}