Skip to content

Commit

Permalink
Serve HTTPS with ACME certs (ordinals#256)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey committed Aug 1, 2022
1 parent db316dc commit 09e0f21
Show file tree
Hide file tree
Showing 10 changed files with 1,339 additions and 55 deletions.
1,111 changes: 1,102 additions & 9 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ autotests = false

[dependencies]
anyhow = { version = "1.0.56", features = ["backtrace"] }
async-rustls = "0.2.0"
axum = "0.5.6"
axum-server = "0.4.0"
bdk = { version = "0.20.0", features = ["keys-bip39", "sqlite"] }
Expand All @@ -19,15 +20,20 @@ ctrlc = "3.2.1"
derive_more = "0.99.17"
dirs = "4.0.0"
env_logger = "0.9.0"
futures = "0.3.21"
http = "0.2.6"
lazy_static = "1.4.0"
log = "0.4.14"
rayon = "1.5.1"
redb = "0.4.0"
rustls-acme = "0.3.0"
serde = { version = "1.0.137", features = ["derive"] }
serde_cbor = "0.11.2"
serde_json = "1.0.81"
tokio = { version = "1.17.0", features = ["rt-multi-thread"] }
tokio-stream = "0.1.9"
tokio-util = {version = "0.7.3", features = ["compat"] }
tower = "0.4.13"
tower-http = { version = "0.3.3", features = ["cors"] }

[dev-dependencies]
Expand All @@ -38,8 +44,8 @@ jsonrpc-core-client = "18.0.0"
jsonrpc-derive = "18.0.0"
jsonrpc-http-server = "18.0.0"
nix = "0.24.1"
regex = "1.5.4"
reqwest = { version = "0.11.10", features = ["blocking"] }
regex = "1.6.0"
tempfile = "3.2.0"
unindent = "0.1.7"

Expand Down
10 changes: 7 additions & 3 deletions deploy/ord.service
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ WorkingDirectory=/var/lib/ord
Environment=RUST_BACKTRACE=1
Environment=RUST_LOG=info
ExecStart=/usr/local/bin/ord \
--index-size 1TiB \
--rpc-url 127.0.0.1:38332 \
--cookie-file /var/lib/bitcoind/signet/.cookie \
server
--max-index-size 1TiB \
--rpc-url 127.0.0.1:38332 \
server \
--acme-cache /var/lib/ord/acme-cache \
--acme-contact mailto:[email protected] \
--acme-domain signet.ordinals.com \
--https-port 443

# Process management
####################
Expand Down
9 changes: 7 additions & 2 deletions deploy/setup
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ set -euxo pipefail

apt-get update --yes
apt-get upgrade --yes
apt-get install --yes clang pkg-config libssl-dev acl
apt-get install --yes \
acl \
clang \
libsqlite3-dev\
libssl-dev \
pkg-config

if ! which bitcoind; then
wget -O bitcoin.tar.gz 'https://bitcoincore.org/bin/bitcoin-core-23.0/bitcoin-23.0-x86_64-linux-gnu.tar.gz'
Expand Down Expand Up @@ -44,4 +49,4 @@ systemctl enable ord
systemctl restart ord

sleep 1
curl http:https://localhost/list
curl https:https://signet.ordinals.com/list
6 changes: 6 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ deploy branch='master':
rsync -avz deploy/checkout [email protected]:deploy/checkout
ssh [email protected] 'cd deploy && ./checkout {{branch}}'

log:
ssh [email protected] 'journalctl -fu ord'

test-deploy:
ssh-keygen -f ~/.ssh/known_hosts -R 192.168.56.4
vagrant up
Expand Down Expand Up @@ -65,6 +68,9 @@ print-paper-wallet path:
wkhtmltopdf -L 25mm -R 25mm -T 50mm -B 25mm {{path}} wallet.pdf
lp -o sides=two-sided-long-edge wallet.pdf

doc:
cargo doc --all --open

# publish current GitHub master branch
publish:
#!/usr/bin/env bash
Expand Down
7 changes: 4 additions & 3 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ impl Index {
pub(crate) fn print_info(&self) -> Result {
let wtx = self.database.begin_write()?;

let height_to_hash = wtx.open_table(HEIGHT_TO_HASH)?;

let blocks_indexed = height_to_hash
let blocks_indexed = wtx
.open_table(HEIGHT_TO_HASH)?
.range(0..)?
.rev()
.next()
Expand All @@ -83,6 +82,8 @@ impl Index {
Bytes(std::fs::metadata("index.redb")?.len().try_into()?)
);

wtx.abort()?;

Ok(())
}

Expand Down
93 changes: 84 additions & 9 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,49 @@
use super::*;

use {
self::tls_acceptor::TlsAcceptor,
clap::ArgGroup,
rustls_acme::{
acme::{ACME_TLS_ALPN_NAME, LETS_ENCRYPT_PRODUCTION_DIRECTORY, LETS_ENCRYPT_STAGING_DIRECTORY},
caches::DirCache,
AcmeConfig,
},
tokio_stream::StreamExt,
};

mod tls_acceptor;

#[derive(Parser)]
#[clap(group = ArgGroup::new("port").multiple(false).required(true))]
pub(crate) struct Server {
#[clap(long, default_value = "0.0.0.0")]
#[clap(
long,
default_value = "0.0.0.0",
help = "Listen on <ADDRESS> for incoming requests."
)]
address: String,
#[clap(long, default_value = "80")]
port: u16,
#[clap(
long,
help = "Request ACME TLS certificate for <ACME_DOMAIN>. This ord instance must be reachable at <ACME_DOMAIN>:443 to respond to Let's Encrypt ACME challenges."
)]
acme_domain: Vec<String>,
#[clap(
long,
group = "port",
help = "Listen on <HTTP_PORT> for incoming HTTP requests."
)]
http_port: Option<u16>,
#[clap(
long,
group = "port",
help = "Listen on <HTTPS_PORT> for incoming HTTPS requests.",
requires_all = &["acme-cache", "acme-domain", "acme-contact"]
)]
https_port: Option<u16>,
#[structopt(long, help = "Store ACME TLS certificates in <ACME_CACHE>.")]
acme_cache: Option<PathBuf>,
#[structopt(long, help = "Provide ACME contact <ACME_CONTACT>.")]
acme_contact: Vec<String>,
}

