Skip to content

Commit 03623d3

Browse files
committed
Remove openssl #192
1 parent 8e01e28 commit 03623d3

File tree

8 files changed

+214
-346
lines changed

8 files changed

+214
-346
lines changed

CONTRIBUTE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ If you want to build `atomic-server` for some other target (e.g. building for li
5353
```sh
5454
cargo install cross
5555
# make sure docker is running!
56-
cross build --target x86_64-unknown-linux-musl
56+
cross build --target x86_64-unknown-linux-musl --bin atomic-server --release
5757
```
5858

5959
## IDE setup (VSCode)
@@ -230,7 +230,7 @@ or:
230230
or do it manually:
231231

232232
1. `cd server`
233-
1. `cargo build --release --target x86_64-unknown-linux-gnu`
233+
1. `cargo build --release --target x86_64-unknown-linux-musl --bin atomic-server` (if it fails, use cross, see above)
234234
1. `scp ../target/x86_64-unknown-linux-gnu/release/atomic-server atomic:~/atomic/server/atomic-server-v0.{version}`
235235
1. `ssh atomic` (@joepio manages server)
236236
2. `service atomic restart`

dockerfile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
FROM frolvlad/alpine-rust as builder
22
WORKDIR /app
33
COPY . .
4-
RUN apk add --no-cache openssl
5-
RUN apk add --no-cache openssl-dev
64
RUN cargo build --release --bin atomic-server
75

86
# We only need a small runtime for this step, but make sure glibc is installed

server/src/bin.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ mod handlers;
1111
mod helpers;
1212
#[cfg(feature = "https")]
1313
mod https;
14-
#[cfg(feature = "https_init")]
15-
mod https_init;
1614
mod jsonerrors;
1715
#[cfg(feature = "process-management")]
1816
mod process;

server/src/errors.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,17 +166,6 @@ impl From<tantivy::TantivyError> for AtomicServerError {
166166
}
167167
}
168168

