Got acme working, syncing for http_domain works
This commit is contained in:
parent
6d8799ce8c
commit
4e412d0677
1
.env.dev
1
.env.dev
@ -4,4 +4,3 @@ export DOMIPLY_ORIGIN_STORE_GIT_DIR_PATH=/tmp/domiply_dev_env/origin/git
|
|||||||
export DOMIPLY_DOMAIN_CHECKER_TARGET_A=127.0.0.1
|
export DOMIPLY_DOMAIN_CHECKER_TARGET_A=127.0.0.1
|
||||||
export DOMIPLY_DOMAIN_CONFIG_STORE_DIR_PATH=/tmp/domiply_dev_env/domain/config
|
export DOMIPLY_DOMAIN_CONFIG_STORE_DIR_PATH=/tmp/domiply_dev_env/domain/config
|
||||||
export DOMIPLY_DOMAIN_ACME_STORE_DIR_PATH=/tmp/domiply_dev_env/domain/acme
|
export DOMIPLY_DOMAIN_ACME_STORE_DIR_PATH=/tmp/domiply_dev_env/domain/acme
|
||||||
export DOMIPLY_DOMAIN_ACME_CONTACT_EMAIL=domiply@example.com
|
|
||||||
|
@ -46,8 +46,11 @@ where
|
|||||||
.await
|
.await
|
||||||
.map_unexpected()?;
|
.map_unexpected()?;
|
||||||
|
|
||||||
|
let mut contact = String::from("mailto:");
|
||||||
|
contact.push_str(contact_email);
|
||||||
|
|
||||||
let mut builder = acme2::AccountBuilder::new(dir);
|
let mut builder = acme2::AccountBuilder::new(dir);
|
||||||
builder.contact(vec![contact_email.to_string()]);
|
builder.contact(vec![contact]);
|
||||||
builder.terms_of_service_agreed(true);
|
builder.terms_of_service_agreed(true);
|
||||||
|
|
||||||
match store.get_account_key() {
|
match store.get_account_key() {
|
||||||
@ -77,6 +80,25 @@ where
|
|||||||
where Self: 'mgr;
|
where Self: 'mgr;
|
||||||
|
|
||||||
fn sync_domain(&self, domain: domain::Name) -> Self::SyncDomainFuture<'_> {
|
fn sync_domain(&self, domain: domain::Name) -> Self::SyncDomainFuture<'_> {
|
||||||
|
// if there's an existing cert, and its expiry (determined by the soonest value of
|
||||||
|
// not_after amongst its parts) is later than 30 days from now, then we consider it to be
|
||||||
|
// synced.
|
||||||
|
if let Ok(cert) = self.store.get_certificate(domain.as_str()) {
|
||||||
|
let thirty_days = openssl::asn1::Asn1Time::days_from_now(30)
|
||||||
|
.expect("parsed thirty days from now as Asn1Time");
|
||||||
|
|
||||||
|
let soonest_not_after = cert[1..]
|
||||||
|
.into_iter()
|
||||||
|
.map(|cert_part| cert_part.not_after())
|
||||||
|
.fold(cert[0].not_after(), |a, b| if a < b { a } else { b });
|
||||||
|
|
||||||
|
if thirty_days < soonest_not_after {
|
||||||
|
return Box::pin(future::ready(Ok(())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("fetching a new certificate for domain {}", domain.as_str());
|
||||||
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mut builder = acme2::OrderBuilder::new(self.account.clone());
|
let mut builder = acme2::OrderBuilder::new(self.account.clone());
|
||||||
builder.add_dns_identifier(domain.as_str().to_string());
|
builder.add_dns_identifier(domain.as_str().to_string());
|
||||||
@ -106,18 +128,27 @@ where
|
|||||||
// At this point the manager is prepared to serve the challenge key via the
|
// At this point the manager is prepared to serve the challenge key via the
|
||||||
// `get_http01_challenge_key` method. It is expected that there is some http
|
// `get_http01_challenge_key` method. It is expected that there is some http
|
||||||
// server, with this domain pointing at it, which is prepared to serve that
|
// server, with this domain pointing at it, which is prepared to serve that
|
||||||
// challenge token/key under the `/.well-known/` path. The `validate()` call below
|
// challenge token/key under the `/.well-known/acme-challenge` path. The
|
||||||
// will instigate the acme server to make this check, and block until it succeeds.
|
// `validate()` call below will instigate the acme server to make this check, and
|
||||||
|
// block until it succeeds.
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"waiting for ACME challenge to be validated for domain {}",
|
||||||
|
domain.as_str(),
|
||||||
|
);
|
||||||
let challenge = challenge.validate().await.map_unexpected()?;
|
let challenge = challenge.validate().await.map_unexpected()?;
|
||||||
|
|
||||||
// Poll the challenge every 5 seconds until it is in either the
|
// Poll the challenge every 5 seconds until it is in either the
|
||||||
// `valid` or `invalid` state.
|
// `valid` or `invalid` state.
|
||||||
let challenge = challenge
|
let challenge_res = challenge.wait_done(time::Duration::from_secs(5), 3).await;
|
||||||
.wait_done(time::Duration::from_secs(5), 3)
|
|
||||||
.await
|
// no matter what the result is, clean up the challenge key
|
||||||
|
self.store
|
||||||
|
.del_http01_challenge_key(&challenge_token)
|
||||||
.map_unexpected()?;
|
.map_unexpected()?;
|
||||||
|
|
||||||
|
let challenge = challenge_res.map_unexpected()?;
|
||||||
|
|
||||||
if challenge.status != acme2::ChallengeStatus::Valid {
|
if challenge.status != acme2::ChallengeStatus::Valid {
|
||||||
return Err(error::Unexpected::from(
|
return Err(error::Unexpected::from(
|
||||||
format!(
|
format!(
|
||||||
@ -128,12 +159,13 @@ where
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.store
|
|
||||||
.del_http01_challenge_key(&challenge_token)
|
|
||||||
.map_unexpected()?;
|
|
||||||
|
|
||||||
// Poll the authorization every 5 seconds until it is in either the
|
// Poll the authorization every 5 seconds until it is in either the
|
||||||
// `valid` or `invalid` state.
|
// `valid` or `invalid` state.
|
||||||
|
println!(
|
||||||
|
"waiting for ACME authorization to be validated for domain {}",
|
||||||
|
domain.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
let authorization = auth
|
let authorization = auth
|
||||||
.wait_done(time::Duration::from_secs(5), 3)
|
.wait_done(time::Duration::from_secs(5), 3)
|
||||||
.await
|
.await
|
||||||
@ -152,6 +184,11 @@ where
|
|||||||
|
|
||||||
// Poll the order every 5 seconds until it is in either the `ready` or `invalid` state.
|
// Poll the order every 5 seconds until it is in either the `ready` or `invalid` state.
|
||||||
// Ready means that it is now ready for finalization (certificate creation).
|
// Ready means that it is now ready for finalization (certificate creation).
|
||||||
|
println!(
|
||||||
|
"waiting for ACME order to be made ready for domain {}",
|
||||||
|
domain.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
let order = order
|
let order = order
|
||||||
.wait_ready(time::Duration::from_secs(5), 3)
|
.wait_ready(time::Duration::from_secs(5), 3)
|
||||||
.await
|
.await
|
||||||
@ -180,6 +217,10 @@ where
|
|||||||
// Poll the order every 5 seconds until it is in either the
|
// Poll the order every 5 seconds until it is in either the
|
||||||
// `valid` or `invalid` state. Valid means that the certificate
|
// `valid` or `invalid` state. Valid means that the certificate
|
||||||
// has been provisioned, and is now ready for download.
|
// has been provisioned, and is now ready for download.
|
||||||
|
println!(
|
||||||
|
"waiting for ACME order to be validated for domain {}",
|
||||||
|
domain.as_str(),
|
||||||
|
);
|
||||||
let order = order
|
let order = order
|
||||||
.wait_done(time::Duration::from_secs(5), 3)
|
.wait_done(time::Duration::from_secs(5), 3)
|
||||||
.await
|
.await
|
||||||
@ -196,6 +237,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Download the certificate, and panic if it doesn't exist.
|
// Download the certificate, and panic if it doesn't exist.
|
||||||
|
println!("fetching certificate for domain {}", domain.as_str());
|
||||||
let cert =
|
let cert =
|
||||||
order
|
order
|
||||||
.certificate()
|
.certificate()
|
||||||
@ -215,6 +257,11 @@ where
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("certificate for {} successfully retrieved", domain.as_str());
|
||||||
|
self.store
|
||||||
|
.set_certificate(domain.as_str(), cert)
|
||||||
|
.map_unexpected()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
20
src/main.rs
20
src/main.rs
@ -28,7 +28,8 @@ struct Cli {
|
|||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "E.g. '[::]:443', if given then SSL certs will automatically be retrieved for all domains using LetsEncrypt",
|
help = "E.g. '[::]:443', if given then SSL certs will automatically be retrieved for all domains using LetsEncrypt",
|
||||||
env = "DOMIPLY_HTTPS_LISTEN_ADDR"
|
env = "DOMIPLY_HTTPS_LISTEN_ADDR",
|
||||||
|
requires = "domain_acme_contact_email"
|
||||||
)]
|
)]
|
||||||
https_listen_addr: Option<SocketAddr>,
|
https_listen_addr: Option<SocketAddr>,
|
||||||
|
|
||||||
@ -50,8 +51,8 @@ struct Cli {
|
|||||||
#[arg(long, required = true, env = "DOMIPLY_DOMAIN_ACME_STORE_DIR_PATH")]
|
#[arg(long, required = true, env = "DOMIPLY_DOMAIN_ACME_STORE_DIR_PATH")]
|
||||||
domain_acme_store_dir_path: path::PathBuf,
|
domain_acme_store_dir_path: path::PathBuf,
|
||||||
|
|
||||||
#[arg(long, required = true, env = "DOMIPLY_DOMAIN_ACME_CONTACT_EMAIL")]
|
#[arg(long, env = "DOMIPLY_DOMAIN_ACME_CONTACT_EMAIL")]
|
||||||
domain_acme_contact_email: String,
|
domain_acme_contact_email: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -102,13 +103,14 @@ fn main() {
|
|||||||
domiply::domain::acme::store::new(&config.domain_acme_store_dir_path)
|
domiply::domain::acme::store::new(&config.domain_acme_store_dir_path)
|
||||||
.expect("domain acme store initialized");
|
.expect("domain acme store initialized");
|
||||||
|
|
||||||
|
// if https_listen_addr is set then domain_acme_contact_email is required, see the Cli/clap
|
||||||
|
// settings.
|
||||||
|
let domain_acme_contact_email = config.domain_acme_contact_email.unwrap();
|
||||||
|
|
||||||
let domain_acme_manager = tokio_runtime.block_on(async {
|
let domain_acme_manager = tokio_runtime.block_on(async {
|
||||||
domiply::domain::acme::manager::new(
|
domiply::domain::acme::manager::new(domain_acme_store, &domain_acme_contact_email)
|
||||||
domain_acme_store,
|
.await
|
||||||
&config.domain_acme_contact_email,
|
.expect("domain acme manager initialized")
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("domain acme manager initialized")
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(domain_acme_manager)
|
Some(domain_acme_manager)
|
||||||
|
@ -341,8 +341,8 @@ where
|
|||||||
return svc.render(200, path, ());
|
return svc.render(200, path, ());
|
||||||
}
|
}
|
||||||
|
|
||||||
if method == Method::GET && path.starts_with("/.well-known/") {
|
if method == Method::GET && path.starts_with("/.well-known/acme-challenge/") {
|
||||||
let token = path.trim_start_matches("/.well-known/");
|
let token = path.trim_start_matches("/.well-known/acme-challenge/");
|
||||||
|
|
||||||
if let Ok(key) = svc.domain_manager.get_acme_http01_challenge_key(token) {
|
if let Ok(key) = svc.domain_manager.get_acme_http01_challenge_key(token) {
|
||||||
let body: hyper::Body = key.into();
|
let body: hyper::Body = key.into();
|
||||||
|
Loading…
Reference in New Issue
Block a user