Browse Source

Add endpoints to the http client

ng
Rodolphe Bréard 4 weeks ago
parent
commit
fe3d21b515
Failed to extract signature
  1. 8
      src/config.rs
  2. 2
      src/config/endpoint.rs
  3. 78
      src/http.rs
  4. 17
      src/main.rs
  5. 2
      tests/config/simple/simple.toml
  6. 33
      tests/root_certs/igc-a 2011.pem
  7. 33
      tests/root_certs/igc-a 2018.pem

8
src/config.rs

@ -29,7 +29,7 @@ const ALLOWED_FILE_EXT: &[&str] = &["toml"];
pub struct AcmedConfig { pub struct AcmedConfig {
pub(in crate::config) global: Option<GlobalOptions>, pub(in crate::config) global: Option<GlobalOptions>,
#[serde(default)] #[serde(default)]
pub(in crate::config) endpoint: HashMap<String, Endpoint>,
pub endpoint: HashMap<String, Endpoint>,
#[serde(default, rename = "rate-limit")] #[serde(default, rename = "rate-limit")]
pub(in crate::config) rate_limit: HashMap<String, RateLimit>, pub(in crate::config) rate_limit: HashMap<String, RateLimit>,
#[serde(default)] #[serde(default)]
@ -42,6 +42,12 @@ pub struct AcmedConfig {
pub(in crate::config) certificate: Vec<Certificate>, pub(in crate::config) certificate: Vec<Certificate>,
} }
impl AcmedConfig {
pub fn get_global_root_certs(&self) -> Option<&[PathBuf]> {
self.global.as_ref().map(|g| g.root_certificates.as_slice())
}
}
impl<'de> Deserialize<'de> for AcmedConfig { impl<'de> Deserialize<'de> for AcmedConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where

2
src/config/endpoint.rs

@ -11,7 +11,7 @@ pub struct Endpoint {
pub(in crate::config) rate_limits: Vec<String>, pub(in crate::config) rate_limits: Vec<String>,
pub(in crate::config) renew_delay: Option<Duration>, pub(in crate::config) renew_delay: Option<Duration>,
#[serde(default)] #[serde(default)]
pub(in crate::config) root_certificates: Vec<PathBuf>,
pub root_certificates: Vec<PathBuf>,
#[serde(default)] #[serde(default)]
pub(in crate::config) tos_agreed: bool, pub(in crate::config) tos_agreed: bool,
pub(in crate::config) url: String, pub(in crate::config) url: String,

78
src/http.rs

@ -1,12 +1,17 @@
use crate::config::AcmedConfig; use crate::config::AcmedConfig;
use anyhow::{Context, Result};
use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::{header, Client, ClientBuilder, Request, Response};
use anyhow::Result;
use reqwest::header::HeaderMap;
use reqwest::{header, Certificate, Client, ClientBuilder, Request, Response};
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
#[derive(Debug)] #[derive(Debug)]
struct RawRequest { struct RawRequest {
request: Request, request: Request,
endpoint: String,
tx: oneshot::Sender<Result<Response, reqwest::Error>>, tx: oneshot::Sender<Result<Response, reqwest::Error>>,
} }
@ -16,9 +21,13 @@ pub struct HttpClient {
} }
impl HttpClient { impl HttpClient {
pub async fn send(&self, request: Request) -> Result<Response> {
pub async fn send<S: AsRef<str>>(&self, endpoint: S, request: Request) -> Result<Response> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let raw_req = RawRequest { request, tx };
let raw_req = RawRequest {
request,
endpoint: endpoint.as_ref().to_string(),
tx,
};
self.tx.send(raw_req)?; self.tx.send(raw_req)?;
let ret = rx.await?; let ret = rx.await?;
if let Err(ref e) = ret { if let Err(ref e) = ret {
@ -28,16 +37,29 @@ impl HttpClient {
} }
} }
#[derive(Debug)]
struct HttpEndpoint {
client: Client,
}
#[derive(Debug)] #[derive(Debug)]
pub(super) struct HttpRoutine { pub(super) struct HttpRoutine {
tx: mpsc::UnboundedSender<RawRequest>, tx: mpsc::UnboundedSender<RawRequest>,
rx: mpsc::UnboundedReceiver<RawRequest>, rx: mpsc::UnboundedReceiver<RawRequest>,
endpoints: HashMap<String, HttpEndpoint>,
} }
impl HttpRoutine { impl HttpRoutine {
pub(super) fn new(config: &AcmedConfig) -> Self { pub(super) fn new(config: &AcmedConfig) -> Self {
let (tx, mut rx) = mpsc::unbounded_channel();
Self { tx, rx }
let (tx, rx) = mpsc::unbounded_channel();
let mut endpoints = HashMap::with_capacity(config.endpoint.len());
for (name, edp) in &config.endpoint {
tracing::debug!("loading endpoint: {name}");
let client = get_http_client(config.get_global_root_certs(), &edp.root_certificates);
let endpoint = HttpEndpoint { client };
endpoints.insert(name.to_owned(), endpoint);
}
Self { tx, rx, endpoints }
} }
pub(super) fn get_client(&self) -> HttpClient { pub(super) fn get_client(&self) -> HttpClient {
@ -48,16 +70,40 @@ impl HttpRoutine {
pub(super) async fn run(mut self) { pub(super) async fn run(mut self) {
tracing::trace!("starting the http routine"); tracing::trace!("starting the http routine");
let client = self.get_http_client();
while let Some(raw_req) = self.rx.recv().await { while let Some(raw_req) = self.rx.recv().await {
tracing::debug!("new http request: {:?}", raw_req.request); tracing::debug!("new http request: {:?}", raw_req.request);
let ret = client.execute(raw_req.request).await;
match self.endpoints.get(&raw_req.endpoint) {
Some(edp) => {
let ret = edp.client.execute(raw_req.request).await;
let _ = raw_req.tx.send(ret); let _ = raw_req.tx.send(ret);
} }
None => {
tracing::error!("{}: endpoint not found", raw_req.endpoint);
}
}
}
tracing::warn!("the http routine has stopped"); tracing::warn!("the http routine has stopped");
} }
}
fn get_http_client(&self) -> Client {
macro_rules! add_root_cert {
($builder: ident, $iter: ident) => {
for cert_path in $iter {
match get_cert_pem(cert_path) {
Ok(cert) => {
$builder = $builder.add_root_certificate(cert);
tracing::debug!("root certificate loaded: {}", cert_path.display());
}
Err(e) => tracing::error!(
"{} unable to load root certificate: {e:#?}",
cert_path.display()
),
}
}
};
}
fn get_http_client(base_certs_opt: Option<&[PathBuf]>, end_certs: &[PathBuf]) -> Client {
let useragent = format!( let useragent = format!(
"{}/{} ({}) {}", "{}/{} ({}) {}",
env!("CARGO_BIN_NAME"), env!("CARGO_BIN_NAME"),
@ -70,6 +116,16 @@ impl HttpRoutine {
default_headers.append(header::ACCEPT_LANGUAGE, "en-US,en;q=0.5".parse().unwrap()); default_headers.append(header::ACCEPT_LANGUAGE, "en-US,en;q=0.5".parse().unwrap());
default_headers.append(header::USER_AGENT, useragent.parse().unwrap()); default_headers.append(header::USER_AGENT, useragent.parse().unwrap());
client_builder = client_builder.default_headers(default_headers); client_builder = client_builder.default_headers(default_headers);
client_builder.build().unwrap()
if let Some(base_certs) = base_certs_opt {
add_root_cert!(client_builder, base_certs);
} }
add_root_cert!(client_builder, end_certs);
client_builder.build().unwrap()
}
fn get_cert_pem(cert_path: &Path) -> Result<Certificate> {
let mut buff = Vec::new();
File::open(cert_path)?.read_to_end(&mut buff)?;
let crt = reqwest::Certificate::from_pem(&buff)?;
Ok(crt)
} }

17
src/main.rs

@ -4,7 +4,7 @@ mod http;
mod log; mod log;
use crate::config::AcmedConfig; use crate::config::AcmedConfig;
use crate::http::{HttpClient, HttpRoutine};
use crate::http::HttpRoutine;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use clap::Parser; use clap::Parser;
use daemonize::Daemonize; use daemonize::Daemonize;
@ -60,17 +60,20 @@ async fn start(cnf: AcmedConfig) {
// TODO: REMOVE ME // TODO: REMOVE ME
use reqwest::{Method, Request, Url}; use reqwest::{Method, Request, Url};
let rsp = http_client let rsp = http_client
.send(Request::new(
Method::GET,
Url::parse("https://example.org").unwrap(),
))
.send(
"my-ca",
Request::new(Method::GET, Url::parse("https://example.org").unwrap()),
)
.await; .await;
tracing::debug!("{rsp:?}"); tracing::debug!("{rsp:?}");
let rsp = http_client let rsp = http_client
.send(Request::new(
.send(
"my-ca",
Request::new(
Method::GET, Method::GET,
Url::parse("https://example.com/directory/").unwrap(), Url::parse("https://example.com/directory/").unwrap(),
))
),
)
.await; .await;
tracing::debug!("{rsp:?}"); tracing::debug!("{rsp:?}");
} }

2
tests/config/simple/simple.toml

@ -1,6 +1,7 @@
[global] [global]
accounts_directory = "/tmp/example/account/dir" accounts_directory = "/tmp/example/account/dir"
certificates_directory = "/tmp/example/cert/dir/" certificates_directory = "/tmp/example/cert/dir/"
root_certificates = ["tests/root_certs/igc-a 2011.pem"]
[account."toto"] [account."toto"]
contacts = [ contacts = [
@ -15,6 +16,7 @@ period = "1s"
[endpoint."my-ca"] [endpoint."my-ca"]
url = "https://acme-v02.ac1.example.org/directory" url = "https://acme-v02.ac1.example.org/directory"
rate_limits = ["my-ca-limit"] rate_limits = ["my-ca-limit"]
root_certificates = ["tests/root_certs/igc-a 2018.pem"]
tos_agreed = true tos_agreed = true
[hook."hook-1"] [hook."hook-1"]

33
tests/root_certs/igc-a 2011.pem

@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFwzCCA6ugAwIBAgISESGFDLOcajL6vmcbgT+khhWPMA0GCSqGSIb3DQEBCwUA
MF4xCzAJBgNVBAYTAkZSMQ4wDAYDVQQKEwVBTlNTSTEXMBUGA1UECxMOMDAwMiAx
MzAwMDc2NjkxJjAkBgNVBAMTHUlHQy9BIEFDIHJhY2luZSBFdGF0IGZyYW5jYWlz
MB4XDTExMDcwODA5MDAwMFoXDTI4MDQxNTA5MDAwMFowXjELMAkGA1UEBhMCRlIx
DjAMBgNVBAoTBUFOU1NJMRcwFQYDVQQLEw4wMDAyIDEzMDAwNzY2OTEmMCQGA1UE
AxMdSUdDL0EgQUMgcmFjaW5lIEV0YXQgZnJhbmNhaXMwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCqfCifETCYzW9uLIUSJjsIBspB/VJPQ73AJidxdhpZ
ltgJ6weqJk5PPkuh45eHhWaBccm5FXZvd1AYkxAtN4hNF7fzRb0iLrcnmFvHBf29
M+2i9VMdKCNlv0A1bs5qC8Op9SUMqyLwuMDEfTcMo2J87rTbPSE5p5yJ45uiEPiK
tkovLphpK2qghtrxCOW+TGcWLSVh89UNCxdERwnURgWdD8CITWHkJMTHaAmvrNKv
uZUmb4AE/HasqscjtuQGkVVE7GTbmYEc0lZ0/dYyKLvLyTcN+2lsb7qjawaMakAu
Fzo56tAM31ocum+kMrC4zD53G9OLH4b6/z4+b1yIRufjD/qrHfN9S/hUbk7M3DJa
Y3iiMq8zeOpD4Ux6TdeUBi3mT6VCkq8oik/DFeypa6nf4N0TArzMff8t5gepvnWW
6kJeWxreojOzY72rBfmL5r1N0W1WmuuJPJ/AeOS+JXAGxRFzoMjKFMs61PKcKjza
Xxcz2XYUN6pJh2XZ9NkuGV/5oM2ouUEybXGmpMv3YyLQKeS6gRpqKR2apaRcRlQk
RdTI7Xp5heyEd25nTWQPQ956g6Sn2Nu1U0z+YsgTw2I2pSgxMpu0lofimcYfVr9G
o6lkMeXVsUuoZsxbof8W/Ao4KmiPdyUmrZF0hWjIfxrlWhS4fQ63IzHAZLcFL0FY
VQIDAQABo3sweTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAWBgNV
HSAEDzANMAsGCSqBegGBXwEBAjAdBgNVHQ4EFgQUn6rTKZbfAOVD4PFjrN4SjsIn
ePowHwYDVR0jBBgwFoAUn6rTKZbfAOVD4PFjrN4SjsInePowDQYJKoZIhvcNAQEL
BQADggIBAHW1ddGONmacSPeFDU4Fu02anLQOKKIEvFAwu/SUTJiQhavgUmRP0tIu
YpOQsIUNiFT7xlRsnuuVeYBeopcWH/JndEGcVfS3aptKFoa9BR9mgHB+ydH1LSFx
UDmlrYimJhyL1yUcOtbj9MIMn1fBZMhXUSMWI40PI2pWS//6xp81k8YiwGXxr96p
bBi+V2VZzfQjVWQh2O2VYWkzcmpR9p/llW2O3mtzJxOUXn6XSMAyFr49N+3W3I68
XC38YqjP9pD3sYsJ6zokYw3IlkXUL3dIQvUtYucnC+ARhhndpxD3YwaRMGladfSs
+aGNl8ag7zofkyVIVjoaiCEZk8OVIEkIVUlNolOcmZxzaS6n9cq3DiXvNyNfkNhD
fu6EF2onXn/SLT+sPq8wp42RxPSPCR3z95EO4xi63ETJfQVTA7duoPN519EaT9C4
bIh2wYCYVYVTYc9EV0zeTg0WUfE9iYGufQutirXuVsTGzBELGNT8/Xn7/gQRnCPv
dnLHjb65Hnh28pocrWNCx9jtbWGQwiEqDwgULSBDJXwYtbegpH25pQwZ/smrPedb
3q/6VxknhecjDvTNDRkwPorkxhEe8LR9aWObDpaGkOD7A29bWT4dIfVXZ1Ym8ocZ
B4S6LJA6wyikBVogzalblXU5fyJQCk5/F/ezrNMHpr4tUgowTHgQ
-----END CERTIFICATE-----

33
tests/root_certs/igc-a 2018.pem

@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----
Loading…
Cancel
Save