use std::convert::TryFrom; use std::env; use std::io; use std::path::PathBuf; use std::str::{from_utf8_unchecked, FromStr}; use clap::{App, Arg, ArgMatches, SubCommand}; use rpki::uri; use crate::cli::report::{ReportError, ReportFormat}; use crate::KRILL_CLIENT_APP; use crate::KRILL_VERSION; use crate::commons::api::{ AddChildRequest, AuthorizationFmtError, CertAuthInit, CertAuthPubMode, ChildAuthRequest, Handle, ParentCaContact, ParentCaReq, ResSetErr, ResourceSet, RouteAuthorizationUpdates, Token, UpdateChildRequest, }; use crate::commons::remote::id::IdCert; use crate::commons::remote::rfc8183; use crate::commons::util::file; use commons::api::{ChildHandle, ParentHandle}; const KRILL_CLI_SERVER_ARG: &str = "server"; const KRILL_CLI_SERVER_ENV: &str = "KRILL_CLI_SERVER"; const KRILL_CLI_TOKEN_ARG: &str = "token"; const KRILL_CLI_TOKEN_ENV: &str = "KRILL_CLI_TOKEN"; const KRILL_CLI_FORMAT_ARG: &str = "format"; const KRILL_CLI_FORMAT_ENV: &str = "KRILL_CLI_FORMAT"; const KRILL_CLI_API_ARG: &str = "api"; pub const KRILL_CLI_API_ENV: &str = "KRILL_CLI_API"; const KRILL_CLI_MY_CA_ARG: &str = "ca"; const KRILL_CLI_MY_CA_ENV: &str = "KRILL_CLI_MY_CA"; struct GeneralArgs { server: uri::Https, token: Token, format: ReportFormat, api: bool, } impl GeneralArgs { fn from_matches(matches: &ArgMatches) -> Result { let server = { let mut server = match env::var(KRILL_CLI_SERVER_ENV) { Ok(server_str) => Some(uri::Https::try_from(server_str)?), Err(_) => None, }; if let Some(server_str) = matches.value_of(KRILL_CLI_SERVER_ARG) { server = Some(uri::Https::from_str(server_str)?); } server.ok_or_else(|| { Error::missing_arg_with_env(KRILL_CLI_SERVER_ARG, KRILL_CLI_SERVER_ENV) })? }; let token = { let mut token = env::var(KRILL_CLI_TOKEN_ENV).ok().map(Token::from); if let Some(token_str) = matches.value_of(KRILL_CLI_TOKEN_ARG) { token = Some(Token::from(token_str)); } token.ok_or_else(|| { Error::missing_arg_with_env(KRILL_CLI_TOKEN_ARG, KRILL_CLI_TOKEN_ENV) })? }; let format = { let mut format = match env::var(KRILL_CLI_FORMAT_ENV) { Ok(fmt_str) => Some(ReportFormat::from_str(&fmt_str)?), Err(_) => None, }; if let Some(fmt_str) = matches.value_of(KRILL_CLI_FORMAT_ARG) { format = Some(ReportFormat::from_str(fmt_str)?); } format.unwrap_or_else(|| ReportFormat::Text) }; let api = env::var(KRILL_CLI_API_ENV).is_ok() || matches.is_present(KRILL_CLI_API_ARG); Ok(GeneralArgs { server, token, format, api, }) } } /// This type holds all the necessary data to connect to a Krill daemon, and /// authenticate, and perform a specific action. Note that this is extracted /// from the bin/krillc.rs, so that we can use this in integration testing /// more easily. pub struct Options { pub server: uri::Https, pub token: Token, pub format: ReportFormat, pub api: bool, pub command: Command, } impl Options { fn make(general: GeneralArgs, command: Command) -> Self { Options { server: general.server, token: general.token, format: general.format, api: general.api, command, } } pub fn format(&self) -> ReportFormat { self.format } /// Creates a new Options explicitly (useful for testing) pub fn new(server: uri::Https, token: &str, format: ReportFormat, command: Command) -> Self { Options { server, token: Token::from(token), format, api: false, command, } } fn add_general_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { app.arg( Arg::with_name(KRILL_CLI_SERVER_ARG) .short("s") .long(KRILL_CLI_SERVER_ARG) .value_name("URI") .help("The full URI to the krill server. Or set env: KRILL_CLI_SERVER") .required(false), ) .arg( Arg::with_name(KRILL_CLI_TOKEN_ARG) .short("t") .long(KRILL_CLI_TOKEN_ARG) .value_name("string") .help("The secret token for the krill server. Or set env: KRILL_CLI_TOKEN") .required(false), ) .arg( Arg::with_name(KRILL_CLI_FORMAT_ARG) .short("f") .long(KRILL_CLI_FORMAT_ARG) .value_name("type") .help("Report format: none|json|text (default) |xml. Or set env: KRILL_CLI_FORMAT") .required(false), ) .arg( Arg::with_name(KRILL_CLI_API_ARG) .long(KRILL_CLI_API_ARG) .help("Only show the API call and exit. Or set env: KRILL_CLI_API=1") .required(false), ) } fn add_my_ca_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { app.arg( Arg::with_name(KRILL_CLI_MY_CA_ARG) .value_name("name") .short("c") .long(KRILL_CLI_MY_CA_ARG) .help("The name of the CA you wish to control. Or set env: KRILL_CLI_MY_CA") .required(false), ) } fn add_child_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { app.arg( Arg::with_name("child") .value_name("name") .long("child") .help("The name of the child CA you wish to control.") .required(true), ) } fn add_child_resource_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { app.arg( Arg::with_name("asn") .short("a") .long("asn") .value_name("AS resources") .help("The delegated AS resources: e.g. AS1, AS3-4") .required(false), ) .arg( Arg::with_name("ipv4") .short("4") .long("ipv4") .value_name("IPv4 resources") .help("The delegated IPv4 resources: e.g. 192.168.0.0/16") .required(false), ) .arg( Arg::with_name("ipv6") .short("6") .long("ipv6") .value_name("IPv6 resources") .help("The delegated IPv6 resources: e.g. 2001:db8::/32") .required(false), ) } fn add_child_embedded_rfc6492_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { app.arg( Arg::with_name("embedded") .long("embedded") .help("Add a child that exists in this Krill instance. Note that an id cert can \ still be added later to allow this child to connect remotely. It's really how the \ child configures its parent that determines how it will connect") .required(false) ) .arg( Arg::with_name("rfc8183") .long("rfc8183") .help("Add a child using an RFC8183 Child Request XML file. This will return \ an RFC8183 Parent Response XML (on stdout)") .value_name("") .required(false) ) } fn add_parent_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { app.arg( Arg::with_name("parent") .long("parent") .short("p") .value_name("name") .help("The local by which your ca refers to this parent.") .required(true), ) } fn add_parent_embedded_rfc6492_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { Self::add_parent_arg(app) .arg( Arg::with_name("embedded") .long("embedded") .help("Parent exists in this Krill instance.") .required(false), ) .arg( Arg::with_name("rfc8183") .long("rfc8183") .help("Parent is remote, uses an RFC8183 Parent Response XML file.") .value_name("") .required(false), ) } fn make_cas_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let sub = SubCommand::with_name("list").about("List the current CAs."); let sub = Self::add_general_args(sub); app.subcommand(sub) } fn make_cas_show_ca_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("show").about("Show details of a CA."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); app.subcommand(sub) } fn make_cas_show_history_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("history").about("Show full history of a CA."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); app.subcommand(sub) } fn make_cas_add_ca_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("add").about("Add a new CA."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); app.subcommand(sub) } fn make_cas_children_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("add").about("Add a child to a CA."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); sub = Self::add_child_arg(sub); sub = Self::add_child_resource_args(sub); sub = Self::add_child_embedded_rfc6492_args(sub); app.subcommand(sub) } fn make_cas_children_update_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("update").about("Update an existing child of a CA."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); sub = Self::add_child_arg(sub); sub = Self::add_child_resource_args(sub); sub = sub.arg( Arg::with_name("idcert") .long("idcert") .help("The child's updated ID certificate") .value_name("DER encoded certificate") .required(false), ); app.subcommand(sub) } fn make_cas_children_response_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("response").about("Get the RFC8183 response for a child."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); sub = Self::add_child_arg(sub); app.subcommand(sub) } fn make_cas_children_remove_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("remove").about("Remove an existing child from a CA."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); sub = Self::add_child_arg(sub); app.subcommand(sub) } fn make_cas_children_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("children").about("Manage children for a CA in Krill."); sub = Self::make_cas_children_add_sc(sub); sub = Self::make_cas_children_update_sc(sub); sub = Self::make_cas_children_remove_sc(sub); sub = Self::make_cas_children_response_sc(sub); app.subcommand(sub) } fn make_cas_parents_myid_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("myid").about("Show this CA's RFC8183 Request XML"); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); app.subcommand(sub) } fn make_cas_parents_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("add").about("Add a parent to this CA."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); sub = Self::add_parent_embedded_rfc6492_args(sub); app.subcommand(sub) } fn make_cas_parents_update_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("update").about("Update an existing parent of this CA."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); sub = Self::add_parent_embedded_rfc6492_args(sub); app.subcommand(sub) } fn make_cas_parents_remove_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("remove").about("Remove an existing parent from this CA."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); sub = Self::add_parent_arg(sub); app.subcommand(sub) } fn make_cas_parents_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("parents").about("Manage parents for a CA."); sub = Self::make_cas_parents_myid_sc(sub); sub = Self::make_cas_parents_add_sc(sub); sub = Self::make_cas_parents_update_sc(sub); sub = Self::make_cas_parents_remove_sc(sub); app.subcommand(sub) } fn make_cas_keyroll_init_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("init").about("Initialise roll for all keys held by this CA."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); app.subcommand(sub) } fn make_cas_keyroll_activate_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("activate").about("Finish roll for all keys held by this CA."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); app.subcommand(sub) } fn make_cas_keyroll_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("keyroll").about("Perform a manual key-roll in Krill."); sub = Self::make_cas_keyroll_init_sc(sub); sub = Self::make_cas_keyroll_activate_sc(sub); app.subcommand(sub) } fn make_cas_routes_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("list").about("Show current authorizations."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); app.subcommand(sub) } fn make_cas_routes_update_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("update").about("Update authorizations."); sub = Self::add_general_args(sub); sub = Self::add_my_ca_arg(sub); sub = sub.arg( Arg::with_name("delta") .long("delta") .help(concat!( "Provide a delta file using the following format:\n", "# Some comment\n", " # Indented comment\n", "\n", // empty line "A: 192.168.0.0/16 => 64496 # inline comment\n", "A: 192.168.1.0/24 => 64496\n", "R: 192.168.3.0/24 => 64496\n", )) .value_name("") .required(true), ); app.subcommand(sub) } fn make_cas_routes_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("roas").about("Manage ROAs for your CA."); sub = Self::make_cas_routes_list_sc(sub); sub = Self::make_cas_routes_update_sc(sub); app.subcommand(sub) } fn make_publishers_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("list").about("List all publishers."); sub = Self::add_general_args(sub); app.subcommand(sub) } fn add_publisher_arg<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { app.arg( Arg::with_name("publisher") .value_name("handle") .short("p") .long("publisher") .help("The handle (name) of the publisher.") .required(true), ) } fn make_publishers_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("add").about("Add a publisher."); sub = Self::add_general_args(sub); sub = Self::add_publisher_arg(sub); app.subcommand(sub) } fn make_publishers_show_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("show").about("Show details for a publisher."); sub = Self::add_general_args(sub); sub = Self::add_publisher_arg(sub); app.subcommand(sub) } fn make_publishers_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("publishers").about("Manage publishers in Krill."); sub = Self::make_publishers_list_sc(sub); sub = Self::make_publishers_add_sc(sub); sub = Self::make_publishers_show_sc(sub); app.subcommand(sub) } fn make_health_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { app.subcommand( SubCommand::with_name("health").about("Perform an authenticated health check"), ) } fn make_matches<'a>() -> ArgMatches<'a> { let mut app = App::new(KRILL_CLIENT_APP).version(KRILL_VERSION); app = Self::make_cas_list_sc(app); app = Self::make_cas_show_ca_sc(app); app = Self::make_cas_show_history_sc(app); app = Self::make_cas_add_ca_sc(app); app = Self::make_cas_children_sc(app); app = Self::make_cas_parents_sc(app); app = Self::make_cas_keyroll_sc(app); app = Self::make_cas_routes_sc(app); app = Self::make_publishers_sc(app); app = Self::make_health_sc(app); app.get_matches() } //---------------------- Parsing fn parse_my_ca(matches: &ArgMatches) -> Result { let my_ca = { let mut my_ca = None; if let Ok(my_ca_env) = env::var(KRILL_CLI_MY_CA_ENV) { my_ca = Some(Handle::from_str(&my_ca_env).map_err(|_| Error::InvalidHandle)?); } if let Some(my_ca_str) = matches.value_of(KRILL_CLI_MY_CA_ARG) { my_ca = Some(Handle::from_str(my_ca_str).map_err(|_| Error::InvalidHandle)?); } my_ca.ok_or_else(|| { Error::missing_arg_with_env(KRILL_CLI_MY_CA_ARG, KRILL_CLI_MY_CA_ENV) })? }; Ok(my_ca) } fn parse_resource_args(matches: &ArgMatches) -> Result, Error> { let asn = matches.value_of("asn"); let v4 = matches.value_of("ipv4"); let v6 = matches.value_of("ipv6"); if asn.is_some() || v4.is_some() || v6.is_some() { let asn = asn.unwrap_or_else(|| ""); let v4 = v4.unwrap_or_else(|| ""); let v6 = v6.unwrap_or_else(|| ""); Ok(Some(ResourceSet::from_strs(asn, v4, v6)?)) } else { Ok(None) } } fn parse_matches_cas_list(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let command = Command::CertAuth(CaCommand::List); Ok(Options::make(general_args, command)) } fn parse_matches_cas_add(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let init = CertAuthInit::new(my_ca, CertAuthPubMode::Embedded); let command = Command::CertAuth(CaCommand::Init(init)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_show(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let command = Command::CertAuth(CaCommand::Show(my_ca)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_history(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let command = Command::CertAuth(CaCommand::ShowHistory(my_ca)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_children_add(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let child = matches.value_of("child").unwrap(); let child = Handle::from_str(child).map_err(|_| Error::InvalidHandle)?; let resources = Self::parse_resource_args(matches)?.ok_or_else(|| Error::MissingResources)?; let auth_request = { if matches.is_present("embedded") { ChildAuthRequest::Embedded } else if let Some(path) = matches.value_of("rfc8183") { let xml = PathBuf::from(path); let bytes = file::read(&xml)?; let cr = rfc8183::ChildRequest::validate(bytes.as_ref())?; ChildAuthRequest::Rfc8183(cr) } else { return Err(Error::MissingChildAuth); } }; let child_request = AddChildRequest::new(child, resources, auth_request); let command = Command::CertAuth(CaCommand::ChildAdd(my_ca, child_request)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_children_update(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let child = matches.value_of("child").unwrap(); let child = Handle::from_str(child).map_err(|_| Error::InvalidHandle)?; let id_cert = { if let Some(path) = matches.value_of("idcert") { let bytes = file::read(&PathBuf::from(path))?; let id_cert = IdCert::decode(bytes).map_err(|_| Error::InvalidChildIdCert)?; Some(id_cert) } else { None } }; let resources = Self::parse_resource_args(matches)?; let update = UpdateChildRequest::force(id_cert, resources); let command = Command::CertAuth(CaCommand::ChildUpdate(my_ca, child, update)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_children_response(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let child = matches.value_of("child").unwrap(); let child = Handle::from_str(child).map_err(|_| Error::InvalidHandle)?; let command = Command::CertAuth(CaCommand::ParentResponse(my_ca, child)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_children_remove(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let child = matches.value_of("child").unwrap(); let child = Handle::from_str(child).map_err(|_| Error::InvalidHandle)?; let command = Command::CertAuth(CaCommand::ChildDelete(my_ca, child)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_children(matches: &ArgMatches) -> Result { if let Some(m) = matches.subcommand_matches("add") { Self::parse_matches_cas_children_add(m) } else if let Some(m) = matches.subcommand_matches("response") { Self::parse_matches_cas_children_response(m) } else if let Some(m) = matches.subcommand_matches("update") { Self::parse_matches_cas_children_update(m) } else if let Some(m) = matches.subcommand_matches("remove") { Self::parse_matches_cas_children_remove(m) } else { Err(Error::UnrecognisedSubCommand) } } fn parse_matches_cas_parents_myid(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let command = Command::CertAuth(CaCommand::ChildRequest(my_ca)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_parents_add(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let parent_req = Self::parse_parent_ca_req(matches)?; let command = Command::CertAuth(CaCommand::AddParent(my_ca, parent_req)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_parents_update(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let parent_req = Self::parse_parent_ca_req(matches)?; let (parent, contact) = parent_req.unwrap(); let command = Command::CertAuth(CaCommand::UpdateParentContact(my_ca, parent, contact)); Ok(Options::make(general_args, command)) } fn parse_parent_ca_req(matches: &ArgMatches) -> Result { let parent = matches.value_of("parent").unwrap(); let parent = Handle::from_str(parent).map_err(|_| Error::InvalidHandle)?; if matches.is_present("embedded") { Ok(ParentCaReq::new(parent, ParentCaContact::Embedded)) } else if let Some(path) = matches.value_of("rfc8183") { let xml = PathBuf::from(path); let bytes = file::read(&xml)?; let res = rfc8183::ParentResponse::validate(bytes.as_ref())?; Ok(ParentCaReq::new(parent, ParentCaContact::for_rfc6492(res))) } else { Err(Error::MissingChildAuth) } } fn parse_matches_cas_parents_remove(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let parent = matches.value_of("parent").unwrap(); let parent = Handle::from_str(parent).map_err(|_| Error::InvalidHandle)?; let command = Command::CertAuth(CaCommand::RemoveParent(my_ca, parent)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_parents(matches: &ArgMatches) -> Result { if let Some(m) = matches.subcommand_matches("myid") { Self::parse_matches_cas_parents_myid(m) } else if let Some(m) = matches.subcommand_matches("add") { Self::parse_matches_cas_parents_add(m) } else if let Some(m) = matches.subcommand_matches("update") { Self::parse_matches_cas_parents_update(m) } else if let Some(m) = matches.subcommand_matches("remove") { Self::parse_matches_cas_parents_remove(m) } else { Err(Error::UnrecognisedSubCommand) } } fn parse_matches_cas_keyroll_init(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let command = Command::CertAuth(CaCommand::KeyRollInit(my_ca)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_keyroll_activate(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let command = Command::CertAuth(CaCommand::KeyRollActivate(my_ca)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_keyroll(matches: &ArgMatches) -> Result { if let Some(m) = matches.subcommand_matches("init") { Self::parse_matches_cas_keyroll_init(m) } else if let Some(m) = matches.subcommand_matches("activate") { Self::parse_matches_cas_keyroll_activate(m) } else { Err(Error::UnrecognisedSubCommand) } } fn parse_matches_cas_routes_list(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let command = Command::CertAuth(CaCommand::RouteAuthorizationsList(my_ca)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_routes_update(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; let updates = { let path = matches.value_of("delta").map(PathBuf::from).unwrap(); let bytes = file::read(&path)?; let updates_str = unsafe { from_utf8_unchecked(&bytes) }; RouteAuthorizationUpdates::from_str(updates_str)? }; let command = Command::CertAuth(CaCommand::RouteAuthorizationsUpdate(my_ca, updates)); Ok(Options::make(general_args, command)) } fn parse_matches_cas_routes(matches: &ArgMatches) -> Result { if let Some(m) = matches.subcommand_matches("list") { Self::parse_matches_cas_routes_list(m) } else if let Some(m) = matches.subcommand_matches("update") { Self::parse_matches_cas_routes_update(m) } else { Err(Error::UnrecognisedSubCommand) } } fn parse_matches_health(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let command = Command::Health; Ok(Options::make(general_args, command)) } fn parse_matches(matches: ArgMatches) -> Result { if let Some(m) = matches.subcommand_matches("list") { Self::parse_matches_cas_list(m) } else if let Some(m) = matches.subcommand_matches("add") { Self::parse_matches_cas_add(m) } else if let Some(m) = matches.subcommand_matches("show") { Self::parse_matches_cas_show(m) } else if let Some(m) = matches.subcommand_matches("history") { Self::parse_matches_cas_history(m) } else if let Some(m) = matches.subcommand_matches("children") { Self::parse_matches_cas_children(m) } else if let Some(m) = matches.subcommand_matches("parents") { Self::parse_matches_cas_parents(m) } else if let Some(m) = matches.subcommand_matches("keyroll") { Self::parse_matches_cas_keyroll(m) } else if let Some(m) = matches.subcommand_matches("roas") { Self::parse_matches_cas_routes(m) } else if let Some(m) = matches.subcommand_matches("health") { Self::parse_matches_health(m) } else { Err(Error::UnrecognisedSubCommand) } } pub fn from_args() -> Result { let matches = Self::make_matches(); Self::parse_matches(matches) } } #[derive(Clone, Debug, Eq, PartialEq)] #[allow(clippy::large_enum_variant)] pub enum Command { NotSet, Health, CertAuth(CaCommand), Publishers(PublishersCommand), Rfc8181(Rfc8181Command), } #[derive(Clone, Debug, Eq, PartialEq)] #[allow(clippy::large_enum_variant)] pub enum CaCommand { // Initialise a CA Init(CertAuthInit), // Update CA id UpdateId(Handle), // Get an RFC8183 parent response ParentResponse(Handle, ChildHandle), // Get the RFC8183 child request ChildRequest(Handle), // Add a parent to this CA AddParent(Handle, ParentCaReq), // Update parent contact UpdateParentContact(Handle, ParentHandle, ParentCaContact), // Remove a parent RemoveParent(Handle, ParentHandle), // Children ChildAdd(Handle, AddChildRequest), ChildUpdate(Handle, ChildHandle, UpdateChildRequest), ChildDelete(Handle, ChildHandle), // Initialise a manual key-roll now KeyRollInit(Handle), // Activate all new keys now (finish key roll, provided new key was certified) KeyRollActivate(Handle), // List the current RouteAuthorizations RouteAuthorizationsList(Handle), // Update the Route Authorizations for this CA RouteAuthorizationsUpdate(Handle, RouteAuthorizationUpdates), // Show details for this CA Show(Handle), // Show the history for this CA ShowHistory(Handle), // List all CAs List, // Refresh all CAs: let them update from parents, and shrink children if needed RefreshAll, } #[derive(Clone, Debug, Eq, PartialEq)] #[allow(clippy::large_enum_variant)] pub enum PublishersCommand { Add(AddPublisher), Show(Handle), Deactivate(Handle), List, } #[derive(Clone, Debug, Eq, PartialEq)] pub struct AddPublisher { pub handle: Handle, pub id_cert: Option, pub base_uri: uri::Rsync, } #[derive(Clone, Debug, Eq, PartialEq)] pub enum Rfc8181Command { List, Add(AddRfc8181Client), RepoRes(Handle), } #[derive(Clone, Debug, Eq, PartialEq)] pub struct AddRfc8181Client { pub xml: PathBuf, } //------------ Error --------------------------------------------------------- #[derive(Debug, Display)] pub enum Error { #[display(fmt = "{}", _0)] UriError(uri::Error), #[display(fmt = "{}", _0)] IoError(io::Error), #[display(fmt = "{}", _0)] ReportError(ReportError), #[display(fmt = "Invalid RFC8183 XML: {}", _0)] Rfc8183(rfc8183::Error), #[display(fmt = "Invalid resources requested: {}", _0)] ResSetErr(ResSetErr), #[display(fmt = "{}", _0)] InvalidRouteDelta(AuthorizationFmtError), #[display(fmt = "The publisher handle may only contain -_A-Za-z0-9, (\\ /) see issue #83")] InvalidHandle, #[display( fmt = "Missing argument: --{}, alternatively you may use env var: {}", _0, _1 )] MissingArgWithEnv(String, String), #[display(fmt = "You must specify resources when adding a CA (--asn, --ipv4, --ipv6)")] MissingResources, #[display(fmt = "You must specify either --embedded or --rfc8183 when adding a child")] MissingChildAuth, #[display(fmt = "Invalid ID cert for child.")] InvalidChildIdCert, #[display(fmt = "Unrecognised sub-command. Use 'help'.")] UnrecognisedSubCommand, } impl Error { fn missing_arg_with_env(arg: &str, env_var: &str) -> Self { Error::MissingArgWithEnv(arg.to_string(), env_var.to_string()) } } impl From for Error { fn from(e: rfc8183::Error) -> Self { Error::Rfc8183(e) } } impl From for Error { fn from(e: uri::Error) -> Self { Error::UriError(e) } } impl From for Error { fn from(e: io::Error) -> Self { Error::IoError(e) } } impl From for Error { fn from(e: ReportError) -> Self { Error::ReportError(e) } } impl From for Error { fn from(e: ResSetErr) -> Self { Error::ResSetErr(e) } } impl From for Error { fn from(e: AuthorizationFmtError) -> Self { Error::InvalidRouteDelta(e) } }