diff --git a/docs/user/vsphere/per-component-credentials.md b/docs/user/vsphere/per-component-credentials.md new file mode 100644 index 00000000000..6425b17ddd0 --- /dev/null +++ b/docs/user/vsphere/per-component-credentials.md @@ -0,0 +1,521 @@ +# vSphere Per-Component Credentials + +## Overview + +The vSphere per-component credentials feature enables OpenShift clusters to use distinct vCenter accounts for different cluster components. Instead of using a single high-privilege account for all operations, each component (Machine API, CSI Driver, Cloud Controller Manager, Diagnostics) receives only the vCenter permissions it needs. + +### Benefits + +- **Reduced Security Blast Radius**: A compromised component cannot access other components' vCenter operations +- **Principle of Least Privilege**: Each component has minimal required permissions, meeting compliance requirements +- **Improved Auditability**: vCenter audit logs distinguish actions by component via separate usernames +- **Independent Credential Rotation**: Update credentials for individual components without cluster downtime +- **Compliance Support**: Meets enterprise IAM requirements for separation of duties + +### User Personas + +- **Security Administrators**: Require strict IAM policies and blast radius reduction +- **Cloud Architects**: Need to migrate brownfield clusters to meet hardened security standards +- **vSphere Infrastructure Teams**: Manage vCenter accounts through established provisioning processes + +## Architecture + +### Component Privilege Requirements + +| Component | Privilege Count | Key Operations | Account Scope | +|-----------|-----------------|----------------|---------------| +| **Installer** | ~45 | Full deployment operations: create folders, resource pools, VMs, networks | Installation only (can be disabled post-install) | +| **Machine API** | ~35 | VM lifecycle: create, delete, configure, power operations | Runtime (required for cluster scaling) | +| **CSI Driver** | ~12 | Storage provisioning: create/delete disks, manage datastore files | Runtime (required for PV provisioning) | +| **Cloud Controller Manager** | ~9 | Read-only node discovery and metadata | Runtime (required for node management) | +| **Diagnostics** | ~5 | Read-only troubleshooting and log access | Runtime (optional, used for must-gather) | + +### Workflow + +``` +Administrator vCenter OpenShift Installer OpenShift Cluster + | | | | + |--1. Create Roles--------->| | | + | (5 component roles) | | | + | | | | + |--2. Create Users--------->| | | + | (5 component accounts) | | | + | | | | + |--3. Assign Roles--------->| | | + | | | | + |--4. Generate Creds------->| | | + | (credentials file) | | | + | | | | + |--5. Run Installer--------------------------------->| | + | | | | + | |<--6. Validate Privileges-----| | + | | | | + | |<--7. Deploy Infrastructure---| | + | | (uses installer account) | | + | | | | + | | |--8. Create Secrets---------->| + | | | (component-specific) | + | | | | + | |<--9. Machine API Operations---------------(machine-api account) + | |<--10. CSI Operations----------------------(csi-driver account) + | |<--11. CCM Operations----------------------(cloud-controller account) + | |<--12. Diagnostics--------------------------(diagnostics account) +``` + +## Prerequisites + +### Tools Required + +**For Linux/macOS Administrators:** +- `govc` CLI tool ([installation guide](https://github.com/vmware/govmomi/tree/main/govc)) +- vCenter administrator credentials +- Network connectivity to vCenter + +**For Windows Administrators:** +- VMware PowerCLI (`Install-Module -Name VMware.PowerCLI -Scope CurrentUser`) +- vCenter administrator credentials +- Network connectivity to vCenter + +### vCenter Requirements + +- vCenter 6.7 or later +- Administrator access to create roles and users +- Sufficient vCenter licenses for user accounts + +## Step-by-Step Guide + +### Step 1: Create vCenter Roles + +Use the provided automation scripts to create five vCenter roles with precisely scoped privileges. + +#### Linux/macOS (using govc) + +```bash +cd docs/user/vsphere/scripts/ + +# Single vCenter +./create-component-roles.sh vcenter.example.com administrator@vsphere.local 'password' + +# Multiple vCenters +export VCENTERS="vcenter1.example.com vcenter2.example.com" +export VCENTER_USER="administrator@vsphere.local" +export VCENTER_PASSWORD="password" +./create-component-roles.sh +``` + +#### Windows (using PowerCLI) + +```powershell +cd docs\user\vsphere\scripts\ + +# Single vCenter +.\create-component-roles.ps1 -VCenter "vcenter.example.com" -Username "administrator@vsphere.local" -Password "password" + +# Multiple vCenters +$vCenters = @("vcenter1.example.com", "vcenter2.example.com") +.\create-component-roles.ps1 -VCenters $vCenters -Username "administrator@vsphere.local" -Password "password" +``` + +**Roles Created:** +- `openshift-installer` (~45 privileges) +- `openshift-machine-api` (~35 privileges) +- `openshift-csi-driver` (~12 privileges) +- `openshift-cloud-controller` (~9 privileges) +- `openshift-diagnostics` (~5 privileges) + +### Step 2: Create vCenter User Accounts + +Create five user accounts in vCenter, one for each component. Use descriptive usernames for clear audit trails. + +**Recommended Naming Convention:** +- `ocp-installer@vsphere.local` → Installer role +- `ocp-machine-api@vsphere.local` → Machine API role +- `ocp-csi-driver@vsphere.local` → CSI Driver role +- `ocp-cloud-controller@vsphere.local` → Cloud Controller Manager role +- `ocp-diagnostics@vsphere.local` → Diagnostics role + +**Password Requirements:** +- Minimum 20 characters +- Random, generated passwords +- Store in enterprise password vault + +### Step 3: Assign Roles to Users + +In vCenter, assign the appropriate role to each user account: + +1. Navigate to **Administration > Access Control > Global Permissions** +2. Click **+** to add permission +3. Select user (e.g., `ocp-machine-api@vsphere.local`) +4. Select role (e.g., `openshift-machine-api`) +5. Check **Propagate to children** +6. Click **OK** +7. Repeat for all five component accounts + +**Alternative:** Assign roles at Datacenter or Cluster level for more granular control. + +### Step 4: Generate Credentials File + +Use the provided script to generate a template credentials file: + +```bash +cd docs/user/vsphere/scripts/ + +# Single vCenter +./generate-credentials-file.sh vcenter.example.com + +# Multiple vCenters +./generate-credentials-file.sh vcenter1.example.com vcenter2.example.com +``` + +**Output:** `~/.vsphere/credentials` with 0600 permissions + +### Step 5: Populate Credentials File + +Edit `~/.vsphere/credentials` and replace all `` values with actual credentials: + +```ini +[vcenter.example.com] +user = ocp-installer@vsphere.local +password = +machine-api.user = ocp-machine-api@vsphere.local +machine-api.password = +csi-driver.user = ocp-csi-driver@vsphere.local +csi-driver.password = +cloud-controller.user = ocp-cloud-controller@vsphere.local +cloud-controller.password = +diagnostics.user = ocp-diagnostics@vsphere.local +diagnostics.password = +``` + +**Verify Permissions:** +```bash +ls -la ~/.vsphere/credentials +# Should show: -rw------- (0600) +``` + +### Step 6: Create Cluster + +#### Option A: Using Credentials File + +If credentials are in `~/.vsphere/credentials`, the installer will automatically read them: + +```bash +openshift-install create cluster --dir +``` + +The installer will: +1. Read credentials from `~/.vsphere/credentials` +2. Validate each component's privileges using vSphere AuthorizationManager API +3. Deploy infrastructure using installer account +4. Create component-specific secrets in appropriate namespaces +5. Components will authenticate with their respective accounts + +#### Option B: Using install-config.yaml + +Alternatively, embed credentials in `install-config.yaml`: + +```yaml +apiVersion: v1 +baseDomain: example.com +metadata: + name: vsphere-per-component +platform: + vsphere: + vCenter: vcenter.example.com + datacenter: DC1 + defaultDatastore: datastore1 + componentCredentials: + installer: + username: ocp-installer@vsphere.local + password: + machineAPI: + username: ocp-machine-api@vsphere.local + password: + csiDriver: + username: ocp-csi-driver@vsphere.local + password: + cloudController: + username: ocp-cloud-controller@vsphere.local + password: + diagnostics: + username: ocp-diagnostics@vsphere.local + password: +``` + +**Precedence:** install-config.yaml credentials override `~/.vsphere/credentials` + +## Multi-vCenter Support + +For deployments spanning multiple vCenter servers: + +### Credentials File Format + +```ini +[vcenter1.example.com] +user = ocp-installer@vsphere.local +password = +machine-api.user = ocp-machine-api@vsphere.local +machine-api.password = +csi-driver.user = ocp-csi-driver@vsphere.local +csi-driver.password = + +[vcenter2.example.com] +user = ocp-installer@vsphere.local +password = +machine-api.user = ocp-machine-api@vsphere.local +machine-api.password = +``` + +### install-config.yaml Format + +```yaml +platform: + vsphere: + vCenter: vcenter1.example.com # Default vCenter + componentCredentials: + installer: + username: ocp-installer@vsphere.local + password: + machineAPI: + username: ocp-machine-api@vsphere.local + password: + vCenter: vcenter1.example.com + csiDriver: + username: ocp-csi-driver@vsphere.local + password: + vCenter: vcenter2.example.com # CSI uses different vCenter +``` + +**Secrets:** Multi-vCenter secrets use FQDN-keyed format (`vcenter1.example.com.username`) + +## Brownfield Migration + +Migrate existing clusters from single-account (passthrough) mode to per-component credentials. + +### Prerequisites + +- Existing vSphere IPI cluster (installed with single account) +- Per-component vCenter accounts created and assigned roles +- Credentials file with per-component credentials + +### Migration Command + +```bash +openshift-install vsphere migrate-to-per-component \ + --kubeconfig=/path/to/kubeconfig \ + --credentials-file=~/.vsphere/credentials \ + --validate-privileges +``` + +### Migration Steps + +The migration tool will: +1. Read per-component credentials from credentials file +2. Validate each component's privileges using vSphere API +3. Create backup of original passthrough-mode secret +4. Create component-specific secrets in appropriate namespaces +5. Update CCO configuration to enable per-component mode +6. Restart component operators (Machine API, CSI, CCM) +7. Verify each component reconnects successfully +8. Log migration completion + +### Rollback + +If migration fails, the tool automatically: +1. Restores original passthrough secret from backup +2. Deletes component-specific secrets +3. Reverts CCO configuration +4. Logs detailed error messages + +**Manual Rollback** (if automatic rollback fails): +```bash +# Restore original secret +oc get secret vsphere-cloud-credentials-backup -n kube-system -o yaml | \ + sed 's/vsphere-cloud-credentials-backup/vsphere-cloud-credentials/' | \ + oc apply -f - + +# Restart operators +oc delete pod -n openshift-machine-api -l api=clusterapi +oc delete pod -n openshift-cluster-csi-drivers -l app=vsphere-csi-driver +oc delete pod -n openshift-cloud-controller-manager -l app=vsphere-cloud-controller-manager +``` + +## Security Considerations + +### Credential Storage + +**Risk:** Component credentials in cluster could be compromised + +**Mitigation:** +- Each component's credentials stored in separate secrets in component-specific namespaces +- Kubernetes RBAC limits access to component secrets +- Components can only access their own credentials +- Enable cluster encryption-at-rest for additional protection + +### Installer Account Management + +**Recommendation:** Disable installer account after installation completes + +```bash +# Installation complete +# In vCenter: Administration > Single Sign On > Users and Groups +# Select: ocp-installer@vsphere.local +# Click: Disable Account +``` + +Installer account is only needed during installation and cluster upgrades. + +### Credential Rotation + +Rotate component credentials independently: + +```bash +# Example: Rotate Machine API credentials + +# 1. Update vCenter password for ocp-machine-api@vsphere.local + +# 2. Update cluster secret +oc create secret generic machine-api-vsphere-credentials \ + --from-literal=username=ocp-machine-api@vsphere.local \ + --from-literal=password= \ + --dry-run=client -o yaml | \ + oc apply -n openshift-machine-api -f - + +# 3. Restart Machine API operator +oc delete pod -n openshift-machine-api -l api=clusterapi + +# 4. Verify reconnection +oc logs -n openshift-machine-api -l api=clusterapi | grep "Successfully connected to vCenter" +``` + +### Audit Trail + +Each component uses a distinct vCenter username, enabling clear audit trails: + +**vCenter Event Log:** +``` +[2026-04-14 10:30:15] ocp-machine-api@vsphere.local: Created VM ocp-worker-3 +[2026-04-14 10:32:20] ocp-csi-driver@vsphere.local: Created disk pvc-abc123 +[2026-04-14 10:35:10] ocp-cloud-controller@vsphere.local: Read VM metadata for node worker-1 +``` + +Query vCenter events by username to track component-specific actions. + +## Troubleshooting + +### Privilege Validation Failures + +**Error:** `Component machine-api missing required privilege: VirtualMachine.Provisioning.Clone on Datacenter` + +**Resolution:** +1. Verify role assignment in vCenter +2. Ensure role includes required privilege +3. Check propagation is enabled (for datacenter/cluster-level assignments) +4. Re-run installer or migration command + +### Component Authentication Failures + +**Error:** `Component csi-driver failed to authenticate with vCenter vcenter.example.com` + +**Resolution:** +1. Verify username and password in credentials file or secret +2. Test authentication using govc: + ```bash + export GOVC_URL=vcenter.example.com + export GOVC_USERNAME=ocp-csi-driver@vsphere.local + export GOVC_PASSWORD= + govc about + ``` +3. Check vCenter account is not locked/disabled +4. Verify network connectivity from cluster to vCenter + +### Component Fails to Reconnect After Migration + +**Error:** `Component machine-api failed to reconnect with new credentials` + +**Resolution:** +1. Check component operator logs: + ```bash + oc logs -n openshift-machine-api -l api=clusterapi + ``` +2. Verify component secret exists and contains correct credentials: + ```bash + oc get secret machine-api-vsphere-credentials -n openshift-machine-api -o yaml + ``` +3. If incorrect, recreate secret and restart operator +4. If persistent, rollback migration and investigate privilege/network issues + +### Multi-vCenter Credential Issues + +**Error:** `Component csi-driver references vCenter vcenter2.example.com but no credentials provided` + +**Resolution:** +1. Ensure credentials file contains section for all referenced vCenters +2. Verify vCenter FQDN matches exactly (case-sensitive) +3. Check component-specific vCenter override in install-config.yaml + +### File Permission Errors + +**Error:** `Credentials file ~/.vsphere/credentials has permissions 0644, must be 0600` + +**Resolution:** +```bash +chmod 0600 ~/.vsphere/credentials +``` + +## Reference + +### Component Secret Locations + +| Component | Secret Name | Namespace | +|-----------|-------------|-----------| +| Machine API | `machine-api-vsphere-credentials` | `openshift-machine-api` | +| CSI Driver | `vsphere-csi-credentials` | `openshift-cluster-csi-drivers` | +| Cloud Controller Manager | `vsphere-ccm-credentials` | `openshift-cloud-controller-manager` | +| Diagnostics | `vsphere-diagnostics-credentials` | `openshift-config` | + +### Complete Privilege Lists + +See automation scripts for complete privilege lists: +- `scripts/create-component-roles.sh` (govc, Linux/macOS) +- `scripts/create-component-roles.ps1` (PowerCLI, Windows) + +Or view in code: +- `pkg/asset/installconfig/vsphere/privilegevalidator.go` + +### Related Documentation + +- [vSphere Installation Guide](install.md) +- [vSphere Privileges (Legacy)](privileges.md) +- [vSphere Requirements](requirements.md) + +## FAQ + +**Q: Can I use per-component credentials with Assisted Installer?** +A: Yes, Assisted Installer UI provides fields for per-component credentials. + +**Q: What happens if I only provide some component credentials?** +A: Components without specific credentials fall back to legacy username/password (passthrough mode). + +**Q: Can I use the same username for multiple components?** +A: Yes, but it defeats the auditability benefit. Distinct usernames are recommended. + +**Q: Do I need different accounts for multiple clusters?** +A: No, the same component accounts can be used across multiple clusters. vSphere permissions are scoped to datacenter/folder. + +**Q: Can I disable the installer account after installation?** +A: Yes, recommended. Installer account is only needed during installation and upgrades. + +**Q: How do I verify per-component mode is active?** +A: Check for component-specific secrets in their namespaces: +```bash +oc get secret machine-api-vsphere-credentials -n openshift-machine-api +oc get secret vsphere-csi-credentials -n openshift-cluster-csi-drivers +oc get secret vsphere-ccm-credentials -n openshift-cloud-controller-manager +``` + +**Q: Can I mix per-component and passthrough mode?** +A: Yes, partial component credentials are supported. Unpopulated components use passthrough mode. + +**Q: How do I update credentials after installation?** +A: Update the secret and restart the component operator (see Credential Rotation section). diff --git a/docs/user/vsphere/scripts/README.md b/docs/user/vsphere/scripts/README.md new file mode 100644 index 00000000000..c84dfb13f57 --- /dev/null +++ b/docs/user/vsphere/scripts/README.md @@ -0,0 +1,165 @@ +# vSphere Per-Component Credentials - Automation Scripts + +This directory contains automation scripts to help vSphere administrators set up per-component credentials for OpenShift clusters. + +## Scripts + +### 1. create-component-roles.sh (Linux/macOS) + +Creates five vCenter roles using the `govc` CLI tool. + +**Prerequisites:** +- govc installed: https://github.com/vmware/govmomi/tree/main/govc +- vCenter administrator credentials + +**Usage:** +```bash +# Single vCenter +./create-component-roles.sh vcenter.example.com administrator@vsphere.local 'password' + +# Multiple vCenters +export VCENTERS="vcenter1.example.com vcenter2.example.com" +export VCENTER_USER="administrator@vsphere.local" +export VCENTER_PASSWORD="password" +./create-component-roles.sh +``` + +**Roles Created:** +- `openshift-installer` (~45 privileges) +- `openshift-machine-api` (~35 privileges) +- `openshift-csi-driver` (~12 privileges) +- `openshift-cloud-controller` (~9 privileges) +- `openshift-diagnostics` (~5 privileges) + +### 2. create-component-roles.ps1 (Windows) + +PowerCLI version of the role creation script for Windows administrators. + +**Prerequisites:** +- VMware PowerCLI: `Install-Module -Name VMware.PowerCLI -Scope CurrentUser` +- vCenter administrator credentials + +**Usage:** +```powershell +# Single vCenter +.\create-component-roles.ps1 -VCenter "vcenter.example.com" -Username "administrator@vsphere.local" -Password "password" + +# Multiple vCenters +$vCenters = @("vcenter1.example.com", "vcenter2.example.com") +.\create-component-roles.ps1 -VCenters $vCenters -Username "administrator@vsphere.local" -Password "password" +``` + +### 3. generate-credentials-file.sh + +Generates a template YAML credentials file with proper permissions. + +**Prerequisites:** +- None (generates template only) + +**Usage:** +```bash +# Single vCenter (default) +./generate-credentials-file.sh + +# Single vCenter (custom) +./generate-credentials-file.sh vcenter.example.com + +# Multiple vCenters +./generate-credentials-file.sh vcenter1.example.com vcenter2.example.com +``` + +**Output:** +- File: `~/.vsphere/credentials` +- Permissions: `0600` (read/write owner only) +- Format: INI-style sections with component credentials + +## Workflow + +### Quick Start + +```bash +# 1. Create vCenter roles +./create-component-roles.sh vcenter.example.com admin@vsphere.local 'password' + +# 2. Create vCenter user accounts (manual, in vCenter UI) +# - ocp-installer@vsphere.local +# - ocp-machine-api@vsphere.local +# - ocp-csi-driver@vsphere.local +# - ocp-cloud-controller@vsphere.local +# - ocp-diagnostics@vsphere.local + +# 3. Assign roles to users (manual, in vCenter UI) +# Administration > Access Control > Global Permissions + +# 4. Generate credentials file +./generate-credentials-file.sh vcenter.example.com + +# 5. Edit credentials file, replace values +vi ~/.vsphere/credentials + +# 6. Install cluster +openshift-install create cluster --dir +``` + +### Detailed Guide + +See [../per-component-credentials.md](../per-component-credentials.md) for complete step-by-step instructions. + +## Security Notes + +- **Credentials File:** Must have `0600` permissions (enforced by installer) +- **Passwords:** Use strong, random passwords (20+ characters) +- **Storage:** Store credentials in enterprise password vault +- **Version Control:** NEVER commit credentials file to git +- **Post-Install:** Disable installer account after installation completes + +## Troubleshooting + +### govc: command not found + +Install govc: +```bash +# macOS +brew install govmomi/tap/govc + +# Linux +curl -L -o - "https://github.com/vmware/govmomi/releases/latest/download/govc_$(uname -s)_$(uname -m).tar.gz" | tar -C /usr/local/bin -xvzf - govc +``` + +### PowerCLI not found + +Install PowerCLI: +```powershell +Install-Module -Name VMware.PowerCLI -Scope CurrentUser +``` + +### Role already exists + +Scripts will update existing roles if they already exist. To force recreation: +```bash +# govc +govc role.remove openshift-installer +./create-component-roles.sh ... + +# PowerCLI +Remove-VIRole -Name "openshift-installer" -Confirm:$false +.\create-component-roles.ps1 ... +``` + +### Invalid certificate errors + +For self-signed certificates: +```bash +# govc +export GOVC_INSECURE=1 + +# PowerCLI (already set in script) +Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false +``` + +## Support + +For issues, questions, or feature requests: +- Documentation: [../per-component-credentials.md](../per-component-credentials.md) +- OpenShift Installation Guide: [../install.md](../install.md) +- Privileges Reference: [../privileges.md](../privileges.md) diff --git a/docs/user/vsphere/scripts/create-component-roles.ps1 b/docs/user/vsphere/scripts/create-component-roles.ps1 new file mode 100644 index 00000000000..9772360ef1a --- /dev/null +++ b/docs/user/vsphere/scripts/create-component-roles.ps1 @@ -0,0 +1,267 @@ +# create-component-roles.ps1 +# Creates vCenter roles for OpenShift per-component credentials using PowerCLI +# +# Usage: +# Single vCenter: +# .\create-component-roles.ps1 -VCenter "vcenter.example.com" -Username "administrator@vsphere.local" -Password "password" +# +# Multiple vCenters: +# $vCenters = @("vcenter1.example.com", "vcenter2.example.com") +# .\create-component-roles.ps1 -VCenters $vCenters -Username "administrator@vsphere.local" -Password "password" +# +# Prerequisites: +# - VMware PowerCLI installed (Install-Module -Name VMware.PowerCLI -Scope CurrentUser) +# - vCenter administrator credentials +# - Network connectivity to vCenter servers +# +# This script creates five vCenter roles with precisely scoped privileges for OpenShift components: +# - openshift-installer: Installation and infrastructure provisioning (~45 privileges) +# - openshift-machine-api: VM lifecycle management (~35 privileges) +# - openshift-csi-driver: Storage provisioning (~12 privileges) +# - openshift-cloud-controller: Read-only node discovery (~9 privileges) +# - openshift-diagnostics: Read-only troubleshooting (~5 privileges) + +param( + [Parameter(Mandatory=$false)] + [string]$VCenter, + + [Parameter(Mandatory=$false)] + [string[]]$VCenters, + + [Parameter(Mandatory=$true)] + [string]$Username, + + [Parameter(Mandatory=$true)] + [string]$Password +) + +# Set PowerCLI configuration to ignore invalid certificates (optional) +Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false -Scope Session | Out-Null +Set-PowerCLIConfiguration -ParticipateInCEIP $false -Confirm:$false -Scope Session | Out-Null + +# Function to write colored output +function Write-Info { + param([string]$Message) + Write-Host "[INFO] $Message" -ForegroundColor Green +} + +function Write-Warn { + param([string]$Message) + Write-Host "[WARN] $Message" -ForegroundColor Yellow +} + +function Write-Error-Custom { + param([string]$Message) + Write-Host "[ERROR] $Message" -ForegroundColor Red +} + +# Check if PowerCLI is installed +if (-not (Get-Module -ListAvailable -Name VMware.PowerCLI)) { + Write-Error-Custom "VMware PowerCLI is not installed" + Write-Info "Install PowerCLI: Install-Module -Name VMware.PowerCLI -Scope CurrentUser" + exit 1 +} + +# Import PowerCLI module +Import-Module VMware.PowerCLI -ErrorAction Stop + +# Determine vCenter list +if ($VCenter) { + $vCenterList = @($VCenter) +} elseif ($VCenters) { + $vCenterList = $VCenters +} else { + Write-Error-Custom "Must specify either -VCenter or -VCenters parameter" + exit 1 +} + +# Function to create or update a role +function New-OrUpdate-VIRole { + param( + [string]$Name, + [string[]]$Privileges + ) + + Write-Info "Creating role: $Name ($($Privileges.Count) privileges)" + + try { + # Check if role exists + $existingRole = Get-VIRole -Name $Name -ErrorAction SilentlyContinue + + if ($existingRole) { + Write-Warn "Role '$Name' already exists, updating privileges" + Set-VIRole -Role $existingRole -AddPrivilege (Get-VIPrivilege -Id $Privileges) -ErrorAction Stop | Out-Null + } else { + New-VIRole -Name $Name -Privilege (Get-VIPrivilege -Id $Privileges) -ErrorAction Stop | Out-Null + } + + Write-Info "✓ Role '$Name' created/updated successfully" + return $true + } catch { + Write-Error-Custom "✗ Failed to create role '$Name': $_" + return $false + } +} + +# Main execution loop for each vCenter +foreach ($vCenterServer in $vCenterList) { + Write-Info "==========================================" + Write-Info "Processing vCenter: $vCenterServer" + Write-Info "==========================================" + + try { + # Connect to vCenter + $connection = Connect-VIServer -Server $vCenterServer -User $Username -Password $Password -ErrorAction Stop + Write-Info "Successfully connected to $vCenterServer" + + # Create Installer role (~45 privileges) + # Required for cluster infrastructure deployment + $installerPrivileges = @( + "Folder.Create", + "Folder.Delete", + "Folder.Move", + "Folder.Rename", + "ResourcePool.Create", + "ResourcePool.Delete", + "ResourcePool.Assign", + "VirtualMachine.Provisioning.Clone", + "VirtualMachine.Provisioning.DeployTemplate", + "VirtualMachine.Provisioning.MarkAsTemplate", + "VirtualMachine.Provisioning.MarkAsVM", + "VirtualMachine.Provisioning.CustomizeGuest", + "VirtualMachine.Provisioning.ReadCustSpecs", + "VirtualMachine.Provisioning.ModifyCustSpecs", + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.AddRemoveDevice", + "VirtualMachine.Config.AdvancedConfig", + "VirtualMachine.Config.CPUCount", + "VirtualMachine.Config.Memory", + "VirtualMachine.Config.Settings", + "VirtualMachine.Config.Resource", + "VirtualMachine.Config.EditDevice", + "VirtualMachine.Config.RemoveDisk", + "VirtualMachine.Config.Rename", + "VirtualMachine.Config.Annotation", + "VirtualMachine.Interact.PowerOn", + "VirtualMachine.Interact.PowerOff", + "VirtualMachine.Interact.Reset", + "VirtualMachine.Interact.Suspend", + "VirtualMachine.Interact.ConsoleInteract", + "VirtualMachine.Interact.DeviceConnection", + "VirtualMachine.Interact.SetCDMedia", + "VirtualMachine.Interact.GuestControl", + "VirtualMachine.Inventory.Create", + "VirtualMachine.Inventory.Delete", + "VirtualMachine.Inventory.Move", + "VirtualMachine.Inventory.Register", + "VirtualMachine.Inventory.Unregister", + "Network.Assign", + "Datastore.AllocateSpace", + "Datastore.Browse", + "Datastore.FileManagement" + ) + New-OrUpdate-VIRole -Name "openshift-installer" -Privileges $installerPrivileges + + # Create Machine API role (~35 privileges) + # Required for VM lifecycle operations + $machineAPIPrivileges = @( + "VirtualMachine.Provisioning.Clone", + "VirtualMachine.Provisioning.DeployTemplate", + "VirtualMachine.Provisioning.CustomizeGuest", + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.AddRemoveDevice", + "VirtualMachine.Config.AdvancedConfig", + "VirtualMachine.Config.CPUCount", + "VirtualMachine.Config.Memory", + "VirtualMachine.Config.Settings", + "VirtualMachine.Config.Resource", + "VirtualMachine.Config.EditDevice", + "VirtualMachine.Config.RemoveDisk", + "VirtualMachine.Config.Rename", + "VirtualMachine.Config.Annotation", + "VirtualMachine.Interact.PowerOn", + "VirtualMachine.Interact.PowerOff", + "VirtualMachine.Interact.Reset", + "VirtualMachine.Interact.Suspend", + "VirtualMachine.Interact.DeviceConnection", + "VirtualMachine.Interact.GuestControl", + "VirtualMachine.Inventory.Create", + "VirtualMachine.Inventory.Delete", + "VirtualMachine.Inventory.Move", + "VirtualMachine.Inventory.Register", + "VirtualMachine.Inventory.Unregister", + "Network.Assign", + "Datastore.AllocateSpace", + "Datastore.Browse", + "Datastore.FileManagement", + "ResourcePool.Assign", + "Folder.Create", + "Folder.Delete" + ) + New-OrUpdate-VIRole -Name "openshift-machine-api" -Privileges $machineAPIPrivileges + + # Create CSI Driver role (~12 privileges) + # Required for storage provisioning + $csiDriverPrivileges = @( + "Datastore.AllocateSpace", + "Datastore.Browse", + "Datastore.FileManagement", + "Datastore.DeleteFile", + "Datastore.UpdateVirtualMachineFiles", + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.AddRemoveDevice", + "VirtualMachine.Config.RemoveDisk", + "VirtualMachine.Config.EditDevice", + "System.Anonymous", + "System.Read", + "System.View" + ) + New-OrUpdate-VIRole -Name "openshift-csi-driver" -Privileges $csiDriverPrivileges + + # Create Cloud Controller Manager role (~9 privileges) + # Required for read-only node discovery + $cloudControllerPrivileges = @( + "System.Anonymous", + "System.Read", + "System.View", + "VirtualMachine.Inventory.Register", + "Host.Config.Network", + "Network.Assign", + "Datacenter.Read", + "Folder.Read", + "ClusterComputeResource.Read" + ) + New-OrUpdate-VIRole -Name "openshift-cloud-controller" -Privileges $cloudControllerPrivileges + + # Create Diagnostics role (~5 privileges) + # Required for read-only troubleshooting + $diagnosticsPrivileges = @( + "System.Anonymous", + "System.Read", + "System.View", + "VirtualMachine.GuestOperations.Query", + "Global.LogEvent" + ) + New-OrUpdate-VIRole -Name "openshift-diagnostics" -Privileges $diagnosticsPrivileges + + Write-Info "✓ All roles created successfully on $vCenterServer" + + # Disconnect from vCenter + Disconnect-VIServer -Server $vCenterServer -Confirm:$false + + } catch { + Write-Error-Custom "Failed to process vCenter $vCenterServer : $_" + continue + } +} + +Write-Info "==========================================" +Write-Info "Role creation complete!" +Write-Info "==========================================" +Write-Info "" +Write-Info "Next steps:" +Write-Info "1. Create vCenter user accounts for each component (e.g., ocp-machine-api@vsphere.local)" +Write-Info "2. Assign the appropriate role to each user account" +Write-Info "3. Generate credentials file or configure install-config.yaml with componentCredentials" +Write-Info "" +Write-Info "For more information, see: docs/user/vsphere/per-component-credentials.md" diff --git a/docs/user/vsphere/scripts/create-component-roles.sh b/docs/user/vsphere/scripts/create-component-roles.sh new file mode 100755 index 00000000000..67c05a211fd --- /dev/null +++ b/docs/user/vsphere/scripts/create-component-roles.sh @@ -0,0 +1,244 @@ +#!/bin/bash +# create-component-roles.sh +# Creates vCenter roles for OpenShift per-component credentials across multiple vCenters +# +# Usage: +# Single vCenter: +# ./create-component-roles.sh vcenter.example.com administrator@vsphere.local 'password' +# +# Multiple vCenters: +# export VCENTERS="vcenter1.example.com vcenter2.example.com" +# export VCENTER_USER="administrator@vsphere.local" +# export VCENTER_PASSWORD="password" +# ./create-component-roles.sh +# +# Prerequisites: +# - govc CLI tool installed (https://github.com/vmware/govmomi/tree/main/govc) +# - vCenter administrator credentials +# - Network connectivity to vCenter servers +# +# This script creates five vCenter roles with precisely scoped privileges for OpenShift components: +# - openshift-installer: Installation and infrastructure provisioning (~45 privileges) +# - openshift-machine-api: VM lifecycle management (~35 privileges) +# - openshift-csi-driver: Storage provisioning (~12 privileges) +# - openshift-cloud-controller: Read-only node discovery (~9 privileges) +# - openshift-diagnostics: Read-only troubleshooting (~5 privileges) + +set -euo pipefail + +# Color output for better readability +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if govc is installed +if ! command -v govc &> /dev/null; then + log_error "govc CLI tool is not installed" + log_info "Install govc: https://github.com/vmware/govmomi/tree/main/govc" + exit 1 +fi + +# Determine vCenter list from arguments or environment +if [ $# -eq 3 ]; then + VCENTERS="$1" + VCENTER_USER="$2" + VCENTER_PASSWORD="$3" +elif [ -n "${VCENTERS:-}" ] && [ -n "${VCENTER_USER:-}" ] && [ -n "${VCENTER_PASSWORD:-}" ]; then + : # Use environment variables +else + log_error "Usage: $0 " + log_error " OR: Set VCENTERS, VCENTER_USER, VCENTER_PASSWORD environment variables" + exit 1 +fi + +# Function to create a role with privileges +create_role() { + local role_name="$1" + shift + local privileges=("$@") + + log_info "Creating role: $role_name (${#privileges[@]} privileges)" + + # Check if role already exists + if govc role.ls | grep -q "^${role_name}$"; then + log_warn "Role '$role_name' already exists, updating privileges" + govc role.update "$role_name" "${privileges[@]}" + else + govc role.create "$role_name" "${privileges[@]}" + fi + + if [ $? -eq 0 ]; then + log_info "✓ Role '$role_name' created/updated successfully" + else + log_error "✗ Failed to create role '$role_name'" + return 1 + fi +} + +# Main execution loop for each vCenter +for vcenter in $VCENTERS; do + log_info "==========================================" + log_info "Processing vCenter: $vcenter" + log_info "==========================================" + + export GOVC_URL="$vcenter" + export GOVC_USERNAME="$VCENTER_USER" + export GOVC_PASSWORD="$VCENTER_PASSWORD" + export GOVC_INSECURE=1 # Set to 0 if using trusted certificates + + # Test connection + if ! govc about &> /dev/null; then + log_error "Failed to connect to vCenter: $vcenter" + log_error "Please verify credentials and network connectivity" + continue + fi + + log_info "Successfully connected to $vcenter" + + # Create Installer role (~45 privileges) + # Required for cluster infrastructure deployment + create_role "openshift-installer" \ + "Folder.Create" \ + "Folder.Delete" \ + "Folder.Move" \ + "Folder.Rename" \ + "ResourcePool.Create" \ + "ResourcePool.Delete" \ + "ResourcePool.Assign" \ + "VirtualMachine.Provisioning.Clone" \ + "VirtualMachine.Provisioning.DeployTemplate" \ + "VirtualMachine.Provisioning.MarkAsTemplate" \ + "VirtualMachine.Provisioning.MarkAsVM" \ + "VirtualMachine.Provisioning.CustomizeGuest" \ + "VirtualMachine.Provisioning.ReadCustSpecs" \ + "VirtualMachine.Provisioning.ModifyCustSpecs" \ + "VirtualMachine.Config.AddNewDisk" \ + "VirtualMachine.Config.AddRemoveDevice" \ + "VirtualMachine.Config.AdvancedConfig" \ + "VirtualMachine.Config.CPUCount" \ + "VirtualMachine.Config.Memory" \ + "VirtualMachine.Config.Settings" \ + "VirtualMachine.Config.Resource" \ + "VirtualMachine.Config.EditDevice" \ + "VirtualMachine.Config.RemoveDisk" \ + "VirtualMachine.Config.Rename" \ + "VirtualMachine.Config.Annotation" \ + "VirtualMachine.Interact.PowerOn" \ + "VirtualMachine.Interact.PowerOff" \ + "VirtualMachine.Interact.Reset" \ + "VirtualMachine.Interact.Suspend" \ + "VirtualMachine.Interact.ConsoleInteract" \ + "VirtualMachine.Interact.DeviceConnection" \ + "VirtualMachine.Interact.SetCDMedia" \ + "VirtualMachine.Interact.GuestControl" \ + "VirtualMachine.Inventory.Create" \ + "VirtualMachine.Inventory.Delete" \ + "VirtualMachine.Inventory.Move" \ + "VirtualMachine.Inventory.Register" \ + "VirtualMachine.Inventory.Unregister" \ + "Network.Assign" \ + "Datastore.AllocateSpace" \ + "Datastore.Browse" \ + "Datastore.FileManagement" + + # Create Machine API role (~35 privileges) + # Required for VM lifecycle operations + create_role "openshift-machine-api" \ + "VirtualMachine.Provisioning.Clone" \ + "VirtualMachine.Provisioning.DeployTemplate" \ + "VirtualMachine.Provisioning.CustomizeGuest" \ + "VirtualMachine.Config.AddNewDisk" \ + "VirtualMachine.Config.AddRemoveDevice" \ + "VirtualMachine.Config.AdvancedConfig" \ + "VirtualMachine.Config.CPUCount" \ + "VirtualMachine.Config.Memory" \ + "VirtualMachine.Config.Settings" \ + "VirtualMachine.Config.Resource" \ + "VirtualMachine.Config.EditDevice" \ + "VirtualMachine.Config.RemoveDisk" \ + "VirtualMachine.Config.Rename" \ + "VirtualMachine.Config.Annotation" \ + "VirtualMachine.Interact.PowerOn" \ + "VirtualMachine.Interact.PowerOff" \ + "VirtualMachine.Interact.Reset" \ + "VirtualMachine.Interact.Suspend" \ + "VirtualMachine.Interact.DeviceConnection" \ + "VirtualMachine.Interact.GuestControl" \ + "VirtualMachine.Inventory.Create" \ + "VirtualMachine.Inventory.Delete" \ + "VirtualMachine.Inventory.Move" \ + "VirtualMachine.Inventory.Register" \ + "VirtualMachine.Inventory.Unregister" \ + "Network.Assign" \ + "Datastore.AllocateSpace" \ + "Datastore.Browse" \ + "Datastore.FileManagement" \ + "ResourcePool.Assign" \ + "Folder.Create" \ + "Folder.Delete" + + # Create CSI Driver role (~12 privileges) + # Required for storage provisioning + create_role "openshift-csi-driver" \ + "Datastore.AllocateSpace" \ + "Datastore.Browse" \ + "Datastore.FileManagement" \ + "Datastore.DeleteFile" \ + "Datastore.UpdateVirtualMachineFiles" \ + "VirtualMachine.Config.AddNewDisk" \ + "VirtualMachine.Config.AddRemoveDevice" \ + "VirtualMachine.Config.RemoveDisk" \ + "VirtualMachine.Config.EditDevice" \ + "System.Anonymous" \ + "System.Read" \ + "System.View" + + # Create Cloud Controller Manager role (~9 privileges) + # Required for read-only node discovery + create_role "openshift-cloud-controller" \ + "System.Anonymous" \ + "System.Read" \ + "System.View" \ + "VirtualMachine.Inventory.Register" \ + "Host.Config.Network" \ + "Network.Assign" \ + "Datacenter.Read" \ + "Folder.Read" \ + "ClusterComputeResource.Read" + + # Create Diagnostics role (~5 privileges) + # Required for read-only troubleshooting + create_role "openshift-diagnostics" \ + "System.Anonymous" \ + "System.Read" \ + "System.View" \ + "VirtualMachine.GuestOperations.Query" \ + "Global.LogEvent" + + log_info "✓ All roles created successfully on $vcenter" +done + +log_info "==========================================" +log_info "Role creation complete!" +log_info "==========================================" +log_info "" +log_info "Next steps:" +log_info "1. Create vCenter user accounts for each component (e.g., ocp-machine-api@vsphere.local)" +log_info "2. Assign the appropriate role to each user account" +log_info "3. Generate credentials file using: ./generate-credentials-file.sh" +log_info "4. Create install-config.yaml with componentCredentials or use ~/.vsphere/credentials" +log_info "" +log_info "For more information, see: docs/user/vsphere/per-component-credentials.md" diff --git a/docs/user/vsphere/scripts/generate-credentials-file.sh b/docs/user/vsphere/scripts/generate-credentials-file.sh new file mode 100755 index 00000000000..8ecabe0eff9 --- /dev/null +++ b/docs/user/vsphere/scripts/generate-credentials-file.sh @@ -0,0 +1,164 @@ +#!/bin/bash +# generate-credentials-file.sh +# Generates a template YAML credentials file for OpenShift vSphere per-component credentials +# +# Usage: +# Single vCenter: +# ./generate-credentials-file.sh vcenter1.example.com +# +# Multiple vCenters: +# ./generate-credentials-file.sh vcenter1.example.com vcenter2.example.com +# +# Default (generates single vCenter template): +# ./generate-credentials-file.sh +# +# Prerequisites: +# - None (this script only generates a template file) +# +# Output: +# - Creates ~/.vsphere/credentials with 0600 permissions +# - File contains YAML-formatted credential templates for all components +# +# Security: +# - File is created with 0600 permissions (read/write for owner only) +# - Contains placeholder passwords that MUST be replaced before use + +set -euo pipefail + +# Color output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +# Default credentials file path +CREDENTIALS_FILE="${HOME}/.vsphere/credentials" + +# Determine vCenter list +if [ $# -eq 0 ]; then + VCENTERS=("vcenter.example.com") + log_info "No vCenters specified, using default template" +else + VCENTERS=("$@") + log_info "Generating credentials file for vCenters: ${VCENTERS[*]}" +fi + +# Create directory if it doesn't exist +mkdir -p "$(dirname "$CREDENTIALS_FILE")" + +# Check if file already exists +if [ -f "$CREDENTIALS_FILE" ]; then + log_warn "Credentials file already exists at: $CREDENTIALS_FILE" + read -p "Overwrite? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + log_info "Exiting without changes" + exit 0 + fi + # Backup existing file + BACKUP_FILE="${CREDENTIALS_FILE}.backup.$(date +%Y%m%d-%H%M%S)" + cp "$CREDENTIALS_FILE" "$BACKUP_FILE" + log_info "Backed up existing file to: $BACKUP_FILE" +fi + +# Generate credentials file +log_info "Generating credentials file at: $CREDENTIALS_FILE" + +cat > "$CREDENTIALS_FILE" << 'CREDENTIALS_TEMPLATE' +# OpenShift vSphere Per-Component Credentials File +# ================================================ +# +# This file contains credentials for OpenShift components to authenticate with vCenter. +# Each component uses a separate account with minimal required privileges. +# +# File format: INI-style sections keyed by vCenter FQDN +# File permissions: MUST be 0600 (read/write for owner only) +# +# Components: +# - installer: Used during cluster installation only (can be disabled post-install) +# - machine-api: VM lifecycle management (create, delete, modify VMs) +# - csi-driver: Storage provisioning (PV creation, disk attachment) +# - cloud-controller: Read-only node discovery and metadata +# - diagnostics: Read-only troubleshooting and log access +# +# Security Notes: +# - Each component should use a DIFFERENT username for auditability +# - Passwords should be strong (20+ characters, random) +# - Installer account can be disabled after installation completes +# - This file should NEVER be committed to version control +# - After installation, delete or secure this file (chmod 0400 or move to vault) +# +# Prerequisites: +# 1. Create vCenter roles using create-component-roles.sh (govc) or create-component-roles.ps1 (PowerCLI) +# 2. Create vCenter user accounts for each component +# 3. Assign appropriate role to each user account +# 4. Replace values below with actual credentials +# +# Usage: +# - Place this file at ~/.vsphere/credentials +# - Replace all values with actual credentials +# - Run: chmod 0600 ~/.vsphere/credentials +# - The installer will automatically read this file if install-config.yaml doesn't contain credentials +# +CREDENTIALS_TEMPLATE + +# Add vCenter sections +for vcenter in "${VCENTERS[@]}"; do + cat >> "$CREDENTIALS_FILE" << VCENTER_SECTION + +[${vcenter}] +# Main installer account (used during installation only) +user = installer@vsphere.local +password = + +# Machine API account (VM lifecycle management) +machine-api.user = ocp-machine-api@vsphere.local +machine-api.password = + +# CSI Driver account (storage provisioning) +csi-driver.user = ocp-csi-driver@vsphere.local +csi-driver.password = + +# Cloud Controller Manager account (read-only node discovery) +cloud-controller.user = ocp-cloud-controller@vsphere.local +cloud-controller.password = + +# Diagnostics account (read-only troubleshooting) +diagnostics.user = ocp-diagnostics@vsphere.local +diagnostics.password = +VCENTER_SECTION +done + +# Set secure file permissions +chmod 0600 "$CREDENTIALS_FILE" + +log_info "✓ Credentials file generated successfully" +log_info "" +log_info "File location: $CREDENTIALS_FILE" +log_info "File permissions: $(stat -c %a "$CREDENTIALS_FILE" 2>/dev/null || stat -f %A "$CREDENTIALS_FILE")" +log_info "" +log_warn "IMPORTANT: Replace all values with actual credentials" +log_info "" +log_info "Next steps:" +log_info "1. Edit $CREDENTIALS_FILE and replace all values" +log_info "2. Verify file permissions: ls -la $CREDENTIALS_FILE (should be -rw-------)" +log_info "3. Create install-config.yaml (credentials will be read from this file)" +log_info "4. Run: openshift-install create cluster --dir " +log_info "" +log_info "For multi-vCenter deployments:" +log_info "- Add additional [vcenter-fqdn] sections as needed" +log_info "- Each section should contain credentials for all components on that vCenter" +log_info "" +log_info "Security reminder:" +log_info "- NEVER commit this file to version control" +log_info "- After installation, consider: chmod 0400 $CREDENTIALS_FILE" +log_info "- Or move to a secure vault and delete from filesystem" +log_info "" +log_info "For more information, see: docs/user/vsphere/per-component-credentials.md" diff --git a/pkg/asset/installconfig/vsphere/privilegevalidator.go b/pkg/asset/installconfig/vsphere/privilegevalidator.go new file mode 100644 index 00000000000..86eb25fd96d --- /dev/null +++ b/pkg/asset/installconfig/vsphere/privilegevalidator.go @@ -0,0 +1,258 @@ +package vsphere + +import ( + "context" + "fmt" + + "github.com/openshift/installer/pkg/types/vsphere" +) + +// ValidationResult holds the result of privilege validation for a component. +type ValidationResult struct { + // Valid indicates whether all required privileges are present + Valid bool + // MissingPrivileges contains the list of missing privilege names + MissingPrivileges []string + // Scope identifies the entity type where privileges are required (e.g., "Datacenter", "Datastore") + Scope string +} + +// PrivilegeValidator validates vSphere component credentials have required privileges. +type PrivilegeValidator struct { + // In a real implementation, this would hold a vSphere client connection + // For now, this is a placeholder for the interface +} + +// NewPrivilegeValidator creates a new PrivilegeValidator instance. +func NewPrivilegeValidator() *PrivilegeValidator { + return &PrivilegeValidator{} +} + +// ValidateComponentPrivileges validates that the given component credentials have all required privileges. +// Returns ValidationResult indicating success/failure and any missing privileges. +func (v *PrivilegeValidator) ValidateComponentPrivileges(ctx context.Context, component string, creds *vsphere.AccountCredentials, vcenter string) (*ValidationResult, error) { + // Get required privileges for this component + required := GetRequiredPrivileges(component) + if len(required) == 0 { + return nil, fmt.Errorf("unknown component: %s", component) + } + + // In a real implementation, this would: + // 1. Connect to vCenter using creds + // 2. Call AuthorizationManager.FetchUserPrivilegeOnEntities() + // 3. Compare returned privileges against required list + // 4. Identify missing privileges and their scopes + // + // For now, we return a stub that allows tests to define behavior via mocking + + result := &ValidationResult{ + Valid: true, + MissingPrivileges: []string{}, + Scope: getDefaultScopeForComponent(component), + } + + return result, nil +} + +// GetRequiredPrivileges returns the list of required vSphere privileges for the given component. +func GetRequiredPrivileges(component string) []string { + privilegeMap := map[string][]string{ + "installer": installerPrivileges, + "machine-api": machineAPIPrivileges, + "csi-driver": csiDriverPrivileges, + "cloud-controller": cloudControllerPrivileges, + "diagnostics": diagnosticsPrivileges, + } + + return privilegeMap[component] +} + +// getDefaultScopeForComponent returns the default vSphere entity scope for privilege checks. +func getDefaultScopeForComponent(component string) string { + scopeMap := map[string]string{ + "installer": "Datacenter", + "machine-api": "Datacenter", + "csi-driver": "Datastore", + "cloud-controller": "Datacenter", + "diagnostics": "Datacenter", + } + + if scope, ok := scopeMap[component]; ok { + return scope + } + return "Datacenter" +} + +// Privilege lists for each component based on design doc requirements. +// These are comprehensive lists of vSphere privileges required for each component's operations. + +// installerPrivileges contains ~45 privileges required for cluster infrastructure deployment. +var installerPrivileges = []string{ + // Folder management + "Folder.Create", + "Folder.Delete", + "Folder.Move", + "Folder.Rename", + + // Resource pool management + "ResourcePool.Create", + "ResourcePool.Delete", + "ResourcePool.Assign", + + // Virtual machine provisioning + "VirtualMachine.Provisioning.Clone", + "VirtualMachine.Provisioning.DeployTemplate", + "VirtualMachine.Provisioning.MarkAsTemplate", + "VirtualMachine.Provisioning.MarkAsVM", + "VirtualMachine.Provisioning.CustomizeGuest", + "VirtualMachine.Provisioning.ReadCustSpecs", + "VirtualMachine.Provisioning.ModifyCustSpecs", + + // Virtual machine configuration + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.AddRemoveDevice", + "VirtualMachine.Config.AdvancedConfig", + "VirtualMachine.Config.CPUCount", + "VirtualMachine.Config.Memory", + "VirtualMachine.Config.Settings", + "VirtualMachine.Config.Resource", + "VirtualMachine.Config.EditDevice", + "VirtualMachine.Config.RemoveDisk", + "VirtualMachine.Config.Rename", + "VirtualMachine.Config.Annotation", + + // Virtual machine interaction + "VirtualMachine.Interact.PowerOn", + "VirtualMachine.Interact.PowerOff", + "VirtualMachine.Interact.Reset", + "VirtualMachine.Interact.Suspend", + "VirtualMachine.Interact.ConsoleInteract", + "VirtualMachine.Interact.DeviceConnection", + "VirtualMachine.Interact.SetCDMedia", + "VirtualMachine.Interact.GuestControl", + + // Virtual machine inventory + "VirtualMachine.Inventory.Create", + "VirtualMachine.Inventory.Delete", + "VirtualMachine.Inventory.Move", + "VirtualMachine.Inventory.Register", + "VirtualMachine.Inventory.Unregister", + + // Network assignment + "Network.Assign", + + // Datastore allocation + "Datastore.AllocateSpace", + "Datastore.Browse", + "Datastore.FileManagement", +} + +// machineAPIPrivileges contains ~35 privileges required for VM lifecycle operations. +var machineAPIPrivileges = []string{ + // Virtual machine provisioning + "VirtualMachine.Provisioning.Clone", + "VirtualMachine.Provisioning.DeployTemplate", + "VirtualMachine.Provisioning.CustomizeGuest", + + // Virtual machine configuration + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.AddRemoveDevice", + "VirtualMachine.Config.AdvancedConfig", + "VirtualMachine.Config.CPUCount", + "VirtualMachine.Config.Memory", + "VirtualMachine.Config.Settings", + "VirtualMachine.Config.Resource", + "VirtualMachine.Config.EditDevice", + "VirtualMachine.Config.RemoveDisk", + "VirtualMachine.Config.Rename", + "VirtualMachine.Config.Annotation", + + // Virtual machine interaction + "VirtualMachine.Interact.PowerOn", + "VirtualMachine.Interact.PowerOff", + "VirtualMachine.Interact.Reset", + "VirtualMachine.Interact.Suspend", + "VirtualMachine.Interact.DeviceConnection", + "VirtualMachine.Interact.GuestControl", + + // Virtual machine inventory + "VirtualMachine.Inventory.Create", + "VirtualMachine.Inventory.Delete", + "VirtualMachine.Inventory.Move", + "VirtualMachine.Inventory.Register", + "VirtualMachine.Inventory.Unregister", + + // Network assignment + "Network.Assign", + + // Datastore allocation + "Datastore.AllocateSpace", + "Datastore.Browse", + "Datastore.FileManagement", + + // Resource pool + "ResourcePool.Assign", + + // Folder operations + "Folder.Create", + "Folder.Delete", +} + +// csiDriverPrivileges contains ~10-15 privileges required for storage provisioning. +var csiDriverPrivileges = []string{ + // Datastore operations (primary focus) + "Datastore.AllocateSpace", + "Datastore.Browse", + "Datastore.FileManagement", + "Datastore.DeleteFile", + "Datastore.UpdateVirtualMachineFiles", + + // Virtual machine disk operations + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.AddRemoveDevice", + "VirtualMachine.Config.RemoveDisk", + "VirtualMachine.Config.EditDevice", + + // System operations + "System.Anonymous", + "System.Read", + "System.View", +} + +// cloudControllerPrivileges contains ~10 read-only privileges required for node discovery. +var cloudControllerPrivileges = []string{ + // Read-only system access + "System.Anonymous", + "System.Read", + "System.View", + + // Virtual machine read operations + "VirtualMachine.Inventory.Register", + + // Resource discovery (read-only) + "Host.Config.Network", + "Network.Assign", + + // Datacenter read operations + "Datacenter.Read", + + // Folder read operations + "Folder.Read", + + // Cluster read operations + "ClusterComputeResource.Read", +} + +// diagnosticsPrivileges contains ~5 read-only privileges required for troubleshooting. +var diagnosticsPrivileges = []string{ + // Read-only system access + "System.Anonymous", + "System.Read", + "System.View", + + // Virtual machine read operations + "VirtualMachine.GuestOperations.Query", + + // Log access + "Global.LogEvent", +} diff --git a/pkg/asset/installconfig/vsphere/privilegevalidator_test.go b/pkg/asset/installconfig/vsphere/privilegevalidator_test.go new file mode 100644 index 00000000000..f2955d24340 --- /dev/null +++ b/pkg/asset/installconfig/vsphere/privilegevalidator_test.go @@ -0,0 +1,203 @@ +package vsphere + +import ( + "testing" +) + +// TestValidateComponentPrivileges_MachineAPI_MissingPrivilege tests validation failure +// when machine-api credentials lack VirtualMachine.Provisioning.Clone privilege +// +// Acceptance Criteria (AC1): +// Given machine-api credentials lacking VirtualMachine.Provisioning.Clone privilege +// When the installer validates privileges +// Then validation fails with error "Component machine-api missing required privilege: VirtualMachine.Provisioning.Clone on Datacenter" +func TestValidateComponentPrivileges_MachineAPI_MissingPrivilege(t *testing.T) { + t.Skip("Implementation pending: Story #4 - Privilege Validation") + + // Test setup: + // 1. Create mock vSphere client with machine-api credentials + // 2. Mock AuthorizationManager.FetchUserPrivilegeOnEntities() to return privileges WITHOUT VirtualMachine.Provisioning.Clone + // 3. Create PrivilegeValidator instance + + // Test execution: + // validator := NewPrivilegeValidator(mockClient) + // result, err := validator.ValidateComponentPrivileges(ctx, "machine-api", machineAPICreds, "vcenter.example.com") + + // Assertions: + // - result.Valid should be false + // - result.MissingPrivileges should contain "VirtualMachine.Provisioning.Clone" + // - result.Scope should be "Datacenter" + // - Error message should match AC1 format +} + +// TestValidateComponentPrivileges_CSIDriver_MissingPrivilege tests validation failure +// when csi-driver credentials lack Datastore.AllocateSpace privilege +// +// Acceptance Criteria (AC2): +// Given csi-driver credentials lacking Datastore.AllocateSpace privilege +// When the installer validates privileges +// Then validation fails with error "Component csi-driver missing required privilege: Datastore.AllocateSpace on Datastore" +func TestValidateComponentPrivileges_CSIDriver_MissingPrivilege(t *testing.T) { + t.Skip("Implementation pending: Story #4 - Privilege Validation") + + // Test setup: + // 1. Create mock vSphere client with csi-driver credentials + // 2. Mock AuthorizationManager.FetchUserPrivilegeOnEntities() to return privileges WITHOUT Datastore.AllocateSpace + // 3. Create PrivilegeValidator instance + + // Test execution: + // validator := NewPrivilegeValidator(mockClient) + // result, err := validator.ValidateComponentPrivileges(ctx, "csi-driver", csiDriverCreds, "vcenter.example.com") + + // Assertions: + // - result.Valid should be false + // - result.MissingPrivileges should contain "Datastore.AllocateSpace" + // - result.Scope should be "Datastore" + // - Error message should match AC2 format +} + +// TestValidateComponentPrivileges_AllComponentsValid tests successful validation +// when all component credentials have required privileges +// +// Acceptance Criteria (AC3): +// Given all component credentials have required privileges +// When the installer validates privileges +// Then validation passes for all components +func TestValidateComponentPrivileges_AllComponentsValid(t *testing.T) { + t.Skip("Implementation pending: Story #4 - Privilege Validation") + + // Test setup: + // 1. Create mock vSphere client + // 2. Mock AuthorizationManager.FetchUserPrivilegeOnEntities() to return ALL required privileges for each component + // 3. Create PrivilegeValidator instance + // 4. Prepare credentials for all 5 components: installer, machine-api, csi-driver, cloud-controller, diagnostics + + // Test execution: + // validator := NewPrivilegeValidator(mockClient) + // components := map[string]*AccountCredentials{ + // "installer": installerCreds, + // "machine-api": machineAPICreds, + // "csi-driver": csiDriverCreds, + // "cloud-controller": cloudControllerCreds, + // "diagnostics": diagnosticsCreds, + // } + // + // for component, creds := range components { + // result, err := validator.ValidateComponentPrivileges(ctx, component, creds, "vcenter.example.com") + // // Assert result.Valid == true for each component + // // Assert len(result.MissingPrivileges) == 0 + // } + + // Assertions: + // - All components should pass validation (result.Valid == true) + // - No missing privileges for any component + // - No errors returned +} + +// TestValidateComponentPrivileges_Installer_FullPrivilegeSet tests privilege validation +// for installer component with ~45 required privileges +func TestValidateComponentPrivileges_Installer_FullPrivilegeSet(t *testing.T) { + t.Skip("Implementation pending: Story #4 - Privilege Validation") + + // Test setup: + // 1. Create mock vSphere client with installer credentials + // 2. Mock AuthorizationManager to return all ~45 installer privileges + // 3. Verify installer privilege list includes: + // - Folder.Create + // - ResourcePool.Create + // - VirtualMachine.Provisioning.* + // - Network.Assign + // - Datastore.AllocateSpace + + // Test execution: + // result, err := validator.ValidateComponentPrivileges(ctx, "installer", installerCreds, "vcenter.example.com") + + // Assertions: + // - result.Valid == true + // - Verify all ~45 installer privileges are checked +} + +// TestValidateComponentPrivileges_CloudController_ReadOnly tests privilege validation +// for cloud-controller component with ~10 read-only privileges +func TestValidateComponentPrivileges_CloudController_ReadOnly(t *testing.T) { + t.Skip("Implementation pending: Story #4 - Privilege Validation") + + // Test setup: + // 1. Create mock vSphere client with cloud-controller credentials + // 2. Mock AuthorizationManager to return read-only privileges: + // - System.Anonymous + // - System.Read + // - System.View + // 3. Verify cloud-controller does NOT require write privileges + + // Test execution: + // result, err := validator.ValidateComponentPrivileges(ctx, "cloud-controller", cloudControllerCreds, "vcenter.example.com") + + // Assertions: + // - result.Valid == true + // - Verify all ~10 cloud-controller privileges are read-only + // - No write privileges required +} + +// TestValidateComponentPrivileges_MultipleComponentsMissingPrivileges tests validation +// when multiple components lack required privileges +func TestValidateComponentPrivileges_MultipleComponentsMissingPrivileges(t *testing.T) { + t.Skip("Implementation pending: Story #4 - Privilege Validation") + + // Test setup: + // 1. Create mock vSphere client + // 2. Mock AuthorizationManager to return incomplete privilege sets for multiple components + // 3. Test scenario: + // - machine-api missing VirtualMachine.Provisioning.Clone + // - csi-driver missing Datastore.AllocateSpace + // - diagnostics missing System.Read + + // Test execution: + // Validate each component and collect errors + + // Assertions: + // - Each component validation should fail independently + // - Error messages should identify specific missing privileges + // - Validation should report all missing privileges, not just first failure +} + +// TestValidateComponentPrivileges_vSphereAPIError tests error handling +// when vSphere AuthorizationManager API call fails +func TestValidateComponentPrivileges_vSphereAPIError(t *testing.T) { + t.Skip("Implementation pending: Story #4 - Privilege Validation") + + // Test setup: + // 1. Create mock vSphere client + // 2. Mock AuthorizationManager.FetchUserPrivilegeOnEntities() to return an error (e.g., network timeout, auth failure) + + // Test execution: + // result, err := validator.ValidateComponentPrivileges(ctx, "machine-api", machineAPICreds, "vcenter.example.com") + + // Assertions: + // - err should not be nil + // - Error message should indicate vSphere API failure + // - Error should include context (component, vCenter FQDN) +} + +// TestGetRequiredPrivileges_AllComponents tests privilege list retrieval for all components +func TestGetRequiredPrivileges_AllComponents(t *testing.T) { + t.Skip("Implementation pending: Story #4 - Privilege Validation") + + // Test setup: + // Verify GetRequiredPrivileges() returns correct privilege counts for each component + + // Test execution: + // installerPrivs := GetRequiredPrivileges("installer") + // machineAPIPrivs := GetRequiredPrivileges("machine-api") + // csiDriverPrivs := GetRequiredPrivileges("csi-driver") + // cloudControllerPrivs := GetRequiredPrivileges("cloud-controller") + // diagnosticsPrivs := GetRequiredPrivileges("diagnostics") + + // Assertions: + // - len(installerPrivs) ~= 45 + // - len(machineAPIPrivs) ~= 35 + // - len(csiDriverPrivs) ~= 10-15 + // - len(cloudControllerPrivs) ~= 10 + // - len(diagnosticsPrivs) ~= 5 + // - Each privilege list contains specific required privileges per design doc +} diff --git a/pkg/asset/installconfig/vsphere/validation_test.go b/pkg/asset/installconfig/vsphere/validation_test.go index f6bceb20bf0..18d8af6e1d3 100644 --- a/pkg/asset/installconfig/vsphere/validation_test.go +++ b/pkg/asset/installconfig/vsphere/validation_test.go @@ -878,3 +878,21 @@ func Test_compareCurrentToTemplate(t *testing.T) { }) } } + +// TestInstallConfigValidation_ComponentCredentials validates full install-config with componentCredentials +func TestInstallConfigValidation_ComponentCredentials(t *testing.T) { + // TODO: Create full install-config YAML with componentCredentials + // TODO: Parse install-config + // TODO: Run validation + // TODO: Assert all component credentials are parsed correctly + t.Skip("Implementation pending") +} + +// TestInstallConfigValidation_LegacyMode validates install-config with legacy credentials +func TestInstallConfigValidation_LegacyMode(t *testing.T) { + // TODO: Create full install-config YAML with only username/password + // TODO: Parse install-config + // TODO: Run validation + // TODO: Assert passthrough mode is detected + t.Skip("Implementation pending") +} diff --git a/pkg/types/vsphere/defaults_test.go b/pkg/types/vsphere/defaults_test.go new file mode 100644 index 00000000000..c1fba0c2071 --- /dev/null +++ b/pkg/types/vsphere/defaults_test.go @@ -0,0 +1,15 @@ +package vsphere + +import ( + "testing" +) + +// TestPartialComponentCredentials_Fallback validates partial credentials with fallback to legacy +func TestPartialComponentCredentials_Fallback(t *testing.T) { + // TODO: Create Platform instance with partial componentCredentials + // TODO: Run schema validation + // TODO: Assert validation passes + // TODO: Assert specified component uses its credentials + // TODO: Assert unspecified components fall back to legacy credentials + t.Skip("Implementation pending") +} diff --git a/pkg/types/vsphere/platform.go b/pkg/types/vsphere/platform.go index 7c1c81bf0c1..f33c10e5b70 100644 --- a/pkg/types/vsphere/platform.go +++ b/pkg/types/vsphere/platform.go @@ -152,6 +152,62 @@ type Platform struct { LoadBalancer *configv1.VSpherePlatformLoadBalancer `json:"loadBalancer,omitempty"` // Hosts defines network configurations to be applied by the installer. Hosts is available in TechPreview. Hosts []*Host `json:"hosts,omitempty"` + + // ComponentCredentials defines per-component vSphere credentials for improved security posture + // through principle of least privilege. When specified, each OpenShift component receives distinct + // vCenter credentials matched to its operational needs. + // +optional + ComponentCredentials *ComponentCredentials `json:"componentCredentials,omitempty"` +} + +// ComponentCredentials holds per-component credential accounts for vSphere operations. +// Each component receives only the vCenter permissions it needs, reducing security blast radius. +// If a component credential is not specified, the component falls back to the deprecated +// legacy credentials (DeprecatedUsername/DeprecatedPassword) from the Platform struct. +type ComponentCredentials struct { + // Installer credentials used for cluster infrastructure deployment. + // Requires full deployment operations privileges (~45 permissions). + // +optional + Installer *AccountCredentials `json:"installer,omitempty"` + + // MachineAPI credentials used for VM lifecycle operations. + // Requires VM provisioning and configuration privileges (~35 permissions). + // +optional + MachineAPI *AccountCredentials `json:"machineAPI,omitempty"` + + // CSIDriver credentials used for storage provisioning. + // Requires datastore and disk management privileges (~10-15 permissions). + // +optional + CSIDriver *AccountCredentials `json:"csiDriver,omitempty"` + + // CloudController credentials used for node discovery. + // Requires read-only privileges (~10 permissions). + // +optional + CloudController *AccountCredentials `json:"cloudController,omitempty"` + + // Diagnostics credentials used for troubleshooting. + // Requires read-only privileges (~5 permissions). + // +optional + Diagnostics *AccountCredentials `json:"diagnostics,omitempty"` +} + +// AccountCredentials holds vSphere account credentials for a component. +// Supports multi-vCenter topologies via optional vCenter field override. +type AccountCredentials struct { + // Username is the vCenter username for this component. + // +kubebuilder:validation:Required + Username string `json:"username"` + + // Password is the vCenter password for this component. + // +kubebuilder:validation:Required + Password string `json:"password"` + + // VCenter is the vCenter server FQDN override for this component. + // When specified, this component will use credentials for a different vCenter + // than the default Platform.DeprecatedVCenter. This enables multi-vCenter topologies + // where different components connect to different vCenter servers. + // +optional + VCenter string `json:"vCenter,omitempty"` } // FailureDomain holds the region and zone failure domain and diff --git a/pkg/types/vsphere/validation_test.go b/pkg/types/vsphere/validation_test.go new file mode 100644 index 00000000000..08993e470a2 --- /dev/null +++ b/pkg/types/vsphere/validation_test.go @@ -0,0 +1,48 @@ +package vsphere + +import ( + "testing" +) + +// TestComponentCredentials_FullSet validates schema with all 5 component accounts +func TestComponentCredentials_FullSet(t *testing.T) { + // TODO: Create Platform instance with full componentCredentials + // TODO: Run schema validation + // TODO: Assert validation passes + // TODO: Assert all 5 component accounts are accessible + t.Skip("Implementation pending") +} + +// TestLegacyPassthroughMode validates legacy username/password fields +func TestLegacyPassthroughMode(t *testing.T) { + // TODO: Create Platform instance with only username/password + // TODO: Run schema validation + // TODO: Assert validation passes + // TODO: Assert passthrough mode is enabled + t.Skip("Implementation pending") +} + +// TestComponentCredentials_EmptyStruct validates rejection of empty componentCredentials +func TestComponentCredentials_EmptyStruct(t *testing.T) { + // TODO: Create Platform instance with empty componentCredentials struct + // TODO: Run schema validation + // TODO: Assert validation fails with expected error message + t.Skip("Implementation pending") +} + +// TestPartialComponentCredentials_NoLegacyFallback validates partial credentials without legacy fallback +func TestPartialComponentCredentials_NoLegacyFallback(t *testing.T) { + // TODO: Create Platform instance with partial componentCredentials + // TODO: Run schema validation + // TODO: Assert validation fails if no legacy fallback (OR passes if design allows) + t.Skip("Implementation pending") +} + +// TestComponentCredentials_MultiVCenter validates multi-vCenter support +func TestComponentCredentials_MultiVCenter(t *testing.T) { + // TODO: Create Platform instance with vCenter overrides per component + // TODO: Run schema validation + // TODO: Assert validation passes + // TODO: Assert components reference correct vCenter FQDNs + t.Skip("Implementation pending") +}