# Profile-based Access Control # Implements permission system for restricted environments like CI/CD # Load profile configuration export def load-profile [profile_name?: string]: nothing -> record { let active_profile = if ($profile_name | is-not-empty) { $profile_name } else { $env.PROVISIONING_PROFILE? | default "" } if ($active_profile | is-empty) { return { name: "default" allowed: { commands: [] providers: [] taskservs: [] } blocked: { commands: [] providers: [] taskservs: [] } restricted: false } } # Check user profile first let user_profile_path = ($env.HOME | path join ".provisioning-extensions" "profiles" $"($active_profile).yaml") let system_profile_path = ("/opt/provisioning-extensions/profiles" | path join $"($active_profile).yaml") let project_profile_path = ($env.PWD | path join ".provisioning" "profiles" $"($active_profile).yaml") # Load in priority order: project > user > system let available_files = [ $project_profile_path $user_profile_path $system_profile_path ] | where ($it | path exists) if ($available_files | length) > 0 { open ($available_files | first) } else { # Default restricted profile { name: $active_profile allowed: { commands: ["list", "status", "show", "query", "help", "version"] providers: ["local"] taskservs: [] } blocked: { commands: ["delete", "create", "sops", "secrets"] providers: ["aws", "upcloud"] taskservs: [] } restricted: true } } } # Check if command is allowed export def is-command-allowed [command: string, subcommand?: string]: nothing -> bool { let profile = (load-profile) if not $profile.restricted { return true } let full_command = if ($subcommand | is-not-empty) { $"($command) ($subcommand)" } else { $command } # Check blocked first if ($profile.blocked.commands | any {|cmd| $full_command =~ $cmd}) { return false } # If allowed list is empty, allow everything not blocked if ($profile.allowed.commands | is-empty) { return true } # Check if explicitly allowed ($profile.allowed.commands | any {|cmd| $full_command =~ $cmd}) } # Check if provider is allowed export def is-provider-allowed [provider: string]: nothing -> bool { let profile = (load-profile) if not $profile.restricted { return true } # Check blocked first if ($profile.blocked.providers | any {|prov| $provider == $prov}) { return false } # If allowed list is empty, allow everything not blocked if ($profile.allowed.providers | is-empty) { return true } # Check if explicitly allowed ($profile.allowed.providers | any {|prov| $provider == $prov}) } # Check if taskserv is allowed export def is-taskserv-allowed [taskserv: string]: nothing -> bool { let profile = (load-profile) if not $profile.restricted { return true } # Check blocked first if ($profile.blocked.taskservs | any {|ts| $taskserv == $ts}) { return false } # If allowed list is empty, allow everything not blocked if ($profile.allowed.taskservs | is-empty) { return true } # Check if explicitly allowed ($profile.allowed.taskservs | any {|ts| $taskserv == $ts}) } # Enforce profile restrictions on command execution export def enforce-profile [command: string, subcommand?: string, target?: string]: nothing -> bool { if not (is-command-allowed $command $subcommand) { print $"🛑 Command '($command) ($subcommand | default "")' is not allowed by profile ($env.PROVISIONING_PROFILE)" return false } # Additional checks based on target type if ($target | is-not-empty) { match $command { "server" => { if ($subcommand | default "") in ["create", "delete"] { let settings = (find_get_settings) let server = ($settings.data.servers | where hostname == $target | first?) if ($server | is-not-empty) { if not (is-provider-allowed $server.provider) { print $"🛑 Provider '($server.provider)' is not allowed by profile" return false } } } } "taskserv" => { if not (is-taskserv-allowed $target) { print $"🛑 TaskServ '($target)' is not allowed by profile" return false } } } } return true } # Show current profile information export def show-profile []: nothing -> record { let profile = (load-profile) { active_profile: ($env.PROVISIONING_PROFILE? | default "default") extension_mode: ($env.PROVISIONING_EXTENSION_MODE? | default "full") profile_config: $profile status: (if $profile.restricted { "restricted" } else { "unrestricted" }) } } # Create example profile files export def create-example-profiles []: nothing -> nothing { let user_profiles_dir = ($env.HOME | path join ".provisioning-extensions" "profiles") mkdir $user_profiles_dir # CI/CD profile let cicd_profile = { profile: "cicd" description: "Restricted profile for CI/CD agents" restricted: true allowed: { commands: ["server list", "server status", "taskserv list", "taskserv status", "query", "show", "help", "version"] providers: ["local"] taskservs: ["kubernetes", "containerd", "kubectl"] } blocked: { commands: ["server create", "server delete", "taskserv create", "taskserv delete", "sops", "secrets"] providers: ["aws", "upcloud"] taskservs: ["postgres", "gitea"] } } # Developer profile let developer_profile = { profile: "developer" description: "Profile for developers with limited production access" restricted: true allowed: { commands: ["server list", "server create", "taskserv list", "taskserv create", "query", "show"] providers: ["local", "aws"] taskservs: [] } blocked: { commands: ["server delete", "sops"] providers: ["upcloud"] taskservs: ["postgres"] } } # Save example profiles $cicd_profile | to yaml | save ($user_profiles_dir | path join "cicd.yaml") $developer_profile | to yaml | save ($user_profiles_dir | path join "developer.yaml") print $"Created example profiles in ($user_profiles_dir)" }