mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-28 07:57:01 +00:00
Openstack Keystone integration - v1 keeps the same mechanism as (#1961)
Co-authored-by: loverustfs <hello@rustfs.com> Co-authored-by: 安正超 <anzhengchao@gmail.com>
This commit is contained in:
@@ -0,0 +1,725 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
## 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"**
|
||||
- If using self-signed certificates, set `RUSTFS_KEYSTONE_VERIFY_SSL=false`
|
||||
- Or install Keystone's CA certificate in system trust store
|
||||
|
||||
**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.
|
||||
Reference in New Issue
Block a user