Skip to content
This repository was archived by the owner on Mar 20, 2023. It is now read-only.

Commit 5186bf2

Browse files
feat: give starred users benefits
- Allow for the max main.wasm size to be set via the CLI. - Allow starred workload size/lifetime to be set via the CLI. - Increase defaults for workload size/lifetimes. - Determine if a user is logged in via server side templates instead of a cookie. - Use minutes instead of seconds for specifying the lifetime of workloads. Signed-off-by: Nicholas Farshidmehr <nicholas@profian.com>
1 parent f3d686d commit 5186bf2

File tree

8 files changed

+190
-20
lines changed

8 files changed

+190
-20
lines changed

Cargo.lock

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.nix

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,16 +218,19 @@ in
218218
axum = rustPackages."registry+https://github.com/rust-lang/crates.io-index".axum."0.5.11" { inherit profileName; };
219219
clap = rustPackages."registry+https://github.com/rust-lang/crates.io-index".clap."3.2.8" { inherit profileName; };
220220
enarx_config = rustPackages."registry+https://github.com/rust-lang/crates.io-index".enarx-config."0.6.1" { inherit profileName; };
221+
humansize = rustPackages."registry+https://github.com/rust-lang/crates.io-index".humansize."1.1.1" { inherit profileName; };
221222
num_cpus = rustPackages."registry+https://github.com/rust-lang/crates.io-index".num_cpus."1.13.1" { inherit profileName; };
222223
once_cell = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.13.0" { inherit profileName; };
223224
openidconnect = rustPackages."registry+https://github.com/rust-lang/crates.io-index".openidconnect."2.3.2" { inherit profileName; };
224225
serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.138" { inherit profileName; };
226+
serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.82" { inherit profileName; };
225227
tempfile = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tempfile."3.3.0" { inherit profileName; };
226228
tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.19.2" { inherit profileName; };
227229
toml = rustPackages."registry+https://github.com/rust-lang/crates.io-index".toml."0.5.9" { inherit profileName; };
228230
tower_http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tower-http."0.3.4" { inherit profileName; };
229231
tracing = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.35" { inherit profileName; };
230232
tracing_subscriber = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing-subscriber."0.3.14" { inherit profileName; };
233+
ureq = rustPackages."registry+https://github.com/rust-lang/crates.io-index".ureq."2.5.0" { inherit profileName; };
231234
uuid = rustPackages."registry+https://github.com/rust-lang/crates.io-index".uuid."1.1.2" { inherit profileName; };
232235
};
233236
});
@@ -697,6 +700,13 @@ in
697700
src = fetchCratesIo { inherit name version; sha256 = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"; };
698701
});
699702

703+
"registry+https://github.com/rust-lang/crates.io-index".humansize."1.1.1" = overridableMkRustCrate (profileName: rec {
704+
name = "humansize";
705+
version = "1.1.1";
706+
registry = "registry+https://github.com/rust-lang/crates.io-index";
707+
src = fetchCratesIo { inherit name version; sha256 = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"; };
708+
});
709+
700710
"registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.19" = overridableMkRustCrate (profileName: rec {
701711
name = "hyper";
702712
version = "0.14.19";
@@ -1051,7 +1061,7 @@ in
10511061
serde_path_to_error = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_path_to_error."0.1.7" { inherit profileName; };
10521062
sha2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sha2."0.10.2" { inherit profileName; };
10531063
thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.31" { inherit profileName; };
1054-
ureq = rustPackages."registry+https://github.com/rust-lang/crates.io-index".ureq."2.4.0" { inherit profileName; };
1064+
ureq = rustPackages."registry+https://github.com/rust-lang/crates.io-index".ureq."2.5.0" { inherit profileName; };
10551065
url = rustPackages."registry+https://github.com/rust-lang/crates.io-index".url."2.2.2" { inherit profileName; };
10561066
};
10571067
});
@@ -1984,11 +1994,11 @@ in
19841994
src = fetchCratesIo { inherit name version; sha256 = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"; };
19851995
});
19861996

