Skip to content

feat(iroh-relay): allow to authenticate nodes via a HTTP POST request #3246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 7, 2025
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 63 additions & 1 deletion iroh-relay/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::{

use anyhow::{bail, Context as _, Result};
use clap::Parser;
use http::StatusCode;
use iroh_base::NodeId;
use iroh_relay::{
defaults::{
Expand All @@ -22,8 +23,9 @@ use iroh_relay::{
use n0_future::FutureExt;
use serde::{Deserialize, Serialize};
use tokio_rustls_acme::{caches::DirCache, AcmeConfig};
use tracing::debug;
use tracing::{debug, warn};
use tracing_subscriber::{prelude::*, EnvFilter};
use url::Url;

/// The default `http_bind_port` when using `--dev`.
const DEV_MODE_HTTP_PORT: u16 = 3340;
Expand Down Expand Up @@ -181,6 +183,14 @@ enum AccessConfig {
Allowlist(Vec<NodeId>),
/// Allows everyone, except these nodes.
Denylist(Vec<NodeId>),
/// Performs a HTTP POST request to determine access for each node that connects to the relay.
///
/// The request will have a header `X-Iroh-Node-Id` set to the hex-encoded node id attempting
/// to connect to the relay.
///
/// To grant access, the HTTP endpoint must return a `200` response with `true` as the response text.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the motivation to need a true in the body?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought of this as a safeguard, so that the endpoint implementing this would have to be more explicit. Maybe it's not needed, no strong opinion here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't have a strong opinion... I don't know if this will turn out to be really handy, really annoying, or just completely fine 😄

/// In all other cases, the node will be denied access.
Http(Url),
}

impl From<AccessConfig> for iroh_relay::server::AccessConfig {
Expand Down Expand Up @@ -215,10 +225,62 @@ impl From<AccessConfig> for iroh_relay::server::AccessConfig {
.boxed()
}))
}
AccessConfig::Http(url) => {
let client = reqwest::Client::default();
iroh_relay::server::AccessConfig::Restricted(Box::new(move |node_id| {
let client = client.clone();
let url = url.clone();
async move { http_access_check(&client, url, node_id).await }.boxed()
}))
}
}
}
}

#[tracing::instrument("http-access-check", skip_all, fields(node_id=%node_id.fmt_short()))]
async fn http_access_check(
client: &reqwest::Client,
url: Url,
node_id: NodeId,
) -> iroh_relay::server::Access {
use iroh_relay::server::Access;
debug!(%url, "Check relay access via HTTP POST");
let res = match client
.post(url)
.header("X-Iroh-NodeId", node_id.to_string())
.send()
.await
{
Ok(t) => t,
Err(err) => {
warn!("HTTP access check failed to retrieve response: {err}");
return Access::Deny;
}
};
if res.status() == StatusCode::OK {
match res.text().await {
Ok(text) if text == "true" => {
debug!("HTTP access check successful: grant access.");
Access::Allow
}
Ok(_) => {
warn!("HTTP access check return invalid response text: deny access.");
Access::Deny
}
Err(err) => {
warn!("HTTP access check failed to read response: {err}");
Access::Deny
}
}
} else {
debug!(
"HTTP access check response has status code {}: deny access",
res.status()
);
Access::Deny
}
}

impl Config {
fn http_bind_addr(&self) -> SocketAddr {
self.http_bind_addr
Expand Down
Loading