Skip to content

Commit bb2e50d

Browse files
committed
feat: make deployer only answer its own project
1 parent 7471c08 commit bb2e50d

File tree

6 files changed

+61
-3
lines changed

6 files changed

+61
-3
lines changed

CONTRIBUTING.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ This prevents `gateway` from starting up. Now you can start deployer only using:
108108

109109
```bash
110110
provisioner_address=$(docker inspect --format '{{(index .NetworkSettings.Networks "shuttle_default").IPAddress}}' shuttle_prod_hello-world-rocket-app_run)
111-
cargo run -p shuttle-deployer -- --provisioner-address $provisioner_address --provisioner-port 8000 --proxy-fqdn local.rs --admin-secret test-key
111+
cargo run -p shuttle-deployer -- --provisioner-address $provisioner_address --provisioner-port 8000 --proxy-fqdn local.rs --admin-secret test-key --project <project_name>
112112
```
113113

114-
The `--admin-secret` can safely be changed to your api-key to make testing easier.
114+
The `--admin-secret` can safely be changed to your api-key to make testing easier. While `<project_name>` needs to match the name of the project that will be deployed to this deployer. This is the `Cargo.toml` or `Shuttle.toml` name for the project.
115115

116116
### Using Podman instead of Docker
117117
If you are using Podman over Docker, then expose a rootless socket of Podman using the following command:

deployer/src/args.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{net::SocketAddr, path::PathBuf};
22

33
use clap::Parser;
44
use fqdn::FQDN;
5-
use shuttle_common::Port;
5+
use shuttle_common::{project::ProjectName, Port};
66

77
/// Program to handle the deploys for a single project
88
/// Handling includes, building, testing, and running each service
@@ -33,6 +33,10 @@ pub struct Args {
3333
#[clap(long, default_value = "0.0.0.0:8000")]
3434
pub proxy_address: SocketAddr,
3535

36+
/// Project being served by this deployer
37+
#[clap(long)]
38+
pub project: ProjectName,
39+
3640
/// Secret that will be used to perform admin tasks on this deployer
3741
#[clap(long)]
3842
pub admin_secret: String,

deployer/src/handlers/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use axum::body::{Body, BoxBody};
44
use axum::extract::ws::{self, WebSocket};
55
use axum::extract::{Extension, Path, Query};
66
use axum::http::{Request, Response};
7+
use axum::middleware::from_extractor;
78
use axum::routing::{get, Router};
89
use axum::{extract::BodyStream, Json};
910
use bytes::BufMut;
@@ -13,6 +14,7 @@ use futures::StreamExt;
1314
use opentelemetry::global;
1415
use opentelemetry_http::HeaderExtractor;
1516
use shuttle_common::models::secret;
17+
use shuttle_common::project::ProjectName;
1618
use shuttle_common::LogItem;
1719
use tower_http::auth::RequireAuthorizationLayer;
1820
use tower_http::trace::TraceLayer;
@@ -28,11 +30,14 @@ use std::time::Duration;
2830

2931
pub use {self::error::Error, self::error::Result};
3032

33+
mod project;
34+
3135
pub fn make_router(
3236
persistence: Persistence,
3337
deployment_manager: DeploymentManager,
3438
proxy_fqdn: FQDN,
3539
admin_secret: String,
40+
project_name: ProjectName,
3641
) -> Router<Body> {
3742
Router::new()
3843
.route("/projects/:project_name/services", get(list_services))
@@ -78,6 +83,8 @@ pub fn make_router(
7883
},
7984
),
8085
)
86+
.route_layer(from_extractor::<project::ProjectNameGuard>())
87+
.layer(Extension(project_name))
8188
}
8289

8390
async fn list_services(

deployer/src/handlers/project.rs

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use std::collections::HashMap;
2+
3+
use async_trait::async_trait;
4+
use axum::extract::{FromRequest, Path, RequestParts};
5+
use hyper::StatusCode;
6+
use shuttle_common::project::ProjectName;
7+
8+
/// Gaurd to ensure request are for the project served by this deployer
9+
/// Note: this guard needs the `ProjectName` extension to be set
10+
pub struct ProjectNameGuard;
11+
12+
#[async_trait]
13+
impl<B> FromRequest<B> for ProjectNameGuard
14+
where
15+
B: Send,
16+
{
17+
type Rejection = StatusCode;
18+
19+
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
20+
// We expect some path parameters
21+
let Path(path): Path<HashMap<String, String>> = match req.extract().await {
22+
Ok(path) => path,
23+
Err(_) => return Err(StatusCode::NOT_FOUND),
24+
};
25+
26+
// All our routes have the `project_name` parameter
27+
let project_name = match path.get("project_name") {
28+
Some(project_name) => project_name,
29+
None => return Err(StatusCode::INTERNAL_SERVER_ERROR),
30+
};
31+
32+
// This extractor requires the ProjectName extension to be set
33+
let expected_project_name: &ProjectName = match req.extensions().get() {
34+
Some(expected) => expected,
35+
None => return Err(StatusCode::INTERNAL_SERVER_ERROR),
36+
};
37+
38+
if project_name == expected_project_name.as_str() {
39+
Ok(ProjectNameGuard)
40+
} else {
41+
Err(StatusCode::BAD_REQUEST)
42+
}
43+
}
44+
}

deployer/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub async fn start(
5252
deployment_manager,
5353
args.proxy_fqdn,
5454
args.admin_secret,
55+
args.project,
5556
);
5657
let make_service = router.into_make_service();
5758

gateway/src/project.rs

+2
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,8 @@ impl ProjectCreating {
392392
"Cmd": [
393393
"--admin-secret",
394394
initial_key,
395+
"--project",
396+
project_name,
395397
"--api-address",
396398
format!("0.0.0.0:{RUNTIME_API_PORT}"),
397399
"--provisioner-address",

0 commit comments

Comments
 (0)