mirror of
https://github.com/ivabus/binhost
synced 2024-11-25 01:05:06 +03:00
0.3.0: Full authenticity check
Signed-off-by: Ivan Bushchik <ivabus@ivabus.dev>
This commit is contained in:
parent
6cc6e28c40
commit
0c6e251d1b
8 changed files with 391 additions and 125 deletions
53
API.md
Normal file
53
API.md
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
### Get list of binaries
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
```http request
|
||||||
|
GET / HTTP/1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example response
|
||||||
|
|
||||||
|
```
|
||||||
|
- hello (platforms: ["Linux-aarch64", "Darwin-arm64", "Darwin-x86_64"])
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get script for specific binary (suitable for `curl | sh` syntax)
|
||||||
|
|
||||||
|
This script will determine platform and arch and download necessary binary (and check hashsum if `sha256sum` binary is present in `$PATH`).
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
```http request
|
||||||
|
GET /<BIN> HTTP/1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get binary for specific platform
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
```http request
|
||||||
|
GET /bin/<BIN>/<PLATFORM>/<ARCH> HTTP/1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get ed25519 signature
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
```http request
|
||||||
|
GET /bin/<BIN>/<PLATFORM>/<ARCH>/sign HTTP/1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get manifest
|
||||||
|
|
||||||
|
Manifest is a file, that contains ED25519 public key and SHA256sums list
|
||||||
|
|
||||||
|
```http request
|
||||||
|
GET /runner/manifest HTTP/1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get binary runner
|
||||||
|
|
||||||
|
```http request
|
||||||
|
GET /runner/<runner> HTTP/1.1
|
||||||
|
```
|
25
Cargo.toml
25
Cargo.toml
|
@ -1,17 +1,28 @@
|
||||||
|
workspace = { members = [ "runner" ] }
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "binhost"
|
name = "binhost"
|
||||||
version = "0.2.2"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/ivabus/binhost"
|
repository = "https://github.com/ivabus/binhost"
|
||||||
description = "HTTP server to easily serve files"
|
description = "HTTP server to easily serve files"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.4.11", features = ["derive"] }
|
clap = { version = "4.4.18", features = ["derive"] }
|
||||||
rocket = "0.5.0"
|
rocket = "0.5.0"
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
sha2 = { version = "0.10.8", optional = true }
|
sha2 = { version = "0.10"}
|
||||||
|
ed25519-compact = { version = "2.0.6", default-features = false, features = [ "random", "self-verify", "std"] }
|
||||||
|
|
||||||
[features]
|
# Reducing size as much as possible
|
||||||
default = [ "sha256" ]
|
[profile.release]
|
||||||
sha256 = [ "dep:sha2" ]
|
strip = true
|
||||||
|
opt-level = "s"
|
||||||
|
lto = true
|
||||||
|
panic = "abort"
|
||||||
|
codegen-units = 1
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
# Doesn't build on my machine without
|
||||||
|
opt-level = 1
|
||||||
|
|
70
README.md
70
README.md
|
@ -1,6 +1,6 @@
|
||||||
# BinHost
|
# BinHost
|
||||||
|
|
||||||
> HTTP server to easily serve (prebuilt) binaries for any (UNIX-like) platform
|
> HTTP server to easily serve (prebuilt) binaries for any (UNIX-like) platform with authenticity check
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -33,55 +33,51 @@ bin
|
||||||
└── hello
|
└── hello
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Runners
|
||||||
|
|
||||||
|
Runner is a (necessary) subprogram, that checks ED25519 signature of a binary file and needs to be statically compiled for every platform, that could use binaries from `binhost` server.
|
||||||
|
|
||||||
|
Directory, passed to `binhost` `--runners-dir` option (defaults to `./runners`) should look like (for `Linux-x86_64`, `Linux-aarch64` and `Darwin-arm64` compiled runners)
|
||||||
|
|
||||||
|
```tree
|
||||||
|
runners
|
||||||
|
├── runner-Darwin-arm64
|
||||||
|
├── runner-Linux-aarch64
|
||||||
|
└── runner-Linux-x86_64
|
||||||
|
```
|
||||||
|
|
||||||
## Client usage
|
## Client usage
|
||||||
|
|
||||||
### Get available binaries
|
### Execute specific binary <bin> with manifest validity check
|
||||||
|
|
||||||
#### Request
|
Manifest validity check provides a fully-secured binary distribution chain.
|
||||||
|
|
||||||
```http request
|
```shell
|
||||||
GET / HTTP/1.1
|
curl ADDRESS:PORT/<bin> | KEY=... bash
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Example response
|
`KEY` first few symbols from hex representation of SHA256 sum of manifest (printed to stdout on `binhost` startup).
|
||||||
|
|
||||||
```
|
Only this option should be considered as secure.
|
||||||
- hello (platforms: ["Linux-aarch64", "Darwin-arm64", "Darwin-x86_64"])
|
|
||||||
|
### Execute specific binary <bin> without validity check
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl ADDRESS:PORT/<bin> | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get script for specific binary (suitable for `curl | sh` syntax)
|
### Download and reuse script
|
||||||
|
|
||||||
This script will determine platform and arch and download necessary binary (and check hashsum if `sha256sum` binary is present in `$PATH`).
|
```shell
|
||||||
|
curl ADDRESS:PORT/<bin> -o script.sh
|
||||||
#### Request
|
./script.sh # Execute preloaded bin configuration
|
||||||
|
BIN=<newbin> ./script.sh # Execute newbin (download)
|
||||||
```http request
|
BIN=<newbin> EXTERNAL_ADDRESS=<newaddress> ./script.sh # Execute newbin from newaddress
|
||||||
GET /<BIN> HTTP/1.1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get binary for specific platform
|
### API
|
||||||
|
|
||||||
#### Request
|
See full HTTP API in [API.md](./API.md)
|
||||||
|
|
||||||
```http request
|
|
||||||
GET /bin/<BIN>/<PLATFORM>/<ARCH> HTTP/1.1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get sha256 hash of binary for specific platform
|
|
||||||
|
|
||||||
Only with "sha256" feature (recalculates hash on each request, may be bad on large files or lots of requests)
|
|
||||||
|
|
||||||
#### Request
|
|
||||||
|
|
||||||
```http request
|
|
||||||
GET /bin/<BIN>/<PLATFORM>/<ARCH>/sha256 HTTP/1.1
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Example response
|
|
||||||
|
|
||||||
```text
|
|
||||||
a5d1fba1c28b60038fb1008a3c482b4119070a537af86a05046dedbe8f85e18d hello
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
11
runner/Cargo.toml
Normal file
11
runner/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "runner"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libc = { version = "0.2.152", default-features = false }
|
||||||
|
ed25519-compact = { version = "2.0.6", default-features = false }
|
||||||
|
numtoa = "0.2.4"
|
82
runner/src/main.rs
Normal file
82
runner/src/main.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// "Runner" is written in no_std Rust for the smaller executable size: ~49KiB (Darwin arm64) vs ~300KiB
|
||||||
|
|
||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use core::slice::from_raw_parts;
|
||||||
|
use ed25519_compact::{PublicKey, Signature};
|
||||||
|
use libc::{c_char, c_int, c_void, exit, open, printf, read, O_RDONLY};
|
||||||
|
|
||||||
|
// 1 KiB seems fine
|
||||||
|
const BUFFER_SIZE: usize = 1024;
|
||||||
|
const PUBKEY_LEN: usize = PublicKey::BYTES;
|
||||||
|
const SIGNATURE_LEN: usize = Signature::BYTES;
|
||||||
|
|
||||||
|
#[allow(clippy::missing_safety_doc)]
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn main(_argc: i32, _argv: *const *const c_char) -> i32 {
|
||||||
|
printf("Starting runner\n\0".as_bytes().as_ptr() as *const c_char);
|
||||||
|
|
||||||
|
let mut buff_public_key = [0_u8; PUBKEY_LEN];
|
||||||
|
read(
|
||||||
|
open("public_key\0".as_bytes().as_ptr() as *const c_char, O_RDONLY),
|
||||||
|
buff_public_key.as_mut_ptr() as *mut c_void,
|
||||||
|
PUBKEY_LEN,
|
||||||
|
);
|
||||||
|
let public_key = PublicKey::new(buff_public_key);
|
||||||
|
|
||||||
|
let mut signature = [0_u8; SIGNATURE_LEN];
|
||||||
|
read(
|
||||||
|
open("signature\0".as_bytes().as_ptr() as *const c_char, O_RDONLY),
|
||||||
|
signature.as_mut_ptr() as *mut c_void,
|
||||||
|
SIGNATURE_LEN,
|
||||||
|
);
|
||||||
|
|
||||||
|
let arg = from_raw_parts(_argv, _argc as usize)[1]; // Getting path to binary
|
||||||
|
let binary_fd = open(arg, O_RDONLY);
|
||||||
|
let mut buffer = [0u8; BUFFER_SIZE];
|
||||||
|
let mut state = public_key.verify_incremental(&Signature::new(signature)).unwrap();
|
||||||
|
loop {
|
||||||
|
let bytes_read: usize =
|
||||||
|
read(binary_fd, buffer.as_mut_ptr() as *mut c_void, BUFFER_SIZE) as usize;
|
||||||
|
state.absorb(&buffer[0..bytes_read]);
|
||||||
|
if bytes_read != BUFFER_SIZE {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Signature: \0".as_bytes().as_ptr() as *const c_char);
|
||||||
|
if state.verify().is_ok() {
|
||||||
|
printf("OK\n\0".as_bytes().as_ptr() as *const c_char);
|
||||||
|
} else {
|
||||||
|
printf("Bad\n\0".as_bytes().as_ptr() as *const c_char);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||||
|
use numtoa::NumToA;
|
||||||
|
let mut buff = [0u8; 8];
|
||||||
|
unsafe {
|
||||||
|
printf("Panicked\0".as_bytes().as_ptr() as *const c_char);
|
||||||
|
if let Some(location) = info.location() {
|
||||||
|
printf(" at \0".as_bytes().as_ptr() as *const c_char);
|
||||||
|
location.line().numtoa(10, &mut buff).iter().for_each(|ch| {
|
||||||
|
libc::putchar(*ch as c_int);
|
||||||
|
});
|
||||||
|
printf(":\0".as_bytes().as_ptr() as *const c_char);
|
||||||
|
location.column().numtoa(10, &mut buff).iter().for_each(|ch| {
|
||||||
|
libc::putchar(*ch as c_int);
|
||||||
|
});
|
||||||
|
printf(" in \0".as_bytes().as_ptr() as *const c_char);
|
||||||
|
location.file().chars().for_each(|ch| {
|
||||||
|
libc::putchar(ch as c_int);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
libc::putchar(b'\n' as c_int);
|
||||||
|
exit(3)
|
||||||
|
}
|
||||||
|
}
|
164
src/main.rs
164
src/main.rs
|
@ -1,45 +1,39 @@
|
||||||
mod structs;
|
// SPDX-License-Identifier: MIT
|
||||||
use structs::*;
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use rocket::figment::Figment;
|
|
||||||
use rocket::fs::NamedFile;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use ed25519_compact::{KeyPair, Noise};
|
||||||
|
use rocket::figment::Figment;
|
||||||
|
use rocket::fs::{FileServer, NamedFile};
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::response::content::RawText;
|
use rocket::response::content::RawText;
|
||||||
|
use sha2::digest::FixedOutput;
|
||||||
|
use sha2::Digest;
|
||||||
|
|
||||||
|
use structs::*;
|
||||||
|
|
||||||
|
mod structs;
|
||||||
|
|
||||||
static mut BINS: Option<(HashMap<String, Bin>, Instant)> = None;
|
static mut BINS: Option<(HashMap<String, Bin>, Instant)> = None;
|
||||||
|
static mut MANIFEST: Option<Vec<u8>> = None;
|
||||||
|
static mut KEYPAIR: Option<ed25519_compact::KeyPair> = None;
|
||||||
static WEB_SH: &str = include_str!("../web.sh");
|
static WEB_SH: &str = include_str!("../web.sh");
|
||||||
|
|
||||||
#[cfg(feature = "sha256")]
|
|
||||||
static HASH_CALCULATION_SH: &str = r#"
|
|
||||||
if ! which sha256sum > /dev/null; then
|
|
||||||
echo "No \`sha256sum\` command found, continuing without checking" 1>&2
|
|
||||||
else
|
|
||||||
echo ":: Checking hashsum" 1>&2
|
|
||||||
if ! ($DOWNLOAD_COMMAND {{EXTERNAL_ADDRESS}}/bin/$NAME/$(uname)/$(uname -m)/sha256 $OUTPUT_ARG - | sha256sum -c - > /dev/null); then
|
|
||||||
echo "sha256 is invalid" 1>&2
|
|
||||||
exit 255
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
"#;
|
|
||||||
#[cfg(not(feature = "sha256"))]
|
|
||||||
static HASH_CALCULATION_SH: &str = "";
|
static HASH_CALCULATION_SH: &str = "";
|
||||||
|
|
||||||
async fn reload_bins(bins: (&mut HashMap<String, Bin>, &mut Instant), args: &Args) {
|
fn reload_bins(bins: (&mut HashMap<String, Bin>, &mut Instant), args: &Args) {
|
||||||
if (Instant::now() - *bins.1).as_secs() > args.refresh {
|
if (Instant::now() - *bins.1).as_secs() > args.refresh {
|
||||||
*bins.0 = get_bins(args).await;
|
*bins.0 = get_bins(args);
|
||||||
*bins.1 = Instant::now();
|
*bins.1 = Instant::now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_bins(args: &Args) -> HashMap<String, Bin> {
|
fn get_bins(args: &Args) -> HashMap<String, Bin> {
|
||||||
let mut bins: HashMap<String, Bin> = HashMap::new();
|
let mut bins: HashMap<String, Bin> = HashMap::new();
|
||||||
std::fs::read_dir(&args.dir).unwrap().for_each(|entry| {
|
std::fs::read_dir(&args.dir).unwrap().for_each(|entry| {
|
||||||
let en = entry.unwrap();
|
let en = entry.unwrap();
|
||||||
|
@ -79,7 +73,7 @@ async fn index() -> RawText<String> {
|
||||||
let mut ret = String::new();
|
let mut ret = String::new();
|
||||||
unsafe {
|
unsafe {
|
||||||
if let Some((bins, time)) = &mut BINS {
|
if let Some((bins, time)) = &mut BINS {
|
||||||
reload_bins((bins, time), &args).await;
|
reload_bins((bins, time), &args);
|
||||||
if bins.is_empty() {
|
if bins.is_empty() {
|
||||||
return RawText(String::from("No binaries found"));
|
return RawText(String::from("No binaries found"));
|
||||||
}
|
}
|
||||||
|
@ -98,12 +92,21 @@ async fn index() -> RawText<String> {
|
||||||
RawText(ret)
|
RawText(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/runner/manifest")]
|
||||||
|
async fn get_manifest() -> Vec<u8> {
|
||||||
|
unsafe {
|
||||||
|
match &MANIFEST {
|
||||||
|
Some(man) => man.clone(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#[get("/<bin>")]
|
#[get("/<bin>")]
|
||||||
async fn get_script(bin: &str) -> ScriptResponse {
|
async fn get_script(bin: &str) -> ScriptResponse {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
unsafe {
|
unsafe {
|
||||||
if let Some((bins, time)) = &mut BINS {
|
if let Some((bins, time)) = &mut BINS {
|
||||||
reload_bins((bins, time), &args).await;
|
reload_bins((bins, time), &args);
|
||||||
return match bins.get(bin) {
|
return match bins.get(bin) {
|
||||||
None => ScriptResponse::Status(Status::NotFound),
|
None => ScriptResponse::Status(Status::NotFound),
|
||||||
Some(bin) => {
|
Some(bin) => {
|
||||||
|
@ -121,6 +124,21 @@ async fn get_script(bin: &str) -> ScriptResponse {
|
||||||
ScriptResponse::Status(Status::NotFound)
|
ScriptResponse::Status(Status::NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/<bin>/platforms")]
|
||||||
|
async fn get_platforms(bin: &str) -> ScriptResponse {
|
||||||
|
let args = Args::parse();
|
||||||
|
unsafe {
|
||||||
|
if let Some((bins, time)) = &mut BINS {
|
||||||
|
reload_bins((bins, time), &args);
|
||||||
|
return match bins.get(bin) {
|
||||||
|
None => ScriptResponse::Status(Status::NotFound),
|
||||||
|
Some(bin) => ScriptResponse::Text(RawText(format_platform_list(bin))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScriptResponse::Status(Status::NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/bin/<bin>/<platform>/<arch>")]
|
#[get("/bin/<bin>/<platform>/<arch>")]
|
||||||
async fn get_binary(bin: &str, platform: &str, arch: &str) -> BinaryResponse {
|
async fn get_binary(bin: &str, platform: &str, arch: &str) -> BinaryResponse {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
@ -139,44 +157,27 @@ async fn get_binary(bin: &str, platform: &str, arch: &str) -> BinaryResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sha256")]
|
#[get("/bin/<bin>/<platform>/<arch>/sign")]
|
||||||
#[get("/bin/<bin>/<platform>/<arch>/sha256")]
|
async fn get_binary_sign(bin: &str, platform: &str, arch: &str) -> SignResponse {
|
||||||
async fn get_binary_hash(bin: &str, platform: &str, arch: &str) -> ScriptResponse {
|
|
||||||
use rocket::tokio::io::AsyncReadExt;
|
|
||||||
use sha2::digest::FixedOutput;
|
|
||||||
use sha2::Digest;
|
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let file = NamedFile::open(format!(
|
let file = match std::fs::read(format!(
|
||||||
"{}/{}/{}/{}/{}",
|
"{}/{}/{}/{}/{}",
|
||||||
args.dir.file_name().unwrap().to_str().unwrap(),
|
args.dir.file_name().unwrap().to_str().unwrap(),
|
||||||
bin,
|
bin,
|
||||||
platform,
|
platform,
|
||||||
arch,
|
arch,
|
||||||
bin
|
bin
|
||||||
))
|
)) {
|
||||||
.await;
|
Ok(f) => f,
|
||||||
|
Err(_) => return SignResponse::Status(Status::BadRequest),
|
||||||
match file {
|
};
|
||||||
Ok(mut f) => {
|
let keypair = unsafe {
|
||||||
let mut hasher = sha2::Sha256::new();
|
match &KEYPAIR {
|
||||||
let mut contents: Vec<u8> = vec![];
|
Some(pair) => pair,
|
||||||
f.read_to_end(&mut contents).await.unwrap();
|
_ => unreachable!(),
|
||||||
hasher.update(contents);
|
|
||||||
ScriptResponse::Text(RawText(format!(
|
|
||||||
"{:x} {}",
|
|
||||||
hasher.finalize_fixed(),
|
|
||||||
f.path().file_name().unwrap().to_str().unwrap()
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
Err(_) => ScriptResponse::Status(Status::BadRequest),
|
};
|
||||||
}
|
SignResponse::Bin(keypair.sk.sign(file.as_slice(), Some(Noise::generate())).as_slice().to_vec())
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "sha256"))]
|
|
||||||
#[get("/bin/<_bin>/<_platform>/<_arch>/sha256")]
|
|
||||||
async fn get_binary_hash(_bin: &str, _platform: &str, _arch: &str) -> ScriptResponse {
|
|
||||||
ScriptResponse::Status(Status::BadRequest)
|
|
||||||
}
|
}
|
||||||
#[launch]
|
#[launch]
|
||||||
async fn rocket() -> _ {
|
async fn rocket() -> _ {
|
||||||
|
@ -187,12 +188,55 @@ async fn rocket() -> _ {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
BINS = Some((get_bins(&args).await, Instant::now()));
|
BINS = Some((get_bins(&args), Instant::now()));
|
||||||
|
println!("Generating keypair");
|
||||||
|
let kp = KeyPair::generate();
|
||||||
|
KEYPAIR = Some(kp.clone());
|
||||||
|
println!(
|
||||||
|
"Keypair generated. Public key: {}",
|
||||||
|
&kp.pk.iter().map(|x| format!("{:x}", x)).collect::<Vec<String>>().join("")
|
||||||
|
);
|
||||||
|
println!("Generating manifest");
|
||||||
|
let mut manifest: Vec<u8> = Vec::new();
|
||||||
|
let mut bin_pub_key: Vec<u8> = kp.pk.to_vec();
|
||||||
|
manifest.append(&mut bin_pub_key);
|
||||||
|
let mut runners = 0;
|
||||||
|
|
||||||
|
for element in std::fs::read_dir(args.runners_dir).unwrap() {
|
||||||
|
let en = element.unwrap();
|
||||||
|
if en.path().is_file() {
|
||||||
|
let mut hasher = sha2::Sha256::new();
|
||||||
|
hasher.update(std::fs::read(en.path()).unwrap().as_slice());
|
||||||
|
let mut contents = Vec::from(
|
||||||
|
format!(
|
||||||
|
"{:x} {}\n",
|
||||||
|
hasher.finalize_fixed(),
|
||||||
|
en.path().file_name().unwrap().to_str().unwrap()
|
||||||
|
)
|
||||||
|
.as_bytes(),
|
||||||
|
);
|
||||||
|
runners += 1;
|
||||||
|
manifest.append(&mut contents);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
let mut hasher = sha2::Sha256::new();
|
||||||
|
hasher.update(&manifest);
|
||||||
|
println!(
|
||||||
|
"Manifest generated with {} runners and SHA256: {:x}",
|
||||||
|
runners,
|
||||||
|
hasher.finalize_fixed()
|
||||||
|
);
|
||||||
|
MANIFEST = Some(manifest)
|
||||||
|
};
|
||||||
|
|
||||||
let figment = Figment::from(rocket::Config::default())
|
let figment = Figment::from(rocket::Config::default())
|
||||||
.merge(("ident", "BinHost"))
|
.merge(("ident", "Binhost"))
|
||||||
.merge(("port", args.port))
|
.merge(("port", args.port))
|
||||||
.merge(("address", args.address));
|
.merge(("address", args.address));
|
||||||
rocket::custom(figment).mount("/", routes![index, get_script, get_binary, get_binary_hash])
|
rocket::custom(figment)
|
||||||
|
.mount(
|
||||||
|
"/",
|
||||||
|
routes![index, get_manifest, get_script, get_platforms, get_binary, get_binary_sign],
|
||||||
|
)
|
||||||
|
.mount("/runner", FileServer::from("runners"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,10 @@ pub struct Args {
|
||||||
#[arg(long, default_value = "bin")]
|
#[arg(long, default_value = "bin")]
|
||||||
pub dir: PathBuf,
|
pub dir: PathBuf,
|
||||||
|
|
||||||
|
/// Directory where runners are contained
|
||||||
|
#[arg(long, default_value = "runners")]
|
||||||
|
pub runners_dir: PathBuf,
|
||||||
|
|
||||||
/// Refresh time (in secs)
|
/// Refresh time (in secs)
|
||||||
#[arg(long, default_value = "300")]
|
#[arg(long, default_value = "300")]
|
||||||
pub refresh: u64,
|
pub refresh: u64,
|
||||||
|
@ -52,3 +56,9 @@ pub enum BinaryResponse {
|
||||||
Status(Status),
|
Status(Status),
|
||||||
Bin(NamedFile),
|
Bin(NamedFile),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Responder, Clone)]
|
||||||
|
pub enum SignResponse {
|
||||||
|
Status(Status),
|
||||||
|
Bin(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
99
web.sh
99
web.sh
|
@ -1,44 +1,103 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
set -e -o pipefail
|
set -e
|
||||||
|
|
||||||
NAME="{{NAME}}"
|
print() {
|
||||||
|
echo "$@" >&2
|
||||||
|
}
|
||||||
|
|
||||||
if ! uname > /dev/null; then
|
fail() {
|
||||||
echo "No \`uname\` command was found, cannot continue" 1>&2
|
print "$@"
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! expr "{{PLATFORM_LIST}}" : "\(.*$(uname)-$(uname -m).*\)" > /dev/null; then
|
|
||||||
echo Platform $(uname)-$(uname -m) is not supported 1>&2
|
|
||||||
exit 1
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
requireCommands() {
|
||||||
|
for cmd in $*; do
|
||||||
|
if ! command -v $cmd &> /dev/null; then
|
||||||
|
fail "Cannot find required command: $cmd"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
requireCommands uname sha256sum cut dd chmod rm realpath expr
|
||||||
|
|
||||||
|
if [ "$(realpath $(command -v sha256sum))" = "/bin/busybox" ]; then
|
||||||
|
fail "Busybox sha256sum detected, will not work. Refusing to continue"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Script could be used for any binary
|
||||||
|
NAME=${BIN:="{{NAME}}"}
|
||||||
|
EXTERNAL_ADDRESS=${EXTERNAL_ADDRESS:="{{EXTERNAL_ADDRESS}}"}
|
||||||
DOWNLOAD_COMMAND="curl"
|
DOWNLOAD_COMMAND="curl"
|
||||||
OUTPUT_ARG="-o"
|
OUTPUT_ARG="-o"
|
||||||
DIR="/tmp/binhost-$NAME-$(date +%s)"
|
DIR="/tmp/binhost-$NAME-$(date +%s)"
|
||||||
FILE="$DIR/$NAME"
|
FILE="$DIR/$NAME"
|
||||||
|
PLATFORM="$(uname)"
|
||||||
|
ARCH="$(uname -m)"
|
||||||
|
|
||||||
if ! which curl > /dev/null; then
|
if ! command -v curl &> /dev/null; then
|
||||||
if ! which wget > /dev/null; then
|
if ! command -v wget &> /dev/null; then
|
||||||
echo "No curl or wget found, install one and rerun the script" 1>&2
|
fail "No curl or wget found, install one and rerun the script"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
export DOWNLOAD_COMMAND="wget"
|
export DOWNLOAD_COMMAND="wget"
|
||||||
export OUTPUT_ARG="-O"
|
export OUTPUT_ARG="-O"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
PLATFORM_LIST="{{PLATFORM_LIST}}"
|
||||||
|
# Making script truly portable
|
||||||
|
if [ ! "{{NAME}}" = $NAME ]; then
|
||||||
|
print ":: Fetching platforms"
|
||||||
|
export PLATFORM_LIST=$($DOWNLOAD_COMMAND $EXTERNAL_ADDRESS/$NAME/platforms $OUTPUT_ARG /dev/stdout)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! expr "$PLATFORM_LIST" : "\(.*$(uname)-$(uname -m).*\)" > /dev/null; then
|
||||||
|
fail "Platform \"$(uname)-$(uname -m)\" is not supported"
|
||||||
|
fi
|
||||||
|
|
||||||
mkdir $DIR
|
mkdir $DIR
|
||||||
|
cd $DIR
|
||||||
|
|
||||||
echo ":: Downloading binary" 1>&2
|
print ":: Downloading manifest"
|
||||||
|
$DOWNLOAD_COMMAND $EXTERNAL_ADDRESS/runner/manifest $OUTPUT_ARG manifest
|
||||||
|
|
||||||
# shellcheck disable=SC1083
|
MANIFEST_HASHSUM=$(sha256sum manifest)
|
||||||
$DOWNLOAD_COMMAND {{EXTERNAL_ADDRESS}}/bin/$NAME/$(uname)/$(uname -m) $OUTPUT_ARG "$FILE"
|
|
||||||
|
if [ ! -z $KEY ]; then
|
||||||
|
if [ ! $KEY = "$(echo $MANIFEST_HASHSUM | cut -c 1-${#KEY})" ]; then
|
||||||
|
fail "Invalid manifest hashsum"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print "Manifest KEY missing, skipping manifest check"
|
||||||
|
fi
|
||||||
|
|
||||||
|
print ":: Downloading signature"
|
||||||
|
$DOWNLOAD_COMMAND $EXTERNAL_ADDRESS/bin/$NAME/$PLATFORM/$ARCH/sign $OUTPUT_ARG signature
|
||||||
|
|
||||||
|
dd if=manifest of=public_key count=32 bs=1 2> /dev/null
|
||||||
|
dd if=manifest of=hashes skip=32 bs=1 2> /dev/null
|
||||||
|
|
||||||
|
print ":: Downloading runner"
|
||||||
|
|
||||||
|
$DOWNLOAD_COMMAND $EXTERNAL_ADDRESS/runner/runner-$PLATFORM-$ARCH $OUTPUT_ARG "runner-$PLATFORM-$ARCH"
|
||||||
|
|
||||||
|
if ! sha256sum -c hashes --ignore-missing; then
|
||||||
|
fail "Incorrect hashsum of runner"
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod +x "runner-$PLATFORM-$ARCH"
|
||||||
|
|
||||||
|
print ":: Downloading binary"
|
||||||
|
|
||||||
|
$DOWNLOAD_COMMAND $EXTERNAL_ADDRESS/bin/$NAME/$PLATFORM/$ARCH $OUTPUT_ARG "$FILE"
|
||||||
|
|
||||||
|
if ! ./runner-$PLATFORM-$ARCH "$FILE" >&2; then
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
chmod +x "$FILE"
|
chmod +x "$FILE"
|
||||||
|
|
||||||
cd $DIR
|
|
||||||
{{HASH_CALCULATION}}
|
|
||||||
$FILE < /dev/tty
|
$FILE < /dev/tty
|
||||||
|
|
||||||
rm "$FILE"
|
cd
|
||||||
|
|
||||||
|
rm -rf "$DIR"
|
||||||
|
|
Loading…
Reference in a new issue