use std::{error, env, fmt, io};
use std::fs::File;
use std::io::BufReader;
use std::str::FromStr;
use rpki::ca::idexchange;
use rpki::ca::idcert::IdCert;
use rpki::repository::resources::{
AsBlocks, Ipv4Blocks, Ipv6Blocks, ResourceSet,
};
use crate::{api, constants};
use crate::api::ta::{
ApiTrustAnchorSignedRequest, TrustAnchorSignerInfo,
TrustAnchorSignedResponse,
};
use crate::cli::client::KrillClient;
use crate::cli::options::GeneralOptions;
use crate::cli::options::args::JsonFile;
use crate::cli::options::repo::RepositoryResponseFile;
use crate::cli::report::Report;
use crate::commons::error::Error as KrillError;
use crate::commons::httpclient;
#[derive(clap::Args)]
pub struct Command {
#[command(flatten)]
pub general: GeneralOptions,
#[command(subcommand)]
pub command: Subcommand,
}
impl Command {
pub async fn run(self) -> Report {
let client = KrillClient::new(
self.general.server, self.general.token
);
let client = match client {
Ok(client) => client,
Err(err) => return Report::from_err(err)
};
if self.general.api {
unsafe { env::set_var(constants::KRILL_CLI_API_ENV, "1") }
}
self.command.run(&client).await
}
}
#[derive(clap::Subcommand)]
#[allow(clippy::large_enum_variant)]
pub enum Subcommand {
Init(Init),
Id(Id),
#[command(subcommand)]
Repo(Repo),
#[command(subcommand)]
Signer(Signer),
#[command(subcommand)]
Children(Children),
}
impl Subcommand {
pub async fn run(self, client: &KrillClient) -> Report {
match self {
Self::Init(cmd) => cmd.run(client).await.into(),
Self::Id(cmd) => cmd.run(client).await.into(),
Self::Repo(cmd) => cmd.run(client).await,
Self::Signer(cmd) => cmd.run(client).await,
Self::Children(cmd) => cmd.run(client).await,
}
}
}
#[derive(clap::Args)]
pub struct Init;
impl Init {
pub async fn run(
self, client: &KrillClient
) -> Result<api::status::Success, httpclient::Error> {
client.ta_proxy_init().await
}
}
#[derive(clap::Args)]
pub struct Id;
impl Id {
pub async fn run(
self, client: &KrillClient
) -> Result<api::ca::IdCertInfo, httpclient::Error> {
client.ta_proxy_id().await
}
}
#[derive(clap::Subcommand)]
pub enum Repo {
Request(RepoRequest),
Contact(RepoContact),
Configure(RepoConfigure),
}
impl Repo {
pub async fn run(self, client: &KrillClient) -> Report {
match self {
Self::Request(cmd) => cmd.run(client).await.into(),
Self::Contact(cmd) => cmd.run(client).await.into(),
Self::Configure(cmd) => cmd.run(client).await.into(),
}
}
}
#[derive(clap::Args)]
pub struct RepoRequest;
impl RepoRequest {
pub async fn run(
self, client: &KrillClient
) -> Result<idexchange::PublisherRequest, httpclient::Error> {
client.ta_proxy_repo_request().await
}
}
#[derive(clap::Args)]
pub struct RepoContact;
impl RepoContact {
pub async fn run(
self, client: &KrillClient
) -> Result<api::admin::RepositoryContact, httpclient::Error> {
client.ta_proxy_repo_contact().await
}
}
#[derive(clap::Args)]
pub struct RepoConfigure {
#[arg(long, short, value_name = "path")]
response: RepositoryResponseFile,
}
impl RepoConfigure {
pub async fn run(
self, client: &KrillClient
) -> Result<api::status::Success, httpclient::Error> {
client.ta_proxy_repo_configure(self.response.into()).await
}
}
#[derive(clap::Subcommand)]
pub enum Signer {
Init(SignerInit),
Update(SignerUpdate),
MakeRequest(SignerMakeRequest),
ShowRequest(SignerShowRequest),
ProcessResponse(SignerProcessResponse),
}
impl Signer {
pub async fn run(self, client: &KrillClient) -> Report {
match self {
Self::Init(cmd) => cmd.run(client).await.into(),
Self::Update(cmd) => cmd.run(client).await.into(),
Self::MakeRequest(cmd) => cmd.run(client).await.into(),
Self::ShowRequest(cmd) => cmd.run(client).await.into(),
Self::ProcessResponse(cmd) => cmd.run(client).await.into(),
}
}
}
#[derive(clap::Args)]
pub struct SignerInit {
#[arg(long, short, value_name="path")]
info: JsonFile<TrustAnchorSignerInfo, TasiMsg>,
}
impl SignerInit {
pub async fn run(
self, client: &KrillClient
) -> Result<api::status::Success, httpclient::Error> {
client.ta_proxy_signer_add(self.info.content).await
}
}
#[derive(Clone, Copy, Default, Debug)]
struct TasiMsg;
impl fmt::Display for TasiMsg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("signer info")
}
}
#[derive(clap::Args)]
pub struct SignerUpdate {
#[arg(long, short, value_name="path")]
info: JsonFile<TrustAnchorSignerInfo, TasiMsg>,
}
impl SignerUpdate {
pub async fn run(
self, client: &KrillClient
) -> Result<api::status::Success, httpclient::Error> {
client.ta_proxy_signer_update(self.info.content).await
}
}
#[derive(clap::Args)]
pub struct SignerMakeRequest;
impl SignerMakeRequest {
pub async fn run(
self, client: &KrillClient
) -> Result<ApiTrustAnchorSignedRequest, httpclient::Error> {
client.ta_proxy_signer_make_request().await
}
}
#[derive(clap::Args)]
pub struct SignerShowRequest;
impl SignerShowRequest {
pub async fn run(
self, client: &KrillClient
) -> Result<ApiTrustAnchorSignedRequest, httpclient::Error> {
client.ta_proxy_signer_show_request().await
}
}
#[derive(clap::Args)]
pub struct SignerProcessResponse {
#[arg(long, short, value_name="path")]
response: JsonFile<TrustAnchorSignedResponse, TasrMsg>,
}
impl SignerProcessResponse {
pub async fn run(
self, client: &KrillClient
) -> Result<api::status::Success, httpclient::Error> {
client.ta_proxy_signer_response(self.response.content).await
}
}
#[derive(Clone, Copy, Default, Debug)]
struct TasrMsg;
impl fmt::Display for TasrMsg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("response")
}
}
#[derive(clap::Subcommand)]
#[allow(clippy::large_enum_variant)]
pub enum Children {
Add(ChildrenAdd),
Response(ChildrenResponse),
}
impl Children {
pub async fn run(self, client: &KrillClient) -> Report {
match self {
Self::Add(cmd) => cmd.run(client).await.into(),
Self::Response(cmd) => cmd.run(client).await.into(),
}
}
}
#[derive(clap::Args)]
pub struct ChildrenAdd {
#[arg(long, short, value_name = "path")]
info: CertAuthInfoFile,
#[arg(
long, short,
value_name = "ASN resources",
default_value = "AS0-AS4294967295"
)]
asn: AsBlocks,
#[arg(
long, short = '4',
value_name = "IPv4 resources",
default_value = "0.0.0.0/0",
)]
ipv4: Ipv4Blocks,
#[arg(
long, short = '6',
value_name = "IPv6 resources",
default_value = "::/0"
)]
ipv6: Ipv6Blocks,
}
impl ChildrenAdd {
pub async fn run(
self, client: &KrillClient
) -> Result<idexchange::ParentResponse, httpclient::Error> {
client.ta_proxy_children_add(
api::admin::AddChildRequest {
handle: self.info.handle,
resources: ResourceSet::new(self.asn, self.ipv4, self.ipv6),
id_cert: self.info.id_cert,
}
).await
}
}
#[derive(clap::Args)]
pub struct ChildrenResponse {
#[arg(long, value_name = "name")]
child: idexchange::ChildHandle,
}
impl ChildrenResponse {
pub async fn run(
self, client: &KrillClient
) -> Result<idexchange::ParentResponse, httpclient::Error> {
client.ta_proxy_child_response(&self.child).await
}
}
#[derive(Clone, Debug)]
pub struct CertAuthInfoFile {
handle: idexchange::ChildHandle,
id_cert: IdCert,
}
impl FromStr for CertAuthInfoFile {
type Err = CertAuthInfoFileError;
fn from_str(path: &str) -> Result<Self, Self::Err> {
let info = serde_json::from_reader::<_, api::ca::CertAuthInfo>(
BufReader::new(
File::open(path).map_err(|err| {
CertAuthInfoFileError::Io(path.into(), err)
})?
)
).map_err(|err| {
CertAuthInfoFileError::Parse(path.into(), err)
})?;
Ok(Self {
handle: info.handle.convert(),
id_cert: (&info.id_cert).try_into().map_err(|err| {
CertAuthInfoFileError::Cert(path.into(), err)
})?
})
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum CertAuthInfoFileError {
Io(String, io::Error),
Parse(String, serde_json::Error),
Cert(String, KrillError),
}
impl fmt::Display for CertAuthInfoFileError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Io(path, err) => {
write!(
f, "Failed to read child info file '{path}': {err}'"
)
}
Self::Parse(path, err) => {
write!(
f, "Failed to parse child info file '{path}': {err}'"
)
}
Self::Cert(path, err) => {
write!(
f, "Failed to parse child info file '{path}': {err}'"
)
}
}
}
}
impl error::Error for CertAuthInfoFileError { }