@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
JavaScript
/**
* 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;