2021-02-10 20:22:31 +02:00
|
|
|
use std::{
|
2021-02-10 18:57:14 -08:00
|
|
|
convert::TryFrom,
|
2021-02-10 20:22:31 +02:00
|
|
|
io::{self, Cursor},
|
2021-02-10 17:34:52 -08:00
|
|
|
path::{Path, PathBuf},
|
2021-02-11 21:01:31 +02:00
|
|
|
process::{ExitStatus, Stdio},
|
2021-02-10 20:22:31 +02:00
|
|
|
};
|
2021-02-08 18:38:14 -08:00
|
|
|
|
|
|
|
use regex::Regex;
|
2021-02-10 20:22:31 +02:00
|
|
|
use serde::{Deserialize, Serialize};
|
2021-02-11 21:01:31 +02:00
|
|
|
use snafu::Snafu;
|
2021-02-10 18:08:47 -08:00
|
|
|
use tokio::{
|
|
|
|
fs::File,
|
|
|
|
io::AsyncRead,
|
|
|
|
process::Command,
|
|
|
|
};
|
2021-02-08 18:38:14 -08:00
|
|
|
use validator::{Validate, ValidationError};
|
|
|
|
|
2021-02-11 21:01:31 +02:00
|
|
|
#[non_exhaustive]
|
|
|
|
#[derive(Debug, Snafu)]
|
|
|
|
pub enum KeyError {
|
|
|
|
#[snafu(display("I/O Error: {}", error))]
|
|
|
|
IoError { error: io::Error },
|
|
|
|
#[snafu(display("Key command failed: {}, stderr: {}", status, stderr))]
|
|
|
|
KeyCommandStatus { status: ExitStatus, stderr: String },
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<std::io::Error> for KeyError {
|
|
|
|
fn from(error: std::io::Error) -> Self {
|
|
|
|
Self::IoError { error }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-10 17:34:52 -08:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
2021-02-10 18:57:14 -08:00
|
|
|
#[serde(try_from = "KeySources")]
|
2021-02-10 17:34:52 -08:00
|
|
|
enum KeySource {
|
|
|
|
#[serde(rename = "text")]
|
|
|
|
Text(String),
|
|
|
|
|
|
|
|
#[serde(rename = "keyCommand")]
|
|
|
|
Command(Vec<String>),
|
|
|
|
|
|
|
|
#[serde(rename = "keyFile")]
|
|
|
|
File(PathBuf),
|
|
|
|
}
|
|
|
|
|
2021-02-10 18:57:14 -08:00
|
|
|
impl TryFrom<KeySources> for KeySource {
|
|
|
|
type Error = String;
|
|
|
|
|
|
|
|
fn try_from(ks: KeySources) -> Result<Self, Self::Error> {
|
|
|
|
match (ks.text, ks.command, ks.file) {
|
|
|
|
(Some(text), None, None) => {
|
|
|
|
Ok(KeySource::Text(text))
|
|
|
|
}
|
|
|
|
(None, Some(command), None) => {
|
|
|
|
Ok(KeySource::Command(command))
|
|
|
|
}
|
|
|
|
(None, None, Some(file)) => {
|
|
|
|
Ok(KeySource::File(file))
|
|
|
|
}
|
|
|
|
x => {
|
|
|
|
Err(format!("Somehow 0 or more than 1 key source was specified: {:?}", x))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
struct KeySources {
|
|
|
|
text: Option<String>,
|
|
|
|
|
|
|
|
#[serde(rename = "keyCommand")]
|
|
|
|
command: Option<Vec<String>>,
|
|
|
|
|
|
|
|
#[serde(rename = "keyFile")]
|
|
|
|
file: Option<PathBuf>,
|
|
|
|
}
|
|
|
|
|
2021-08-24 23:25:46 -07:00
|
|
|
/// When to upload a given key.
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
|
|
|
pub enum UploadAt {
|
|
|
|
/// Before activating the system profile.
|
|
|
|
#[serde(rename = "pre-activation")]
|
|
|
|
PreActivation,
|
|
|
|
|
|
|
|
/// After successfully activating the system profile.
|
|
|
|
#[serde(rename = "post-activation")]
|
|
|
|
PostActivation,
|
|
|
|
}
|
|
|
|
|
2021-02-08 18:38:14 -08:00
|
|
|
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
|
|
|
|
pub struct Key {
|
2021-02-10 17:34:52 -08:00
|
|
|
#[serde(flatten)]
|
|
|
|
source: KeySource,
|
|
|
|
|
2021-02-08 18:38:14 -08:00
|
|
|
#[validate(custom = "validate_dest_dir")]
|
|
|
|
#[serde(rename = "destDir")]
|
2021-02-10 17:34:52 -08:00
|
|
|
dest_dir: PathBuf,
|
|
|
|
|
2021-02-08 18:38:14 -08:00
|
|
|
#[validate(custom = "validate_unix_name")]
|
2021-02-10 17:34:52 -08:00
|
|
|
user: String,
|
|
|
|
|
2021-02-08 18:38:14 -08:00
|
|
|
#[validate(custom = "validate_unix_name")]
|
2021-02-10 17:34:52 -08:00
|
|
|
group: String,
|
|
|
|
|
|
|
|
permissions: String,
|
2021-08-24 23:25:46 -07:00
|
|
|
|
|
|
|
#[serde(rename = "uploadAt")]
|
|
|
|
upload_at: UploadAt,
|
2021-02-08 18:38:14 -08:00
|
|
|
}
|
|
|
|
|
2021-02-10 20:22:31 +02:00
|
|
|
impl Key {
|
2021-02-11 21:01:31 +02:00
|
|
|
pub async fn reader(&'_ self) -> Result<Box<dyn AsyncRead + Send + Unpin + '_>, KeyError> {
|
2021-02-10 17:34:52 -08:00
|
|
|
match &self.source {
|
|
|
|
KeySource::Text(content) => {
|
|
|
|
Ok(Box::new(Cursor::new(content)))
|
|
|
|
}
|
2021-02-10 18:08:47 -08:00
|
|
|
KeySource::Command(command) => {
|
|
|
|
let pathname = &command[0];
|
|
|
|
let argv = &command[1..];
|
|
|
|
|
2021-02-11 21:01:31 +02:00
|
|
|
let output = Command::new(pathname)
|
2021-02-10 18:08:47 -08:00
|
|
|
.args(argv)
|
|
|
|
.stdin(Stdio::null())
|
|
|
|
.stdout(Stdio::piped())
|
2021-02-11 21:01:31 +02:00
|
|
|
.stderr(Stdio::piped())
|
2021-02-10 18:08:47 -08:00
|
|
|
.spawn()?
|
2021-02-11 21:01:31 +02:00
|
|
|
.wait_with_output().await?;
|
|
|
|
|
|
|
|
if output.status.success() {
|
|
|
|
Ok(Box::new(Cursor::new(output.stdout)))
|
|
|
|
} else {
|
|
|
|
Err(KeyError::KeyCommandStatus {
|
|
|
|
status: output.status,
|
|
|
|
stderr: std::str::from_utf8(&output.stderr)
|
|
|
|
.unwrap_or_default()
|
|
|
|
.trim_end()
|
|
|
|
.into(),
|
|
|
|
})
|
|
|
|
}
|
2021-02-10 17:34:52 -08:00
|
|
|
}
|
|
|
|
KeySource::File(path) => {
|
|
|
|
Ok(Box::new(File::open(path).await?))
|
|
|
|
}
|
2021-02-10 20:22:31 +02:00
|
|
|
}
|
|
|
|
}
|
2021-02-10 17:34:52 -08:00
|
|
|
|
|
|
|
pub fn dest_dir(&self) -> &Path { &self.dest_dir }
|
|
|
|
pub fn user(&self) -> &str { &self.user }
|
2021-04-19 15:40:17 -07:00
|
|
|
pub fn group(&self) -> &str { &self.group }
|
2021-02-10 17:34:52 -08:00
|
|
|
pub fn permissions(&self) -> &str { &self.permissions }
|
2021-08-24 23:25:46 -07:00
|
|
|
pub fn upload_at(&self) -> UploadAt { self.upload_at }
|
2021-02-10 20:22:31 +02:00
|
|
|
}
|
|
|
|
|
2021-02-08 18:38:14 -08:00
|
|
|
fn validate_unix_name(name: &str) -> Result<(), ValidationError> {
|
|
|
|
let re = Regex::new(r"^[a-z][-a-z0-9]*$").unwrap();
|
|
|
|
if re.is_match(name) {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(ValidationError::new("Invalid user/group name"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn validate_dest_dir(dir: &PathBuf) -> Result<(), ValidationError> {
|
|
|
|
if dir.has_root() {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(ValidationError::new("Secret key destination directory must be absolute"))
|
|
|
|
}
|
|
|
|
}
|