#[macro_use]
extern crate tracing;
use aws_iam::document;
use aws_iam::document::{LatexGenerator, MarkdownGenerator};
use aws_iam::io;
use aws_iam::model::Policy;
use std::error::Error;
use std::fmt;
use std::fs::OpenOptions;
use std::io::{stdin, Write};
use std::path::PathBuf;
use std::str::FromStr;
use structopt::StructOpt;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::{EnvFilter, FmtSubscriber};
#[derive(Debug, StructOpt)]
#[structopt(name = "policy")]
struct Cli {
#[structopt(long, short = "v", parse(from_occurrences))]
verbose: i8,
#[structopt(subcommand)]
cmd: Command,
}
#[derive(Debug, StructOpt)]
enum Command {
New {
#[structopt(long, short)]
template: String,
#[structopt(long, short)]
force: bool,
#[structopt(name = "FILE", parse(from_os_str))]
file_name: Option<PathBuf>,
},
Verify {
#[structopt(long, short)]
format: Option<Format>,
#[structopt(parse(from_os_str))]
file_name: Option<PathBuf>,
},
}
#[derive(Debug)]
enum Format {
Rust,
Markdown,
Latex,
}
#[derive(Debug)]
enum FormatError {
MissingFormat,
InvalidFormat,
}
impl ToString for Format {
fn to_string(&self) -> String {
match self {
Format::Rust => "rust".to_string(),
Format::Markdown => "markdown".to_string(),
Format::Latex => "latex".to_string(),
}
}
}
impl FromStr for Format {
type Err = FormatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
Err(FormatError::MissingFormat)
} else if s == "rust" {
Ok(Format::Rust)
} else if s == "markdown" {
Ok(Format::Markdown)
} else if s == "latex" {
Ok(Format::Latex)
} else {
Err(FormatError::InvalidFormat)
}
}
}
impl ToString for FormatError {
fn to_string(&self) -> String {
match self {
FormatError::MissingFormat => "No format was provided".to_string(),
FormatError::InvalidFormat => "Input not a valid format".to_string(),
}
}
}
#[derive(Debug)]
enum ToolError {
CannotOpenForRead(String),
CannotOpenForWrite(String),
InvalidTemplateName(String),
WriteToFile,
VerifyFailed,
}
fn main() -> Result<(), ToolError> {
let args = Cli::from_args();
init_tracing(args.verbose);
match args.cmd {
Command::New {
file_name,
force,
template,
} => {
if template == "list" {
list_templates()
} else {
create_new_file(file_name, &template, force)
}
}
Command::Verify { file_name, format } => verify_file(file_name, format),
}
}
fn init_tracing(verbosity: i8) {
let log_level = match verbosity {
0 => LevelFilter::OFF,
1 => LevelFilter::ERROR,
2 => LevelFilter::WARN,
3 => LevelFilter::INFO,
4 => LevelFilter::DEBUG,
_ => LevelFilter::TRACE,
};
let filter = EnvFilter::from_default_env()
.add_directive(
format!("{}={}", module_path!(), log_level)
.parse()
.expect("Issue with command-line trace directive"),
)
.add_directive(
format!("aws_iam={}", log_level)
.parse()
.expect("Issue with library trace directive"),
);
let subscriber = FmtSubscriber::builder().with_env_filter(filter).finish();
tracing::subscriber::set_global_default(subscriber)
.expect("Unable to set global default tracing subscriber");
info!("Log level set to `LevelFilter::{:?}`", log_level);
}
fn list_templates() -> Result<(), ToolError> {
let span = debug_span!("list_templates");
let _enter = span.enter();
println!("templates: {:?}", templates::all_templates().keys());
Ok(())
}
fn create_new_file(
file_name: Option<PathBuf>,
template: &String,
force_write: bool,
) -> Result<(), ToolError> {
let span = debug_span!("create_new_file", ?file_name, ?template, ?force_write);
let _enter = span.enter();
if !templates::all_templates().contains_key(template) {
error!("'{}' is not a valid template name", template);
return Err(ToolError::InvalidTemplateName(template.clone()));
}
match file_name {
Some(file_name) => {
if file_name.exists() && file_name.is_file() && !force_write {
error!("could not open file for write, not a file, or missing -f");
Err(ToolError::CannotOpenForWrite(
file_name
.to_str()
.unwrap_or("{error in file name}")
.to_string(),
))
} else {
debug!("opening output file");
match OpenOptions::new()
.write(true)
.create_new(!force_write)
.create(true)
.truncate(true)
.open(file_name.clone())
{
Ok(mut f) => {
match write!(f, "{}", templates::all_templates().get(template).unwrap()) {
Ok(()) => Ok(()),
Err(e) => {
error!("write error: {:?}", e);
Err(ToolError::WriteToFile)
}
}
}
Err(e) => {
error!("could not open file for write, error {:?}", e);
Err(ToolError::CannotOpenForWrite(
file_name
.to_str()
.unwrap_or("{error in file name}")
.to_string(),
))
}
}
}
}
None => {
debug!("writing to stdout");
println!("{}", templates::all_templates().get(template).unwrap());
Ok(())
}
}
}
fn verify_file(file_name: Option<PathBuf>, format: Option<Format>) -> Result<(), ToolError> {
let span = debug_span!("verify_file", ?file_name, ?format);
let _enter = span.enter();
match file_name {
Some(file_name) => {
if file_name.exists() && file_name.is_file() {
debug!("reading file");
verify_file_result(io::read_from_file(&file_name), format)
} else {
error!("could not read from file");
Err(ToolError::CannotOpenForRead(
file_name
.to_str()
.unwrap_or("{error in file name}")
.to_string(),
))
}
}
None => {
debug!("reading from stdin");
verify_file_result(io::read_from_reader(stdin()), format)
}
}
}
fn verify_file_result(
result: Result<Policy, io::Error>,
format: Option<Format>,
) -> Result<(), ToolError> {
let span = debug_span!("verify_file_result", ?result, ?format);
let _enter = span.enter();
match result {
Ok(policy) => {
match format {
Some(format) => {
debug!("file parsed successfully");
match format {
Format::Rust => println!("{:#?}", policy),
Format::Markdown => {
let mut generator = MarkdownGenerator::default();
document::visitor::walk_policy(&policy, &mut generator);
}
Format::Latex => {
let mut generator = LatexGenerator::default();
document::visitor::walk_policy(&policy, &mut generator);
}
}
}
None => debug!("parsed successfully"),
}
Ok(())
}
Err(e) => {
match e {
io::Error::DeserializingJson(s) => {
error!("failed to parse, error: {:?}", s);
}
io::Error::ReadingFile(e) => {
error!(
"failed to read, error: {:?}, cause: {}",
e,
match e.source() {
Some(source) => source.to_string(),
None => "unknown".to_string(),
}
);
}
err => {
error!("failed with an unexpected error: {:?}", err);
}
}
Err(ToolError::VerifyFailed)
}
}
}
impl fmt::Display for ToolError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
ToolError::CannotOpenForRead(file_name) => {
write!(f, "Error reading from file: {}", file_name)
}
ToolError::CannotOpenForWrite(file_name) => {
write!(f, "Error writing to file: {}", file_name)
}
ToolError::InvalidTemplateName(name) => {
write!(f, "No template named '{}' supported", name)
}
ToolError::WriteToFile => write!(f, "Write operation to file failed"),
ToolError::VerifyFailed => write!(f, "Verification of policy failed"),
}
}
}
mod templates;