Cloud Credentials Management
DeployStack provides a secure cloud credentials management system that allows teams to store and manage cloud provider credentials for deployments. This system features encryption, role-based access control, and provider validation.
Architecture Overview
The cloud credentials system consists of several key components:
- Provider Configuration: Defines supported cloud providers and their required fields
- Encryption Service: Handles secure storage of credential values
- Validation System: Validates credential data against provider schemas
- Role-Based Access: Different response formats based on user permissions
- API Layer: RESTful endpoints for credential management
Database Schema
Team Cloud Credentials Table
CREATE TABLE team_cloud_credentials (
id TEXT PRIMARY KEY,
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
provider_id TEXT NOT NULL,
name TEXT NOT NULL,
comment TEXT,
credentials TEXT NOT NULL, -- Encrypted JSON
created_by TEXT NOT NULL REFERENCES authUser(id),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
UNIQUE(team_id, provider_id, name)
);
Key Features
- Team Isolation: Credentials are scoped to specific teams
- Provider Support: Multiple cloud providers per team
- Encrypted Storage: All credential values are encrypted
- Audit Trail: Tracks creation and modification metadata
- Unique Constraints: Prevents duplicate credential names per provider/team
Provider Configuration
Cloud providers are configured in services/backend/config/cloud-providers.ts
:
export interface CloudProvider {
id: string;
name: string;
description: string;
fields: CloudProviderField[];
enabled: boolean;
}
export interface CloudProviderField {
key: string;
label: string;
type: 'text' | 'password' | 'textarea';
required: boolean;
secret: boolean;
placeholder?: string;
description?: string;
validation?: {
pattern?: string;
minLength?: number;
maxLength?: number;
};
}
Example Provider Configuration
{
id: 'aws',
name: 'Amazon Web Services',
description: 'AWS cloud platform credentials',
fields: [
{
key: 'access_key_id',
label: 'Access Key ID',
type: 'text',
required: true,
secret: false,
placeholder: 'AKIAIOSFODNN7EXAMPLE'
},
{
key: 'secret_access_key',
label: 'Secret Access Key',
type: 'password',
required: true,
secret: true,
validation: {
minLength: 40
}
}
],
enabled: true
}
Encryption System
Storage Format
Credentials are stored as encrypted JSON with metadata:
interface StoredCredentials {
[fieldKey: string]: {
value: string; // Encrypted value
secret: boolean; // Field type from provider config
updatedAt: string; // ISO timestamp
};
}
Encryption Process
- Field Validation: Validate against provider schema
- Individual Encryption: Each field value encrypted separately
- Metadata Storage: Include field type and timestamp
- JSON Serialization: Store as encrypted JSON string
Security Features
- AES-256-GCM: Industry-standard encryption algorithm
- Separate Keys: Encryption keys managed separately from data
- Field-Level: Each credential field encrypted individually
- No Plaintext: Credential values never stored in plaintext
Role-Based Access Control
The cloud credentials system uses team-contextual permissions rather than global permissions. For detailed role information and permission matrices, see Role-Based Access Control.
Access Levels
User Type | Access Level | Field Information | Credential Values |
---|---|---|---|
Global Admin | Metadata only | ✅ Field types & status | ❌ No values shown |
Team Admin | Full CRUD | ✅ Field types & status | 🔒 Placeholders for non-secret |
Team User | Read-only basic | ❌ No field details | ❌ No values shown |
Non-member | No access | ❌ Blocked | ❌ Blocked |
Key Security Features
- Team Isolation: Users can only access credentials from teams they belong to
- No Secret Exposure: Secret values are never returned in API responses
- Role-Based Responses: API responses vary based on user's role within the team
- Global Admin Limitations: Even global admins cannot see credential values
API Implementation
Service Layer
The CloudCredentialsService
provides the core business logic:
export class CloudCredentialsService {
// Role-specific methods
async getTeamCredentials(teamId: string): Promise<CloudCredentialResponse[]>
async getTeamCredentialsGlobalAdmin(teamId: string): Promise<CloudCredentialResponse[]>
async getTeamCredentialsBasic(teamId: string): Promise<CloudCredentialBasicResponse[]>
// CRUD operations
async createCredentials(teamId: string, userId: string, input: CreateCloudCredentialRequest): Promise<CloudCredentialResponse>
async updateCredentials(credentialId: string, teamId: string, input: UpdateCloudCredentialRequest): Promise<CloudCredentialResponse | null>
async deleteCredentials(credentialId: string, teamId: string): Promise<boolean>
// Internal methods
async getDecryptedCredentials(credentialId: string, teamId: string): Promise<Record<string, string> | null>
}
Route Implementation
Routes automatically detect user role and call appropriate service methods:
// Check user permissions and role
const roleService = new RoleService();
const hasAdminPermissions = await roleService.userHasPermission(userId, 'cloud_credentials.edit');
const userRole = await roleService.getUserRole(userId);
const isGlobalAdmin = userRole?.id === 'global_admin';
let credentials;
if (hasAdminPermissions && !isGlobalAdmin) {
// Team admin - full details with placeholders
credentials = await cloudCredentialsService.getTeamCredentials(teamId);
} else if (isGlobalAdmin) {
// Global admin - metadata only, no values
credentials = await cloudCredentialsService.getTeamCredentialsGlobalAdmin(teamId);
} else {
// Team member - basic details only
credentials = await cloudCredentialsService.getTeamCredentialsBasic(teamId);
}
API Endpoints
List Cloud Providers
GET /api/teams/:teamId/cloud-providers
Authorization: Required (cloud_credentials.view permission)
Returns available cloud providers with their field schemas.
List Team Credentials
GET /api/teams/:teamId/cloud-credentials
Authorization: Required (cloud_credentials.view permission)
Returns credentials list with response format based on user role.
Create Credentials
POST /api/teams/:teamId/cloud-credentials
Authorization: Required (cloud_credentials.create permission)
Content-Type: application/json
{
"providerId": "aws",
"name": "Production AWS",
"comment": "Production environment credentials",
"credentials": {
"access_key_id": "AKIAIOSFODNN7EXAMPLE",
"secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}
}
Update Credentials
PUT /api/teams/:teamId/cloud-credentials/:credentialId
Authorization: Required (cloud_credentials.edit permission)
Supports partial updates of name, comment, and credential values.
Delete Credentials
DELETE /api/teams/:teamId/cloud-credentials/:credentialId
Authorization: Required (cloud_credentials.delete permission)
Permanently removes credentials from the team.
Validation System
Create vs Update Validation
The system provides two validation functions to handle different scenarios:
Full Validation (Create)
export function validateCredentialData(
providerId: string,
credentials: Record<string, string>
): ValidationResult {
const provider = getCloudProvider(providerId);
if (!provider) {
return { valid: false, errors: ['Invalid provider ID'] };
}
const errors: string[] = [];
// Check ALL required fields
for (const field of provider.fields) {
if (field.required && !credentials[field.key]) {
errors.push(`${field.label} is required`);
}
}
// Validate all provided fields
for (const [key, value] of Object.entries(credentials)) {
const field = provider.fields.find(f => f.key === key);
if (field?.validation) {
// Apply validation rules
}
}
return { valid: errors.length === 0, errors };
}
Partial Validation (Update)
export function validateCredentialDataForUpdate(
providerId: string,
credentials: Record<string, string>
): ValidationResult {
const provider = getCloudProvider(providerId);
if (!provider) {
return { valid: false, errors: ['Invalid provider ID'] };
}
const errors: string[] = [];
// Only validate fields that are actually provided
for (const field of provider.fields) {
const value = credentials[field.key];
// Skip validation if field is not provided
if (value === null || value === undefined) {
continue;
}
// Validate provided fields
if (field.required && value.trim() === '') {
errors.push(`${field.label} cannot be empty`);
}
// Apply format validation if present
if (field.validation) {
// Apply validation rules
}
}
return { valid: errors.length === 0, errors };
}
Update Process
When updating credentials, the system:
- Validates only provided fields using
validateCredentialDataForUpdate
- Retrieves existing credentials from encrypted storage
- Merges updates with existing values
- Re-encrypts the complete credential set
This allows partial updates without requiring users to re-submit secret values.
Validation Rules
- Required Fields: Enforced based on provider configuration
- Field Types: Text, password, textarea validation
- Format Validation: Pattern matching, length constraints
- Provider Schema: Validates against defined field structure
- Partial Updates: Only validates fields being updated
Error Handling
Common Error Scenarios
// Provider not found
throw new Error('Invalid provider ID');
// Validation failure
throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
// Duplicate name
throw new Error('A credential set with this name already exists for this provider');
// Not found
return null; // Handled as 404 in routes
Error Response Format
{
"success": false,
"error": "Validation failed",
"details": ["Access Key ID is required", "Secret Access Key must be at least 40 characters"]
}
Security Considerations
Data Protection
- Encryption at Rest: All credential values encrypted before storage
- No Plaintext Logs: Credential values never logged in plaintext
- Secure Transmission: HTTPS required for all API calls
- Access Control: Role-based response filtering
Best Practices
- Principle of Least Privilege: Users see only necessary information
- Audit Logging: Track all credential operations
- Regular Rotation: Encourage credential rotation
- Secure Defaults: Safe fallbacks for all operations
Adding New Providers
1. Define Provider Configuration
Add new provider to cloud-providers.ts
:
{
id: 'new-provider',
name: 'New Cloud Provider',
description: 'Description of the provider',
fields: [
{
key: 'api_key',
label: 'API Key',
type: 'password',
required: true,
secret: true
}
],
enabled: true
}
2. Update Provider Registry
Add to the providers array and export:
export const CLOUD_PROVIDERS: CloudProvider[] = [
AWS_PROVIDER,
RENDER_PROVIDER,
NEW_PROVIDER, // Add here
];
3. Test Integration
- Validate field schemas work correctly
- Test encryption/decryption of new field types
- Verify API responses include new provider
- Test credential creation and validation
Troubleshooting
Common Issues
Encryption Errors
- Verify encryption service is properly configured
- Check that encryption keys are available
- Ensure proper error handling for encryption failures
Validation Failures
- Check provider configuration matches expected format
- Verify required fields are properly marked
- Test validation rules with sample data
Permission Errors
- Confirm user has required permissions
- Check role assignments are correct
- Verify middleware is properly applied to routes
Debug Commands
// Test provider configuration
const provider = getCloudProvider('aws');
console.log('Provider config:', provider);
// Test validation
const validation = validateCredentialData('aws', testCredentials);
console.log('Validation result:', validation);
// Check user permissions
const hasPermission = await roleService.userHasPermission(userId, 'cloud_credentials.view');
console.log('Has permission:', hasPermission);
Performance Considerations
Optimization Strategies
- Lazy Loading: Load provider configurations on demand
- Caching: Cache provider configurations in memory
- Batch Operations: Support bulk credential operations
- Pagination: Implement pagination for large credential lists
Monitoring
- API Response Times: Monitor credential API performance
- Encryption Overhead: Track encryption/decryption performance
- Database Queries: Optimize credential lookup queries
- Memory Usage: Monitor provider configuration memory usage
Future Enhancements
Planned Features
- Credential Sharing: Share credentials between teams
- Expiration Dates: Set expiration dates for credentials
- Usage Tracking: Track which deployments use which credentials
- Backup/Restore: Export/import encrypted credential backups
- Integration Testing: Test credentials against actual providers
Extension Points
- Custom Providers: Plugin system for custom cloud providers
- Validation Plugins: Custom validation rules for specific providers
- Encryption Backends: Support for different encryption systems
- Audit Plugins: Custom audit logging implementations
Backend Authentication System
Technical documentation for the DeployStack backend authentication implementation, including session management, password hashing, and authentication flows.
SQLite Database Development Guide
Technical implementation details and best practices for SQLite integration in DeployStack Backend development.