impl Server {
Expand All @@ -31,7 +69,37 @@ impl Server {
.allow_origin(Any),
);

let addr = (self.address, self.port)
let (port, acceptor) = match (self.http_port, self.https_port) {
(Some(http_port), None) => (http_port, None),
(None, Some(https_port)) => {
let config = AcmeConfig::new(self.acme_domain)
.contact(self.acme_contact)
.cache_option(Some(DirCache::new(self.acme_cache.unwrap())))
.directory(if cfg!(test) {
LETS_ENCRYPT_STAGING_DIRECTORY
} else {
LETS_ENCRYPT_PRODUCTION_DIRECTORY
});

let mut state = config.state();

let acceptor = state.acceptor();

tokio::spawn(async move {
while let Some(result) = state.next().await {
match result {
Ok(ok) => log::info!("ACME event: {:?}", ok),
Err(err) => log::error!("ACME error: {:?}", err),
}
}
});

(https_port, Some(acceptor))
}
(None, None) | (Some(_), Some(_)) => unreachable!(),
};

let addr = (self.address, port)
.to_socket_addrs()?
.next()
.ok_or_else(|| anyhow!("Failed to get socket addrs"))?;
Expand All @@ -40,12 +108,19 @@ impl Server {

LISTENERS.lock().unwrap().push(handle.clone());

axum_server::Server::bind(addr)
.handle(handle)
.serve(app.into_make_service())
.await?;
let server = axum_server::Server::bind(addr).handle(handle);

match acceptor {
Some(acceptor) => {
server
.acceptor(TlsAcceptor(acceptor))
.serve(app.into_make_service())
.await?
}
None => server.serve(app.into_make_service()).await?,
}

Ok::<(), Error>(())
Ok(())
})
}

Expand Down
34 changes: 34 additions & 0 deletions src/subcommand/server/tls_acceptor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use super::*;

