-
Notifications
You must be signed in to change notification settings - Fork 69
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 Unix peer credentials authenticator #214
Add Unix peer credentials authenticator #214
Conversation
Note: CI failure is expected since this patch depends on a feature that is not yet in the stdlib. |
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.
Great to see this appearing! I have put in a few comments for consideration.
//! | ||
//! The `UDSAuthenticator` uses peer credentials from Unix domain sockets to perform | ||
//! authentication. As such, it uses the UID/GID to grab a username for the owner of the connecting | ||
//! process. This is used as the application name. |
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.
Do we gain anything by converting to a user name as the application name, rather than just using the stringified UID itself as the application ID string? This seems to have additional dependencies and operations that could conceivably fail. I'm also slightly fearful about converting to usernames given that the environment may be containerised, which could have implications in terms of how the UIDs convert to names in the transition between containers.
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.
Do we gain anything by converting to a user name as the application name, rather than just using the stringified UID itself as the application ID string?
I think the only thing we gain is for it to look nicer in the mappings
folder and in logs like "New request received from application name \"{}\""
. So mostly for esthetics/debugging. Also, if we add any operation which takes an application name as parameter, it might be easier to specify the user name rather than the UID.
I guess I supposed that every UID must have an username mapping to it but if that is not true in some environments, I agree using the plain UID would be better.
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.
That's a good point.
I can't think of any reason why the stringified UID wouldn't work just fine. The only comment that I have is that the username is perhaps more 'human-friendly', but I don't think these strings will ever necessarily be exposed anyways.
Happy to change this. 🙂
src/authenticators/mod.rs
Outdated
@@ -12,6 +12,7 @@ | |||
//! Currently only a simple Direct Authenticator component is implemented. | |||
|
|||
pub mod direct_authenticator; | |||
pub mod uds_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.
Is it useful to perhaps wrap this authenticator in a Rust/Cargo feature so that it can be selected for at build time? (Actually, looking at the rest of the change, perhaps you've already done that? But there's an indication that it's only temporary. I actually think there is value in having this be a selectable thing even longer term, because it's useful for people to be able to compile down a Parsec executable with only the precise pieces that their deployment needs. Providers and authenticators are the kinds of things that we might want to snip out, because they can be heavyweight, especially in terms of the dependencies that get dragged in with them).
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, good idea. Will do this. 👍
src/authenticators/mod.rs
Outdated
@@ -12,6 +12,7 @@ | |||
//! Currently only a simple Direct Authenticator component is implemented. |
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 comment is outdated by the change.
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.
Ah, well spotted, thanks!
@@ -0,0 +1,129 @@ | |||
// Copyright 2019 Contributors to the Parsec project. |
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 guess this should be 2020 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.
Fixed. :)
9a65b40
to
f783921
Compare
fn authenticate( | ||
&self, | ||
_auth: &RequestAuth, | ||
meta: Option<ConnectionMetadata>, | ||
) -> Result<ApplicationName> { | ||
let meta = match meta { | ||
Some(meta) => meta, | ||
None => { | ||
error!("Authenticator did not receive any metadata; cannot authenticate."); | ||
return Err(ResponseStatus::AuthenticationError); | ||
} | ||
}; | ||
|
||
let (uid, _gid) = match meta { | ||
ConnectionMetadata::PeerCredentials{uid, gid} => (uid, gid), | ||
// TODO: add wildcard pattern when `ConnectionMetadata` has more possibilities. | ||
}; | ||
|
||
Ok(ApplicationName(uid.to_string())) | ||
} |
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 thought the authentication was meant to verify that the RequestAuth
contains a string with that UID, just as a "sanity" check
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.
Ah, I wasn't aware. Will fix that. 👍
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.
Agreed, I think it should. I didn't follow through every line here and I thought there was some Rusty magic going on. My opinion is that this system should be fully explicit with a well-defined authentication type and expected header contents. The calling process should "declare" its UID in the auth header, and the service should use the peer credential to check that it matches.
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.
Yeah, it's definitely a fair point. This is now implemented. 🙂
f783921
to
8b4bfb6
Compare
src/authenticators/mod.rs
Outdated
|
||
pub mod direct_authenticator; | ||
|
||
#[cfg(feature = "uds-authenticator")] | ||
pub mod uds_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.
I should have noticed this before, but I'm not sure why this is called UDS authenticator? Assuming UDS stands for Unix Domain Socket, that's the name of the transport rather than the method of authentication (which is UID). You could have all sorts of different authenticators on a UDS transport, so calling this a UDS authenticator doesn't quite characterise it accurately.
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.
Does PeerCredentialAuthenticator
make more sense? I can imagine we'd be able to get peer credentials for other IPC mechanisms as well and we could apply the same logic for authentication there
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 bad; I made the wrong assumption that using a Unix domain socket as a transport meant that we would always want to use peer credentials for authentication. I agree with @ionut-arm, PeerCredentialsAuthenticator
seems like a good name here. 🙂
8b4bfb6
to
179675e
Compare
393aaab
to
f193c43
Compare
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.
Thanks for adding it! Probably better to wait for the specs on the book and the PR in Rust to be merged before merging this one but thought of some early comments!
Cargo.toml
Outdated
@@ -70,3 +70,4 @@ pkcs11-provider = ["pkcs11", "picky-asn1-der", "picky-asn1", "picky-asn1-x509", | |||
tpm-provider = ["tss-esapi", "picky-asn1-der", "picky-asn1", "picky-asn1-x509"] | |||
all-providers = ["tpm-provider", "pkcs11-provider", "mbed-crypto-provider"] | |||
docs = ["pkcs11-provider", "tpm-provider", "tss-esapi/docs", "mbed-crypto-provider"] | |||
peer-credentials-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.
See my related comment, if it is agreed, I would personally prefer all occurences of "peer credentials" to be renamed "Unix peer credentials".
src/front/domain_socket.rs
Outdated
@@ -202,11 +203,10 @@ impl Listen for DomainSocketListener { | |||
format_error!("Failed to set stream as blocking", err); | |||
None | |||
} else { | |||
let metadata = stream.metadata(); |
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.
Is the GetMetadata
trait needed here if we are already in Unix specific code? As in, could you just replace the implementation of that trait on UnixStream
here?
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.
Just pinging this question as I think it is still valid
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.
We could -- this was more just thinking for the future. Will revert this, apologies for the lag on responding to this comment. 😄
src/front/domain_socket.rs
Outdated
fn metadata(&self) -> Option<ConnectionMetadata> { | ||
let ucred = self | ||
.peer_cred() | ||
.expect("Failed to get peer credentials for Unix domain socket connection."); |
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 prefer returning an error instead of panicking. In our case, you can log something and return None
!
f193c43
to
11030c7
Compare
|
||
let boxed_slice = expected_uid_bytes.into_boxed_slice(); | ||
let boxed_array: Box<[u8; 4]> = boxed_slice.try_into().unwrap(); | ||
let expected_uid = u32::from_le_bytes(*boxed_array); |
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 think you can directly use the TryInto
from the slice to the owned type and use the failed case as a way to check that the auth buffer was not 4 bytes (so even checks if not empty 😃 ):
let expected_uid = auth.buffer.expose_secret();
let expected_uid: [u8; 4] = expected_uid_bytes.try_into().ok_or_else(|| {
error!(
"UID in authentication request is not the right size (expected: {}, got: {}).",
EXPECTED_UID_SIZE_BYTES,
expected_uid_bytes.len()
);
return Err(ResponseStatus::AuthenticationError);
})?;
let expected_uid = u32::from_le_bytes(expected_uid);
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.
Ooh, that's much nicer. 😄
ba9529c
to
bcd8de5
Compare
908e428
to
7298258
Compare
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 idea of having that code directly here 👌
if expected_uid_bytes.is_empty() { | ||
error!("Expected UID in authentication request, but it is empty."); | ||
return Err(ResponseStatus::AuthenticationError); | ||
} |
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.
Small nit but I think that the empty case will also be checked in the following try_into
(as the buffer will not be 4 bytes).
I would also say that you can use the hardcoded 4 in the following code instead of EXPECTED_UID_SIZE_BYTES
as this size os probably not something meant to change often.
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.
Small nit but I think that the empty case will also be checked in the following try_into (as the buffer will not be 4 bytes).
Nice catch, done.
I would also say that you can use the hardcoded 4 in the following code instead of EXPECTED_UID_SIZE_BYTES as this size os probably not something meant to change often.
I don't know -- I kind of like this. It gives some obvious context to the magic number; when the user reads it, it is clear that the UID is supposed to be 4 bytes long. If that variable didn't exist it would be as clear, I don't think. 🙂
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 don't know -- I kind of like this. It gives some obvious context to the magic number; when the user reads it, it is clear that the UID is supposed to be 4 bytes long. If that variable didn't exist it would be as clear, I don't think. 🙂
Yes, it's true that this is more readable!
// secret when using Unix peer credentials authentication with Unix domain sockets. | ||
|
||
// Create two connected sockets. | ||
let (sock_a, _sock_b) = UnixStream::pair().unwrap(); |
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.
Pretty good idea to use that for unit tests!
src/front/domain_socket.rs
Outdated
@@ -202,11 +203,10 @@ impl Listen for DomainSocketListener { | |||
format_error!("Failed to set stream as blocking", err); | |||
None | |||
} else { | |||
let metadata = stream.metadata(); |
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.
Just pinging this question as I think it is still valid
// | ||
// The code below has been cherry-picked from the following PR: | ||
// | ||
// https://github.com/rust-lang/rust/pull/75148 |
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.
Could you also please add the stabilizing issue rust-lang/rust#42839?
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.
Done.
7298258
to
6ea9a16
Compare
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.
Thanks for applying the changes, it looks pretty good to me 👌
|
||
let (uid, _gid) = match meta { | ||
ConnectionMetadata::UnixPeerCredentials { uid, gid } => (uid, gid), | ||
// TODO: add wildcard pattern when `ConnectionMetadata` has more possibilities. |
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.
Can you add the wildcard pattern and "disable" the clippy warning? Or is it something the compiler doesn't like?
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.
We can -- done.
fc8744d
to
90b8f76
Compare
Will be required for testing the Unix peer credentials authenticator. Signed-off-by: Joe Ellis <joe.ellis@arm.com>
This authenticator uses peer credentials for authentication. The specific type of peer credentials in mind at the moment are Unix peer credentials, but this can be extended in the future. Unix peer credentials provide direct access to the (effective) uid/gid on the other end of a domain socket connect, without cooperation between the endpoints. This means that we can trivially determine the uid/gid of the connecting process, which we can then use for authentication. This authenticator: - grabs the (uid, gid) pair of the connecting process. - grabs the self-declared uid sent in the authentication request. - verifies that authentication is successful by checking that the self-declared uid in the authentication request is equal to the actual uid from the peer credentials. - if authentication was successful, creates an `ApplicationName` based on the uid. The authenticator is hidden behind the Cargo feature `peer-credentials-authenticator`. Note that gid is currently unused by the authenticator. Also note that this patch depends on the following PR being merged: rust-lang/rust#75148 At the time of writing, this PR is currently under review and is not merged into the Rust stdlib. This patch therefore will not build with the current a stable/nightly compiler. Signed-off-by: Joe Ellis <joe.ellis@arm.com>
The code introduced in this patch has been cherry-picked from the following PR: rust-lang/rust#75148 At the time of writing (16/09/20), this patch is in the nightly Rust channel. To avoid needing to use the nightly compiler to build Parsec, this patch includes the change from the standard library to allow us to use this feature 'early'. Once the feature hits the stable branch, it should be safe to revert this commit with `git revert`. Signed-off-by: Joe Ellis <joe.ellis@arm.com>
90b8f76
to
0b48895
Compare
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.
Thank you! 💯
This authenticator uses Unix peer credentials for authentication. Unix
peer credentials provide direct access to the (effective) uid/gid on the
other end of a domain socket connect, without cooperation between the
endpoints. This means that we can determine the uid/gid of the
connecting process, and therefore infer the username of the user that
owns said process.
This authenticator:
the uid.
ApplicationName
based on the username.Note that this patch depends on the following PR being merged:
At the time of writing, this PR is currently under review and is not
merged into the Rust stdlib. This patch therefore will not build with
the current a stable/nightly compiler.
Signed-off-by: Joe Ellis joe.ellis@arm.com