169-
#[cfg(feature = "https_init")]
170-
impl From<acme_lib::Error> for AtomicServerError {
171-
fn from(error: acme_lib::Error) -> Self {
172-
AtomicServerError {
173-
message: error.to_string(),
174-
error_type: AppErrorType::Other,
175-
error_resource: None,
176-
}
177-
}
178-
}
179-
180169
impl From<actix_web::Error> for AtomicServerError {
181170
fn from(error: actix_web::Error) -> Self {
182171
AtomicServerError {

server/src/https.rs

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,214 @@ pub fn should_renew_certs_check(config: &crate::config::Config) -> bool {
7979
};
8080
expired
8181
}
82+
83+
use actix_web::{App, HttpServer};
84+
use instant_acme::OrderStatus;
85+
use tracing::info;
86+
87+
use std::sync::mpsc;
88+
89+
/// Starts an HTTP Actix server for HTTPS certificate initialization
90+
pub async fn cert_init_server(config: &crate::config::Config) -> AtomicServerResult<()> {
91+
let address = format!("{}:{}", config.opts.ip, config.opts.port);
92+
tracing::warn!("Server temporarily running in HTTP mode at {}, running Let's Encrypt Certificate initialization...", address);
93+
94+
if config.opts.port != 80 {
95+
tracing::warn!(
96+
"HTTP port is {}, not 80. Should be 80 in most cases during LetsEncrypt setup.",
97+
config.opts.port
98+
);
99+
}
100+
101+
let mut well_known_folder = config.static_path.clone();
102+
well_known_folder.push("well-known");
103+
fs::create_dir_all(&well_known_folder)?;
104+
105+
let (tx, rx) = mpsc::channel();
106+
107+
let address_clone = address.clone();
108+
109+
std::thread::spawn(move || {
110+
actix_web::rt::System::new().block_on(async move {
111+
let init_server = HttpServer::new(move || {
112+
App::new().service(
113+
actix_files::Files::new("/.well-known", well_known_folder.clone())
114+
.show_files_listing(),
115+
)
116+
});
117+
118+
let running_server = init_server.bind(&address_clone)?.run();
119+
120+
tx.send(running_server.handle())
121+
.expect("Error sending handle during HTTPS init.");
122+
123+
running_server.await
124+
})
125+
});
126+
127+
let handle = rx
128+
.recv()
129+
.map_err(|e| format!("Error receiving handle during HTTPS init. {}", e))?;
130+
131+
let agent = ureq::builder()
132+
.timeout(std::time::Duration::from_secs(2))
133+
.build();
134+
135+
let well_known_url = format!("http://{}/.well-known/", &config.opts.domain);
136+
tracing::info!("Testing availability of {}", &well_known_url);
137+
let resp = agent.get(&well_known_url).call().map_err(|e| {
138+
format!(
139+
"Unable to send request for Let's Encrypt initialization. {}",
140+
e
141+
)
142+
})?;
143+
if resp.status() != 200 {
144+
return Err(
145+
"Server for HTTP initialization not available, returning a non-200 status code".into(),
146+
);
147+
} else {
148+
tracing::info!("Server for HTTP initialization running correctly");
149+
}
150+
151+
request_cert(config)
152+
.await
153+
.map_err(|e| format!("Certification init failed: {}", e))?;
154+
tracing::warn!("HTTPS TLS Cert init sucesful! Stopping HTTP server, starting HTTPS...");
155+
handle.stop(true).await;
156+
Ok(())
157+
}
158+
159+
async fn request_cert(config: &crate::config::Config) -> AtomicServerResult<()> {
160+
// Create a new account. This will generate a fresh ECDSA key for you.
161+
// Alternatively, restore an account from serialized credentials by
162+
// using `Account::from_credentials()`.
163+
164+
let url = if config.opts.development {
165+
instant_acme::LetsEncrypt::Staging.url()
166+
} else {
167+
instant_acme::LetsEncrypt::Production.url()
168+
};
169+
170+
let email =
171+
config.opts.email.clone().expect(
172+
"No email set - required for HTTPS certificate initialization with LetsEncrypt",
173+
);
174+
175+
info!("Creating LetsEncrypt account with email {}", email);
176+
177+
let account = instant_acme::Account::create(
178+
&instant_acme::NewAccount {
179+
contact: &[&email],
180+
terms_of_service_agreed: true,
181+
only_return_existing: false,
182+
},
183+
url,
184+
)
185+
.await
186+
.map_err(|e| format!("Failed to create account: {}", e))?;
187+
188+
// Create the ACME order based on the given domain names.
189+
// Note that this only needs an `&Account`, so the library will let you
190+
// process multiple orders in parallel for a single account.
191+
192+
let identifier = instant_acme::Identifier::Dns(config.opts.domain.clone());
193+
let (mut order, state) = account
194+
.new_order(&instant_acme::NewOrder {
195+
identifiers: &[identifier],
196+
})
197+
.await
198+
.unwrap();
199+
200+
tracing::info!("order state: {:#?}", state);
201+
assert!(matches!(state.status, instant_acme::OrderStatus::Pending));
202+
203+
// Pick the desired challenge type and prepare the response.
204+
205+
let authorizations = order.authorizations(&state.authorizations).await.unwrap();
206+
let mut challenges = Vec::with_capacity(authorizations.len());
207+
for authz in &authorizations {
208+
match authz.status {
209+
instant_acme::AuthorizationStatus::Pending => {}
210+
instant_acme::AuthorizationStatus::Valid => continue,
211+
_ => todo!(),
212+
}
213+
214+
let challenge = authz
215+
.challenges
216+
.iter()
217+
.find(|c| c.r#type == instant_acme::ChallengeType::Http01)
218+
.ok_or("no Http01 challenge found")?;
219+
220+
let instant_acme::Identifier::Dns(identifier) = &authz.identifier;
221+
222+
println!("Please set the following DNS record then press any key:");
223+
println!(
224+
"_acme-challenge.{} IN TXT {}",
225+
identifier,
226+
order.key_authorization(challenge).dns_value()
227+
);
228+
std::io::stdin().read_line(&mut String::new()).unwrap();
229+
230+
challenges.push((identifier, &challenge.url));
231+
}
232+
233+
// Let the server know we're ready to accept the challenges.
234+
for (_, url) in &challenges {
235+
order.set_challenge_ready(url).await.unwrap();
236+
}
237+
238+
// Exponentially back off until the order becomes ready or invalid.
239+
let mut tries = 1u8;
240+
let mut delay = std::time::Duration::from_millis(250);
241+
let state = loop {
242+
actix::clock::sleep(delay).await;
243+
let state = order.state().await.unwrap();
244+
if let instant_acme::OrderStatus::Ready | instant_acme::OrderStatus::Invalid = state.status
245+
{
246+
tracing::info!("order state: {:#?}", state);
247+
break state;
248+
}
249+
250+
delay *= 2;
251+
tries += 1;
252+
match tries < 5 {
253+
true => info!(?state, tries, "order is not ready, waiting {delay:?}"),
254+
false => {
255+
return Err("order is not ready".into());
256+
}
257+
}
258+
};
259+
260+
if state.status == OrderStatus::Invalid {
261+
return Err("order is invalid".into());
262+
}
263+
264+
let mut names = Vec::with_capacity(challenges.len());
265+
for (identifier, _) in challenges {
266+
names.push(identifier.to_owned());
267+
}
268+
269+
// If the order is ready, we can provision the certificate.
270+
// Use the rcgen library to create a Certificate Signing Request.
271+
272+
let mut params = rcgen::CertificateParams::new(names.clone());
273+
params.distinguished_name = rcgen::DistinguishedName::new();
274+
let cert = rcgen::Certificate::from_params(params).unwrap();
275+
let csr = cert.serialize_request_der().map_err(|e| e.to_string())?;
276+
277+
// Finalize the order and print certificate chain, private key and account credentials.
278+
279+
let cert_chain_pem = order.finalize(&csr, &state.finalize).await.unwrap();
280+
info!("certficate chain:\n\n{}", cert_chain_pem,);
281+
info!("private key:\n\n{}", cert.serialize_private_key_pem());
282+
info!(
283+
"account credentials:\n\n{}",
284+
serde_json::to_string_pretty(&account.credentials()).unwrap()
285+
);
286+
fs::write(&config.cert_path, cert_chain_pem).expect("Unable to write cert file");
287+
fs::write(&config.key_path, cert.serialize_private_key_pem())
288+
.expect("Unable to write key file");
289+
set_certs_created_at_file(config);
290+
291+
Ok(())
292+
}

0 commit comments

Comments
 (0)