UNPKG

@escher-dbai/rag-module

Version:

Enterprise RAG module with chat context storage, vector search, and session management. Complete chat history retrieval and streaming content extraction for Electron apps.

541 lines (471 loc) 17.3 kB
/** * IAMService - Estate resource permission management per business architecture * Integrates with AWS IAM, Azure RBAC, and GCP IAM for unified permission checking */ class IAMService { constructor(config = {}) { this.config = { // Permission checking modes strictMode: config.strictMode !== false, // Default to strict cacheTimeout: config.cacheTimeout || 300000, // 5 minutes maxCacheSize: config.maxCacheSize || 10000, // Cloud provider configurations aws: { enabled: config.aws?.enabled !== false, region: config.aws?.region || 'us-east-1', assumeRoleArn: config.aws?.assumeRoleArn, externalId: config.aws?.externalId }, azure: { enabled: config.azure?.enabled || false, tenantId: config.azure?.tenantId, subscriptionId: config.azure?.subscriptionId }, gcp: { enabled: config.gcp?.enabled || false, projectId: config.gcp?.projectId } }; // Permission cache for performance this.permissionCache = new Map(); this.cacheTimestamps = new Map(); // Default permissions per business architecture this.defaultPermissions = { read: ['viewer', 'reader', 'observer', 'read-only'], write: ['editor', 'writer', 'contributor', 'operator'], admin: ['admin', 'administrator', 'owner', 'manager'] }; // Resource type patterns for permission mapping this.resourcePatterns = { aws: /^arn:aws:([^:]+):([^:]*):([^:]+):(.+)$/, azure: /^\/subscriptions\/([^\/]+)\/resourceGroups\/([^\/]+)\/providers\/([^\/]+)\/(.+)$/, gcp: /^projects\/([^\/]+)\/([^\/]+)\/(.+)$/ }; } // ============ BUSINESS ARCHITECTURE PERMISSION CHECKS ============ /** * Check if user has access to a specific resource * @param {Object} iamContext - IAM context (user, roles, permissions) * @param {string} resourceId - Resource identifier (ARN/URI) * @param {string} action - Required action ('read', 'write', 'admin') * @returns {Promise<boolean>} Access granted/denied */ async checkResourceAccess(iamContext, resourceId, action = 'read') { if (!iamContext || !resourceId) { console.warn('⚠️ IAM context or resource ID missing, denying access'); return false; } // Check cache first const cacheKey = this._generateCacheKey(iamContext, resourceId, action); const cachedResult = this._getCachedPermission(cacheKey); if (cachedResult !== null) { return cachedResult; } try { // Determine cloud provider from resource ID const cloudProvider = this._detectCloudProvider(resourceId); if (!cloudProvider) { console.warn(`⚠️ Unknown cloud provider for resource: ${resourceId}`); return this._handleUnknownProvider(iamContext, resourceId, action); } // Check permissions based on cloud provider let hasAccess = false; switch (cloudProvider) { case 'aws': hasAccess = await this._checkAWSPermissions(iamContext, resourceId, action); break; case 'azure': hasAccess = await this._checkAzurePermissions(iamContext, resourceId, action); break; case 'gcp': hasAccess = await this._checkGCPPermissions(iamContext, resourceId, action); break; default: hasAccess = false; } // Cache the result this._cachePermission(cacheKey, hasAccess); return hasAccess; } catch (error) { console.error(`❌ Permission check failed for ${resourceId}:`, error.message); // Fail-safe behavior based on strict mode if (this.config.strictMode) { return false; // Deny access on errors in strict mode } else { return true; // Allow access on errors in permissive mode (availability over security) } } } /** * Check bulk resource access permissions * @param {Object} iamContext - IAM context * @param {string[]} resourceIds - Array of resource identifiers * @param {string} action - Required action * @returns {Promise<Object>} Map of resourceId -> boolean */ async checkBulkResourceAccess(iamContext, resourceIds, action = 'read') { const results = {}; // Process in parallel for better performance const promises = resourceIds.map(async (resourceId) => { const hasAccess = await this.checkResourceAccess(iamContext, resourceId, action); return { resourceId, hasAccess }; }); const resolvedResults = await Promise.allSettled(promises); for (const result of resolvedResults) { if (result.status === 'fulfilled') { const { resourceId, hasAccess } = result.value; results[resourceId] = hasAccess; } else { console.error('❌ Bulk permission check failed:', result.reason); // Handle failed checks based on strict mode results[result.resourceId] = !this.config.strictMode; } } return results; } // ============ CLOUD PROVIDER SPECIFIC CHECKS ============ /** * Check AWS IAM permissions * @param {Object} iamContext - IAM context * @param {string} arn - AWS ARN * @param {string} action - Required action * @returns {Promise<boolean>} Access granted/denied */ async _checkAWSPermissions(iamContext, arn, action) { if (!this.config.aws.enabled) { console.warn('⚠️ AWS IAM checking disabled'); return !this.config.strictMode; } try { // Parse ARN components const arnMatch = arn.match(this.resourcePatterns.aws); if (!arnMatch) { console.warn(`⚠️ Invalid AWS ARN format: ${arn}`); return false; } const [, service, region, accountId, resourcePath] = arnMatch; // Check user's AWS context if (iamContext.aws) { // Check account access first if (iamContext.aws.allowedAccounts && !iamContext.aws.allowedAccounts.includes(accountId)) { return false; } // Check region access if (iamContext.aws.allowedRegions && region && !iamContext.aws.allowedRegions.includes(region)) { return false; } // Check service-specific permissions if (iamContext.aws.permissions) { return this._checkServicePermissions( iamContext.aws.permissions, service, action ); } // Check role-based permissions if (iamContext.aws.roles) { return this._checkRolePermissions(iamContext.aws.roles, action); } } // Default AWS behavior: deny if no specific permissions found return false; } catch (error) { console.error('❌ AWS permission check error:', error.message); return !this.config.strictMode; } } /** * Check Azure RBAC permissions * @param {Object} iamContext - IAM context * @param {string} resourceUri - Azure resource URI * @param {string} action - Required action * @returns {Promise<boolean>} Access granted/denied */ async _checkAzurePermissions(iamContext, resourceUri, action) { if (!this.config.azure.enabled) { console.warn('⚠️ Azure RBAC checking disabled'); return !this.config.strictMode; } try { // Parse Azure resource URI const uriMatch = resourceUri.match(this.resourcePatterns.azure); if (!uriMatch) { console.warn(`⚠️ Invalid Azure resource URI format: ${resourceUri}`); return false; } const [, subscriptionId, resourceGroup, provider, resourcePath] = uriMatch; // Check user's Azure context if (iamContext.azure) { // Check subscription access if (iamContext.azure.allowedSubscriptions && !iamContext.azure.allowedSubscriptions.includes(subscriptionId)) { return false; } // Check resource group access if (iamContext.azure.allowedResourceGroups && !iamContext.azure.allowedResourceGroups.includes(resourceGroup)) { return false; } // Check RBAC assignments if (iamContext.azure.roleAssignments) { return this._checkAzureRoleAssignments( iamContext.azure.roleAssignments, resourceUri, action ); } } return false; } catch (error) { console.error('❌ Azure permission check error:', error.message); return !this.config.strictMode; } } /** * Check GCP IAM permissions * @param {Object} iamContext - IAM context * @param {string} resourceName - GCP resource name * @param {string} action - Required action * @returns {Promise<boolean>} Access granted/denied */ async _checkGCPPermissions(iamContext, resourceName, action) { if (!this.config.gcp.enabled) { console.warn('⚠️ GCP IAM checking disabled'); return !this.config.strictMode; } try { // Parse GCP resource name const nameMatch = resourceName.match(this.resourcePatterns.gcp); if (!nameMatch) { console.warn(`⚠️ Invalid GCP resource name format: ${resourceName}`); return false; } const [, projectId, resourceType, resourcePath] = nameMatch; // Check user's GCP context if (iamContext.gcp) { // Check project access if (iamContext.gcp.allowedProjects && !iamContext.gcp.allowedProjects.includes(projectId)) { return false; } // Check IAM policy bindings if (iamContext.gcp.iamBindings) { return this._checkGCPIAMBindings( iamContext.gcp.iamBindings, resourceName, action ); } } return false; } catch (error) { console.error('❌ GCP permission check error:', error.message); return !this.config.strictMode; } } // ============ PERMISSION HELPERS ============ /** * Check service-specific permissions * @param {Object} permissions - Service permissions object * @param {string} service - AWS service name * @param {string} action - Required action * @returns {boolean} Access granted/denied */ _checkServicePermissions(permissions, service, action) { if (!permissions[service]) { return false; } const servicePerms = permissions[service]; const requiredPerms = this.defaultPermissions[action] || [action]; return requiredPerms.some(perm => servicePerms.includes(perm)); } /** * Check role-based permissions * @param {string[]} roles - User roles * @param {string} action - Required action * @returns {boolean} Access granted/denied */ _checkRolePermissions(roles, action) { const requiredPerms = this.defaultPermissions[action] || [action]; return requiredPerms.some(perm => roles.includes(perm)); } /** * Check Azure RBAC role assignments * @param {Object[]} roleAssignments - Azure role assignments * @param {string} resourceUri - Azure resource URI * @param {string} action - Required action * @returns {boolean} Access granted/denied */ _checkAzureRoleAssignments(roleAssignments, resourceUri, action) { for (const assignment of roleAssignments) { // Check scope (resource-specific or inherited) if (resourceUri.startsWith(assignment.scope)) { const requiredPerms = this.defaultPermissions[action] || [action]; if (requiredPerms.some(perm => assignment.roleDefinitionName.toLowerCase().includes(perm))) { return true; } } } return false; } /** * Check GCP IAM policy bindings * @param {Object[]} iamBindings - GCP IAM bindings * @param {string} resourceName - GCP resource name * @param {string} action - Required action * @returns {boolean} Access granted/denied */ _checkGCPIAMBindings(iamBindings, resourceName, action) { for (const binding of iamBindings) { // Check if binding applies to this resource if (this._isGCPResourceInScope(resourceName, binding.resource)) { const requiredPerms = this.defaultPermissions[action] || [action]; if (requiredPerms.some(perm => binding.role.toLowerCase().includes(perm))) { return true; } } } return false; } /** * Check if GCP resource is in IAM binding scope * @param {string} resourceName - Target resource name * @param {string} bindingResource - IAM binding resource * @returns {boolean} Resource is in scope */ _isGCPResourceInScope(resourceName, bindingResource) { // Resource hierarchy check - binding applies to resource or its parents return resourceName.startsWith(bindingResource) || bindingResource === '*'; } // ============ UTILITY METHODS ============ /** * Detect cloud provider from resource identifier * @param {string} resourceId - Resource identifier * @returns {string|null} Cloud provider ('aws', 'azure', 'gcp', null) */ _detectCloudProvider(resourceId) { if (resourceId.startsWith('arn:aws:')) return 'aws'; if (resourceId.startsWith('/subscriptions/')) return 'azure'; if (resourceId.startsWith('projects/')) return 'gcp'; return null; } /** * Handle unknown cloud provider * @param {Object} iamContext - IAM context * @param {string} resourceId - Resource identifier * @param {string} action - Required action * @returns {boolean} Access decision */ _handleUnknownProvider(iamContext, resourceId, action) { console.warn(`⚠️ Unknown cloud provider for resource: ${resourceId}`); // Check for generic permissions in IAM context if (iamContext.generic && iamContext.generic.permissions) { const requiredPerms = this.defaultPermissions[action] || [action]; return requiredPerms.some(perm => iamContext.generic.permissions.includes(perm) ); } // Default behavior based on strict mode return !this.config.strictMode; } /** * Generate cache key for permission check * @param {Object} iamContext - IAM context * @param {string} resourceId - Resource identifier * @param {string} action - Required action * @returns {string} Cache key */ _generateCacheKey(iamContext, resourceId, action) { // Create a hash of the IAM context for caching const contextKey = JSON.stringify({ userId: iamContext.userId || iamContext.user?.id, roles: iamContext.roles || [], permissions: iamContext.permissions || {} }); return `${contextKey}:${resourceId}:${action}`; } /** * Get cached permission result * @param {string} cacheKey - Cache key * @returns {boolean|null} Cached result or null if not found/expired */ _getCachedPermission(cacheKey) { const timestamp = this.cacheTimestamps.get(cacheKey); if (!timestamp) return null; // Check if cache entry is expired if (Date.now() - timestamp > this.config.cacheTimeout) { this.permissionCache.delete(cacheKey); this.cacheTimestamps.delete(cacheKey); return null; } return this.permissionCache.get(cacheKey) || null; } /** * Cache permission result * @param {string} cacheKey - Cache key * @param {boolean} result - Permission result */ _cachePermission(cacheKey, result) { // Implement LRU eviction if cache is full if (this.permissionCache.size >= this.config.maxCacheSize) { this._evictOldestCacheEntry(); } this.permissionCache.set(cacheKey, result); this.cacheTimestamps.set(cacheKey, Date.now()); } /** * Evict oldest cache entry (LRU) */ _evictOldestCacheEntry() { let oldestKey = null; let oldestTime = Date.now(); for (const [key, timestamp] of this.cacheTimestamps.entries()) { if (timestamp < oldestTime) { oldestTime = timestamp; oldestKey = key; } } if (oldestKey) { this.permissionCache.delete(oldestKey); this.cacheTimestamps.delete(oldestKey); } } /** * Clear permission cache */ clearCache() { this.permissionCache.clear(); this.cacheTimestamps.clear(); console.log('🧹 IAM permission cache cleared'); } /** * Get IAM service health status * @returns {Object} Health status */ getHealthStatus() { return { enabled: true, cacheSize: this.permissionCache.size, maxCacheSize: this.config.maxCacheSize, cacheHitRate: this._calculateCacheHitRate(), cloudProviders: { aws: this.config.aws.enabled, azure: this.config.azure.enabled, gcp: this.config.gcp.enabled }, strictMode: this.config.strictMode }; } /** * Calculate cache hit rate for monitoring * @returns {number} Cache hit rate (0-1) */ _calculateCacheHitRate() { // This would be implemented with proper metrics tracking // For now, return a placeholder return 0.85; } } module.exports = IAMService;