1987-
"registry+https://github.com/rust-lang/crates.io-index".ureq."2.4.0" = overridableMkRustCrate (profileName: rec {
1997+
"registry+https://github.com/rust-lang/crates.io-index".ureq."2.5.0" = overridableMkRustCrate (profileName: rec {
19881998
name = "ureq";
1989-
version = "2.4.0";
1999+
version = "2.5.0";
19902000
registry = "registry+https://github.com/rust-lang/crates.io-index";
1991-
src = fetchCratesIo { inherit name version; sha256 = "9399fa2f927a3d327187cbd201480cee55bee6ac5d3c77dd27f0c6814cff16d5"; };
2001+
src = fetchCratesIo { inherit name version; sha256 = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f"; };
19922002
features = builtins.concatLists [
19932003
[ "default" ]
19942004
[ "flate2" ]

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ uuid = { version = "*", features = ["v4"] }
1414
once_cell = "1.12.0"
1515
tempfile = "3.3.0"
1616
anyhow = { version = "1.0.57", default-features = false, features = ["std"] }
17+
ureq = { version = "2.5.0", default-features = false }
1718
clap = { version = "3.2.3", default-features = false, features = ["derive", "std"] }
1819
openidconnect = { version = "2.3.1", default-features = false, features = ["ureq"] }
1920
toml = { version = "0.5.9", default-features = false }
2021
num_cpus = "1.13.1"
2122
serde = { version = "1.0.136", default-features = false }
22-
# TODO: use enarx-config when a new version is published
23+
serde_json = { version = "1.0.82", default-features = false }
2324
enarx-config = { version = "0.6.1", default-features = false }
25+
humansize = { version = "1.1.1", default-features = false }
26+

src/auth.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
use std::ops::Deref;
55

6-
use crate::redirect;
6+
use crate::{github, redirect};
77

8+
use anyhow::{anyhow, bail};
89
use axum::extract::{Extension, FromRequest, Query, RequestParts};
910
use axum::headers;
1011
use axum::http::header::SET_COOKIE;
@@ -61,6 +62,22 @@ impl IntoResponse for ClaimsError {
6162
#[derive(Clone, Debug)]
6263
pub struct Claims(CoreUserInfoClaims);
6364

65+
impl Claims {
66+
// TODO: use auth0 for this instead of github directly: https://github.com/profianinc/benefice/issues/71
67+
pub fn has_starred(&self, repo_full_name: &str) -> anyhow::Result<bool> {
68+
let (user_type, user_id) = self
69+
.subject()
70+
.split_once('|')
71+
.ok_or_else(|| anyhow!("Failed to extract user id from OpenID subject"))?;
72+
73+
if user_type != "github" {
74+
bail!("Cannot get the stars of a non-github user");
75+
}
76+
77+
github::has_starred(user_id, repo_full_name)
78+
}
79+
}
80+
6481
impl Deref for Claims {
6582
type Target = CoreUserInfoClaims;
6683

src/github.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-FileCopyrightText: 2022 Profian Inc. <opensource@profian.com>
2+
// SPDX-License-Identifier: AGPL-3.0-only
3+
4+
use anyhow::{bail, Context};
5+
use serde::Deserialize;
6+
7+
#[derive(Debug, Deserialize)]
8+
struct Repo {
9+
pub full_name: String,
10+
}
11+
12+
pub fn has_starred(github_id: &str, repo_full_name: &str) -> anyhow::Result<bool> {
13+
let response = ureq::get(&format!("https://api.github.com/user/{github_id}/starred")).call()?;
14+
15+
if response.status() != 200 {
16+
bail!("Failed to get starred repos for user {github_id}");
17+
}
18+
19+
let body = response
20+
.into_string()
21+
.with_context(|| "Failed to get body from /starred request")?;
22+
let repos = serde_json::from_str::<Vec<Repo>>(&body)
23+
.with_context(|| "Failed to parse body from /starred request")?;
24+
25+
Ok(repos.iter().any(|repo| repo.full_name == *repo_full_name))
26+
}

src/main.rs

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
#![warn(clippy::all, rust_2018_idioms, unused_lifetimes)]
66

77
mod auth;
8+
mod github;
89
mod redirect;
910
mod templates;
1011

12+
use crate::auth::{Claims, ClaimsError};
1113
use crate::templates::{HtmlTemplate, RootGetTemplate, UuidGetTemplate};
1214

1315
use std::collections::HashMap;
@@ -18,7 +20,6 @@ use std::process::Stdio;
1820
use std::sync::Arc;
1921
use std::time::Duration;
2022

21-
use auth::{Claims, ClaimsError};
2223
use axum::extract::Multipart;
2324
use axum::extract::{Extension, Path};
2425
use axum::http::StatusCode;
@@ -28,6 +29,7 @@ use axum::{Router, Server};
2829

2930
use anyhow::{bail, Context as _};
3031
use clap::Parser;
32+
use humansize::{file_size_opts as options, FileSize};
3133
use once_cell::sync::Lazy;
3234
use openidconnect::core::{CoreClient, CoreProviderMetadata};
3335
use openidconnect::ureq::http_client;
@@ -39,12 +41,12 @@ use tokio::process::{Child, Command};
3941
use tokio::sync::{Mutex, RwLock};
4042
use tokio::time::{sleep, timeout};
4143
use tower_http::trace::TraceLayer;
42-
use tracing::error;
44+
use tracing::{debug, error};
4345
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
4446
use uuid::Uuid;
4547

48+
const ENARX_REPO: &str = "enarx/enarx";
4649
const READ_TIMEOUT: Duration = Duration::from_secs(5);
47-
const WASM_MAX: usize = 50 * 1024 * 1024; // 50 MiB
4850
const TOML_MAX: usize = 256 * 1024; // 256 KiB
4951

5052
#[allow(dead_code)]
@@ -81,10 +83,22 @@ struct Args {
8183
#[clap(long, default_value_t = num_cpus::get())]
8284
jobs: usize,
8385

84-
/// Job timeout (in seconds).
85-
#[clap(long, default_value_t = 15)]
86+
/// Max Wasm file size (in bytes, default is 50 MB).
87+
#[clap(long, default_value_t = 50 * 1024 * 1024)]
88+
workload_max: usize,
89+
90+
/// Max Wasm file size for starred users (in bytes, default is 50 MB).
91+
#[clap(long, default_value_t = 50 * 1024 * 1024)]
92+
workload_max_starred: usize,
93+
94+
/// Job timeout (in minutes).
95+
#[clap(long, default_value_t = 5)]
8696
timeout: u64,
8797

98+
/// Job timeout for starred users (in minutes).
99+
#[clap(long, default_value_t = 30)]
100+
timeout_starred: u64,
101+
88102
/// Command to execute, normally path to `enarx` binary.
89103
/// This command will be executed as: `<cmd> run --wasmcfgfile <path-to-config> <path-to-wasm>`
90104
#[clap(long, default_value = "enarx")]
@@ -109,7 +123,10 @@ async fn main() -> anyhow::Result<()> {
109123
addr,
110124
url,
111125
jobs,
126+
workload_max,
127+
workload_max_starred,
112128
timeout,
129+
timeout_starred,
113130
command,
114131
oidc_issuer,
115132
oidc_client,
@@ -185,7 +202,23 @@ async fn main() -> anyhow::Result<()> {
185202
.route("/:uuid/err", post(uuid_err_post))
186203
.route(
187204
"/",
188-
get(root_get).post(move |claims, mp| root_post(claims, mp, command, timeout, jobs)),
205+
get(move |claims| {
206+
root_get(
207+
claims,
208+
(workload_max, workload_max_starred),
209+
(timeout, timeout_starred),
210+
)
211+
})
212+
.post(move |claims, mp| {
213+
root_post(
214+
claims,
215+
mp,
216+
command,
217+
(workload_max, workload_max_starred),
218+
(timeout, timeout_starred),
219+
jobs,
220+
)
221+
}),
189222
)
190223
.layer(Extension(openid_client))
191224
.layer(TraceLayer::new_for_http());
@@ -194,8 +227,43 @@ async fn main() -> anyhow::Result<()> {
194227
Ok(())
195228
}
196229

197-
async fn root_get() -> impl IntoResponse {
230+
async fn root_get(
231+
claims: Option<Claims>,
232+
workload_max: (usize, usize),
233+
timeouts: (u64, u64),
234+
) -> impl IntoResponse {
235+
let logged_in = claims.is_some();
236+
let starred = claims
237+
.map(|claims| {
238+
claims.has_starred(ENARX_REPO).unwrap_or_else(|e| {
239+
let user = claims.subject().to_string();
240+
error!("Failed to get stars for user {user}: {e}");
241+
false
242+
})
243+
})
244+
.unwrap_or(false);
245+
198246
HtmlTemplate(RootGetTemplate {
247+
logged_in,
248+
starred,
249+
workload_upload_limits: (
250+
// NOTE: This will only fail if sized are negative: https://docs.rs/humansize/latest/humansize/trait.FileSize.html#errors
251+
workload_max
252+
.0
253+
.file_size(options::CONVENTIONAL)
254+
.unwrap_or_else(|e| {
255+
error!("Failed to get human readable size string: {e}");
256+
"?".to_string()
257+
}),
258+
workload_max
259+
.1
260+
.file_size(options::CONVENTIONAL)
261+
.unwrap_or_else(|e| {
262+
error!("Failed to get human readable size string: {e}");
263+
"?".to_string()
264+
}),
265+
),
266+
workload_timeouts: timeouts,
199267
enarx_toml_template: enarx_config::CONFIG_TEMPLATE,
200268
})
201269
}
@@ -205,11 +273,26 @@ async fn root_post(
205273
claims: Result<Claims, ClaimsError>,
206274
mut multipart: Multipart,
207275
command: String,
208-
timeout: u64,
276+
workload_max: (usize, usize),
277+
timeouts: (u64, u64),
209278
jobs: usize,
210279
) -> impl IntoResponse {
211280
let claims = claims.map_err(|e| e.redirect_response().into_response())?;
212281
let user = claims.subject().to_string();
282+
let (wasm_max, timeout) = match claims.has_starred(ENARX_REPO) {
283+
Err(e) => {
284+
error!("Failed to get stars for user {user}: {e}");
285+
return Err(redirect::internal_error().into_response());
286+
}
287+
Ok(false) => {
288+
debug!("{user} is NOT a starred user");
289+
(workload_max.0, timeouts.0)
290+
}
291+
Ok(true) => {
292+
debug!("{user} IS a starred user");
293+
(workload_max.1, timeouts.1)
294+
}
295+
};
213296

214297
// Detect too many jobs early.
215298
{
@@ -257,7 +340,7 @@ async fn root_post(
257340
.map_err(|_| StatusCode::BAD_REQUEST.into_response())?
258341
{
259342
len += chunk.len();
260-
if len > WASM_MAX {
343+
if len > wasm_max {
261344
return Err(StatusCode::PAYLOAD_TOO_LARGE.into_response());
262345
}
263346

@@ -338,6 +421,8 @@ async fn root_post(
338421
);
339422

340423
tokio::spawn(async move {
424+
// Convert from minutes to seconds.
425+
let timeout = timeout * 60;
341426
sleep(Duration::from_secs(timeout)).await;
342427
OUT.write().await.remove(&uuid);
343428
});

src/templates.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ use axum::{
1010
#[derive(Template)]
1111
#[template(path = "root_get.html")]
1212
pub struct RootGetTemplate {
13+
pub logged_in: bool,
14+
pub starred: bool,
15+
pub workload_upload_limits: (String, String),
16+
pub workload_timeouts: (u64, u64),
1317
pub enarx_toml_template: &'static str,
1418
}
1519

0 commit comments

Comments
 (0)