VCF Fleet Manager Password Extractor: Automating Access to Stored Credentials

Posted by

⚠️ Important Ethical Note ⚠️
This script should only be used in environments you own—like a home lab—where you are explicitly authorized to access these credentials.
Never use this in production, and never use it to access passwords you’re not supposed to know.
Automation is a tool; use it responsibly. Don’t be the reason someone disables API access for everyone.

 

Problem

Unlike Aria Lifecycle Manager, VCF Fleet Manager gives you no bulk export, no CLI command, and no straightforward API to fetch plain-text passwords. Everything is locked behind a multi-step process designed for security—which is great in production, but frustrating during lab operations.
Every time I redeployed VCF Automation or debugged a failed workflow, I had to:
  • Open the browser
  • Navigate to Locker
  • Click through each credential
  • Re-enter the root password (yes, every time)
After the fifth time in one afternoon, I knew: this has to be scriptable.

Troubleshooting the API

My first attempt was straightforward:
  1. Authenticate with Basic Auth
  2. GET /lcm/locker/api/passwords → get list of password IDs
  3. POST to /lcm/locker/api/v2/passwords/{id}/decrypted with the root password
But I kept getting HTTP 500 Internal Server Errors on decryption—even though the same request worked in Postman.
After digging through logs and comparing raw requests, I discovered two key quirks:

🔸 Quirk #1: The JSON body must be exactly formatted

VCF’s decryption endpoint is picky. Using PowerShell’s ConvertTo-Json added subtle formatting that the Java backend rejected. The only format that worked reliably was:
{"rootPassword": "VMware123!"}
…as a literal string, no extra spaces, no PowerShell object conversion.

🔸 Quirk #2: Not all passwords are decryptable

Only system-generated passwords (like lcm_root_user) can be decrypted using the global root password. Manually added credentials? They require their own passphrase—and won’t work with this method. That’s why you might see 9 entries but only decrypt 2–3.
Also, yes—VCF’s login API returns "Login succeessfully" (with three e’s). I’m not kidding. You have to match that typo in your success check.

The Solution: A Reusable PowerShell Script

After ironing out the issues, I built a clean, parameterized PowerShell script that:
  • Accepts VCF FQDN, username, auth password, and root password as inputs
  • Bypasses SSL validation (safe in a home lab)
  • Retrieves all password metadata
  • Attempts to decrypt each one using the exact JSON format VCF expects
  • Outputs a clear table of results
Here’s the core logic:
# Decrypt using literal JSON string (critical!)
$body = "{`"rootPassword`": `"$RootPassword`"}"
$response = Invoke-RestMethod -Method POST -Uri $url -Headers $headers -Body $body

You run it like this:

.\Get-VcfPasswords.ps1 `
  -VcfFqdn "vcffleet.home.lab" `
  -Username "admin@local" `
  -Password "VMware123!" `
  -RootPassword "VMware123!"

Sample output:

============================================================
DECRYPTED PASSWORDS REPORT
============================================================
Name                     Id                                   Tenant   Password
----                     --                                   ------   --------
lcm_root_user            2b3dfa03-...                         default  VMware123!
VCF Automation - Root    8e6d2556-...                         default  VMware123!

Why This Matters for Home Lab Operators

In a lab, speed and repeatability win. Being able to:
  • Rebuild an environment from scratch
  • Validate credential consistency
  • Debug failed deployments by checking actual passwords
…saves hours over manual clicking. Plus, this script integrates nicely into larger automation workflows (like my VCF policy creator).
It’s not fancy—but it solves a real pain point I faced weekly.

Final Thoughts

This script isn’t just about passwords—it’s about taking control of your lab’s operational data instead of being blocked by UI limitations.
If you’re running a VCF home lab, give it a try. And if you improve it (maybe add CSV export or credential vault support), I’d love to hear about it!

Script

Get-VcfPasswords.ps1

#requires -Version 5.1

<#
.SYNOPSIS
    Retrieves and decrypts stored passwords from VMware Cloud Foundation (VCF) Fleet Manager.
.DESCRIPTION
    This script authenticates to a VCF Fleet Manager instance, retrieves password metadata,
    and decrypts each password using the provided root password. Designed for home lab use.
.NOTES
    Author: Kabir Ali - info@whatkabirwrites.nl
    Date: February 2026
    Environment: Trusted home lab – SSL validation bypassed.
#>

#region Parameters
Param (
    [Parameter(Mandatory = $true)][string]$VcfFqdn,
    [Parameter(Mandatory = $true)][string]$Username,
    [Parameter(Mandatory = $true)][string]$Password,
    [Parameter(Mandatory = $true)][string]$RootPassword
)
#endregion

#region SSL Bypass (Home Lab Only!)
Add-Type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
    public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem) {
        return true;
    }
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
#endregion


