Files
gt-ai-os-community/packages/utils/dist/tenant.js
HackWeasel b9dfb86260 GT AI OS Community Edition v2.0.33
Security hardening release addressing CodeQL and Dependabot alerts:

- Fix stack trace exposure in error responses
- Add SSRF protection with DNS resolution checking
- Implement proper URL hostname validation (replaces substring matching)
- Add centralized path sanitization to prevent path traversal
- Fix ReDoS vulnerability in email validation regex
- Improve HTML sanitization in validation utilities
- Fix capability wildcard matching in auth utilities
- Update glob dependency to address CVE
- Add CodeQL suppression comments for verified false positives

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 17:04:45 -05:00

339 lines
9.0 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateTenantNamespace = generateTenantNamespace;
exports.generateTenantSubdomain = generateTenantSubdomain;
exports.generateTenantUserId = generateTenantUserId;
exports.generateTenantGroupId = generateTenantGroupId;
exports.getTenantDataPath = getTenantDataPath;
exports.getTemplateResourceLimits = getTemplateResourceLimits;
exports.getTemplateMaxUsers = getTemplateMaxUsers;
exports.isDomainAvailable = isDomainAvailable;
exports.generateTenantConfig = generateTenantConfig;
exports.generateTenantDeploymentYAML = generateTenantDeploymentYAML;
exports.calculateTenantCosts = calculateTenantCosts;
/**
* Generate Kubernetes namespace name for tenant
*/
function generateTenantNamespace(domain) {
return `gt-${domain}`;
}
/**
* Generate tenant subdomain
*/
function generateTenantSubdomain(domain) {
return domain; // For now, subdomain matches domain
}
/**
* Generate OS user ID for tenant isolation
*/
function generateTenantUserId(tenantId) {
const baseUserId = 10000; // Start user IDs from 10000
return baseUserId + tenantId;
}
/**
* Generate OS group ID for tenant isolation
*/
function generateTenantGroupId(tenantId) {
return generateTenantUserId(tenantId); // Use same ID for group
}
/**
* Get tenant data directory path
*/
function getTenantDataPath(domain, baseDataDir = '/data') {
return `${baseDataDir}/${domain}`;
}
/**
* Get default resource limits based on template
*/
function getTemplateResourceLimits(template) {
switch (template) {
case 'basic':
return {
cpu: '500m',
memory: '1Gi',
storage: '5Gi'
};
case 'professional':
return {
cpu: '1000m',
memory: '2Gi',
storage: '20Gi'
};
case 'enterprise':
return {
cpu: '2000m',
memory: '4Gi',
storage: '100Gi'
};
default:
return {
cpu: '500m',
memory: '1Gi',
storage: '5Gi'
};
}
}
/**
* Get default max users based on template
*/
function getTemplateMaxUsers(template) {
switch (template) {
case 'basic':
return 10;
case 'professional':
return 100;
case 'enterprise':
return 1000;
default:
return 10;
}
}
/**
* Validate tenant domain availability (placeholder - would check database in real implementation)
*/
function isDomainAvailable(domain) {
// In real implementation, this would check the database
// For now, just check format
const reservedDomains = ['admin', 'api', 'www', 'mail', 'ftp', 'localhost', 'gt2'];
return !reservedDomains.includes(domain.toLowerCase());
}
/**
* Generate complete tenant configuration from create request
*/
function generateTenantConfig(request, masterEncryptionKey) {
const template = request.template || 'basic';
const resourceLimits = request.resource_limits || getTemplateResourceLimits(template);
const maxUsers = request.max_users || getTemplateMaxUsers(template);
return {
name: request.name.trim(),
domain: request.domain.toLowerCase(),
template,
max_users: maxUsers,
resource_limits: resourceLimits,
namespace: generateTenantNamespace(request.domain),
subdomain: generateTenantSubdomain(request.domain),
status: 'pending'
};
}
/**
* Generate Kubernetes deployment YAML for tenant
*/
function generateTenantDeploymentYAML(tenant, tenantUserId) {
return `
apiVersion: v1
kind: Namespace
metadata:
name: ${tenant.namespace}
labels:
gt.tenant: ${tenant.domain}
gt.template: ${tenant.template}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ${tenant.domain}-isolation
namespace: ${tenant.namespace}
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
ingress:
- from:
- namespaceSelector:
matchLabels:
name: gt-admin
egress:
- to:
- namespaceSelector:
matchLabels:
name: gt-resource
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ${tenant.domain}-config
namespace: ${tenant.namespace}
data:
TENANT_ID: "${tenant.id}"
TENANT_DOMAIN: "${tenant.domain}"
TENANT_NAME: "${tenant.name}"
DATABASE_PATH: "/data/${tenant.domain}/app.db"
CHROMA_COLLECTION: "gt2_${tenant.domain.replace(/-/g, '_')}_documents"
REDIS_PREFIX: "gt2:${tenant.domain}:"
MINIO_BUCKET: "gt2-${tenant.domain}-files"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${tenant.domain}-app
namespace: ${tenant.namespace}
labels:
app: ${tenant.domain}-app
tenant: ${tenant.domain}
spec:
replicas: 1
selector:
matchLabels:
app: ${tenant.domain}-app
template:
metadata:
labels:
app: ${tenant.domain}-app
tenant: ${tenant.domain}
spec:
securityContext:
runAsUser: ${tenantUserId}
runAsGroup: ${tenantUserId}
fsGroup: ${tenantUserId}
containers:
- name: frontend
image: gt2/tenant-frontend:latest
ports:
- containerPort: 3000
name: frontend
env:
- name: NEXT_PUBLIC_API_URL
value: "http://localhost:8000"
- name: NEXT_PUBLIC_WS_URL
value: "ws://localhost:8000"
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "${tenant.resource_limits.cpu}"
memory: "${tenant.resource_limits.memory}"
volumeMounts:
- name: tenant-data
mountPath: /data/${tenant.domain}
- name: backend
image: gt2/tenant-backend:latest
ports:
- containerPort: 8000
name: backend
envFrom:
- configMapRef:
name: ${tenant.domain}-config
env:
- name: ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: ${tenant.domain}-secrets
key: encryption-key
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "${tenant.resource_limits.cpu}"
memory: "${tenant.resource_limits.memory}"
volumeMounts:
- name: tenant-data
mountPath: /data/${tenant.domain}
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: tenant-data
persistentVolumeClaim:
claimName: ${tenant.domain}-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ${tenant.domain}-data
namespace: ${tenant.namespace}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: ${tenant.resource_limits.storage}
---
apiVersion: v1
kind: Secret
metadata:
name: ${tenant.domain}-secrets
namespace: ${tenant.namespace}
type: Opaque
data:
encryption-key: ${Buffer.from(tenant.encryption_key || '').toString('base64')}
---
apiVersion: v1
kind: Service
metadata:
name: ${tenant.domain}-service
namespace: ${tenant.namespace}
spec:
selector:
app: ${tenant.domain}-app
ports:
- name: frontend
port: 3000
targetPort: 3000
- name: backend
port: 8000
targetPort: 8000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${tenant.domain}-ingress
namespace: ${tenant.namespace}
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: ${tenant.subdomain}.gt2.local
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: ${tenant.domain}-service
port:
number: 8000
- path: /
pathType: Prefix
backend:
service:
name: ${tenant.domain}-service
port:
number: 3000
`.trim();
}
/**
* Calculate tenant usage costs
*/
function calculateTenantCosts(cpuUsage, // CPU hours
memoryUsage, // Memory GB-hours
storageUsage, // Storage GB-hours
aiTokens // AI tokens used
) {
// Pricing (example rates)
const CPU_COST_PER_HOUR = 5; // 5 cents per CPU hour
const MEMORY_COST_PER_GB_HOUR = 1; // 1 cent per GB-hour
const STORAGE_COST_PER_GB_HOUR = 0.1; // 0.1 cents per GB-hour
const AI_COST_PER_1K_TOKENS = 0.5; // 0.5 cents per 1K tokens
const cpu_cost_cents = Math.round(cpuUsage * CPU_COST_PER_HOUR);
const memory_cost_cents = Math.round(memoryUsage * MEMORY_COST_PER_GB_HOUR);
const storage_cost_cents = Math.round(storageUsage * STORAGE_COST_PER_GB_HOUR);
const ai_cost_cents = Math.round((aiTokens / 1000) * AI_COST_PER_1K_TOKENS);
return {
cpu_cost_cents,
memory_cost_cents,
storage_cost_cents,
ai_cost_cents,
total_cost_cents: cpu_cost_cents + memory_cost_cents + storage_cost_cents + ai_cost_cents
};
}