Skip to main content
Glama
lib.rs6.68 kB
//! This crate provides a client for interacting with AWS ACM PCA #![warn( bad_style, clippy::missing_panics_doc, clippy::panic, clippy::panic_in_result_fn, clippy::unwrap_in_result, clippy::unwrap_used, dead_code, improper_ctypes, missing_debug_implementations, missing_docs, no_mangle_generic_items, non_shorthand_field_patterns, overflowing_literals, path_statements, patterns_in_fns_without_body, unconditional_recursion, unreachable_pub, unused, unused_allocation, unused_comparisons, unused_parens, while_true )] use std::{ fmt::Debug, time::Duration, }; use aws_sdk_acmpca::{ error::{ BuildError, SdkError, }, operation::get_certificate::GetCertificateError, primitives::Blob, types::{ SigningAlgorithm, Validity, ValidityPeriodType, }, }; use rcgen::{ CertificateParams, DistinguishedName, DnType, ExtendedKeyUsagePurpose, KeyPair, KeyUsagePurpose, }; use si_aws_config::{ AwsConfig, AwsConfigError, }; use si_tls::{ CertificateSource, KeySource, }; use telemetry::prelude::*; use thiserror::Error; const DEFAULT_CERT_VALIDITY: i64 = 7; #[allow(missing_docs)] #[remain::sorted] #[derive(Debug, Error)] pub enum PrivateCertManagerClientError { #[error("AWS Config Error error: {0}")] AwsConfig(#[from] AwsConfigError), #[error("AWS ACM PCA error: {0}")] AwsPrivateCertManager(String), #[error("Certificate Authority not found with arn: {0}")] CertificateAuthorityNotFound(String), #[error("Failed to get cert: {0} from ca: {1}")] GetCertificate(String, String), #[error("Failed to issue cert from ca: {0}")] IssueCertificate(String), #[error("RCGen error: {0}")] RcGen(#[from] rcgen::Error), #[error("Validity build error: {0}")] Validity(#[from] BuildError), } impl PrivateCertManagerClientError { fn from_sdk_error<T: Debug>(error: SdkError<T>) -> Self { PrivateCertManagerClientError::AwsPrivateCertManager(format!("{error:?}")) } } type PrivateCertManagerClientResult<T> = Result<T, PrivateCertManagerClientError>; /// A client for communicating with ssm. #[derive(Debug, Clone)] pub struct PrivateCertManagerClient { inner: Box<aws_sdk_acmpca::Client>, } impl PrivateCertManagerClient { /// Creates a new [client for interacting with ACM PCA](PrivateCertManagerClient). #[instrument(name = "private_cert_manager_client.new", level = "info")] pub async fn new() -> PrivateCertManagerClientResult<Self> { let config = AwsConfig::from_env().await?; let client = aws_sdk_acmpca::Client::new(&config); Ok(Self { inner: Box::new(client), }) } /// Gets a CA pub async fn get_certificate_authority( &self, ca_arn: String, ) -> PrivateCertManagerClientResult<CertificateSource> { let result = self .inner .get_certificate_authority_certificate() .certificate_authority_arn(ca_arn.clone()) .send() .await .map_err(PrivateCertManagerClientError::from_sdk_error)?; let cert = result.certificate().ok_or( PrivateCertManagerClientError::CertificateAuthorityNotFound(ca_arn), )?; Ok(CertificateSource::AsString(cert.to_string())) } /// Issues and retrieves a new cert from the CA pub async fn get_new_cert_from_ca( &self, ca_arn: String, for_app: String, for_service: String, ) -> PrivateCertManagerClientResult<(CertificateSource, KeySource)> { let (csr, key) = generate_instance_cert(for_app, for_service)?; let result = self .inner .issue_certificate() .certificate_authority_arn(ca_arn.clone()) .signing_algorithm(SigningAlgorithm::Sha256Withrsa) .csr(Blob::new(csr.as_bytes())) .validity( Validity::builder() .r#type(ValidityPeriodType::Days) .value(DEFAULT_CERT_VALIDITY) .build()?, ) .send() .await .map_err(PrivateCertManagerClientError::from_sdk_error)?; let cert_arn = result .certificate_arn() .ok_or(PrivateCertManagerClientError::IssueCertificate( ca_arn.clone(), ))?; let max_attempts = 5; for attempt in 0..max_attempts { match self .inner .get_certificate() .certificate_authority_arn(ca_arn.clone()) .certificate_arn(cert_arn) .send() .await { Ok(result) => { if let Some(cert) = result.certificate() { return Ok(( CertificateSource::AsString(cert.to_string()), KeySource::AsString(key), )); } } Err(err) => { if let Some(GetCertificateError::RequestInProgressException(_)) = err.as_service_error() { if attempt < max_attempts - 1 { let delay = Duration::from_secs(2u64.pow(attempt as u32)); tokio::time::sleep(delay).await; continue; } } return Err(PrivateCertManagerClientError::from_sdk_error(err)); } } } Err(PrivateCertManagerClientError::GetCertificate( ca_arn, cert_arn.to_string(), )) } } fn generate_instance_cert( app_name: String, ou_name: String, ) -> PrivateCertManagerClientResult<(String, String)> { let key_pair = KeyPair::generate()?; let mut params = CertificateParams::new(vec![format!("{}-{}.systeminit.com", ou_name, app_name)])?; let mut dn = DistinguishedName::new(); dn.push(DnType::CommonName, app_name); dn.push(DnType::OrganizationName, "System Initiative"); dn.push(DnType::OrganizationalUnitName, ou_name); params.key_usages.push(KeyUsagePurpose::DigitalSignature); params .extended_key_usages .push(ExtendedKeyUsagePurpose::ClientAuth); params.distinguished_name = dn; Ok(( params.serialize_request(&key_pair)?.pem()?, key_pair.serialize_pem(), )) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server