dns challenge for aliyun works

This commit is contained in:
2022-02-03 16:55:13 +08:00
parent 53e71c62d6
commit 8d8b1c24f0
5 changed files with 240 additions and 110 deletions

View File

@@ -3,6 +3,7 @@ extern crate lazy_static;
#[macro_use]
extern crate rust_util;
mod util;
mod config;
mod x509;
mod network;
@@ -28,14 +29,16 @@ use async_std::task;
use async_std::channel;
use async_std::channel::Sender;
use config::AcmeMode;
use crate::config::{CertConfig, CERT_NAME, KEY_NAME};
use crate::config::{AcmeChallenge, CertConfig, CERT_NAME, KEY_NAME};
use crate::x509::{X509PublicKeyAlgo, X509EcPublicKeyAlgo};
use std::path::PathBuf;
use aliyun_openapi_core_rust_sdk::RPClient;
use rust_util::util_cmd::run_command_and_wait;
use crate::AcmeChallenge::Http;
use crate::ali_dns::{add_txt_dns_record, build_dns_client, delete_dns_record, list_dns, simple_parse_aliyun_supplier};
use crate::dingtalk::send_dingtalk_message;
use crate::network::{get_local_public_ip, get_resolver, resolve_first_ipv4};
use crate::statics::{AcmeStatics, AcmeStatus};
use crate::util::parse_dns_record;
const NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -46,21 +49,12 @@ lazy_static! {
static ref TOKEN_MAP: RwLock<BTreeMap<String, String>> = RwLock::new(BTreeMap::new());
}
#[derive(Debug, Clone, Copy)]
enum AcmeChallenge {
Http,
Dns,
}
impl Default for AcmeChallenge {
fn default() -> Self {
Http
}
}
#[derive(Debug, Default)]
struct AcmeRequest<'a> {
challenge: AcmeChallenge,
// issue, single acme request can only process one supplier
credential_supplier: Option<&'a str>,
allow_interact: bool,
contract_email: &'a str,
primary_name: &'a str,
alt_names: &'a [&'a str],
@@ -97,6 +91,7 @@ async fn main() -> tide::Result<()> {
.arg(Arg::with_name("hide-logo").long("hide-logo").help("Hide logo"))
.arg(Arg::with_name("skip-verify-ip").short("k").long("skip-verify-ip").help("Skip verify public ip"))
.arg(Arg::with_name("skip-verify-certificate").short("K").long("skip-verify-certificate").help("Skip verify certificate"))
.arg(Arg::with_name("allow-interact").long("allow-interact").help("Allow interact"))
.get_matches();
if matches.is_present("verbose") {
@@ -227,6 +222,7 @@ async fn main() -> tide::Result<()> {
task::sleep(Duration::from_millis(500)).await;
}
let mut dns_cleaned_domains: Vec<String> = vec![];
match cert_config {
None => { // cert config is not assigned
if check {
@@ -253,7 +249,9 @@ async fn main() -> tide::Result<()> {
};
let acme_request = AcmeRequest {
challenge: Http,
challenge: AcmeChallenge::Http,
credential_supplier: None,
allow_interact: false,
contract_email: &email,
primary_name,
alt_names: &alt_names,
@@ -267,7 +265,7 @@ async fn main() -> tide::Result<()> {
outputs_file: matches.value_of("outputs").map(|s| s.into()),
..Default::default()
};
if let Err(e) = request_acme_certificate(acme_request) {
if let Err(e) = request_acme_certificate(acme_request, &mut dns_cleaned_domains) {
failure!("Request certificate by acme failed: {}", e);
exit(1);
}
@@ -282,14 +280,35 @@ async fn main() -> tide::Result<()> {
for item in &filtered_cert_config.cert_items {
if item.common_name.as_ref().map(|n| n.contains('*')).unwrap_or(false)
|| item.dns_names.as_ref().map(|dns_names| dns_names.iter().any(|n| n.contains('*'))).unwrap_or(false) {
warning!("Currently not support wide card domain name");
continue;
if item.get_acme_challenge() != AcmeChallenge::Dns {
warning!("Currently not support wide card domain name");
continue;
}
}
if let (Some(common_name), Some(dns_names)) = (&item.common_name, &item.dns_names) {
information!("Domains, main: {}, alt: {:?}", common_name, dns_names);
let alt_names: Vec<&str> = dns_names.iter().map(|n| n.as_str()).collect();
let challenge = item.get_acme_challenge();
let credential_supplier = if challenge == AcmeChallenge::Dns {
match &item.supplier {
None => None,
Some(supplier) => {
let credential_supplier = filtered_cert_config.credential_suppliers.as_ref()
.map(|m| m.get(supplier)).flatten();
match credential_supplier {
None => {
warning!("DNS challenge no credential supplier found");
None
}
Some(credential_supplier) => Some(credential_supplier.as_str()),
}
}
}
} else { None };
let acme_request = AcmeRequest {
challenge: Http,
challenge,
credential_supplier,
allow_interact: matches.is_present("allow-interact"),
contract_email: &email,
primary_name: common_name,
alt_names: &alt_names,
@@ -304,7 +323,7 @@ async fn main() -> tide::Result<()> {
};
let mut domains = vec![common_name.clone()];
dns_names.iter().for_each(|dns_name| domains.push(dns_name.clone()));
if let Err(e) = request_acme_certificate(acme_request) {
if let Err(e) = request_acme_certificate(acme_request, &mut dns_cleaned_domains) {
failure!("Request certificate: {}, by acme failed: {}", item.path, e);
acme_statics.add_item(domains, AcmeStatus::Fail(format!("{}", e)));
} else {
@@ -411,7 +430,7 @@ fn check_cert_config(cert_config: &CertConfig) {
}
}
fn request_acme_certificate(acme_request: AcmeRequest) -> XResult<()> {
fn request_acme_certificate(acme_request: AcmeRequest, dns_cleaned_domains: &mut Vec<String>) -> XResult<()> {
if let Some(local_public_ip) = acme_request.local_public_ip {
let mut all_domains = vec![acme_request.primary_name.to_string()];
for alt_name in acme_request.alt_names {
@@ -420,13 +439,15 @@ fn request_acme_certificate(acme_request: AcmeRequest) -> XResult<()> {
information!("Checking domain dns records, domains: {:?}", all_domains);
let resolver = opt_result!(get_resolver(), "Get resolver failed: {}");
for domain in &all_domains {
debugging!("Checking domain: {}", domain);
let ipv4 = opt_result!(resolve_first_ipv4(&resolver, domain), "{}");
match ipv4 {
None => return simple_error!("Resolve domain ip failed: {}", domain),
Some(ipv4) => if local_public_ip != ipv4 {
return simple_error!("Check domain ip: {}, mis-match, local: {} vs domain: {}", domain, local_public_ip, ipv4);
if acme_request.challenge == AcmeChallenge::Http {
for domain in &all_domains {
debugging!("Checking domain: {}", domain);
let ipv4 = opt_result!(resolve_first_ipv4(&resolver, domain), "{}");
match ipv4 {
None => return simple_error!("Resolve domain ip failed: {}", domain),
Some(ipv4) => if local_public_ip != ipv4 {
return simple_error!("Check domain ip: {}, mis-match, local: {} vs domain: {}", domain, local_public_ip, ipv4);
}
}
}
}
@@ -438,6 +459,11 @@ fn request_acme_certificate(acme_request: AcmeRequest) -> XResult<()> {
let dir = opt_result!(Directory::from_url(persist, url), "Create directory from url failed: {}");
let acc = opt_result!(dir.account(acme_request.contract_email), "Directory set account failed: {}");
let mut ord_new = opt_result!( acc.new_order(acme_request.primary_name, acme_request.alt_names), "Create order failed: {}");
let ali_yun_client: Option<RPClient> = match acme_request.credential_supplier {
Some(credential_supplier) => Some(build_dns_client(
&opt_result!(simple_parse_aliyun_supplier(credential_supplier), "Parse credential supplier failed: {}"))),
None => None,
};
let mut order_csr_index = 0;
let ord_csr = loop {
@@ -453,7 +479,7 @@ fn request_acme_certificate(acme_request: AcmeRequest) -> XResult<()> {
let auths = opt_result!(ord_new.authorizations(), "Order auth failed: {}");
for auth in &auths {
match acme_request.challenge {
Http => {
AcmeChallenge::Http => {
let chall = auth.http_challenge();
let token = chall.http_token();
let proof = chall.http_proof();
@@ -465,11 +491,53 @@ fn request_acme_certificate(acme_request: AcmeRequest) -> XResult<()> {
debugging!("Valid acme certificate http challenge");
opt_result!(chall.validate(acme_request.timeout), "Validate http challenge failed: {}");
}
Dns => {
AcmeChallenge::Dns => {
let chall = auth.dns_challenge();
let record = format!("_acme-challenge.{}.", auth.domain_name());
let proof = chall.dns_proof();
information!("Add acme dns challenge: {} -> {}",record, proof);
information!("Add acme dns challenge: {} -> {}", record, proof);
let rr_and_domain = opt_result!(parse_dns_record(&record), "Parse record to rr&domain failed: {}");
if !dns_cleaned_domains.contains(&rr_and_domain.1) {
information!("Clearing domain: {}", &rr_and_domain.1);
dns_cleaned_domains.push(rr_and_domain.1.clone());
ali_yun_client.as_ref().map(|client| {
match list_dns(client, &rr_and_domain.1) {
Err(e) => warning!("List dns for: {}, failed: {}", &rr_and_domain.1, e),
Ok(Err(e)) => warning!("List dns for: {}, failed: {:?}", &rr_and_domain.1, e),
Ok(Ok(s)) => {
for r in &s.domain_records.record {
let rr = &r.rr;
if rr == "_acme-challenge" || rr.starts_with("_acme-challenge.") {
match delete_dns_record(client, &r.record_id) {
Err(e) => warning!("Delete dns: {}.{}, failed: {}", r.rr, r.domain_name, e),
Ok(Err(e)) => warning!("Delete dns: {}.{}, failed: {:?}", r.rr, r.domain_name, e),
Ok(Ok(_)) => success!("Delete dns: {}.{}", r.rr, r.domain_name),
}
}
}
}
}
});
}
match &ali_yun_client {
Some(client) => {
let add_txt_dns_result = opt_result!(add_txt_dns_record(client, &rr_and_domain.1, &rr_and_domain.0, &proof), "Add DNS TXT record failed: {}");
match add_txt_dns_result {
Ok(s) => success!("Add dns txt record successes: {}", s.record_id),
Err(e) => return simple_error!("Add dns txt record failed: {:?}", e),
}
}
None => if acme_request.allow_interact {
let mut line = String::new();
information!("You need to config dns manually, press enter to continue...");
let _ = std::io::stdin().read_line(&mut line).unwrap();
} else {
return simple_error!("Interact is not allowed, --allow-interact to allow interact");
}
}
debugging!("Valid acme certificate dns challenge");
opt_result!(chall.validate(acme_request.timeout), "Validate dns challenge failed: {}");