RustFS Signer - Request Signing & Authentication
AWS-compatible request signing and authentication for RustFS object storage
📖 Documentation
· 🐛 Bug Reports
· 💬 Discussions
📖 Overview
RustFS Signer provides AWS-compatible request signing and authentication for the RustFS distributed object storage system. It implements AWS Signature Version 4 (SigV4) signing algorithm, pre-signed URLs, and various authentication methods to ensure secure API access.
Note: This is a security-critical submodule of RustFS that provides essential authentication capabilities for the distributed object storage system. For the complete RustFS experience, please visit the main RustFS repository.
✨ Features
🔐 AWS-Compatible Signing
- SigV4 Implementation: Full AWS Signature Version 4 support
- Pre-signed URLs: Temporary access URLs with expiration
- Chunked Upload: Streaming upload with signature validation
- Multi-part Upload: Signature validation for large files
🛡️ Authentication Methods
- Access Key/Secret: Traditional AWS-style authentication
- STS Token: Temporary security token support
- IAM Role: Role-based authentication
- Anonymous Access: Public read access support
🚀 Performance Features
- Signature Caching: Avoid repeated signature calculations
- Batch Signing: Sign multiple requests efficiently
- Streaming Support: Sign data streams without buffering
- Hardware Acceleration: Use hardware crypto when available
🔧 Advanced Features
- Custom Headers: Support for custom and vendor headers
- Regional Signing: Multi-region signature support
- Clock Skew Handling: Automatic time synchronization
- Signature Validation: Server-side signature verification
📦 Installation
Add this to your Cargo.toml:
[dependencies]
rustfs-signer = "0.1.0"
🔧 Usage
Basic Request Signing
use rustfs_signer::{Signer, SigningConfig, Credentials};
use http::{Request, Method};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create credentials
let credentials = Credentials::new(
"AKIAIOSFODNN7EXAMPLE".to_string(),
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".to_string(),
None, // No session token
);
// Create signing configuration
let config = SigningConfig {
region: "us-east-1".to_string(),
service: "s3".to_string(),
credentials,
..Default::default()
};
// Create signer
let signer = Signer::new(config);
// Create request
let request = Request::builder()
.method(Method::GET)
.uri("https://example-bucket.s3.amazonaws.com/example-object")
.body(Vec::new())?;
// Sign request
let signed_request = signer.sign_request(request).await?;
println!("Authorization header: {:?}", signed_request.headers().get("authorization"));
Ok(())
}
Pre-signed URLs
use rustfs_signer::{Signer, PresignedUrlRequest};
use std::time::Duration;
async fn presigned_url_example() -> Result<(), Box<dyn std::error::Error>> {
let signer = Signer::new(signing_config);
// Create pre-signed URL for GET request
let presigned_request = PresignedUrlRequest {
method: Method::GET,
uri: "https://example-bucket.s3.amazonaws.com/example-object".parse()?,
headers: Default::default(),
expires_in: Duration::from_secs(3600), // 1 hour
};
let presigned_url = signer.presign_url(presigned_request).await?;
println!("Pre-signed URL: {}", presigned_url);
// Create pre-signed URL for PUT request
let put_request = PresignedUrlRequest {
method: Method::PUT,
uri: "https://example-bucket.s3.amazonaws.com/upload-object".parse()?,
headers: {
let mut headers = HeaderMap::new();
headers.insert("content-type", "text/plain".parse()?);
headers
},
expires_in: Duration::from_secs(1800), // 30 minutes
};
let upload_url = signer.presign_url(put_request).await?;
println!("Pre-signed upload URL: {}", upload_url);
Ok(())
}
Streaming Upload Signing
use rustfs_signer::{StreamingSigner, ChunkedUploadSigner};
use tokio::io::AsyncReadExt;
async fn streaming_upload_example() -> Result<(), Box<dyn std::error::Error>> {
let signer = Signer::new(signing_config);
// Create streaming signer
let streaming_signer = StreamingSigner::new(signer, "s3".to_string());
// Create chunked upload signer
let mut chunked_signer = ChunkedUploadSigner::new(
streaming_signer,
"example-bucket".to_string(),
"large-file.dat".to_string(),
);
// Initialize multipart upload
let upload_id = chunked_signer.initiate_multipart_upload().await?;
println!("Upload ID: {}", upload_id);
// Upload chunks
let mut file = tokio::fs::File::open("large-file.dat").await?;
let mut chunk_buffer = vec![0u8; 5 * 1024 * 1024]; // 5MB chunks
let mut part_number = 1;
let mut etags = Vec::new();
loop {
let bytes_read = file.read(&mut chunk_buffer).await?;
if bytes_read == 0 {
break;
}
let chunk = &chunk_buffer[..bytes_read];
let etag = chunked_signer.upload_part(part_number, chunk).await?;
etags.push((part_number, etag));
part_number += 1;
}
// Complete multipart upload
chunked_signer.complete_multipart_upload(upload_id, etags).await?;
println!("Upload completed successfully");
Ok(())
}
Signature Validation
use rustfs_signer::{SignatureValidator, ValidationResult};
use http::HeaderMap;
async fn signature_validation_example() -> Result<(), Box<dyn std::error::Error>> {
let validator = SignatureValidator::new(signing_config);
// Extract signature from request headers
let headers = HeaderMap::new(); // Headers from incoming request
let method = "GET";
let uri = "/example-bucket/example-object";
let body = b""; // Request body
// Validate signature
let validation_result = validator.validate_signature(
method,
uri,
&headers,
body,
).await?;
match validation_result {
ValidationResult::Valid { credentials, .. } => {
println!("Signature valid for user: {}", credentials.access_key);
}
ValidationResult::Invalid { reason } => {
println!("Signature invalid: {}", reason);
}
ValidationResult::Expired { expired_at } => {
println!("Signature expired at: {}", expired_at);
}
}
Ok(())
}
Batch Signing
use rustfs_signer::{BatchSigner, BatchSigningRequest};
async fn batch_signing_example() -> Result<(), Box<dyn std::error::Error>> {
let signer = Signer::new(signing_config);
let batch_signer = BatchSigner::new(signer);
// Create multiple requests
let requests = vec![
BatchSigningRequest {
method: Method::GET,
uri: "https://bucket1.s3.amazonaws.com/object1".parse()?,
headers: HeaderMap::new(),
body: Vec::new(),
},
BatchSigningRequest {
method: Method::PUT,
uri: "https://bucket2.s3.amazonaws.com/object2".parse()?,
headers: HeaderMap::new(),
body: b"Hello, World!".to_vec(),
},
];
// Sign all requests in batch
let signed_requests = batch_signer.sign_batch(requests).await?;
for (i, signed_request) in signed_requests.iter().enumerate() {
println!("Request {}: {:?}", i + 1, signed_request.headers().get("authorization"));
}
Ok(())
}
Custom Authentication
use rustfs_signer::{CustomAuthenticator, AuthenticationResult};
use async_trait::async_trait;
struct CustomAuth {
// Custom authentication logic
}
#[async_trait]
impl CustomAuthenticator for CustomAuth {
async fn authenticate(&self, request: &Request<Vec<u8>>) -> Result<AuthenticationResult, Box<dyn std::error::Error>> {
// Custom authentication logic
let auth_header = request.headers().get("authorization");
if let Some(auth) = auth_header {
// Parse and validate custom authentication
let auth_str = auth.to_str()?;
if auth_str.starts_with("Custom ") {
// Validate custom token
let token = &auth_str[7..];
if self.validate_token(token).await? {
return Ok(AuthenticationResult::Authenticated {
user_id: "custom-user".to_string(),
permissions: vec!["read".to_string(), "write".to_string()],
});
}
}
}
Ok(AuthenticationResult::Unauthenticated)
}
}
impl CustomAuth {
async fn validate_token(&self, token: &str) -> Result<bool, Box<dyn std::error::Error>> {
// Implement token validation logic
Ok(token.len() > 10) // Simple example
}
}
🏗️ Architecture
Signer Architecture
Signer Architecture:
┌─────────────────────────────────────────────────────────────┐
│ Signing API Layer │
├─────────────────────────────────────────────────────────────┤
│ SigV4 │ Pre-signed │ Streaming │ Batch │
├─────────────────────────────────────────────────────────────┤
│ Signature Calculation │
├─────────────────────────────────────────────────────────────┤
│ HMAC-SHA256 │ Canonicalization │ String to Sign │
├─────────────────────────────────────────────────────────────┤
│ Cryptographic Primitives │
└─────────────────────────────────────────────────────────────┘
Signing Process
| Step | Description | Purpose |
|---|---|---|
| 1. Canonicalize | Format request components | Consistent representation |
| 2. Create String to Sign | Combine canonicalized data | Prepare for signing |
| 3. Calculate Signature | HMAC-SHA256 computation | Generate signature |
| 4. Add Headers | Add signature to request | Complete authentication |
🧪 Testing
Run the test suite:
# Run all tests
cargo test
# Test signature generation
cargo test signature
# Test pre-signed URLs
cargo test presigned
# Test validation
cargo test validation
📋 Requirements
- Rust: 1.70.0 or later
- Platforms: Linux, macOS, Windows
- Dependencies: Cryptographic libraries (ring, rustls)
- Compatibility: AWS S3 API compatible
🌍 Related Projects
This module is part of the RustFS ecosystem:
- RustFS Main - Core distributed storage system
- RustFS IAM - Identity and access management
- RustFS Crypto - Cryptographic operations
📚 Documentation
For comprehensive documentation, visit:
🔗 Links
- Documentation - Complete RustFS manual
- Changelog - Release notes and updates
- GitHub Discussions - Community support
🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details.
📄 License
Licensed under the Apache License, Version 2.0. See LICENSE for details.
RustFS is a trademark of RustFS, Inc.
All other trademarks are the property of their respective owners.
Made with 🔐 by the RustFS Team
