mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-10-24 17:29:08 +00:00
Letsencrypt works
This commit is contained in:
@@ -1,16 +1,27 @@
|
||||
use rcgen::{Certificate, CertificateParams, DistinguishedName, DnType, SanType, KeyPair, PKCS_ECDSA_P256_SHA256};
|
||||
use std::net::IpAddr;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::database::repository::DnsProviderRepository;
|
||||
use crate::database::entities::dns_provider::DnsProviderType;
|
||||
use crate::services::acme::{AcmeClient, AcmeError};
|
||||
use sea_orm::DatabaseConnection;
|
||||
|
||||
/// Certificate management service
|
||||
#[derive(Clone)]
|
||||
pub struct CertificateService {
|
||||
db: Option<DatabaseConnection>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl CertificateService {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
Self { db: None }
|
||||
}
|
||||
|
||||
pub fn with_db(db: DatabaseConnection) -> Self {
|
||||
Self { db: Some(db) }
|
||||
}
|
||||
|
||||
/// Generate self-signed certificate optimized for Xray
|
||||
@@ -87,11 +98,132 @@ impl CertificateService {
|
||||
}
|
||||
|
||||
|
||||
/// Renew certificate
|
||||
/// Generate Let's Encrypt certificate using DNS challenge
|
||||
pub async fn generate_letsencrypt_certificate(
|
||||
&self,
|
||||
domain: &str,
|
||||
dns_provider_id: Uuid,
|
||||
acme_email: &str,
|
||||
staging: bool,
|
||||
) -> Result<(String, String), AcmeError> {
|
||||
tracing::info!("Generating Let's Encrypt certificate for domain: {} using DNS challenge", domain);
|
||||
|
||||
// Get database connection
|
||||
let db = self.db.as_ref()
|
||||
.ok_or_else(|| AcmeError::DnsProviderNotFound)?;
|
||||
|
||||
// Get DNS provider
|
||||
let dns_repo = DnsProviderRepository::new(db.clone());
|
||||
let dns_provider = dns_repo.find_by_id(dns_provider_id)
|
||||
.await
|
||||
.map_err(|_| AcmeError::DnsProviderNotFound)?
|
||||
.ok_or_else(|| AcmeError::DnsProviderNotFound)?;
|
||||
|
||||
// Verify provider is Cloudflare (only supported provider for now)
|
||||
if dns_provider.provider_type != DnsProviderType::Cloudflare.as_str() {
|
||||
return Err(AcmeError::CloudflareApi("Only Cloudflare provider is supported".to_string()));
|
||||
}
|
||||
|
||||
if !dns_provider.is_active {
|
||||
return Err(AcmeError::DnsProviderNotFound);
|
||||
}
|
||||
|
||||
// Determine ACME directory URL
|
||||
let directory_url = if staging {
|
||||
"https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
} else {
|
||||
"https://acme-v02.api.letsencrypt.org/directory"
|
||||
};
|
||||
|
||||
// Create ACME client
|
||||
let mut acme_client = AcmeClient::new(
|
||||
dns_provider.api_token.clone(),
|
||||
acme_email,
|
||||
directory_url.to_string(),
|
||||
).await?;
|
||||
|
||||
// Get base domain for DNS operations
|
||||
let base_domain = AcmeClient::get_base_domain(domain)?;
|
||||
|
||||
// Generate certificate
|
||||
let (cert_pem, key_pem) = acme_client
|
||||
.get_certificate(domain, &base_domain)
|
||||
.await?;
|
||||
|
||||
tracing::info!("Successfully generated Let's Encrypt certificate for domain: {}", domain);
|
||||
Ok((cert_pem, key_pem))
|
||||
}
|
||||
|
||||
/// Renew certificate by ID (used for manual renewal)
|
||||
pub async fn renew_certificate_by_id(&self, cert_id: Uuid) -> anyhow::Result<(String, String)> {
|
||||
let db = self.db.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Database connection not available"))?;
|
||||
|
||||
// Get the certificate from database
|
||||
let cert_repo = crate::database::repository::CertificateRepository::new(db.clone());
|
||||
let certificate = cert_repo.find_by_id(cert_id)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("Certificate not found"))?;
|
||||
|
||||
tracing::info!("Renewing certificate '{}' for domain: {}", certificate.name, certificate.domain);
|
||||
|
||||
match certificate.cert_type.as_str() {
|
||||
"letsencrypt" => {
|
||||
// For Let's Encrypt, we need to regenerate using ACME
|
||||
// Find an active Cloudflare DNS provider
|
||||
let dns_repo = crate::database::repository::DnsProviderRepository::new(db.clone());
|
||||
let providers = dns_repo.find_active_by_type("cloudflare").await?;
|
||||
|
||||
if providers.is_empty() {
|
||||
return Err(anyhow::anyhow!("No active Cloudflare DNS provider found for Let's Encrypt renewal"));
|
||||
}
|
||||
|
||||
let dns_provider = &providers[0];
|
||||
let acme_email = "admin@example.com"; // TODO: Store this with certificate
|
||||
|
||||
// Generate new certificate
|
||||
let (cert_pem, key_pem) = self.generate_letsencrypt_certificate(
|
||||
&certificate.domain,
|
||||
dns_provider.id,
|
||||
acme_email,
|
||||
false, // Production
|
||||
).await?;
|
||||
|
||||
// Update in database
|
||||
cert_repo.update_certificate_data(
|
||||
cert_id,
|
||||
&cert_pem,
|
||||
&key_pem,
|
||||
chrono::Utc::now() + chrono::Duration::days(90),
|
||||
).await?;
|
||||
|
||||
Ok((cert_pem, key_pem))
|
||||
}
|
||||
"self_signed" => {
|
||||
// For self-signed, generate a new one
|
||||
let (cert_pem, key_pem) = self.generate_self_signed(&certificate.domain).await?;
|
||||
|
||||
// Update in database
|
||||
cert_repo.update_certificate_data(
|
||||
cert_id,
|
||||
&cert_pem,
|
||||
&key_pem,
|
||||
chrono::Utc::now() + chrono::Duration::days(365),
|
||||
).await?;
|
||||
|
||||
Ok((cert_pem, key_pem))
|
||||
}
|
||||
_ => {
|
||||
Err(anyhow::anyhow!("Cannot renew imported certificates automatically"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Renew certificate (legacy method for backward compatibility)
|
||||
pub async fn renew_certificate(&self, domain: &str) -> anyhow::Result<(String, String)> {
|
||||
tracing::info!("Renewing certificate for domain: {}", domain);
|
||||
|
||||
// For now, just generate a new self-signed certificate
|
||||
// For backward compatibility, just generate a new self-signed certificate
|
||||
self.generate_self_signed(domain).await
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user