-
Notifications
You must be signed in to change notification settings - Fork 180
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
Add protocol level authentication #1957
Conversation
pub fn start( | ||
connection: quinn::Connection, | ||
request: GetRequest, | ||
auth: Authenticator, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to be fancy you could request impl DynAuthenticator
and then implement it for Option<Authenticator>
or maybe for Option<dyn DynAuthenticator>
(not entirely sure what works and what doesn't without trying it out). It would give a more natural API here as a user would probably guess to pass None
instead of having to find NoAuthenticator
and importing it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't quite work unfortunately, tried it
pub const REJECTED_CODE: u32 = 10; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct Authenticator(Arc<dyn DynAuthenticator>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to have this dyn by default. This does not warrant a type parameter.
|
||
type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>; | ||
|
||
pub trait DynAuthenticator: Sync + Send + std::fmt::Debug + 'static { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to have doc comments here to describe the flow. So for each request the request fn is called, which may or may not produce a token. And then response is called with the request and the token from above, and tells us whether to proceed. What is the purpose of the split compared to just having request -> ok/not ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added some docs
|
||
#[derive(Debug, Clone)] | ||
pub struct Request { | ||
pub id: u64, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of this id
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added a comment
iroh-base/src/auth.rs
Outdated
|
||
pub trait DynAuthenticator: Sync + Send + std::fmt::Debug + 'static { | ||
fn request(&self, request: Request) -> BoxFuture<Result<Option<Token>>>; | ||
fn respond(&self, request: Request, token: &Option<Token>) -> BoxFuture<Result<AcceptOutcome>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To verify my understanding, would that be correct doc comments?
/// Optionally add a token to outgoing requests.
///
/// This is called for each outgoing request created by this node.
/// When returning a `Token`, it will be added to the request payload.
fn request(&self, request: Request) -> BoxFuture<Result<Option<Token>>>;
/// Authenticate incoming requests.
///
/// This is called for each incoming request, right after decoding but before any processing takes place.
/// Processing and responding only continues if this method returns `AcceptOutcome::Accept`.
/// Otherwise the request will be declined.
fn respond(&self, request: Request, token: &Option<Token>) -> BoxFuture<Result<AcceptOutcome>>;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, exactly, thank you for writing the docs for me :)
|
||
type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>; | ||
|
||
pub trait DynAuthenticator: Sync + Send + std::fmt::Debug + 'static { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add the NodeId
of the source/destination? We always have this available I think, and I would guess that custom authenticators might e.g. want to do allow-list based authentication?
So
fn request(&self, request: Request, dest: NodeId) -> BoxFuture<Result<Option<Token>>>;
fn respond(&self, request: Request, src: NodeId, token: &Option<Token>) -> BoxFuture<Result<AcceptOutcome>>;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say lets not add it for now, we can easily add it later down the line if we see the need for it, as it doesn't change the wire format.
|
||
type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>; | ||
|
||
pub trait DynAuthenticator: Sync + Send + std::fmt::Debug + 'static { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found the namings not to be super intuitive. Maybe explicit method names would make this easier to parse, e.g.
fn create_token(&self, request: Request, dest: NodeId) -> BoxFuture<Result<Option<Token>>>;
fn accept_request(&self, request: Request, src: NodeId, token: &Option<Token>) -> BoxFuture<Result<AcceptOutcome>>;
but with proper docs the current naming might work too. Not super sure yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed, let me know what you think of the new names
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] | ||
pub struct Token { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to be able to evolve this format without breaking the wire format, maybe wrap it in an enum with a single variant for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my assumption was that if we need to change the token we need to bump the protocol version anyway
4470f0b
to
12cdc78
Compare
9b57558
to
efe707e
Compare
efe707e
to
00b9f42
Compare
Needs more design |
Brings back the general concept of authenticating requests.
This is a protocol level breaking change, which is why all ALPN version numbers have been increased.
Currently there are two authenticators implemented
NoAuthenticator
: does nothingIdAuthenticator
: sends a single UUID for all requests, which allows attribution of incoming traffic to this id. By default this authenticator is use in the iroh cli, and the uuid can be set in the config or on the console usingIROH_UUID="my-uuid"
Open Questions