Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] rustwide-based sandbox #7

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,899 changes: 1,899 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "cargo-sandbox"
description = "Perform Cargo builds inside of a sandboxed environment"
version = "0.0.0"
authors = ["Rust Secure Code WG <[email protected]>"]
authors = ["Rust Secure Code WG"]
edition = "2018"
license = "Apache-2.0 OR MIT"
readme = "README.md"
Expand All @@ -14,3 +14,8 @@ keywords = ["cargo", "sandboxing", "security"]
maintenance = { status = "experimental" }

[dependencies]
crossterm = "0.13"
env_logger = "0.7"
gumdrop = "0.7"
home = "0.5"
rustwide = "0.3"
19 changes: 10 additions & 9 deletions src/bin/cargo-sandbox/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
//! cargo-sandbox command-line utility

#![deny(
warnings,
missing_docs,
trivial_casts,
trivial_numeric_casts,
unused_import_braces,
unused_qualifications
)]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]

use crossterm::style::Colorize;
use std::{env, process};

fn main() {
cargo_sandbox::start();
let args = env::args().collect::<Vec<_>>();

cargo_sandbox::start(&args[1..]).unwrap_or_else(|e| {
eprintln!("{} {}", "error:".red(), e);
process::exit(1);
});
}
108 changes: 108 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//! Error types

use std::fmt;

/// Error type
#[derive(Debug)]
pub struct Error(Box<Context>);

impl Error {
/// Create a new error with the given source
pub fn new(kind: ErrorKind, source: impl Into<BoxError>) -> Self {
Error(Box::new(Context {
kind,
source: Some(source.into()),
}))
}

/// Get the kind of error
pub fn kind(&self) -> &ErrorKind {
&self.0.kind
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind())?;

if let Some(source) = &self.0.source {
write!(f, ": {}", source)?;
}

Ok(())
}
}

impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error(Box::new(Context { kind, source: None }))
}
}

impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.0
.source
.as_ref()
.map(|source| source.as_ref() as &(dyn std::error::Error + 'static))
}
}

/// Kinds of errors
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ErrorKind {
/// Argument errors
Argument,

/// Build errors
Build,

/// Cargo-related errors
Cargo,

/// Errors relating to Docker images
DockerImage,

/// Path-related errors
Path,

/// Toolchain errors
Toolchain,

/// Errors relating to workspaces
Workspace,
}

impl ErrorKind {
/// Create an error of this kind, with another error as the source
pub fn source(&self, error: impl Into<BoxError>) -> Error {
Error::new(self.clone(), error)
}
}

impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
ErrorKind::Argument => "invalid argument",
ErrorKind::Build => "build error",
ErrorKind::Cargo => "cargo error",
ErrorKind::DockerImage => "docker image error",
ErrorKind::Path => "invalid path",
ErrorKind::Toolchain => "toolchain error",
ErrorKind::Workspace => "workspace error",
})
}
}

/// Boxed error type used as an error source
pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;

/// Error context
#[derive(Debug)]
struct Context {
/// Kind of error
kind: ErrorKind,

/// Source of error
source: Option<BoxError>,
}
67 changes: 49 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,57 @@
//! cargo sandbox: perform Cargo builds inside of a sandboxed environment

#![forbid(unsafe_code)]
#![deny(
warnings,
missing_docs,
trivial_casts,
trivial_numeric_casts,
unused_import_braces,
unused_qualifications
)]
#![doc(
html_logo_url = "https://avatars3.githubusercontent.com/u/44121472",
html_root_url = "https://docs.rs/cargo-sandbox/0.0.0"
)]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]

#[macro_use]
pub mod status;

pub mod error;
pub mod sandbox;
pub mod workspace;

pub use self::{
error::{Error, ErrorKind},
sandbox::SandboxCmd,
};
use gumdrop::Options;
use std::ffi::OsStr;

/// Run `cargo sandbox`
pub fn start(args: &[String]) -> Result<(), Error> {
let (sandbox_args, cargo_args) = match args.iter().position(|arg| arg == "--") {
Some(pos) => (&args[..pos], &args[pos..]),
None => {
if args.get(0).map(AsRef::as_ref) == Some("sandbox") {
(&args[0..], &args[1..])
} else {
(&[] as &[String], args)
}
}
};

Command::parse_args_default(sandbox_args)
.map_err(|e| ErrorKind::Argument.source(e))?
.run(cargo_args)
}

