Nugine / s3s

S3 Service Adapter

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

feature request: bucket / object contexts or public bucket/object access

St4NNi opened this issue · comments

Hi again,

The current authorization logic requires either a Some(S3Auth) struct or None to check/skip for request permissions / signatures.

pub async fn check(&mut self) -> S3Result<Option<Credentials>> {
if let Some(result) = self.v2_check().await {
debug!("checked signature v2");
return result.map(Some);
}
if let Some(result) = self.v4_check().await {
debug!("checked signature v4");
return result.map(Some);
}
if self.auth.is_some() {
return Err(s3_error!(AccessDenied, "Signature is required"));
}
Ok(None)
}

It would be nice to have a second validation step that checks bucket or object specific policies to allow for public bucket / object access and a more fine grained authorization logic as well as anonymous public access.

I know that a full IAM implementation similar to Amazon S3 / IAM will be quite complicated to maintain, so I would suggest to leave this to implementers. This could be done by adding a second optional context auth interface that receives the relevant request information (bucket & key and method or SignatureContext / URL) and possible the Option<Credentials> and returns a S3Result::Err if the user has no permissions to access the specific object.

Rough suggestion:

pub struct Context<'a> {
  pub bucket: &'a str, 
  pub key: &'a str,
  pub req_method: &'a Method,
}

/// S3 Context Provider
#[async_trait::async_trait]
pub trait S3ContextProvider: Send + Sync + 'static {
    /// Check if optional user is allowed to access bucket / key with method
    async fn check_context(&self,  context: &Context, credentials: Option<Credentials>) -> S3Result<()>;
}

This would be called after signature validation and if no signature is provided and will return an error if the specified user has no permissions for the specified bucket / key. It could be entirely optional Option<&'a dyn S3ContextProvider> and would not be relevant if it is not implemented (and None), so no breaking change would be introduced. But if it exists it will prevent the error:

if self.auth.is_some() {
return Err(s3_error!(AccessDenied, "Signature is required"));
}

and perform an additional check for bucket / object specific IAM policies.

Just let me know what do you think about such a feature. For us it is a major roadblock to not be able to make a bucket and all of its objects "public". If I should give it a try and prepare a PR just let me know either.

Thanks in advance !

commented

The lack of this feature is caused by mixing signature validation with access control.
(I didn't realize it before.)

To make the access control customizable, I may merge the method you suggested into S3Auth trait.
It can be a default trait method which rejects all anonymous requests by default.

pub trait S3Auth: Send + Sync + 'static {
    /// lookup `secret_key` by `access_key`
    async fn get_secret_key(&self, access_key: &str) -> S3Result<SecretKey>;

+   /// Check if optional user is allowed to access bucket / key with method
+   async fn check_access(&self,  context: &Context, credentials: Option<Credentials>) -> S3Result<()> {
+       todo!()
+   }
}

Yes this sounds reasonable to me. Thinking about it, it would also be really nice if it would be possible to pass some of the checked "meta-information" to the request handling in the S3Trait itself, but I don't really have an concrete idea how this could be done.

Talking a little bit about our use-case check_access would do a database call and retrieve some internal identifiers for the associated objects / buckets. These ids are also quite useful to have in the actual request handling logic of the S3Trait and are sometimes even needed to manage some behavior. With the suggested implementation these information have to be queried again, which is not the end of the world but might be a little bit inefficient.

Maybe we could use the provided http:Extensions for this ?

commented

Maybe we could use the provided http::Extensions for this ?

Using Extensions is a de facto standard way to pass custom information between handlers in many Rust web frameworks.
We cound provide an extensions_mut method in S3AuthContext<'_> type.

Perfect ! Thanks a lot for your great work 🚀