Files
NLnetLabs-krill/src/cli/options.rs
T

1059 lines
35 KiB
Rust

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<Self, Error> {
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("<XML file>")
.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("<XML file>")
.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("<file>")
.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<Handle, Error> {
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<Option<ResourceSet>, 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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<ParentCaReq, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
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<Options, Error> {
let general_args = GeneralArgs::from_matches(matches)?;
let command = Command::Health;
Ok(Options::make(general_args, command))
}
fn parse_matches(matches: ArgMatches) -> Result<Options, Error> {
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<Options, Error> {
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<IdCert>,
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<rfc8183::Error> for Error {
fn from(e: rfc8183::Error) -> Self {
Error::Rfc8183(e)
}
}
impl From<uri::Error> for Error {
fn from(e: uri::Error) -> Self {
Error::UriError(e)
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::IoError(e)
}
}
impl From<ReportError> for Error {
fn from(e: ReportError) -> Self {
Error::ReportError(e)
}
}
impl From<ResSetErr> for Error {
fn from(e: ResSetErr) -> Self {
Error::ResSetErr(e)
}
}
impl From<AuthorizationFmtError> for Error {
fn from(e: AuthorizationFmtError) -> Self {
Error::InvalidRouteDelta(e)
}
}