#region Configuration
$Config = @{
    VcfFqdn      = $VcfFqdn
    Username     = $Username
    Password     = $Password
    RootPassword = $RootPassword
}
#endregion

#region Helper Functions

function Write-Log {
    param(
        [Parameter(Mandatory)]
        [string]$Message,
        [ValidateSet('Info', 'Success', 'Warning', 'Error')]
        [string]$Level = 'Info'
    )
    $ColorMap = @{ Info = 'White'; Success = 'Green'; Warning = 'Yellow'; Error = 'Red' }
    $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    Write-Host "[$Timestamp] [$Level] $Message" -ForegroundColor $ColorMap[$Level]
}

function Get-VcfAuthToken {
    param(
        [string]$VcfFqdn,
        [string]$Username,
        [string]$Password
    )
    try {
        $pair = "$($Username):$($Password)"
        $encoded = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($pair))
        $headers = @{
            "Accept"        = "application/json"
            "Authorization" = "Basic $encoded"
            "Content-Type"   = "application/json"
        }

        $authUrl = "https://$VcfFqdn/lcm/authzn/api/login"
        $response = Invoke-RestMethod -Method POST -Uri $authUrl -Headers $headers -Body "" -ErrorAction Stop

        if ($response -eq "Login succeessfully") {  # Note: VCF has typo
            Write-Log "Authentication successful." -Level Success
            return $headers
        } else {
            throw "Unexpected authentication response: $response"
        }
    } catch {
        Write-Log "Authentication failed: $($_.Exception.Message)" -Level Error
        throw
    }
}

function Get-VcfPasswordMetadata {
    param(
        [string]$VcfFqdn,
        [hashtable]$AuthHeaders
    )
    try {
        $url = "https://$VcfFqdn/lcm/locker/api/passwords"
        $response = Invoke-RestMethod -Method GET -Uri $url -Headers $AuthHeaders -ErrorAction Stop
        Write-Log "Retrieved $($response.Count) password entries." -Level Info
        return $response
    } catch {
        Write-Log "Failed to retrieve password metadata: $($_.Exception.Message)" -Level Error
        throw
    }
}

function Decrypt-VcfPassword {
    param(
        [string]$VcfFqdn,
        [hashtable]$AuthHeaders,
        [string]$PasswordId,
        [string]$RootPassword
    )
    try {
        $url = "https://$VcfFqdn/lcm/locker/api/v2/passwords/$PasswordId/decrypted"
        # Use exact string format as in original working script
        $body = ' { "rootPassword": "VMware123!VMware123!" } '
        $response = Invoke-RestMethod -Method POST -Uri $url -Headers $AuthHeaders -Body $body -ErrorAction Stop
        return $response
    } catch {
        Write-Log "Failed to decrypt password ID '$PasswordId': $($_.Exception.Message)" -Level Warning
        return $null
    }
}

#endregion

#region Main Execution

try {
    Write-Log "Starting VCF Fleet Manager password extraction..." -Level Info

    # Authenticate
    $authHeaders = Get-VcfAuthToken -VcfFqdn $Config.VcfFqdn -Username $Config.Username -Password $Config.Password

    # Fetch password metadata
    $passwordMetadata = Get-VcfPasswordMetadata -VcfFqdn $Config.VcfFqdn -AuthHeaders $authHeaders

    if (-not $passwordMetadata -or $passwordMetadata.Count -eq 0) {
        Write-Log "No passwords found in locker." -Level Warning
        exit
    }

    # Decrypt each password
    $decryptedPasswords = @()
    foreach ($entry in $passwordMetadata) {
        Write-Log "Decrypting password: $($entry.Alias) (ID: $($entry.vmid))" -Level Info
        $plainText = Decrypt-VcfPassword -VcfFqdn $Config.VcfFqdn -AuthHeaders $authHeaders `
                                        -PasswordId $entry.vmid -RootPassword $Config.RootPassword
        if ($plainText) {
            $decryptedPasswords += [PSCustomObject]@{
                Name     = $entry.Alias
                Tenant   = $entry.tenant
                Password = $plainText.password
                Id       = $entry.vmid
            }
        }
    }

    # Final Reporting
    Write-Log "Extraction complete. Found $($decryptedPasswords.Count) decrypted passwords." -Level Success

    if ($decryptedPasswords.Count -gt 0) {
        Write-Host "`n" + ("=" * 60) -ForegroundColor Cyan
        Write-Host "DECRYPTED PASSWORDS REPORT" -ForegroundColor Cyan
        Write-Host ("=" * 60) -ForegroundColor Cyan
        $decryptedPasswords | Format-Table -Property Name, Id, Tenant, Password -AutoSize
    } else {
        Write-Log "No passwords were successfully decrypted." -Level Warning
    }

} catch {
    Write-Log "Script terminated due to error: $($_.Exception.Message)" -Level Error
    exit 1
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *