s3s/access/mod.rs
1//! Access control and authorization
2//!
3//! This module provides the [`S3Access`] trait for implementing fine-grained access control
4//! over S3 operations based on authenticated credentials.
5//!
6//! # Overview
7//!
8//! The access control system allows you to authorize or deny S3 operations. The generated
9//! [`S3Access`] trait provides:
10//!
11//! - A general `check` method that, when authentication is configured, is called before
12//! deserializing operation input; note that per-request credentials may be absent
13//! (for example, for unsigned or otherwise unauthenticated requests)
14//! - Per-operation methods for fine-grained control (e.g., `get_object`, `put_object`)
15//!
16//! > **Security note**
17//! >
18//! > `S3Access::check` (and per-operation access methods) are only invoked when an auth
19//! > provider is configured. If no auth provider is configured (i.e., the internal
20//! > `CallContext.auth` is `None`), S3 operations skip access checks entirely. In other
21//! > words, calling [`S3ServiceBuilder::set_access`](crate::service::S3ServiceBuilder::set_access)
22//! > alone does *not* enforce authentication or authorization.
23//! >
24//! > When an auth provider is configured, access checks run for every request, even if
25//! > the request is not successfully authenticated (for example, unsigned requests or
26//! > requests with invalid credentials). In those cases,
27//! > [`S3AccessContext::credentials`](crate::access::S3AccessContext::credentials) may
28//! > return `None`, and your `S3Access` implementation is responsible for deciding
29//! > whether to allow or deny the operation.
30//!
31//! # Example
32//!
33//! ```
34//! use s3s::access::{S3Access, S3AccessContext};
35//! use s3s::S3Result;
36//!
37//! struct MyAccessControl;
38//!
39//! #[async_trait::async_trait]
40//! impl S3Access for MyAccessControl {
41//! async fn check(&self, cx: &mut S3AccessContext<'_>) -> S3Result<()> {
42//! // Check if request has valid credentials
43//! match cx.credentials() {
44//! Some(creds) => {
45//! // You can check the operation, bucket, key, etc.
46//! let op_name = cx.s3_op().name();
47//! let path = cx.s3_path();
48//!
49//! // Implement your access control logic here
50//! tracing::info!("User {} accessing {} on {:?}",
51//! creds.access_key, op_name, path);
52//! Ok(())
53//! }
54//! None => Err(s3s::s3_error!(AccessDenied, "Authentication required")),
55//! }
56//! }
57//! }
58//! ```
59//!
60//! # Integration with `S3Service`
61//!
62//! ```
63//! use s3s::service::S3ServiceBuilder;
64//! use s3s::access::{S3Access, S3AccessContext};
65//! use s3s::auth::SimpleAuth;
66//! use s3s::{S3, S3Request, S3Response, S3Result};
67//! use s3s::dto::{GetObjectInput, GetObjectOutput};
68//!
69//! #[derive(Clone)]
70//! struct MyS3;
71//!
72//! #[async_trait::async_trait]
73//! impl S3 for MyS3 {
74//! # async fn get_object(&self, _req: S3Request<GetObjectInput>) -> S3Result<S3Response<GetObjectOutput>> {
75//! # Err(s3s::s3_error!(NotImplemented))
76//! # }
77//! // Implement S3 operations
78//! }
79//!
80//! struct MyAccessControl;
81//!
82//! #[async_trait::async_trait]
83//! impl S3Access for MyAccessControl {
84//! async fn check(&self, _cx: &mut S3AccessContext<'_>) -> S3Result<()> {
85//! Ok(())
86//! }
87//! }
88//!
89//! let mut builder = S3ServiceBuilder::new(MyS3);
90//! // Configure both auth and access control for authorization to be enforced
91//! builder.set_auth(SimpleAuth::from_single("ACCESS_KEY", "SECRET_KEY"));
92//! builder.set_access(MyAccessControl);
93//! let service = builder.build();
94//! ```
95
96cfg_if::cfg_if! {
97 if #[cfg(feature = "minio")] {
98 mod generated_minio;
99 use self::generated_minio as generated;
100 } else {
101 mod generated;
102 }
103}
104
105pub use self::generated::S3Access;
106
107mod context;
108pub use self::context::S3AccessContext;
109
110use crate::error::S3Result;
111
112pub(crate) fn default_check(cx: &mut S3AccessContext<'_>) -> S3Result<()> {
113 match cx.credentials() {
114 Some(_) => Ok(()),
115 None => Err(s3_error!(AccessDenied, "Signature is required")),
116 }
117}