From a6a669eae01ee95b2d72a91dd67df98bcb2a617c Mon Sep 17 00:00:00 2001 From: Iulian Barbu Date: Sun, 19 Feb 2023 18:23:39 +0200 Subject: [PATCH] admin: added command to automate certificate renewal Signed-off-by: Iulian Barbu --- admin/src/args.rs | 16 ++++++++++++++++ admin/src/client.rs | 10 ++++++++++ admin/src/main.rs | 14 ++++++++++++++ gateway/src/acme.rs | 20 ++++++++++++-------- gateway/src/api/latest.rs | 2 +- gateway/src/main.rs | 2 +- 6 files changed, 54 insertions(+), 10 deletions(-) diff --git a/admin/src/args.rs b/admin/src/args.rs index e7ae68122..3fb3312b1 100644 --- a/admin/src/args.rs +++ b/admin/src/args.rs @@ -58,6 +58,22 @@ pub enum AcmeCommand { #[arg(long)] credentials: PathBuf, }, + + /// Automate certificate renewal for a FQDN + AutomateCertificateRenewal { + /// Fqdn to automate certificate renewal for + #[arg(long)] + fqdn: String, + + /// Project to automate certificate renewal for + #[arg(long)] + project: ProjectName, + + /// Path to acme credentials file + /// This should have been created with `acme create-account` + #[arg(long)] + credentials: PathBuf, + }, } #[derive(Subcommand, Debug)] diff --git a/admin/src/client.rs b/admin/src/client.rs index 43e756a78..3f42dc080 100644 --- a/admin/src/client.rs +++ b/admin/src/client.rs @@ -39,6 +39,16 @@ impl Client { self.post(&path, Some(credentials)).await } + pub async fn acme_renew_certificate( + &self, + fqdn: &str, + project_name: &ProjectName, + credentials: &serde_json::Value, + ) -> Result { + let path = format!("/admin/acme/renew/{project_name}/{fqdn}"); + self.post(&path, Some(credentials)).await + } + pub async fn get_projects(&self) -> Result> { self.get("/admin/projects").await } diff --git a/admin/src/main.rs b/admin/src/main.rs index 55cb40ee5..16a444272 100644 --- a/admin/src/main.rs +++ b/admin/src/main.rs @@ -50,6 +50,20 @@ async fn main() { .await .expect("to get a certificate challenge response") } + Command::Acme(AcmeCommand::AutomateCertificateRenewal { + fqdn, + project, + credentials, + }) => { + let credentials = fs::read_to_string(credentials).expect("to read credentials file"); + let credentials = + serde_json::from_str(&credentials).expect("to parse content of credentials file"); + + client + .acme_renew_certificate(&fqdn, &project, &credentials) + .await + .expect("to get a certificate challenge response") + } Command::ProjectNames => { let projects = client .get_projects() diff --git a/gateway/src/acme.rs b/gateway/src/acme.rs index b8c03cb17..b89d97d8f 100644 --- a/gateway/src/acme.rs +++ b/gateway/src/acme.rs @@ -10,8 +10,8 @@ use futures::future::BoxFuture; use hyper::server::conn::AddrStream; use hyper::{Body, Request}; use instant_acme::{ - Account, Authorization, AuthorizationStatus, Challenge, ChallengeType, Identifier, - KeyAuthorization, LetsEncrypt, NewAccount, NewOrder, Order, OrderStatus, AccountCredentials, + Account, AccountCredentials, Authorization, AuthorizationStatus, Challenge, ChallengeType, + Identifier, KeyAuthorization, LetsEncrypt, NewAccount, NewOrder, Order, OrderStatus, }; use rcgen::{Certificate, CertificateParams, DistinguishedName}; use tokio::sync::Mutex; @@ -286,12 +286,16 @@ pub struct AccountWrapper(pub Account); impl<'a> From> for AccountWrapper { fn from(value: AccountCredentials<'a>) -> Self { - AccountWrapper(Account::from_credentials(value).map_err(|error| { - error!( - error = &error as &dyn std::error::Error, - "failed to convert acme credentials into account" - ); - }).expect("Account credentials malformed")) + AccountWrapper( + Account::from_credentials(value) + .map_err(|error| { + error!( + error = &error as &dyn std::error::Error, + "failed to convert acme credentials into account" + ); + }) + .expect("Account credentials malformed"), + ) } } diff --git a/gateway/src/api/latest.rs b/gateway/src/api/latest.rs index 523524aa8..fdc833572 100644 --- a/gateway/src/api/latest.rs +++ b/gateway/src/api/latest.rs @@ -28,7 +28,7 @@ use uuid::Uuid; use x509_parser::parse_x509_certificate; use x509_parser::time::ASN1Time; -use crate::acme::{AcmeClient, AcmeClientError, CustomDomain, AccountWrapper}; +use crate::acme::{AccountWrapper, AcmeClient, AcmeClientError, CustomDomain}; use crate::auth::{Admin, ScopedUser, User}; use crate::project::{Project, ProjectCreating}; use crate::task::{self, BoxedTask, TaskResult}; diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 46b283d1b..9ef02ee24 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -3,7 +3,7 @@ use fqdn::FQDN; use futures::prelude::*; use instant_acme::{Account, AccountCredentials, ChallengeType}; use opentelemetry::global; -use shuttle_gateway::acme::{AcmeClient, AcmeClientError, CustomDomain, AccountWrapper}; +use shuttle_gateway::acme::{AccountWrapper, AcmeClient, AcmeClientError, CustomDomain}; use shuttle_gateway::api::latest::{ApiBuilder, SVC_DEGRADED_THRESHOLD}; use shuttle_gateway::args::StartArgs; use shuttle_gateway::args::{Args, Commands, InitArgs, UseTls};