instant-acme/examples/provision.rs

156 lines
4.7 KiB
Rust
Raw Normal View History

2022-11-30 00:17:58 +00:00
use std::{io, time::Duration};
use clap::Parser;
use rcgen::{Certificate, CertificateParams, DistinguishedName};
use tokio::time::sleep;
use tracing::{error, info};
use instant_acme::{
Account, AuthorizationStatus, ChallengeType, Identifier, LetsEncrypt, NewAccount, NewOrder,
OrderStatus,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let opts = Options::parse();
// Create a new account. This will generate a fresh ECDSA key for you.
// Alternatively, restore an account from serialized credentials by
// using `Account::from_credentials()`.
let (account, credentials) = Account::create(
2022-11-30 00:17:58 +00:00
&NewAccount {
contact: &[],
terms_of_service_agreed: true,
only_return_existing: false,
},
LetsEncrypt::Staging.url(),
None,
2022-11-30 00:17:58 +00:00
)
.await?;
info!(
"account credentials:\n\n{}",
serde_json::to_string_pretty(&credentials).unwrap()
);
2022-11-30 00:17:58 +00:00
// Create the ACME order based on the given domain names.
// Note that this only needs an `&Account`, so the library will let you
// process multiple orders in parallel for a single account.
let identifier = Identifier::Dns(opts.name);
2023-03-15 15:26:52 +00:00
let mut order = account
2022-11-30 00:17:58 +00:00
.new_order(&NewOrder {
identifiers: &[identifier],
})
.await
.unwrap();
2023-03-15 15:26:52 +00:00
let state = order.state();
2022-11-30 00:17:58 +00:00
info!("order state: {:#?}", state);
assert!(matches!(state.status, OrderStatus::Pending));
// Pick the desired challenge type and prepare the response.
2023-03-15 15:26:52 +00:00
let authorizations = order.authorizations().await.unwrap();
2022-11-30 00:17:58 +00:00
let mut challenges = Vec::with_capacity(authorizations.len());
for authz in &authorizations {
match authz.status {
AuthorizationStatus::Pending => {}
AuthorizationStatus::Valid => continue,
_ => todo!(),
}
// We'll use the DNS challenges for this example, but you could
// pick something else to use here.
let challenge = authz
.challenges
.iter()
.find(|c| c.r#type == ChallengeType::Dns01)
.ok_or_else(|| anyhow::anyhow!("no dns01 challenge found"))?;
let Identifier::Dns(identifier) = &authz.identifier;
2023-08-01 08:17:16 +00:00
println!("Please set the following DNS record then press the Return key:");
2022-11-30 00:17:58 +00:00
println!(
"_acme-challenge.{} IN TXT {}",
identifier,
order.key_authorization(challenge).dns_value()
);
io::stdin().read_line(&mut String::new()).unwrap();
challenges.push((identifier, &challenge.url));
}
// Let the server know we're ready to accept the challenges.
for (_, url) in &challenges {
order.set_challenge_ready(url).await.unwrap();
}
// Exponentially back off until the order becomes ready or invalid.
let mut tries = 1u8;
let mut delay = Duration::from_millis(250);
2023-03-15 15:26:52 +00:00
loop {
2022-11-30 00:17:58 +00:00
sleep(delay).await;
2023-03-15 15:26:52 +00:00
let state = order.refresh().await.unwrap();
2022-11-30 00:17:58 +00:00
if let OrderStatus::Ready | OrderStatus::Invalid = state.status {
info!("order state: {:#?}", state);
2023-03-15 15:26:52 +00:00
break;
2022-11-30 00:17:58 +00:00
}
delay *= 2;
tries += 1;
match tries < 5 {
true => info!(?state, tries, "order is not ready, waiting {delay:?}"),
false => {
2023-08-01 08:17:16 +00:00
error!(tries, "order is not ready: {state:#?}");
2022-11-30 00:17:58 +00:00
return Err(anyhow::anyhow!("order is not ready"));
}
}
2023-03-15 15:26:52 +00:00
}
2022-11-30 00:17:58 +00:00
2023-03-15 15:26:52 +00:00
let state = order.state();
if state.status != OrderStatus::Ready {
return Err(anyhow::anyhow!(
"unexpected order status: {:?}",
state.status
));
2022-11-30 00:17:58 +00:00
}
let mut names = Vec::with_capacity(challenges.len());
for (identifier, _) in challenges {
names.push(identifier.to_owned());
}
// If the order is ready, we can provision the certificate.
// Use the rcgen library to create a Certificate Signing Request.
let mut params = CertificateParams::new(names.clone());
params.distinguished_name = DistinguishedName::new();
let cert = Certificate::from_params(params).unwrap();
let csr = cert.serialize_request_der()?;
// Finalize the order and print certificate chain, private key and account credentials.
order.finalize(&csr).await.unwrap();
let cert_chain_pem = loop {
match order.certificate().await.unwrap() {
Some(cert_chain_pem) => break cert_chain_pem,
None => sleep(Duration::from_secs(1)).await,
}
};
info!("certficate chain:\n\n{}", cert_chain_pem);
2022-11-30 00:17:58 +00:00
info!("private key:\n\n{}", cert.serialize_private_key_pem());
Ok(())
}
#[derive(Parser)]
struct Options {
#[clap(long)]
name: String,
}