mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-28 00:58:59 +00:00
730 lines
21 KiB
Markdown
730 lines
21 KiB
Markdown
# RustFS Keystone Integration
|
|
|
|
OpenStack Keystone authentication integration for RustFS S3-compatible object storage.
|
|
|
|
## Features
|
|
|
|
- **Keystone v3 API support** - Modern Keystone authentication
|
|
- **Token-based authentication** - Support for X-Auth-Token header
|
|
- **EC2 credentials** - S3 API compatibility with Keystone EC2 credentials
|
|
- **Multi-tenancy** - Project-based bucket isolation
|
|
- **Role mapping** - Map Keystone roles to RustFS IAM policies
|
|
- **Token caching** - High-performance token validation with caching
|
|
- **Swift compatibility** - Support for X-Storage-Token header
|
|
|
|
## Installation
|
|
|
|
Add to your `Cargo.toml`:
|
|
|
|
```toml
|
|
[dependencies]
|
|
rustfs-keystone = "0.0.5"
|
|
```
|
|
|
|
## Usage
|
|
|
|
```rust
|
|
use rustfs_keystone::{KeystoneConfig, KeystoneClient, KeystoneAuthProvider};
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Load configuration from environment
|
|
let config = KeystoneConfig::from_env()?;
|
|
|
|
// Create Keystone client
|
|
let client = KeystoneClient::new(
|
|
config.auth_url.clone(),
|
|
config.get_version()?,
|
|
config.admin_user.clone(),
|
|
config.admin_password.clone(),
|
|
config.admin_project.clone(),
|
|
config.verify_ssl,
|
|
);
|
|
|
|
// Create authentication provider
|
|
let auth_provider = KeystoneAuthProvider::new(
|
|
client,
|
|
config.cache_size,
|
|
config.get_cache_ttl(),
|
|
);
|
|
|
|
// Authenticate with Keystone token
|
|
let token = "your-keystone-token";
|
|
let credentials = auth_provider.authenticate_with_token(token).await?;
|
|
|
|
println!("Authenticated user: {}", credentials.parent_user);
|
|
println!("Project: {:?}", credentials.claims);
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
## Configuration
|
|
|
|
Configure via environment variables:
|
|
|
|
```bash
|
|
# Enable Keystone
|
|
export RUSTFS_KEYSTONE_ENABLE=true
|
|
export RUSTFS_KEYSTONE_AUTH_URL=http://keystone:5000
|
|
export RUSTFS_KEYSTONE_VERSION=v3
|
|
|
|
# Admin credentials (optional, for privileged operations)
|
|
export RUSTFS_KEYSTONE_ADMIN_USER=admin
|
|
export RUSTFS_KEYSTONE_ADMIN_PASSWORD=secret
|
|
export RUSTFS_KEYSTONE_ADMIN_PROJECT=admin
|
|
|
|
# Multi-tenancy
|
|
export RUSTFS_KEYSTONE_TENANT_PREFIX=true
|
|
|
|
# Performance tuning
|
|
export RUSTFS_KEYSTONE_CACHE_SIZE=10000
|
|
export RUSTFS_KEYSTONE_CACHE_TTL=300
|
|
```
|
|
|
|
TLS certificate verification is enabled by default. Set
|
|
`RUSTFS_KEYSTONE_VERIFY_SSL=false` only for an explicitly trusted hop; it allows
|
|
MITM attacks against the Keystone connection and emits a startup warning.
|
|
|
|
## API Documentation
|
|
|
|
### KeystoneClient
|
|
|
|
The `KeystoneClient` provides low-level API access to Keystone services:
|
|
|
|
```rust
|
|
let client = KeystoneClient::new(
|
|
"http://keystone:5000".to_string(),
|
|
KeystoneVersion::V3,
|
|
Some("admin".to_string()),
|
|
Some("secret".to_string()),
|
|
Some("admin".to_string()),
|
|
true, // verify SSL
|
|
);
|
|
|
|
// Validate a token
|
|
let token_info = client.validate_token("token123").await?;
|
|
println!("User: {}, Project: {:?}", token_info.username, token_info.project_name);
|
|
|
|
// Get EC2 credentials
|
|
let ec2_creds = client.get_ec2_credentials("user_id", Some("project_id")).await?;
|
|
```
|
|
|
|
### KeystoneAuthProvider
|
|
|
|
The `KeystoneAuthProvider` provides high-level authentication with caching:
|
|
|
|
```rust
|
|
let provider = KeystoneAuthProvider::new(client, 10000, Duration::from_secs(300));
|
|
|
|
// Authenticate with token
|
|
let cred = provider.authenticate_with_token("token123").await?;
|
|
|
|
// Check if user is admin
|
|
if provider.is_admin(&cred) {
|
|
println!("User has admin privileges");
|
|
}
|
|
|
|
// Get project ID
|
|
if let Some(project_id) = provider.get_project_id(&cred) {
|
|
println!("User's project: {}", project_id);
|
|
}
|
|
```
|
|
|
|
### KeystoneIdentityMapper
|
|
|
|
The `KeystoneIdentityMapper` handles multi-tenancy and role mapping:
|
|
|
|
```rust
|
|
let mapper = KeystoneIdentityMapper::new(Arc::new(client), true);
|
|
|
|
// Apply tenant prefix to bucket name
|
|
let prefixed = mapper.apply_tenant_prefix("mybucket", Some("proj123"));
|
|
// Returns: "proj123:mybucket"
|
|
|
|
// Remove tenant prefix
|
|
let unprefixed = mapper.remove_tenant_prefix("proj123:mybucket", Some("proj123"));
|
|
// Returns: "mybucket"
|
|
|
|
// Map Keystone roles to RustFS policies
|
|
let roles = vec!["Member".to_string(), "admin".to_string()];
|
|
let policies = mapper.map_roles_to_policies(&roles);
|
|
// Returns: ["ReadWritePolicy", "AdminPolicy"]
|
|
|
|
// Check permissions
|
|
if mapper.has_permission(&roles, "s3:PutObject", "bucket/key") {
|
|
println!("User can write objects");
|
|
}
|
|
```
|
|
|
|
## Architecture
|
|
|
|
### Component Architecture
|
|
|
|
```
|
|
KeystoneClient (API calls)
|
|
↓
|
|
KeystoneAuthProvider (Authentication + Caching)
|
|
↓
|
|
KeystoneIdentityMapper (Multi-tenancy + Role Mapping)
|
|
↓
|
|
RustFS Credentials
|
|
```
|
|
|
|
### Middleware Architecture
|
|
|
|
The keystone crate includes a Tower middleware (`KeystoneAuthMiddleware`) that integrates directly into RustFS's HTTP service stack. The middleware is self-contained within this crate and exported via the `middleware` module:
|
|
|
|
```rust
|
|
use rustfs_keystone::{KeystoneAuthLayer, KEYSTONE_CREDENTIALS};
|
|
|
|
// In RustFS HTTP service setup
|
|
let layer = KeystoneAuthLayer::new(keystone_auth_provider);
|
|
```
|
|
|
|
The middleware uses Tokio task-local storage (`KEYSTONE_CREDENTIALS`) to pass authenticated credentials between the middleware layer and authentication handlers without modifying the HTTP request.
|
|
|
|
### RustFS Integration Architecture
|
|
|
|
The Keystone integration uses a middleware-based approach that intercepts HTTP requests before they reach the S3 service layer:
|
|
|
|
```
|
|
HTTP Request
|
|
↓
|
|
RemoteAddr/TrustedProxy Layers (Extract client IP)
|
|
↓
|
|
SetRequestId/CatchPanic Layers (Request metadata)
|
|
↓
|
|
ReadinessGate Layer (System health check)
|
|
↓
|
|
KeystoneAuthMiddleware ⭐ (Token validation)
|
|
├─ No X-Auth-Token? → Pass through to S3 auth
|
|
├─ Has X-Auth-Token? → Validate with Keystone
|
|
│ ├─ Valid? → Store credentials in task-local storage → Continue
|
|
│ └─ Invalid? → Return 401 Unauthorized immediately
|
|
↓
|
|
TraceLayer (Logging/observability)
|
|
↓
|
|
S3 Service Layer
|
|
↓
|
|
IAMAuth (Authentication)
|
|
├─ Keystone credential? (access_key starts with "keystone:")
|
|
│ ├─ Return empty secret_key (bypass signature validation)
|
|
│ └─ Retrieve credentials from task-local storage
|
|
└─ Standard credential? → Normal AWS Signature v4 validation
|
|
↓
|
|
check_key_valid (Authorization)
|
|
├─ Keystone credential?
|
|
│ ├─ Get credentials from task-local storage
|
|
│ ├─ Check user roles (admin/reseller_admin = owner)
|
|
│ └─ Return (Credentials, is_owner)
|
|
└─ Standard credential? → Normal IAM validation
|
|
↓
|
|
S3 Operation (PutObject, GetObject, etc.)
|
|
```
|
|
|
|
## Integration with RustFS
|
|
|
|
### How It Works
|
|
|
|
The Keystone integration provides seamless OpenStack authentication for RustFS S3 API. Here's how the complete request flow works:
|
|
|
|
#### 1. Request with Keystone Token
|
|
|
|
When a client makes an S3 API request with a Keystone token:
|
|
|
|
```bash
|
|
curl -X GET http://rustfs:9000/mybucket/myobject \
|
|
-H "X-Auth-Token: gAAAAABk..."
|
|
```
|
|
|
|
**Flow:**
|
|
1. **Middleware Intercepts**: The `KeystoneAuthMiddleware` extracts the `X-Auth-Token` header
|
|
2. **Token Validation**: Calls Keystone API to validate the token and retrieve user information
|
|
3. **Credential Mapping**: Creates RustFS credentials with:
|
|
- `access_key`: `keystone:<user_id>` (special prefix to identify Keystone users)
|
|
- `parent_user`: Keystone username
|
|
- `claims`: Project ID, roles, and other Keystone attributes in JSON format
|
|
4. **Task-Local Storage**: Stores credentials in async task-local storage (request-scoped)
|
|
5. **Pass Through**: Request continues to S3 service layer
|
|
6. **Authentication**: IAMAuth detects `keystone:` prefix, returns empty secret (bypasses AWS signature check)
|
|
7. **Authorization**: `check_key_valid()` retrieves credentials from task-local storage
|
|
8. **Role Check**: Determines if user is admin based on roles:
|
|
- `admin` role → owner permissions (full access)
|
|
- `reseller_admin` role → owner permissions (full access)
|
|
- Other roles → non-owner permissions (restricted access)
|
|
9. **S3 Operation**: Proceeds with appropriate permissions
|
|
|
|
#### 2. Request without Keystone Token
|
|
|
|
When a client makes a standard S3 request:
|
|
|
|
```bash
|
|
aws s3 cp file.txt s3://mybucket/file.txt \
|
|
--endpoint-url http://rustfs:9000
|
|
```
|
|
|
|
**Flow:**
|
|
1. **Middleware Pass-Through**: No `X-Auth-Token` header found, request passes through unchanged
|
|
2. **Standard S3 Auth**: AWS Signature v4 validation
|
|
3. **IAM Validation**: Normal RustFS IAM authentication
|
|
4. **S3 Operation**: Proceeds with IAM-based permissions
|
|
|
|
#### 3. Invalid Token Handling
|
|
|
|
When a token is invalid or expired:
|
|
|
|
**Flow:**
|
|
1. **Token Validation Fails**: Keystone returns error (invalid/expired token)
|
|
2. **Immediate 401**: Middleware returns `401 Unauthorized` immediately
|
|
3. **No Fallback**: Does NOT fall back to standard S3 authentication
|
|
4. **XML Error Response**: Returns S3-compatible error XML:
|
|
|
|
```xml
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<Error>
|
|
<Code>InvalidToken</Code>
|
|
<Message>Invalid Keystone token</Message>
|
|
<Details>Token validation failed: token expired</Details>
|
|
</Error>
|
|
```
|
|
|
|
### Permission Model
|
|
|
|
The integration uses Keystone roles to determine RustFS permissions:
|
|
|
|
**Owner Permissions (is_owner=true):**
|
|
- Granted to users with `admin` or `reseller_admin` roles
|
|
- Full access to all operations (equivalent to root/admin access)
|
|
- Can create/delete buckets, manage policies, access all objects
|
|
|
|
**Non-Owner Permissions (is_owner=false):**
|
|
- Granted to users with other roles (member, reader, etc.)
|
|
- Restricted access based on bucket policies and IAM policies
|
|
- Cannot perform administrative operations
|
|
|
|
**Example:**
|
|
```json
|
|
{
|
|
"roles": ["admin", "member"]
|
|
}
|
|
```
|
|
→ `is_owner=true` (has admin role)
|
|
|
|
```json
|
|
{
|
|
"roles": ["member", "reader"]
|
|
}
|
|
```
|
|
→ `is_owner=false` (no admin role)
|
|
|
|
### Task-Local Storage
|
|
|
|
The integration uses Tokio task-local storage to pass credentials between middleware and authentication handlers:
|
|
|
|
**Why Task-Local Storage?**
|
|
- **Async-Safe**: Works correctly with async/await and Tokio runtime
|
|
- **Request-Scoped**: Automatically cleaned up when request completes
|
|
- **No Request Modification**: Credentials don't need to be added to HTTP headers/extensions
|
|
- **Thread-Safe**: Each async task has its own isolated storage
|
|
|
|
**How It Works:**
|
|
1. Middleware validates token and stores credentials using `KEYSTONE_CREDENTIALS.scope()`
|
|
2. Auth handlers retrieve credentials using `KEYSTONE_CREDENTIALS.try_with()`
|
|
3. Storage is automatically scoped to the current async task (request)
|
|
4. Storage is empty/inaccessible outside the scope
|
|
|
|
### Token Caching
|
|
|
|
To minimize Keystone API calls, the integration includes a high-performance token cache:
|
|
|
|
**Cache Behavior:**
|
|
- **Cache Hit**: Token found in cache → Returns cached credentials (no Keystone API call)
|
|
- **Cache Miss**: Token not in cache → Validates with Keystone → Caches result
|
|
- **Cache TTL**: Tokens are cached for configured duration (default: 300 seconds)
|
|
- **Cache Invalidation**: Expired entries are automatically removed
|
|
- **Thread-Safe**: Uses `moka::future::Cache` for concurrent access
|
|
|
|
**Performance Impact:**
|
|
- First request with token: ~50-100ms (network call to Keystone)
|
|
- Subsequent requests: ~1-2ms (cache lookup)
|
|
- Recommended cache size: 10,000 tokens (configurable)
|
|
|
|
### Configuration in RustFS
|
|
|
|
To enable Keystone authentication in RustFS:
|
|
|
|
1. **Set Environment Variables:**
|
|
```bash
|
|
export RUSTFS_KEYSTONE_ENABLE=true
|
|
export RUSTFS_KEYSTONE_AUTH_URL=http://keystone:5000
|
|
export RUSTFS_KEYSTONE_VERSION=v3
|
|
export RUSTFS_KEYSTONE_ADMIN_USER=admin
|
|
export RUSTFS_KEYSTONE_ADMIN_PASSWORD=secret
|
|
export RUSTFS_KEYSTONE_ADMIN_PROJECT=admin
|
|
export RUSTFS_KEYSTONE_ADMIN_DOMAIN=Default
|
|
export RUSTFS_KEYSTONE_CACHE_SIZE=10000
|
|
export RUSTFS_KEYSTONE_CACHE_TTL=300
|
|
export RUSTFS_KEYSTONE_VERIFY_SSL=true
|
|
```
|
|
|
|
2. **Start RustFS:**
|
|
```bash
|
|
rustfs --address 127.0.0.1:9000 \
|
|
--access-key minioadmin \
|
|
--secret-key minioadmin \
|
|
volumes /data
|
|
```
|
|
|
|
3. **RustFS will automatically:**
|
|
- Initialize Keystone client on startup (in `rustfs/src/main.rs`)
|
|
- Register `KeystoneAuthLayer` middleware from this crate in HTTP service stack (in `rustfs/src/server/http.rs`)
|
|
- Start accepting both Keystone and standard S3 authentication
|
|
|
|
The middleware is entirely self-contained in the `rustfs-keystone` crate and integrated into RustFS via the exported `KeystoneAuthLayer`. No separate middleware directory is required in the main RustFS binary.
|
|
|
|
### Dual Authentication Support
|
|
|
|
RustFS supports **both** Keystone and standard S3 authentication simultaneously:
|
|
|
|
- **Keystone Users**: Use `X-Auth-Token` header with Keystone token
|
|
- **IAM Users**: Use standard AWS Signature v4 authentication
|
|
- **No Conflict**: Requests are routed based on presence of `X-Auth-Token` header
|
|
- **Automatic Detection**: Middleware automatically detects authentication method
|
|
|
|
This allows gradual migration from standard S3 auth to Keystone auth, or mixed environments where some users authenticate via Keystone and others via IAM.
|
|
|
|
## Manual Testing
|
|
|
|
### Prerequisites
|
|
|
|
1. **Running Keystone Instance**
|
|
|
|
Using Docker:
|
|
```bash
|
|
docker run -d --name keystone \
|
|
-p 5000:5000 \
|
|
-e KEYSTONE_ADMIN_PASSWORD=secret \
|
|
ghcr.io/openstack/keystone:latest
|
|
```
|
|
|
|
Or using DevStack:
|
|
```bash
|
|
# Follow DevStack installation guide
|
|
git clone https://opendev.org/openstack/devstack
|
|
cd devstack
|
|
./stack.sh
|
|
```
|
|
|
|
2. **Running RustFS with Keystone Enabled**
|
|
|
|
```bash
|
|
# Configure Keystone
|
|
export RUSTFS_KEYSTONE_ENABLE=true
|
|
export RUSTFS_KEYSTONE_AUTH_URL=http://localhost:5000
|
|
export RUSTFS_KEYSTONE_VERSION=v3
|
|
export RUSTFS_KEYSTONE_ADMIN_USER=admin
|
|
export RUSTFS_KEYSTONE_ADMIN_PASSWORD=secret
|
|
export RUSTFS_KEYSTONE_ADMIN_PROJECT=admin
|
|
export RUSTFS_KEYSTONE_ADMIN_DOMAIN=Default
|
|
|
|
# Start RustFS
|
|
cargo run --bin rustfs -- \
|
|
--address 127.0.0.1:9000 \
|
|
--access-key minioadmin \
|
|
--secret-key minioadmin \
|
|
volumes /data
|
|
```
|
|
|
|
### Test Scenarios
|
|
|
|
#### Test 1: Get Keystone Token
|
|
|
|
```bash
|
|
# Request scoped token from Keystone
|
|
curl -X POST http://localhost:5000/v3/auth/tokens \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"auth": {
|
|
"identity": {
|
|
"methods": ["password"],
|
|
"password": {
|
|
"user": {
|
|
"name": "admin",
|
|
"domain": {"name": "Default"},
|
|
"password": "secret"
|
|
}
|
|
}
|
|
},
|
|
"scope": {
|
|
"project": {
|
|
"name": "admin",
|
|
"domain": {"name": "Default"}
|
|
}
|
|
}
|
|
}
|
|
}' -i
|
|
|
|
# Look for X-Subject-Token in response headers
|
|
# Example: X-Subject-Token: gAAAAABk1a2b3c...
|
|
```
|
|
|
|
Save the token from the `X-Subject-Token` header.
|
|
|
|
#### Test 2: List Buckets with Keystone Token
|
|
|
|
```bash
|
|
# Replace TOKEN with your actual token
|
|
export KEYSTONE_TOKEN="gAAAAABk1a2b3c..."
|
|
|
|
curl -X GET http://localhost:9000/ \
|
|
-H "X-Auth-Token: $KEYSTONE_TOKEN" \
|
|
-v
|
|
```
|
|
|
|
**Expected Result:**
|
|
- Status: `200 OK`
|
|
- Response: XML list of buckets
|
|
- Logs should show: `Keystone middleware: Authentication successful for user: admin`
|
|
|
|
#### Test 3: Create Bucket
|
|
|
|
```bash
|
|
curl -X PUT http://localhost:9000/test-keystone-bucket \
|
|
-H "X-Auth-Token: $KEYSTONE_TOKEN" \
|
|
-v
|
|
```
|
|
|
|
**Expected Result:**
|
|
- Status: `200 OK`
|
|
- Bucket created successfully
|
|
- Logs show Keystone credentials being used
|
|
|
|
#### Test 4: Upload Object
|
|
|
|
```bash
|
|
echo "Hello from Keystone!" > test.txt
|
|
|
|
curl -X PUT http://localhost:9000/test-keystone-bucket/test.txt \
|
|
-H "X-Auth-Token: $KEYSTONE_TOKEN" \
|
|
-T test.txt \
|
|
-v
|
|
```
|
|
|
|
**Expected Result:**
|
|
- Status: `200 OK`
|
|
- Object uploaded successfully
|
|
|
|
#### Test 5: Download Object
|
|
|
|
```bash
|
|
curl -X GET http://localhost:9000/test-keystone-bucket/test.txt \
|
|
-H "X-Auth-Token: $KEYSTONE_TOKEN" \
|
|
-o downloaded.txt \
|
|
-v
|
|
|
|
cat downloaded.txt
|
|
```
|
|
|
|
**Expected Result:**
|
|
- Status: `200 OK`
|
|
- File content: `Hello from Keystone!`
|
|
|
|
#### Test 6: Invalid Token (Negative Test)
|
|
|
|
```bash
|
|
curl -X GET http://localhost:9000/ \
|
|
-H "X-Auth-Token: invalid-token-12345" \
|
|
-v
|
|
```
|
|
|
|
**Expected Result:**
|
|
- Status: `401 Unauthorized`
|
|
- Response:
|
|
```xml
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<Error>
|
|
<Code>InvalidToken</Code>
|
|
<Message>Invalid Keystone token</Message>
|
|
<Details>...</Details>
|
|
</Error>
|
|
```
|
|
- Logs show: `Keystone middleware: Authentication failed`
|
|
|
|
#### Test 7: No Token (Standard S3 Auth)
|
|
|
|
```bash
|
|
# Using AWS CLI with standard credentials
|
|
aws s3 ls s3:// \
|
|
--endpoint-url http://localhost:9000 \
|
|
--no-sign-request
|
|
```
|
|
|
|
**Expected Result:**
|
|
- Falls back to standard S3 authentication
|
|
- Works as normal (if anonymous access allowed)
|
|
- Logs show: `Keystone middleware: No X-Auth-Token header, passing through to S3 auth`
|
|
|
|
#### Test 8: Admin Role Permissions
|
|
|
|
```bash
|
|
# Create a user with admin role in Keystone
|
|
# Get token for admin user
|
|
|
|
curl -X DELETE http://localhost:9000/test-keystone-bucket \
|
|
-H "X-Auth-Token: $ADMIN_TOKEN" \
|
|
-v
|
|
```
|
|
|
|
**Expected Result:**
|
|
- Status: `204 No Content` (bucket deleted)
|
|
- Admin has owner permissions (`is_owner=true`)
|
|
|
|
#### Test 9: Non-Admin Role Permissions
|
|
|
|
```bash
|
|
# Create a user with only "member" role in Keystone
|
|
# Get token for member user
|
|
|
|
curl -X DELETE http://localhost:9000/test-keystone-bucket \
|
|
-H "X-Auth-Token: $MEMBER_TOKEN" \
|
|
-v
|
|
```
|
|
|
|
**Expected Result:**
|
|
- Status: `403 Forbidden` (depending on bucket policy)
|
|
- Member does not have owner permissions (`is_owner=false`)
|
|
|
|
#### Test 10: Token Caching Performance
|
|
|
|
```bash
|
|
# First request (cache miss)
|
|
time curl -X GET http://localhost:9000/ \
|
|
-H "X-Auth-Token: $KEYSTONE_TOKEN" \
|
|
-o /dev/null -s
|
|
|
|
# Second request (cache hit)
|
|
time curl -X GET http://localhost:9000/ \
|
|
-H "X-Auth-Token: $KEYSTONE_TOKEN" \
|
|
-o /dev/null -s
|
|
```
|
|
|
|
**Expected Result:**
|
|
- First request: ~50-100ms (includes Keystone API call)
|
|
- Second request: ~1-5ms (cache hit, no Keystone call)
|
|
- Logs show: `Cache hit` for second request
|
|
|
|
### Troubleshooting
|
|
|
|
**Issue: "Keystone authentication is not enabled"**
|
|
- Check `RUSTFS_KEYSTONE_ENABLE=true` is set
|
|
- Verify environment variables are exported before starting RustFS
|
|
- Check RustFS startup logs for "Keystone authentication initialized successfully"
|
|
|
|
**Issue: "Connection refused" to Keystone**
|
|
- Verify Keystone is running: `curl http://localhost:5000/v3`
|
|
- Check `RUSTFS_KEYSTONE_AUTH_URL` points to correct Keystone endpoint
|
|
- Verify network connectivity between RustFS and Keystone
|
|
|
|
**Issue: "Invalid token" errors**
|
|
- Check token hasn't expired (Keystone tokens typically expire after 1 hour)
|
|
- Request a fresh token
|
|
- Verify token format is correct (no newlines, extra spaces)
|
|
|
|
**Issue: "SSL verification failed"**
|
|
- Prefer installing Keystone's CA certificate in the system trust store
|
|
- If using a trusted non-production hop, set `RUSTFS_KEYSTONE_VERIFY_SSL=false`; this allows MITM attacks and emits a startup warning
|
|
|
|
**Issue: Slow performance**
|
|
- Increase cache size: `RUSTFS_KEYSTONE_CACHE_SIZE=50000`
|
|
- Increase cache TTL: `RUSTFS_KEYSTONE_CACHE_TTL=600`
|
|
- Check network latency to Keystone
|
|
|
|
**Issue: Permissions denied**
|
|
- Verify user's Keystone roles
|
|
- Check if user needs `admin` or `reseller_admin` role
|
|
- Review RustFS logs for `is_owner` value
|
|
|
|
## Token Cache
|
|
|
|
The token cache improves performance by caching validated tokens:
|
|
|
|
- **Cache Size**: Number of tokens to cache (default: 10,000)
|
|
- **Cache TTL**: Time-to-live for cached tokens (default: 300 seconds)
|
|
- **Thread-Safe**: Uses `moka::future::Cache` for concurrent access
|
|
|
|
## Multi-Tenancy
|
|
|
|
When tenant prefixing is enabled:
|
|
|
|
1. **Bucket Creation**: `mybucket` → stored as `project_id:mybucket`
|
|
2. **Bucket Listing**: Only shows buckets belonging to user's project
|
|
3. **Access Control**: Users can only access their project's buckets
|
|
|
|
## Role Mapping
|
|
|
|
Default role mappings:
|
|
|
|
| Keystone Role | RustFS Policy | Permissions |
|
|
|---------------|---------------|-------------|
|
|
| admin | AdminPolicy | Full access (s3:*) |
|
|
| Member | ReadWritePolicy | Read/write operations |
|
|
| _member_ | ReadOnlyPolicy | Read-only access |
|
|
| ResellerAdmin | AdminPolicy | Full access (s3:*) |
|
|
|
|
Add custom mappings:
|
|
|
|
```rust
|
|
let mut mapper = KeystoneIdentityMapper::new(client, true);
|
|
mapper.add_role_mapping("CustomRole".to_string(), "CustomPolicy".to_string());
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
All operations return `Result<T, KeystoneError>`:
|
|
|
|
```rust
|
|
use rustfs_keystone::{KeystoneError, Result};
|
|
|
|
match auth_provider.authenticate_with_token(token).await {
|
|
Ok(cred) => println!("Success: {}", cred.parent_user),
|
|
Err(KeystoneError::InvalidToken) => eprintln!("Token is invalid"),
|
|
Err(KeystoneError::TokenExpired) => eprintln!("Token has expired"),
|
|
Err(e) => eprintln!("Error: {}", e),
|
|
}
|
|
```
|
|
|
|
## Testing
|
|
|
|
Run tests with:
|
|
|
|
```bash
|
|
cargo test -p rustfs-keystone
|
|
```
|
|
|
|
### Test Structure
|
|
|
|
The crate includes comprehensive test coverage:
|
|
|
|
**Unit Tests** (16 tests in `src/` modules):
|
|
- Config parsing and validation
|
|
- Client creation
|
|
- Auth provider functionality
|
|
- Identity mapping and role permissions
|
|
- Middleware token extraction and validation
|
|
|
|
**Integration Tests** (10 tests in `tests/integration/`):
|
|
- Middleware layer creation and configuration
|
|
- Task-local storage isolation and scope management
|
|
- Credential passing between middleware and auth handlers
|
|
- Nested and sequential scope behavior
|
|
- Multi-task concurrency safety
|
|
|
|
**Total: 27 tests** covering all public APIs and integration scenarios.
|
|
|
|
Integration tests require a running Keystone instance.
|
|
|
|
## License
|
|
|
|
Licensed under the Apache License, Version 2.0. See LICENSE file for details.
|