use {
async_rustls::rustls::Session,
futures::future::{BoxFuture, FutureExt, TryFutureExt},
std::marker::Unpin,
tokio::io::{AsyncRead, AsyncWrite},
tokio_util::compat::{Compat, FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt},
};

#[derive(Clone)]
pub(crate) struct TlsAcceptor(pub(crate) async_rustls::TlsAcceptor);

impl<I: AsyncRead + AsyncWrite + Unpin + Send + 'static, S: Send + 'static>
axum_server::accept::Accept<I, S> for TlsAcceptor
{
type Stream = Compat<async_rustls::server::TlsStream<Compat<I>>>;
type Service = S;
type Future = BoxFuture<'static, io::Result<(Self::Stream, Self::Service)>>;

fn accept(&self, stream: I, service: S) -> Self::Future {
self
.0
.accept(stream.compat())
.map_ok(move |tls| {
let tls = tls.compat();
if let Some(ACME_TLS_ALPN_NAME) = tls.get_ref().get_ref().1.get_alpn_protocol() {
log::info!("received TLS-ALPN-01 validation request");
}
(tls, service)
})
.boxed()
}
}
52 changes: 27 additions & 25 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,31 @@ mod wallet;

type Result<T = ()> = std::result::Result<T, Box<dyn Error>>;

#[derive(Debug)]
enum Expected {
String(String),
Regex(Regex),
Ignore,
}

impl Expected {
fn regex(pattern: &str) -> Self {
Self::Regex(Regex::new(&format!("^(?s){}$", pattern)).unwrap())
}

fn assert_match(&self, output: &str) {
match self {
Self::String(string) => assert_eq!(output, string),
Self::Regex(regex) => assert!(
regex.is_match(output),
"output did not match regex: {:?}",
output
),
Self::Ignore => {}
}
}
}

enum Event {
Block(Block),
Request(String, u16, String),
Expand Down Expand Up @@ -138,9 +157,7 @@ impl Test {

fn stdout_regex(self, expected_stdout: impl AsRef<str>) -> Self {
Self {
expected_stdout: Expected::Regex(
Regex::new(&format!("(?s)^{}$", expected_stdout.as_ref())).unwrap(),
),
expected_stdout: Expected::regex(expected_stdout.as_ref()),
..self
}
}
Expand All @@ -162,9 +179,7 @@ impl Test {

fn stderr_regex(self, expected_stderr: impl AsRef<str>) -> Self {
Self {
expected_stderr: Expected::Regex(
Regex::new(&format!("(?s)^{}$", expected_stderr.as_ref())).unwrap(),
),
expected_stderr: Expected::regex(expected_stderr.as_ref()),
..self
}
}
Expand Down Expand Up @@ -204,6 +219,10 @@ impl Test {
self.test(Some(port)).map(|_| ())
}

fn run_server_output(self, port: u16) -> Output {
self.test(Some(port)).unwrap()
}

fn blocks(&self) -> impl Iterator<Item = &Block> + '_ {
self.events.iter().filter_map(|event| match event {
Event::Block(block) => Some(block),
Expand Down Expand Up @@ -308,25 +327,8 @@ impl Test {

let stripped_stderr = log_line_re.replace_all(stderr, "");

match self.expected_stderr {
Expected::String(expected_stderr) => assert_eq!(stripped_stderr, expected_stderr),
Expected::Regex(expected_stderr) => assert!(
expected_stderr.is_match(&stripped_stderr),
"stderr did not match regex: {:?}",
stripped_stderr
),
Expected::Ignore => {}
}

match self.expected_stdout {
Expected::String(expected_stdout) => assert_eq!(stdout, expected_stdout),
Expected::Regex(expected_stdout) => assert!(
expected_stdout.is_match(stdout),
"stdout did not match regex: {:?}",
stdout
),
Expected::Ignore => {}
}
self.expected_stderr.assert_match(&stripped_stderr);
self.expected_stdout.assert_match(stdout);

assert_eq!(
successful_requests,
Expand Down
Loading

0 comments on commit 09e0f21

Please sign in to comment.