Commit d2ff0a78 authored by Heinz N. Gies's avatar Heinz N. Gies Committed by GitHub

Add validation (#23)

* Add validation
* Add mac generation
* Add nic tag validation
* Use network tags
parent 11d26cc1
......@@ -5,7 +5,10 @@ dependencies = [
"aud 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clippy 0.0.142 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"prettytable-rs 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -18,6 +21,14 @@ dependencies = [
"uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "aho-corasick"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.9.0"
......@@ -293,6 +304,18 @@ name = "redox_syscall"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "regex"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.4.1"
......@@ -522,6 +545,11 @@ dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "utf8-ranges"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "uuid"
version = "0.5.1"
......@@ -557,6 +585,7 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159"
"checksum aud 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9b275fc36d5afbd23cb73bc1524aaaa17050c2d98430f6d1f45492c636b54ed6"
......@@ -593,6 +622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
"checksum redox_syscall 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "e4a357d14a12e90a37d658725df0e6468504750b5948b9710f83f94a0c5818e8"
"checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
"checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
"checksum semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537"
......@@ -622,6 +652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
"checksum uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22"
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
......
......@@ -17,6 +17,9 @@ slog-scope = "2"
slog-bunyan = "2"
aud = "0.1"
prettytable-rs = "^0.6"
lazy_static = "0.2"
regex = "0.2"
rand = "0.3"
# indicatif = "0.5"
[dependencies.clap]
......
{
"cpu_cap": 100,
"image_uuid": "e022d0f8-5630-11e7-b660-9b2d243d440",
"hostname": "test; rm -rf /",
"uuid": "fe0b9b05-1f3e-4b11-b0ae-8494bb6ecd53;",
"max_physical_memory": 1024,
"quota": 100,
"alias": "test",
"nics": [
{
"nic_tag": "not_admin",
"interface": "net 0",
"gateway": "192.168.1.1000",
"netmask": "255.255.255.-10",
"ip": "192.168.1.256",
"primary": true
}
]
}
......@@ -3,12 +3,79 @@
use std::error::Error;
use std::fmt;
/// Validation errors
#[derive(Debug)]
pub struct ValidationErrors {
errors: Vec<ValidationError>,
}
impl ValidationErrors {
/// Create a new error for validations
pub fn new(errors: Vec<ValidationError>) -> Self {
ValidationErrors { errors }
}
/// Create a new error in a box
pub fn bx(errors: Vec<ValidationError>) -> Box<Error> {
Box::new(ValidationErrors::new(errors))
}
}
impl fmt::Display for ValidationErrors {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut r = write!(f, "{} validaiton errors encountered", self.errors.len());
for e in self.errors.clone() {
r = write!(f, "\n {}", e)
}
r
}
}
impl Error for ValidationErrors {
fn description(&self) -> &str {
"Validations Error"
}
}
/// Validation error for input validation
#[derive(Debug, Clone)]
pub struct ValidationError {
field: String,
error: String,
}
impl ValidationError {
/// Initialize a new generic error
pub fn new(field: &str, error: &str) -> ValidationError {
ValidationError {
field: String::from(field),
error: String::from(error),
}
}
/// Create a new error in a box
pub fn bx(field: &str, error: &str) -> Box<Error> {
Box::new(ValidationError::new(field, error))
}
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {}", self.field, self.error)
}
}
impl Error for ValidationError {
fn description(&self) -> &str {
"Validation Error"
}
}
/// Generic error that carries a message string
#[derive(Debug)]
pub struct GenericError {
msg: String,
}
impl GenericError {
/// Initialize a new generic error
pub fn new(msg: &str) -> GenericError {
......
......@@ -8,8 +8,14 @@ use std::process::Command;
#[cfg(target_os = "freebsd")]
use errors::GenericError;
use errors::{ValidationError, ValidationErrors};
use config::Config;
use serde_json;
use uuid::Uuid;
use regex::Regex;
use rand::{thread_rng, Rng};
/// Jail configuration values
......@@ -44,10 +50,11 @@ pub struct IFace {
/// post stop script
pub poststop_script: String,
}
impl NIC {
/// Creates the related interface
#[cfg(target_os = "freebsd")]
pub fn get_iface(self: &NIC, uuid: &str) -> Result<IFace, Box<Error>> {
pub fn get_iface(&self, config: &Config, uuid: &str) -> Result<IFace, Box<Error>> {
let output = Command::new(IFCONFIG)
.args(&["epair", "create", "up"])
.output()
......@@ -60,13 +67,19 @@ impl NIC {
let mut epair = String::from(epaira);
epair.pop();
let output = Command::new(IFCONFIG)
.args(&["bridge0", "addm", epaira])
.output()
.expect("failed ifconfig");
match config.settings.networks.get(self.nic_tag) {
Some(bridge) => {
if !output.status.success() {
return Err(GenericError::bx("could not add epair to bridge"));
let output = Command::new(IFCONFIG)
.args(&[bridge.as_str(), "addm", epaira])
.output()
.expect("failed ifconfig");
if !output.status.success() {
return Err(GenericError::bx("could not add epair to bridge"));
}
}
None => return Err(GenericError::bx("bridge not configured")),
}
let mut script = format!(
......@@ -100,7 +113,7 @@ impl NIC {
}
/// Creates the related interface
#[cfg(not(target_os = "freebsd"))]
pub fn get_iface(self: &NIC, _uuid: &str) -> Result<IFace, Box<Error>> {
pub fn get_iface(&self, _config: &Config, _uuid: &str) -> Result<IFace, Box<Error>> {
let epair = "epair0";
let script = format!(
"/sbin/ifconfig {epair}b inet {ip} {mask};\
......@@ -158,15 +171,24 @@ pub struct JailConfig {
pub max_lwps: u64,
}
lazy_static! {
static ref HOSTNAME_RE: Regex = Regex::new("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?$").unwrap();
static ref ALIAS_RE: Regex = Regex::new("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?$").unwrap();
static ref INTERFACE_RE: Regex = Regex::new("^[a-zA-Z]{1,4}[0-9]{0,3}$").unwrap();
static ref IP_RE: Regex = Regex::new("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$").unwrap();
static ref MAC_RE: Regex = Regex::new("^[a-fA-F0-9]{1,2}([:][a-fA-F0-9]{1,2}){5}$").unwrap();
static ref UUID_RE: Regex = Regex::new("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$").unwrap();
}
impl JailConfig {
/// Reads a new config from a file
pub fn from_file(config_path: &str) -> Result<Self, Box<Error>> {
pub fn from_file(config: &Config, config_path: &str) -> Result<Self, Box<Error>> {
let config_file = File::open(config_path)?;
JailConfig::from_reader(config_file)
JailConfig::from_reader(config, config_file)
}
/// Reads the config from a reader
pub fn from_reader<R>(reader: R) -> Result<Self, Box<Error>>
pub fn from_reader<R>(config: &Config, reader: R) -> Result<Self, Box<Error>>
where
R: Read,
{
......@@ -181,12 +203,77 @@ impl JailConfig {
if conf.autostart.is_none() {
conf.autostart = Some(false);
}
Ok(conf)
match conf.errors(config) {
Some(errors) => Err(ValidationErrors::bx(errors)),
None => Ok(conf),
}
}
/// checks the config for errors
pub fn errors(&self, config: &Config) -> Option<Vec<ValidationError>> {
let mut errors = Vec::new();
if !HOSTNAME_RE.is_match(self.hostname.as_str()) {
errors.push(ValidationError::new("hostname", "Invalid hostname"))
}
if !ALIAS_RE.is_match(self.alias.as_str()) {
errors.push(ValidationError::new("alias", "Invalid alias"))
}
if !UUID_RE.is_match(self.uuid.as_str()) {
errors.push(ValidationError::new("uuid", "Invalid uuid"))
}
if !UUID_RE.is_match(self.image_uuid.as_str()) {
errors.push(ValidationError::new("image_uuid", "Invalid image_uuid"))
}
let mut i = 0;
for nic in self.nics.clone() {
if !INTERFACE_RE.is_match(nic.interface.as_str()) {
errors.push(ValidationError::new(
format!("nic[{}]", i).as_str(),
"Invalid interface name",
))
}
if !IP_RE.is_match(nic.ip.as_str()) {
errors.push(ValidationError::new(
format!("nic[{}]", i).as_str(),
"Invalid ip",
))
}
if !IP_RE.is_match(nic.netmask.as_str()) {
errors.push(ValidationError::new(
format!("nic[{}]", i).as_str(),
"Invalid netmask",
))
}
if !IP_RE.is_match(nic.gateway.as_str()) {
errors.push(ValidationError::new(
format!("nic[{}]", i).as_str(),
"Invalid gateway",
))
}
if !MAC_RE.is_match(nic.mac.as_str()) {
errors.push(ValidationError::new(
format!("nic[{}]", i).as_str(),
"Invalid mac",
))
}
if !config.settings.networks.contains_key(&nic.nic_tag) {
errors.push(ValidationError::new(
format!("nic[{}]", i).as_str(),
"Unknown nic_tag",
))
}
i = i + 1;
}
if errors.is_empty() {
None
} else {
Some(errors)
}
}
/// Translates the config into resource controle limts
pub fn rctl_limits(self: &JailConfig) -> Vec<String> {
pub fn rctl_limits(&self) -> Vec<String> {
let mut res = Vec::new();
let uuid = self.uuid.clone();
let mut base = String::from("jail:");
......@@ -249,5 +336,14 @@ fn empty_nics() -> Vec<NIC> {
}
fn new_mac() -> String {
String::from("00:00:00:00:00:00")
let mut rng = thread_rng();
format!(
"{:x}:{:x}:{:x}:{:x}:{:x}:{:x}",
rng.gen::<u8>(),
rng.gen::<u8>(),
rng.gen::<u8>(),
rng.gen::<u8>(),
rng.gen::<u8>(),
rng.gen::<u8>()
)
}
......@@ -6,6 +6,7 @@ use std::collections::HashMap;
use jdb::Jail;
use std::process::Command;
use jail_config::IFace;
use config::Config;
#[derive(Debug)]
/// Basic information about a ZFS dataset
......@@ -34,8 +35,8 @@ static RCTL: &'static str = "echo";
static JAIL: &'static str = "echo";
/// starts a jail
pub fn start(jail: &Jail) -> Result<i32, Box<Error>> {
let args = create_args(jail)?;
pub fn start(config: &Config, jail: &Jail) -> Result<i32, Box<Error>> {
let args = create_args(config, jail)?;
let limits = jail.config.rctl_limits();
debug!("Setting jail limits"; "vm" => jail.idx.uuid.clone(), "limits" => limits.clone().join(" "));
let output = Command::new(RCTL).args(limits.clone()).output().expect(
......@@ -68,7 +69,7 @@ pub fn start(jail: &Jail) -> Result<i32, Box<Error>> {
}
}
fn create_args(jail: &Jail) -> Result<Vec<String>, Box<Error>> {
fn create_args(config: &Config, jail: &Jail) -> Result<Vec<String>, Box<Error>> {
let uuid = jail.idx.uuid.clone();
let mut name = String::from("name=");
name.push_str(uuid.as_str());
......@@ -102,7 +103,7 @@ fn create_args(jail: &Jail) -> Result<Vec<String>, Box<Error>> {
for nic in jail.config.nics.iter() {
// see https://lists.freebsd.org/pipermail/freebsd-jail//2016-December/003305.html
let iface: IFace = nic.get_iface(uuid.as_str())?;
let iface: IFace = nic.get_iface(config, uuid.as_str())?;
let mut vnet_iface = String::from("vnet.interface=");
vnet_iface.push_str(iface.epair.as_str());
vnet_iface.push('b');
......
......@@ -177,7 +177,7 @@ impl<'a> JDB<'a> {
config_path.push(entry.uuid.clone());
config_path.set_extension("json");
match config_path.to_str() {
Some(path) => JailConfig::from_file(path),
Some(path) => JailConfig::from_file(self.config, path),
None => Err(GenericError::bx("could not generate vm config path")),
}
}
......
......@@ -18,6 +18,11 @@ extern crate serde_derive;
extern crate serde;
extern crate serde_json;
extern crate toml;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate rand;
//extern crate indicatif;
#[macro_use]
......@@ -208,7 +213,7 @@ fn start(conf: &Config, matches: &clap::ArgMatches) -> Result<i32, Box<Error>> {
}
Ok(jail) => {
println!("Starting jail {}", jail.idx.uuid);
jails::start(&jail)
jails::start(conf, &jail)
}
}
}
......@@ -226,7 +231,7 @@ fn reboot(conf: &Config, matches: &clap::ArgMatches) -> Result<i32, Box<Error>>
Ok(jail) => {
println!("Rebooting jail {}", uuid);
jails::stop(&jail)?;
jails::start(&jail)
jails::start(conf, &jail)
}
}
}
......@@ -303,11 +308,11 @@ fn create(conf: &Config, matches: &clap::ArgMatches) -> Result<i32, Box<Error>>
let jail = match value_t!(matches, "file", String) {
Err(_) => {
debug!("Reading from STDIN");
jail_config::JailConfig::from_reader(io::stdin())?
jail_config::JailConfig::from_reader(conf, io::stdin())?
}
Ok(file) => {
debug!("Reading from file"; "file" => file.clone() );
jail_config::JailConfig::from_reader(File::open(file)?)?
jail_config::JailConfig::from_reader(conf, File::open(file)?)?
}
};
let mut dataset = conf.settings.pool.clone();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment