prvng_core/nulib/lib_provisioning/extensions/profiles.nu
Jesús Pérez 1ac3401315
refactor(15 single-star batch 3): selective imports + dead drop (ADR-025 L2)
15 files. Most are dead `use ../config/accessor.nu *` imports dropped.

Selective imports:
  setup/utils.nu                 config/accessor/functions [get-providers-path]
  setup/detection.nu             setup/mod [8 symbols]
  plugins/secretumvault.nu       config/accessor/core [config-get]
  plugins/orchestrator.nu        config/accessor/core [config-get]
  kms/client.nu                  utils/error + utils/interface + plugins/kms
  deploy.nu                      result.nu [7 symbols]
  dependencies/resolver.nu       config/loader [get-config]

Dead imports dropped:
  setup/config.nu                config/accessor
  extensions/profiles.nu         config/accessor
  extensions/loader.nu           config/accessor
  workspace/init.nu              config/accessor
  utils/help.nu                  config/accessor
  utils/error_fixed.nu           config/accessor
  utils/error_clean.nu           config/accessor
  utils/error_final.nu           config/accessor
  dependencies/resolver.nu       oci/client

Validation: all 15 match or IMPROVE baseline:
  - 11 files: 0 errors
  - utils/error_fixed  4 -> 1  (IMPROVED)
  - utils/error_clean  4 -> 1  (IMPROVED)
  - dependencies/resolver  50 = 50 (unchanged pre-existing noise)
  - deploy.nu          19 = 19 (unchanged; file is orphaned — zero importers)

Refs: ADR-025
2026-04-17 12:17:22 +01:00

224 lines
6.8 KiB
Text

# Profile-based Access Control
# Implements permission system for restricted environments like CI/CD
# config/accessor star-import was dead — dropped (ADR-025 Phase 3 Layer 2).
# Load profile configuration
export def load-profile [profile_name?: string] {
let active_profile = if ($profile_name | is-not-empty) {
$profile_name
} else {
(get-provisioning-profile)
}
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] {
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] {
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] {
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] {
if not (is-command-allowed $command $subcommand) {
print $"🛑 Command '($command) ($subcommand | default "")' is not allowed by profile ((get-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 [] {
let profile = (load-profile)
{
active_profile: (get-provisioning-profile)
extension_mode: (get-extension-mode)
profile_config: $profile
status: (if $profile.restricted { "restricted" } else { "unrestricted" })
}
}
# Create example profile files
export def create-example-profiles [] {
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)"
}