/// Command-line option parser
#[derive(Debug, Options)]
pub enum Command {
/// The `cargo sandbox` subcommand
#[options(help = "perform a Cargo command inside of a sandbox")]
Sandbox(SandboxCmd),
}

/// Start the utility
pub fn start() {
println!("cargo sandbox: perform Cargo builds inside of a sandboxed environment");
println!();
println!("WORK IN PROGRESS: This utility does not yet provide any functionality");
println!("If you are interested in contributing, please see the GitHub issues:");
println!();
println!(" https://github.com/rust-secure-code/cargo-sandbox/issues");
println!();
impl Command {
/// Run the command
pub fn run<S: AsRef<OsStr>>(&self, cargo_args: &[S]) -> Result<(), Error> {
match self {
Command::Sandbox(sandbox) => sandbox.run(cargo_args),
}
}
}
112 changes: 112 additions & 0 deletions src/sandbox.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! The `cargo sandbox` subcommand

use crate::{
error::{Error, ErrorKind},
workspace::Workspace,
};
use gumdrop::Options;
use rustwide::{cmd::SandboxImage, Toolchain};
use std::{
ffi::OsStr,
path::PathBuf,
process::{exit, Command},
};

/// The `cargo sandbox` subcommand
#[derive(Default, Debug, Options)]
pub struct SandboxCmd {
/// Get help information
#[options(short = "h", long = "help", help = "output help information and exit")]
help: bool,

/// Get version information
#[options(no_short, long = "version", help = "output version and exit")]
version: bool,
}

impl SandboxCmd {
/// Run the `cargo sandbox` subcommand
pub fn run<S: AsRef<OsStr>>(&self, cargo_args: &[S]) -> Result<(), Error> {
if self.help {
println!("{}", Self::usage());
exit(0);
}

if self.version {
println!("cargo-sandbox {}", env!("CARGO_PKG_VERSION"));
exit(0);
}

// Initialize env-logger
env_logger::builder()
.filter_level("info".parse().unwrap())
.format_timestamp(None)
.init();

// TODO(tarcieri): customize workspace directory
let workspace_dir = self.workspace_dir()?;

// TODO(tarcieri): customize build dir
let build_dir_name = self.build_dir_name()?;

// TODO(tarcieri): customize toolchain
let toolchain = Toolchain::Dist {
name: "stable".into(),
};

// TODO(tarcieri): customize sandbox image
let image_name = "rustops/crates-build-env";
status!("Fetching", "`{}` docker image", image_name);

let image =
SandboxImage::remote(image_name).map_err(|e| ErrorKind::DockerImage.source(e))?;

status!(
"Creating",
"`{}` workspace ({})",
workspace_dir.display(),
toolchain
);

let mut workspace = Workspace::new(&workspace_dir, toolchain, image)?;

status!(
"Running",
"cargo inside workspace: `{}`",
workspace_dir.display(),
);

// TODO(tarcieri): determine project root instead of always using `.`
workspace.run(&build_dir_name, ".", cargo_args)
}

/// Get the workspace directory
fn workspace_dir(&self) -> Result<PathBuf, Error> {
home::cargo_home()
.map(|path| path.join("sandbox"))
.map_err(|e| ErrorKind::Path.source(e))
}

/// Get the build directory for the current Cargo project
fn build_dir_name(&self) -> Result<String, Error> {
let pkgid_bytes = Command::new("cargo")
.arg("pkgid")
.output()
.map_err(|e| ErrorKind::Cargo.source(e))?
.stdout;

let pkgid = String::from_utf8(pkgid_bytes).map_err(|e| ErrorKind::Cargo.source(e))?;

let pkg_name_with_comment = pkgid
.split("/")
.last()
.ok_or_else(|| Error::from(ErrorKind::Cargo))?;

let pkg_name = pkg_name_with_comment
.split("#")
.next()
.ok_or_else(|| Error::from(ErrorKind::Cargo))?;

Ok(pkg_name.to_owned())
}
}
13 changes: 13 additions & 0 deletions src/status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! Status mcaros

macro_rules! status {
($status:expr, $msg:expr) => {
{
use crossterm::style::{Colorize, Styler};
println!("{:>12} {}", $status.green().bold(), $msg);
}
};
($status:expr, $fmt:expr, $($arg:tt)+) => {
status!($status, format!($fmt, $($arg)+));
};
}
Loading