Skip to content

Commit

Permalink
Replace vessel legacy with vessel-rs
Browse files Browse the repository at this point in the history
  • Loading branch information
tarkah committed Nov 18, 2024
1 parent a3d863c commit 26a7bc7
Show file tree
Hide file tree
Showing 26 changed files with 2,480 additions and 109 deletions.
1,710 changes: 1,639 additions & 71 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ members = [
edition = "2021"

[workspace.dependencies]
moss = { git = "https://github.com/serpent-os/tools.git" }
stone = { git = "https://github.com/serpent-os/tools.git" }

axum = "0.7.2"
base64 = "0.21"
bitflags = "2.4.1"
Expand All @@ -16,23 +19,26 @@ chrono = "0.4.30"
color-eyre = "0.6.2"
derive_more = "0.99.17"
futures = "0.3.30"
hex = "0.4.3"
http = "1.0"
http-serde = "2.0"
itertools = "0.12.0"
prost = "0.12.3"
rand = "0.8.5"
serde_json = "1.0"
sha2 = "0.10.8"
thiserror = "1.0.56"
tokio-stream = "0.1.14"
toml = "0.8.8"
tracing = "0.1.40"
url = "2.5.2"

clap = { version = "4.4", features = ["derive"] }
ed25519-dalek = { version = "2.1.0", features = ["rand_core", "pkcs8", "pem"] }
jsonwebtoken = { version = "9.2.0", default-features = false }
reqwest = { version = "0.12.9", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.7.3", features = ["sqlite", "chrono", "uuid", "runtime-tokio"] }
sqlx = { version = "=0.8.0", features = ["sqlite", "chrono", "uuid", "runtime-tokio"] }
strum = { version = "0.25", features = ["derive"] }
tokio = { version = "1.35.1", features = ["full"] }
tower = { version = "0.4.13", features = ["util"] }
Expand Down
12 changes: 8 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
EOT

FROM alpine:3.20 AS summit
VOLUME /state
VOLUME /app/state
VOLUME /app/config.toml
EXPOSE 5000
WORKDIR /app
COPY --from=rust-builder /summit .
CMD ["/app/summit", "0.0.0.0", "--port", "5000", "--root", "/state"]
CMD ["/app/summit", "0.0.0.0", "--port", "5000", "--root", "/app"]

FROM alpine:3.20 AS vessel
VOLUME /state
VOLUME /app/state
VOLUME /app/config.toml
EXPOSE 5001
WORKDIR /app
COPY --from=rust-builder /vessel .
CMD ["/app/vessel", "0.0.0.0", "--port", "5001", "--root", "/state"]
# TODO: Remove
COPY ./test/import/stone.index /app/state/public/volatile/x86_64/stone.index
CMD ["/app/vessel", "0.0.0.0", "--port", "5001", "--root", "/app"]
1 change: 1 addition & 0 deletions crates/service-types/src/api/v1.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod services;
pub mod summit;
pub mod vessel;
11 changes: 11 additions & 0 deletions crates/service-types/src/api/v1/summit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use serde::{Deserialize, Serialize};
use service_core::operation;

operation!(ImportSucceeded, POST, "summit/importSucceeded", ACCESS_TOKEN | SERVICE_ACCOUNT | NOT_EXPIRED, req: ImportBody);
operation!(ImportFailed, POST, "summit/importFailed", ACCESS_TOKEN | SERVICE_ACCOUNT | NOT_EXPIRED, req: ImportBody);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportBody {
#[serde(rename = "taskID")]
pub task_id: u64,
}
2 changes: 1 addition & 1 deletion crates/service-types/src/api/v1/vessel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ operation!(Build, POST, "vessel/build", ACCESS_TOKEN | SERVICE_ACCOUNT | NOT_EXP

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildRequestBody {
#[serde(alias = "taskID")]
#[serde(rename = "taskID")]
pub task_id: u64,
pub collectables: Vec<Collectable>,
}
4 changes: 4 additions & 0 deletions crates/service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ clap.workspace = true
derive_more.workspace = true
ed25519-dalek.workspace = true
futures.workspace = true
hex.workspace = true
http.workspace = true
http-serde.workspace = true
itertools.workspace = true
jsonwebtoken.workspace = true
moss.workspace = true
rand.workspace = true
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
sha2.workspace = true
sqlx.workspace = true
strum.workspace = true
thiserror.workspace = true
Expand All @@ -34,4 +37,5 @@ tower.workspace = true
tracing.workspace = true
tracing-futures.workspace = true
tracing-subscriber.workspace = true
url.workspace = true
uuid.workspace = true
10 changes: 8 additions & 2 deletions crates/service/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,16 @@ where
request = request.json(body);
}

let resp = CLIENT.execute(request.build()?).await?.error_for_status()?;
let resp = CLIENT.execute(request.build()?).await?;

if let Err(e) = resp.error_for_status_ref() {
let status = resp.status();
let body = resp.text().await?;
error!(response = body, %status, "Request error");
Err(e)
}
// Support empty body into ()
if any::TypeId::of::<O::ResponseBody>() == any::TypeId::of::<()>() {
else if any::TypeId::of::<O::ResponseBody>() == any::TypeId::of::<()>() {
Ok(serde_json::from_slice(b"null").expect("null is ()"))
} else {
resp.json::<O::ResponseBody>().await
Expand Down
1 change: 1 addition & 0 deletions crates/service/src/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ pub(crate) fn create_token(
purpose,
account_id: account,
account_type: account::Kind::Service,
admin: false,
});
let account_token = token.sign(&ourself.key_pair)?;

Expand Down
3 changes: 3 additions & 0 deletions crates/service/src/endpoint/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ async fn refresh_token(request: api::Request<api::v1::services::RefreshToken>, s
.token
.ok_or(Error::MissingRequestToken)?
.decoded
// Bearer token is provided, so make sure
// we return an access token
.with_purpose(token::Purpose::Authentication)
.refresh()
.sign(&state.issuer.key_pair)
.map_err(Error::SignToken)
Expand Down
1 change: 1 addition & 0 deletions crates/service/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Handle errors
use itertools::Itertools;

/// Format an error chain
Expand Down
3 changes: 2 additions & 1 deletion crates/service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ pub use self::server::{start, Server};
pub use self::state::State;
pub use self::token::Token;

mod error;
mod middleware;
mod sync;

Expand All @@ -24,6 +23,8 @@ pub mod config;
pub mod crypto;
pub mod database;
pub mod endpoint;
pub mod error;
pub mod request;
pub mod server;
pub mod signal;
pub mod state;
Expand Down
72 changes: 72 additions & 0 deletions crates/service/src/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//! Download local or remote files
use std::{io, path::Path};

use futures::StreamExt;
use sha2::{Digest, Sha256};
use thiserror::Error;
use tokio::{fs::File, io::AsyncWriteExt};
use url::Url;

/// Downloads the file at [`Url`] to destination [`Path`] and validates it matches
/// the provided sha256sum
pub async fn download_and_verify(url: Url, dest: impl AsRef<Path>, sha256sum: &str) -> Result<(), Error> {
let mut stream = moss::request::get(url).await?;

let mut file = File::create(dest).await.map_err(Error::CreateFile)?;
let mut hasher = Sha256::default();

while let Some(bytes) = stream.next().await {
let mut bytes = bytes?;

hasher.update(bytes.as_ref());

file.write_all_buf(&mut bytes).await.map_err(Error::Write)?;
}

file.flush().await.map_err(Error::Write)?;

let hash = hex::encode(hasher.finalize());

if hash != sha256sum {
return Err(Error::Sha256Mismatch {
expected: sha256sum.to_string(),
actual: hash,
});
}

Ok(())
}

/// Request error
#[derive(Debug, Error)]
pub enum Error {
/// Error fetching remote file
#[error("fetch")]
Fetch(#[source] reqwest::Error),
/// Error reading local file
#[error("read")]
Read(#[source] io::Error),
/// Error writing to file
#[error("write")]
Write(#[source] io::Error),
/// Error creating file
#[error("create file")]
CreateFile(#[source] io::Error),
/// Sha256 mismatch
#[error("invalid sha256, expected {expected} actual {actual}")]
Sha256Mismatch {
/// Expected hash
expected: String,
/// Actual hash
actual: String,
},
}

impl From<moss::request::Error> for Error {
fn from(error: moss::request::Error) -> Self {
match error {
moss::request::Error::Fetch(e) => Error::Fetch(e),
moss::request::Error::Read(e) => Error::Read(e),
}
}
}
25 changes: 22 additions & 3 deletions crates/service/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! Shared service state
use std::{io, path::Path};
use std::{
io,
path::{Path, PathBuf},
};

use thiserror::Error;
use tokio::fs;
Expand All @@ -16,6 +19,10 @@ use crate::{
/// Service state
#[derive(Debug, Clone)]
pub struct State {
/// State directory
pub dir: PathBuf,
/// Database directory
pub db_dir: PathBuf,
/// Service database
pub db: Database,
/// Key pair used by the service
Expand All @@ -30,8 +37,15 @@ impl State {
/// Load state from the provided path. If no keypair and/or database exist, they will be created.
#[tracing::instrument(name = "load_state", skip_all)]
pub async fn load(root: impl AsRef<Path>) -> Result<Self, Error> {
let db_path = root.as_ref().join("service.db");
let key_path = root.as_ref().join(".privkey");
let dir = root.as_ref().join("state");
let db_dir = dir.join("db");

if !db_dir.exists() {
fs::create_dir_all(&db_dir).await.map_err(Error::CreateDbDir)?;
}

let db_path = db_dir.join("service.db");
let key_path = dir.join(".privkey");

let db = Database::new(&db_path).await?;
debug!(path = ?db_path, "Database opened");
Expand All @@ -55,6 +69,8 @@ impl State {
};

Ok(Self {
dir,
db_dir,
db,
key_pair,
pending_sent: Default::default(),
Expand All @@ -65,6 +81,9 @@ impl State {
/// A state error
#[derive(Debug, Error)]
pub enum Error {
/// Error creating db directory
#[error("create db directory")]
CreateDbDir(#[source] io::Error),
/// Loading database failed
#[error("load database")]
LoadDatabase(#[from] database::Error),
Expand Down
17 changes: 17 additions & 0 deletions crates/service/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ impl Token {
..self.clone()
}
}

/// Change the purpose of this token
pub fn with_purpose(self, purpose: Purpose) -> Self {
Self {
header: self.header,
payload: Payload {
purpose,
..self.payload
},
}
}
}

/// A token that's been verified via [`Token::verify`]
Expand Down Expand Up @@ -181,6 +192,11 @@ pub struct Payload {
/// Account type of the holder
#[serde(rename = "act")]
pub account_type: account::Kind,
/// Is this an admin account?
///
/// This is needed by legacy infra since it
/// doesn't define admin as an [`account::Kind`]
pub admin: bool,
}

/// Purpose of the token
Expand Down Expand Up @@ -256,6 +272,7 @@ mod test {
purpose: Purpose::Authorization,
account_id: 0.into(),
account_type: account::Kind::Admin,
admin: true,
},
};

Expand Down
8 changes: 8 additions & 0 deletions crates/vessel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@ edition.workspace = true

[dependencies]
service = { path = "../service" }
service-types = { path = "../service-types" }

clap.workspace = true
color-eyre.workspace = true
futures.workspace = true
http.workspace = true
moss.workspace = true
serde.workspace = true
sqlx.workspace = true
stone.workspace = true
strum.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true
uuid.workspace = true
Loading

0 comments on commit 26a7bc7

Please sign in to comment.