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 routesError 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