AWS cloud integrations for LLM Shield - Secrets Manager, S3, CloudWatch, and X-Ray.
This crate provides production-ready AWS implementations of the cloud abstraction traits defined in llm-shield-cloud:
- AWS Secrets Manager - Secure secret storage and retrieval with automatic caching
- AWS S3 - Object storage for models, scan results, and configuration files
- AWS CloudWatch Metrics - Application metrics and performance monitoring
- AWS CloudWatch Logs - Structured logging and log aggregation
- ✅ Automatic AWS credential chain (env → file → IAM role → IRSA)
- ✅ Built-in secret caching with configurable TTL (5 minutes default)
- ✅ Support for AWS KMS encryption
- ✅ IAM policy templates included
- ✅ 30-day secret recovery window
- ✅ Automatic multipart uploads for large objects (>5MB)
- ✅ Batched metrics export (20 per batch)
- ✅ Batched log export (100 per batch)
- ✅ Secret caching reduces API calls by >90%
- ✅ Fully asynchronous with Tokio
- ✅ Structured logging with trace/span IDs
- ✅ Custom CloudWatch namespaces and dimensions
- ✅ Support for all CloudWatch metric units
- ✅ Automatic log stream creation
- ✅ Multi-region support
Add to your Cargo.toml:
[dependencies]
llm-shield-cloud-aws = "0.1"
llm-shield-cloud = "0.1"
tokio = { version = "1.35", features = ["full"] }use llm_shield_cloud_aws::AwsSecretsManager;
use llm_shield_cloud::CloudSecretManager;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize with default configuration
let secrets = AwsSecretsManager::new().await?;
// Fetch a secret (automatically cached for 5 minutes)
let api_key = secrets.get_secret("llm-shield/openai-api-key").await?;
println!("API Key: {}", api_key.as_string());
// Create a new secret
let new_secret = SecretValue::from_string("my-secret-value".to_string());
secrets.create_secret("llm-shield/my-secret", &new_secret).await?;
// List all secrets
let secret_names = secrets.list_secrets().await?;
println!("Found {} secrets", secret_names.len());
Ok(())
}use llm_shield_cloud_aws::AwsS3Storage;
use llm_shield_cloud::{CloudStorage, PutObjectOptions};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let storage = AwsS3Storage::new("llm-shield-models").await?;
// Upload a model (automatically uses multipart for files >5MB)
let model_data = tokio::fs::read("toxicity-model.onnx").await?;
storage.put_object("models/toxicity.onnx", &model_data).await?;
// Upload with options
let options = PutObjectOptions {
content_type: Some("application/octet-stream".to_string()),
storage_class: Some("INTELLIGENT_TIERING".to_string()),
encryption: Some("AES256".to_string()),
..Default::default()
};
storage.put_object_with_options("models/model.onnx", &model_data, &options).await?;
// Download and verify
let downloaded = storage.get_object("models/toxicity.onnx").await?;
assert_eq!(model_data, downloaded);
// List objects with prefix
let models = storage.list_objects("models/").await?;
println!("Found {} models", models.len());
Ok(())
}use llm_shield_cloud_aws::CloudWatchMetrics;
use llm_shield_cloud::{CloudMetrics, Metric};
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let metrics = CloudWatchMetrics::new("LLMShield").await?;
let mut dimensions = HashMap::new();
dimensions.insert("Environment".to_string(), "Production".to_string());
dimensions.insert("Scanner".to_string(), "Toxicity".to_string());
let metric = Metric {
name: "ScanDuration".to_string(),
value: 123.45,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs(),
dimensions,
unit: Some("Milliseconds".to_string()),
};
metrics.export_metric(&metric).await?;
metrics.flush().await?;
Ok(())
}use llm_shield_cloud_aws::CloudWatchLogger;
use llm_shield_cloud::{CloudLogger, LogLevel, LogEntry};
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let logger = CloudWatchLogger::new(
"/llm-shield/api",
"production-instance-1"
).await?;
// Simple logging
logger.log("API server started", LogLevel::Info).await?;
// Structured logging
let mut labels = HashMap::new();
labels.insert("request_id".to_string(), "req-123".to_string());
labels.insert("user_id".to_string(), "user-456".to_string());
let entry = LogEntry {
timestamp: std::time::SystemTime::now(),
level: LogLevel::Info,
message: "Request processed successfully".to_string(),
labels,
trace_id: Some("trace-789".to_string()),
span_id: Some("span-012".to_string()),
};
logger.log_structured(&entry).await?;
logger.flush().await?;
Ok(())
}Configure AWS integrations via YAML or environment variables:
cloud:
provider: aws
aws:
region: us-east-1
secrets_manager:
enabled: true
cache_ttl_seconds: 300
s3:
bucket: llm-shield-models
models_prefix: models/
results_prefix: scan-results/
cloudwatch:
enabled: true
namespace: LLMShield
log_group: /llm-shield/api
log_stream: productionOr use environment variables:
export AWS_REGION=us-east-1
export AWS_ACCESS_KEY_ID=<your-key-id>
export AWS_SECRET_ACCESS_KEY=<your-secret-key>
export LLM_SHIELD_S3_BUCKET=llm-shield-models
export LLM_SHIELD_CLOUDWATCH_NAMESPACE=LLMShieldThis crate uses the AWS SDK's default credential provider chain:
- Environment variables:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY - AWS credentials file:
~/.aws/credentials - ECS container credentials: IAM role for ECS tasks
- EC2 instance profile: IAM role for EC2 instances
- EKS pod identity: IAM Roles for Service Accounts (IRSA)
For local development, configure credentials:
aws configure
# Or set environment variables
export AWS_ACCESS_KEY_ID=<key-id>
export AWS_SECRET_ACCESS_KEY=<secret-key>
export AWS_DEFAULT_REGION=us-east-1For production deployments, use IAM roles instead of access keys:
EC2 Instance:
# Attach IAM role to EC2 instance
aws ec2 associate-iam-instance-profile \
--instance-id i-xxxxx \
--iam-instance-profile Name=LLMShieldEC2ProfileECS Task:
{
"family": "llm-shield-api",
"taskRoleArn": "arn:aws:iam::ACCOUNT_ID:role/LLMShieldECSTaskRole",
"containerDefinitions": [...]
}EKS Pod (IRSA):
apiVersion: v1
kind: Pod
metadata:
name: llm-shield-api
spec:
serviceAccountName: llm-shield-sa
containers:
- name: api
image: llm-shield:latestRequired IAM permissions are provided in iam-policies/ directory:
secrets-manager-policy.json- Secrets Manager permissionss3-policy.json- S3 bucket access permissionscloudwatch-policy.json- CloudWatch metrics and logs permissionsllm-shield-full-policy.json- Combined policy (all permissions)
For production, use least-privilege access:
aws iam put-role-policy \
--role-name LLMShieldRole \
--policy-name SecretsManagerReadOnly \
--policy-document file://iam-policies/secrets-manager-policy.jsonSee iam-policies/README.md for detailed setup instructions.
Follow these conventions for AWS resources:
- Prefix:
llm-shield/ - Examples:
llm-shield/openai-api-keyllm-shield/database-passwordllm-shield/jwt-secret
- Pattern:
llm-shield-* - Examples:
llm-shield-models-prodllm-shield-results-dev
- Models:
models/ - Scan Results:
scan-results/ - Configs:
configs/
- Namespaces:
LLMShieldLLMShield/APILLMShield/Scanners
- Log Groups:
/llm-shield/*/llm-shield/api/llm-shield/scanners
cargo test -p llm-shield-cloud-awsIntegration tests require AWS credentials and appropriate permissions:
# Set test bucket for S3 tests
export TEST_S3_BUCKET=llm-shield-test-123456789012
# Run all integration tests
cargo test -p llm-shield-cloud-aws --test integration -- --ignored
# Run specific integration test
cargo test -p llm-shield-cloud-aws --test integration_secrets -- --ignored
cargo test -p llm-shield-cloud-aws --test integration_storage -- --ignored
cargo test -p llm-shield-cloud-aws --test integration_observability -- --ignoredIntegration tests create resources with UUID suffixes for safety. Cleanup any leftover test resources:
# Delete test secrets
aws secretsmanager list-secrets --query 'SecretList[?starts_with(Name, `llm-shield-test`)].Name' --output text | \
xargs -I {} aws secretsmanager delete-secret --secret-id {} --force-delete-without-recovery
# Delete test S3 objects
aws s3 rm s3://llm-shield-test-ACCOUNT_ID/test/ --recursive
# Delete test log groups
aws logs describe-log-groups --log-group-name-prefix /llm-shield-test/ --query 'logGroups[].logGroupName' --output text | \
xargs -I {} aws logs delete-log-group --log-group-name {}| Operation | Throughput | Latency (p50) | Latency (p99) |
|---|---|---|---|
| Secret fetch (cached) | 100,000/s | <1ms | <5ms |
| Secret fetch (uncached) | 1,000/s | 50ms | 150ms |
| S3 upload (1MB) | 50 MB/s | 20ms | 100ms |
| S3 upload (50MB, multipart) | 80 MB/s | 625ms | 2s |
| S3 download (1MB) | 100 MB/s | 10ms | 50ms |
| Metrics export (batch) | 1,000/s | 10ms | 50ms |
| Logs export (batch) | 10,000/s | 5ms | 25ms |
-
Enable secret caching (default 5 minutes):
let secrets = AwsSecretsManager::new_with_cache_ttl("us-east-1", 600).await?;
-
Use multipart uploads for large files (automatic for >5MB)
-
Batch metrics and logs:
metrics.export_metrics(&batch).await?; logger.log_batch(&entries).await?;
-
Configure batch sizes:
let metrics = CloudWatchMetrics::new_with_config("LLMShield", "us-east-1", 50).await?; let logger = CloudWatchLogger::new_with_config("/llm-shield/api", "stream", "us-east-1", 200).await?;
-
Use S3 Intelligent-Tiering for cost optimization:
let options = PutObjectOptions { storage_class: Some("INTELLIGENT_TIERING".to_string()), ..Default::default() };
Typical monthly costs for production deployment:
| Service | Usage | Cost |
|---|---|---|
| Secrets Manager | 10 secrets, 100K API calls | ~$5 |
| S3 Storage | 100 GB, 1M requests | ~$3 |
| CloudWatch Logs | 50 GB ingested, 10 GB stored | ~$27 |
| CloudWatch Metrics | 50 custom metrics | ~$15 |
| Total | ~$50/month |
- Use secret caching to reduce API calls by >90%
- Enable S3 Lifecycle policies to transition old data to Glacier
- Set CloudWatch log retention to 7-30 days
- Use CloudWatch Contributor Insights for metrics analysis
- Enable S3 Intelligent-Tiering for automatic cost optimization
Check IAM permissions:
# Verify your identity
aws sts get-caller-identity
# Check attached policies
aws iam list-attached-role-policies --role-name LLMShieldRole
# Test secret access
aws secretsmanager get-secret-value --secret-id llm-shield/test-secretEnsure secret follows naming convention:
# List all secrets with prefix
aws secretsmanager list-secrets --query 'SecretList[?starts_with(Name, `llm-shield`)].Name'Check bucket policy and IAM permissions:
# Test bucket access
aws s3 ls s3://llm-shield-models/
# Check bucket policy
aws s3api get-bucket-policy --bucket llm-shield-modelsEnsure log group and stream exist:
# List log groups
aws logs describe-log-groups --log-group-name-prefix /llm-shield/
# Create log group if missing
aws logs create-log-group --log-group-name /llm-shield/apiVerify region configuration:
echo $AWS_DEFAULT_REGION
# Or specify region explicitly
let secrets = AwsSecretsManager::new_with_region("us-west-2").await?;See examples/ directory for complete examples:
secrets_example.rs- Secret managementstorage_example.rs- S3 operationsmetrics_example.rs- CloudWatch metricslogging_example.rs- CloudWatch logscombined_example.rs- Using all services together
Run examples:
cargo run --example secrets_example
cargo run --example storage_example
cargo run --example metrics_example┌─────────────────────────────────────┐
│ LLM Shield Application │
│ (llm-shield-api crate) │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ llm-shield-cloud (traits) │
│ - CloudSecretManager │
│ - CloudStorage │
│ - CloudMetrics/Logger │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ llm-shield-cloud-aws (impl) │
│ - AwsSecretsManager │
│ - AwsS3Storage │
│ - CloudWatchMetrics/Logger │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ AWS SDK for Rust │
│ - aws-sdk-secretsmanager │
│ - aws-sdk-s3 │
│ - aws-sdk-cloudwatch │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ AWS Services │
│ - Secrets Manager │
│ - S3 │
│ - CloudWatch │
└─────────────────────────────────────┘
- Never commit AWS credentials to version control
- Use IAM roles instead of access keys in production
- Enable KMS encryption for secrets and S3 objects
- Set least-privilege IAM policies for each service
- Enable AWS CloudTrail for audit logging
- Rotate secrets regularly using Secrets Manager rotation
- Use VPC endpoints for private connectivity to AWS services
- Enable S3 versioning for critical data
- Set CloudWatch log retention policies
- Review IAM policies quarterly
Report security vulnerabilities to: security@llm-shield.example.com
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
MIT OR Apache-2.0
llm-shield-cloud- Cloud abstraction traitsllm-shield-cloud-gcp- GCP integrationsllm-shield-cloud-azure- Azure integrationsllm-shield-api- LLM Shield REST API