Skip to content

Commit

Permalink
feat(gateway): allow multiple hostnames, proxy caching (#1616)
Browse files Browse the repository at this point in the history
* wip: try to fix forwarded header

* wip: rewrite proxy and bouncer as axum routers

* refactor: clean up all unused code

* fix: handle index route as well

* feat(gateway): cache proxy project lookup

* fix: cache only the ip addr

* fix: use existing ttl cache implementation

* clippy

* feat(gateway): cache custom domain lookups

* fix: no longer need Clone
  • Loading branch information
jonaro00 committed Feb 13, 2024
1 parent 031f3e1 commit 2112f24
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 307 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ once_cell = "1.16.0"
opentelemetry = "0.21.0"
opentelemetry_sdk = { version = "0.21.0", features = ["rt-tokio", "logs"] }
opentelemetry-http = "0.10.0"
opentelemetry-otlp = { version = "0.14.0", features = ["logs", "grpc-tonic"] }
opentelemetry-otlp = { version = "0.14.0", features = ["logs", "grpc-tonic"] }
opentelemetry-proto = "0.4.0"
opentelemetry-contrib = { version = "0.4.0", features = ["datadog"] }
opentelemetry-appender-tracing = "0.2.0"
Expand Down Expand Up @@ -104,7 +104,7 @@ tracing-core = { version = "0.1.32", default-features = false }
tracing-opentelemetry = "0.22.0"
tracing-subscriber = { version = "0.3.16", default-features = false, features = [
"registry",
"json"
"json",
] }
ttl_cache = "0.5.1"
ulid = "1.0.0"
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ services:
- "--stripe-secret-key=${STRIPE_SECRET_KEY}"
- "--jwt-signing-private-key=${AUTH_JWTSIGNING_PRIVATE_KEY}"
healthcheck:
test: curl --fail http://localhost:8000/ || exit 1
test: curl -f -s http://localhost:8000
interval: 1m
timeout: 10s
retries: 5
Expand Down Expand Up @@ -150,7 +150,7 @@ services:
- "--use-tls=${USE_TLS}"
- "--admin-key=${GATEWAY_ADMIN_KEY}"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8001"]
test: curl -f -s http://localhost:8001
interval: 1m
timeout: 15s
retries: 15
Expand Down
106 changes: 1 addition & 105 deletions gateway/src/acme.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
use std::collections::HashMap;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;

use axum::body::boxed;
use axum::response::Response;
use fqdn::FQDN;
use futures::future::BoxFuture;
use hyper::server::conn::AddrStream;
use hyper::{Body, Request};
use instant_acme::{
Account, AccountCredentials, Authorization, AuthorizationStatus, Challenge, ChallengeType,
Identifier, KeyAuthorization, LetsEncrypt, NewAccount, NewOrder, Order, OrderStatus,
Expand All @@ -17,12 +11,8 @@ use rcgen::{Certificate, CertificateParams, DistinguishedName};
use shuttle_common::models::project::ProjectName;
use tokio::sync::Mutex;
use tokio::time::sleep;
use tower::{Layer, Service};
use tracing::{error, trace, warn};

use crate::proxy::AsResponderTo;
use crate::Error;

const MAX_RETRIES: usize = 15;
const MAX_RETRIES_CERTIFICATE_FETCHING: usize = 5;

Expand All @@ -49,7 +39,7 @@ impl AcmeClient {
self.0.lock().await.insert(token, key);
}

async fn get_http01_challenge_authorization(&self, token: &str) -> Option<String> {
pub async fn get_http01_challenge_authorization(&self, token: &str) -> Option<String> {
self.0
.lock()
.await
Expand Down Expand Up @@ -328,97 +318,3 @@ pub enum AcmeClientError {
}

impl std::error::Error for AcmeClientError {}

pub struct ChallengeResponderLayer {
client: AcmeClient,
}

impl ChallengeResponderLayer {
pub fn new(client: AcmeClient) -> Self {
Self { client }
}
}

impl<S> Layer<S> for ChallengeResponderLayer {
type Service = ChallengeResponder<S>;

fn layer(&self, inner: S) -> Self::Service {
ChallengeResponder {
client: self.client.clone(),
inner,
}
}
}

pub struct ChallengeResponder<S> {
client: AcmeClient,
inner: S,
}

impl<'r, S> AsResponderTo<&'r AddrStream> for ChallengeResponder<S>
where
S: AsResponderTo<&'r AddrStream>,
{
fn as_responder_to(&self, req: &'r AddrStream) -> Self {
Self {
client: self.client.clone(),
inner: self.inner.as_responder_to(req),
}
}
}

impl<ReqBody, S> Service<Request<ReqBody>> for ChallengeResponder<S>
where
S: Service<Request<ReqBody>, Response = Response, Error = Error> + Send + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;

fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}

fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
if !req.uri().path().starts_with("/.well-known/acme-challenge/") {
let future = self.inner.call(req);
return Box::pin(async move {
let response: Response = future.await?;
Ok(response)
});
}

let token = match req
.uri()
.path()
.strip_prefix("/.well-known/acme-challenge/")
{
Some(token) => token.to_string(),
None => {
return Box::pin(async {
Ok(Response::builder()
.status(404)
.body(boxed(Body::empty()))
.unwrap())
})
}
};

trace!(token, "responding to certificate challenge");

let client = self.client.clone();

Box::pin(async move {
let (status, body) = match client.get_http01_challenge_authorization(&token).await {
Some(key) => (200, Body::from(key)),
None => (404, Body::empty()),
};

Ok(Response::builder()
.status(status)
.body(boxed(body))
.unwrap())
})
}
}
Loading

0 comments on commit 2112f24

Please sign in to comment.