particle-os-tools/src/apt-layer/scriptlets/15-multi-tenant.sh
robojerk 74c7bede5f Initial commit: Particle-OS tools repository
- Complete Particle-OS rebranding from uBlue-OS
- Professional installation system with standardized paths
- Self-initialization system with --init and --reset commands
- Enhanced error messages and dependency checking
- Comprehensive testing infrastructure
- All source scriptlets updated with runtime improvements
- Clean codebase with redundant files moved to archive
- Complete documentation suite
2025-07-11 21:14:33 -07:00

641 lines
21 KiB
Bash

#!/bin/bash
# Multi-Tenant Support for apt-layer
# Enables enterprise deployments with multiple organizations, departments, or environments
# Provides tenant isolation, resource quotas, and cross-tenant management
# Multi-tenant configuration
MULTI_TENANT_ENABLED="${MULTI_TENANT_ENABLED:-false}"
TENANT_ISOLATION_LEVEL="${TENANT_ISOLATION_LEVEL:-strict}" # strict, moderate, permissive
TENANT_RESOURCE_QUOTAS="${TENANT_RESOURCE_QUOTAS:-true}"
TENANT_CROSS_ACCESS="${TENANT_CROSS_ACCESS:-false}"
# Tenant management functions
init_multi_tenant_system() {
log_info "Initializing multi-tenant system..." "multi-tenant"
# Create tenant directories
local tenant_base="${WORKSPACE}/tenants"
mkdir -p "$tenant_base"
mkdir -p "$tenant_base/shared"
mkdir -p "$tenant_base/templates"
# Initialize tenant database
local tenant_db="$tenant_base/tenants.json"
if [[ ! -f "$tenant_db" ]]; then
cat > "$tenant_db" << 'EOF'
{
"tenants": [],
"policies": {
"default_isolation": "strict",
"default_quotas": {
"max_layers": 100,
"max_storage_gb": 50,
"max_users": 10
},
"cross_tenant_access": false
},
"metadata": {
"created": "",
"version": "1.0"
}
}
EOF
# Set creation timestamp
jq --arg created "$(date -Iseconds)" '.metadata.created = $created' "$tenant_db" > "$tenant_db.tmp" && mv "$tenant_db.tmp" "$tenant_db"
fi
log_success "Multi-tenant system initialized" "multi-tenant"
}
# Tenant creation and management
create_tenant() {
local tenant_name="$1"
local tenant_config="$2"
if [[ -z "$tenant_name" ]]; then
log_error "Tenant name is required" "multi-tenant"
return 1
fi
# Validate tenant name
if [[ ! "$tenant_name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
log_error "Invalid tenant name: $tenant_name (use alphanumeric, underscore, hyphen only)" "multi-tenant"
return 1
fi
local tenant_base="${WORKSPACE}/tenants"
local tenant_db="$tenant_base/tenants.json"
local tenant_dir="$tenant_base/$tenant_name"
# Check if tenant already exists
if jq -e ".tenants[] | select(.name == \"$tenant_name\")" "$tenant_db" > /dev/null 2>&1; then
log_error "Tenant '$tenant_name' already exists" "multi-tenant"
return 1
fi
# Create tenant directory structure
mkdir -p "$tenant_dir"
mkdir -p "$tenant_dir/layers"
mkdir -p "$tenant_dir/deployments"
mkdir -p "$tenant_dir/users"
mkdir -p "$tenant_dir/audit"
mkdir -p "$tenant_dir/backups"
mkdir -p "$tenant_dir/config"
# Create tenant configuration
local tenant_config_file="$tenant_dir/config/tenant.json"
cat > "$tenant_config_file" << EOF
{
"name": "$tenant_name",
"created": "$(date -Iseconds)",
"status": "active",
"isolation_level": "$TENANT_ISOLATION_LEVEL",
"quotas": {
"max_layers": 100,
"max_storage_gb": 50,
"max_users": 10,
"used_layers": 0,
"used_storage_gb": 0,
"used_users": 0
},
"policies": {
"allowed_packages": [],
"blocked_packages": [],
"security_level": "standard",
"audit_retention_days": 90
},
"integrations": {
"oci_registries": [],
"external_audit": null,
"monitoring": null
}
}
EOF
# Merge custom configuration if provided
if [[ -n "$tenant_config" && -f "$tenant_config" ]]; then
if jq empty "$tenant_config" 2>/dev/null; then
jq -s '.[0] * .[1]' "$tenant_config_file" "$tenant_config" > "$tenant_config_file.tmp" && mv "$tenant_config_file.tmp" "$tenant_config_file"
else
log_warning "Invalid JSON in tenant configuration, using defaults" "multi-tenant"
fi
fi
# Add tenant to database
local tenant_info
tenant_info=$(jq -r '.' "$tenant_config_file")
jq --arg name "$tenant_name" --argjson info "$tenant_info" '.tenants += [$info]' "$tenant_db" > "$tenant_db.tmp" && mv "$tenant_db.tmp" "$tenant_db"
log_success "Tenant '$tenant_name' created successfully" "multi-tenant"
log_info "Tenant directory: $tenant_dir" "multi-tenant"
}
# Tenant deletion
delete_tenant() {
local tenant_name="$1"
local force="${2:-false}"
if [[ -z "$tenant_name" ]]; then
log_error "Tenant name is required" "multi-tenant"
return 1
fi
local tenant_base="${WORKSPACE}/tenants"
local tenant_db="$tenant_base/tenants.json"
local tenant_dir="$tenant_base/$tenant_name"
# Check if tenant exists
if ! jq -e ".tenants[] | select(.name == \"$tenant_name\")" "$tenant_db" > /dev/null 2>&1; then
log_error "Tenant '$tenant_name' does not exist" "multi-tenant"
return 1
fi
# Check for active resources
local active_layers=0
local active_deployments=0
if [[ -d "$tenant_dir/layers" ]]; then
active_layers=$(find "$tenant_dir/layers" -name "*.squashfs" 2>/dev/null | wc -l)
fi
if [[ -d "$tenant_dir/deployments" ]]; then
active_deployments=$(find "$tenant_dir/deployments" -name "*.json" 2>/dev/null | wc -l)
fi
if [[ $active_layers -gt 0 || $active_deployments -gt 0 ]]; then
if [[ "$force" != "true" ]]; then
log_error "Tenant '$tenant_name' has active resources ($active_layers layers, $active_deployments deployments)" "multi-tenant"
log_error "Use --force to delete anyway" "multi-tenant"
return 1
else
log_warning "Force deleting tenant with active resources" "multi-tenant"
fi
fi
# Remove from database
jq --arg name "$tenant_name" 'del(.tenants[] | select(.name == $name))' "$tenant_db" > "$tenant_db.tmp" && mv "$tenant_db.tmp" "$tenant_db"
# Remove tenant directory
if [[ -d "$tenant_dir" ]]; then
rm -rf "$tenant_dir"
fi
log_success "Tenant '$tenant_name' deleted successfully" "multi-tenant"
}
# Tenant listing and information
list_tenants() {
local format="${1:-table}"
local tenant_base="${WORKSPACE}/tenants"
local tenant_db="$tenant_base/tenants.json"
if [[ ! -f "$tenant_db" ]]; then
log_error "Tenant database not found" "multi-tenant"
return 1
fi
case "$format" in
"json")
jq -r '.' "$tenant_db"
;;
"csv")
echo "name,status,created,layers,storage_gb,users"
jq -r '.tenants[] | [.name, .status, .created, .quotas.used_layers, .quotas.used_storage_gb, .quotas.used_users] | @csv' "$tenant_db"
;;
"table"|*)
echo "Tenants:"
echo "========"
jq -r '.tenants[] | "\(.name) (\(.status)) - Layers: \(.quotas.used_layers)/\(.quotas.max_layers), Storage: \(.quotas.used_storage_gb)GB/\(.quotas.max_storage_gb)GB"' "$tenant_db"
;;
esac
}
# Tenant information
get_tenant_info() {
local tenant_name="$1"
local format="${2:-json}"
if [[ -z "$tenant_name" ]]; then
log_error "Tenant name is required" "multi-tenant"
return 1
fi
local tenant_base="${WORKSPACE}/tenants"
local tenant_db="$tenant_base/tenants.json"
local tenant_info
tenant_info=$(jq -r ".tenants[] | select(.name == \"$tenant_name\")" "$tenant_db" 2>/dev/null)
if [[ -z "$tenant_info" ]]; then
log_error "Tenant '$tenant_name' not found" "multi-tenant"
return 1
fi
case "$format" in
"json")
echo "$tenant_info"
;;
"yaml")
echo "$tenant_info" | jq -r '.' | sed 's/^/ /'
;;
"summary")
local name status created layers storage users
name=$(echo "$tenant_info" | jq -r '.name')
status=$(echo "$tenant_info" | jq -r '.status')
created=$(echo "$tenant_info" | jq -r '.created')
layers=$(echo "$tenant_info" | jq -r '.quotas.used_layers')
storage=$(echo "$tenant_info" | jq -r '.quotas.used_storage_gb')
users=$(echo "$tenant_info" | jq -r '.quotas.used_users')
echo "Tenant: $name"
echo "Status: $status"
echo "Created: $created"
echo "Resources: $layers layers, ${storage}GB storage, $users users"
;;
esac
}
# Tenant quota management
update_tenant_quotas() {
local tenant_name="$1"
local quota_type="$2"
local value="$3"
if [[ -z "$tenant_name" || -z "$quota_type" || -z "$value" ]]; then
log_error "Usage: update_tenant_quotas <tenant> <quota_type> <value>" "multi-tenant"
return 1
fi
local tenant_base="${WORKSPACE}/tenants"
local tenant_db="$tenant_base/tenants.json"
# Validate quota type
case "$quota_type" in
"max_layers"|"max_storage_gb"|"max_users")
;;
*)
log_error "Invalid quota type: $quota_type" "multi-tenant"
log_error "Valid types: max_layers, max_storage_gb, max_users" "multi-tenant"
return 1
;;
esac
# Update quota
jq --arg name "$tenant_name" --arg type "$quota_type" --arg value "$value" \
'.tenants[] | select(.name == $name) | .quotas[$type] = ($value | tonumber)' "$tenant_db" > "$tenant_db.tmp" && mv "$tenant_db.tmp" "$tenant_db"
log_success "Updated quota for tenant '$tenant_name': $quota_type = $value" "multi-tenant"
}
# Tenant isolation and access control
check_tenant_access() {
local tenant_name="$1"
local user="$2"
local operation="$3"
if [[ -z "$tenant_name" || -z "$user" || -z "$operation" ]]; then
log_error "Usage: check_tenant_access <tenant> <user> <operation>" "multi-tenant"
return 1
fi
local tenant_base="${WORKSPACE}/tenants"
local tenant_db="$tenant_base/tenants.json"
# Check if tenant exists
if ! jq -e ".tenants[] | select(.name == \"$tenant_name\")" "$tenant_db" > /dev/null 2>&1; then
log_error "Tenant '$tenant_name' not found" "multi-tenant"
return 1
fi
# Get tenant isolation level
local isolation_level
isolation_level=$(jq -r ".tenants[] | select(.name == \"$tenant_name\") | .isolation_level" "$tenant_db")
# Check user access (simplified - in real implementation, this would check user roles)
local user_file="$tenant_base/$tenant_name/users/$user.json"
if [[ ! -f "$user_file" ]]; then
log_error "User '$user' not found in tenant '$tenant_name'" "multi-tenant"
return 1
fi
# Check operation permissions
local user_role
user_role=$(jq -r '.role' "$user_file" 2>/dev/null)
case "$operation" in
"read")
[[ "$user_role" =~ ^(admin|package_manager|viewer)$ ]] && return 0
;;
"write")
[[ "$user_role" =~ ^(admin|package_manager)$ ]] && return 0
;;
"admin")
[[ "$user_role" == "admin" ]] && return 0
;;
*)
log_error "Unknown operation: $operation" "multi-tenant"
return 1
;;
esac
log_error "Access denied: User '$user' with role '$user_role' cannot perform '$operation' operation" "multi-tenant"
return 1
}
# Tenant resource usage tracking
update_tenant_usage() {
local tenant_name="$1"
local resource_type="$2"
local amount="$3"
if [[ -z "$tenant_name" || -z "$resource_type" || -z "$amount" ]]; then
log_error "Usage: update_tenant_usage <tenant> <resource_type> <amount>" "multi-tenant"
return 1
fi
local tenant_base="${WORKSPACE}/tenants"
local tenant_db="$tenant_base/tenants.json"
# Update usage
jq --arg name "$tenant_name" --arg type "$resource_type" --arg amount "$amount" \
'.tenants[] | select(.name == $name) | .quotas["used_" + $type] = (.quotas["used_" + $type] + ($amount | tonumber))' "$tenant_db" > "$tenant_db.tmp" && mv "$tenant_db.tmp" "$tenant_db"
log_debug "Updated usage for tenant '$tenant_name': $resource_type += $amount" "multi-tenant"
}
# Tenant quota enforcement
enforce_tenant_quotas() {
local tenant_name="$1"
local resource_type="$2"
local requested_amount="$3"
if [[ -z "$tenant_name" || -z "$resource_type" || -z "$requested_amount" ]]; then
log_error "Usage: enforce_tenant_quotas <tenant> <resource_type> <amount>" "multi-tenant"
return 1
fi
local tenant_base="${WORKSPACE}/tenants"
local tenant_db="$tenant_base/tenants.json"
# Get current usage and quota
local current_usage max_quota
current_usage=$(jq -r ".tenants[] | select(.name == \"$tenant_name\") | .quotas.used_$resource_type" "$tenant_db")
max_quota=$(jq -r ".tenants[] | select(.name == \"$tenant_name\") | .quotas.max_$resource_type" "$tenant_db")
# Check if request would exceed quota
local new_total=$((current_usage + requested_amount))
if [[ $new_total -gt $max_quota ]]; then
log_error "Quota exceeded for tenant '$tenant_name': $resource_type" "multi-tenant"
log_error "Current: $current_usage, Requested: $requested_amount, Max: $max_quota" "multi-tenant"
return 1
fi
return 0
}
# Cross-tenant operations (when enabled)
cross_tenant_operation() {
local source_tenant="$1"
local target_tenant="$2"
local operation="$3"
local user="$4"
if [[ "$TENANT_CROSS_ACCESS" != "true" ]]; then
log_error "Cross-tenant operations are disabled" "multi-tenant"
return 1
fi
if [[ -z "$source_tenant" || -z "$target_tenant" || -z "$operation" || -z "$user" ]]; then
log_error "Usage: cross_tenant_operation <source> <target> <operation> <user>" "multi-tenant"
return 1
fi
# Check user has admin access to both tenants
if ! check_tenant_access "$source_tenant" "$user" "admin"; then
log_error "User '$user' lacks admin access to source tenant '$source_tenant'" "multi-tenant"
return 1
fi
if ! check_tenant_access "$target_tenant" "$user" "admin"; then
log_error "User '$user' lacks admin access to target tenant '$target_tenant'" "multi-tenant"
return 1
fi
log_info "Cross-tenant operation: $operation from '$source_tenant' to '$target_tenant' by '$user'" "multi-tenant"
# Implement specific cross-tenant operations here
case "$operation" in
"copy_layer")
# Copy layer from source to target tenant
log_info "Copying layer between tenants..." "multi-tenant"
;;
"sync_config")
# Sync configuration between tenants
log_info "Syncing configuration between tenants..." "multi-tenant"
;;
*)
log_error "Unknown cross-tenant operation: $operation" "multi-tenant"
return 1
;;
esac
}
# Tenant backup and restore
backup_tenant() {
local tenant_name="$1"
local backup_path="$2"
if [[ -z "$tenant_name" ]]; then
log_error "Tenant name is required" "multi-tenant"
return 1
fi
local tenant_base="${WORKSPACE}/tenants"
local tenant_dir="$tenant_base/$tenant_name"
if [[ ! -d "$tenant_dir" ]]; then
log_error "Tenant directory not found: $tenant_dir" "multi-tenant"
return 1
fi
# Create backup
local backup_file
if [[ -n "$backup_path" ]]; then
backup_file="$backup_path"
else
backup_file="$tenant_dir/backups/tenant-${tenant_name}-$(date +%Y%m%d-%H%M%S).tar.gz"
fi
mkdir -p "$(dirname "$backup_file")"
tar -czf "$backup_file" -C "$tenant_base" "$tenant_name"
log_success "Tenant '$tenant_name' backed up to: $backup_file" "multi-tenant"
}
restore_tenant() {
local backup_file="$1"
local tenant_name="$2"
if [[ -z "$backup_file" || -z "$tenant_name" ]]; then
log_error "Usage: restore_tenant <backup_file> <tenant_name>" "multi-tenant"
return 1
fi
if [[ ! -f "$backup_file" ]]; then
log_error "Backup file not found: $backup_file" "multi-tenant"
return 1
fi
local tenant_base="${WORKSPACE}/tenants"
local tenant_dir="$tenant_base/$tenant_name"
# Check if tenant already exists
if [[ -d "$tenant_dir" ]]; then
log_error "Tenant '$tenant_name' already exists. Delete it first or use a different name." "multi-tenant"
return 1
fi
# Restore tenant
tar -xzf "$backup_file" -C "$tenant_base"
log_success "Tenant '$tenant_name' restored from: $backup_file" "multi-tenant"
}
# Tenant health check
check_tenant_health() {
local tenant_name="$1"
if [[ -z "$tenant_name" ]]; then
log_error "Tenant name is required" "multi-tenant"
return 1
fi
local tenant_base="${WORKSPACE}/tenants"
local tenant_dir="$tenant_base/$tenant_name"
local tenant_db="$tenant_base/tenants.json"
echo "Tenant Health Check: $tenant_name"
echo "================================"
# Check tenant exists
if [[ ! -d "$tenant_dir" ]]; then
echo "❌ Tenant directory not found"
return 1
fi
if ! jq -e ".tenants[] | select(.name == \"$tenant_name\")" "$tenant_db" > /dev/null 2>&1; then
echo "❌ Tenant not found in database"
return 1
fi
echo "✅ Tenant exists"
# Check directory structure
local missing_dirs=()
for dir in layers deployments users audit backups config; do
if [[ ! -d "$tenant_dir/$dir" ]]; then
missing_dirs+=("$dir")
fi
done
if [[ ${#missing_dirs[@]} -gt 0 ]]; then
echo "⚠️ Missing directories: ${missing_dirs[*]}"
else
echo "✅ Directory structure complete"
fi
# Check quota usage
local tenant_info
tenant_info=$(jq -r ".tenants[] | select(.name == \"$tenant_name\")" "$tenant_db")
local layers_used layers_max storage_used storage_max
layers_used=$(echo "$tenant_info" | jq -r '.quotas.used_layers')
layers_max=$(echo "$tenant_info" | jq -r '.quotas.max_layers')
storage_used=$(echo "$tenant_info" | jq -r '.quotas.used_storage_gb')
storage_max=$(echo "$tenant_info" | jq -r '.quotas.max_storage_gb')
echo "📊 Resource Usage:"
echo " Layers: $layers_used/$layers_max"
echo " Storage: ${storage_used}GB/${storage_max}GB"
# Check for quota warnings
local layer_percent=$((layers_used * 100 / layers_max))
local storage_percent=$((storage_used * 100 / storage_max))
if [[ $layer_percent -gt 80 ]]; then
echo "⚠️ Layer quota usage high: ${layer_percent}%"
fi
if [[ $storage_percent -gt 80 ]]; then
echo "⚠️ Storage quota usage high: ${storage_percent}%"
fi
echo "✅ Tenant health check complete"
}
# Multi-tenant command handler
handle_multi_tenant_command() {
local command="$1"
shift
case "$command" in
"init")
init_multi_tenant_system
;;
"create")
local tenant_name="$1"
local config_file="$2"
create_tenant "$tenant_name" "$config_file"
;;
"delete")
local tenant_name="$1"
local force="$2"
delete_tenant "$tenant_name" "$force"
;;
"list")
local format="$1"
list_tenants "$format"
;;
"info")
local tenant_name="$1"
local format="$2"
get_tenant_info "$tenant_name" "$format"
;;
"quota")
local tenant_name="$1"
local quota_type="$2"
local value="$3"
update_tenant_quotas "$tenant_name" "$quota_type" "$value"
;;
"backup")
local tenant_name="$1"
local backup_path="$2"
backup_tenant "$tenant_name" "$backup_path"
;;
"restore")
local backup_file="$1"
local tenant_name="$2"
restore_tenant "$backup_file" "$tenant_name"
;;
"health")
local tenant_name="$1"
check_tenant_health "$tenant_name"
;;
"help"|*)
echo "Multi-Tenant Commands:"
echo "====================="
echo " init - Initialize multi-tenant system"
echo " create <tenant> [config_file] - Create new tenant"
echo " delete <tenant> [--force] - Delete tenant"
echo " list [format] - List tenants (json|csv|table)"
echo " info <tenant> [format] - Get tenant info (json|yaml|summary)"
echo " quota <tenant> <type> <value> - Update tenant quota"
echo " backup <tenant> [path] - Backup tenant"
echo " restore <backup_file> <tenant> - Restore tenant"
echo " health <tenant> - Check tenant health"
echo " help - Show this help"
;;
esac
}