Someone recently asked me if there was an API to replace the Aria Operations for Logs SSL certificate programmatically. In this case, Aria Suite Lifecycle was already deployed and used to manage multiple Aria Operations for Logs clusters, primarily used in regional data centers to forward events to a centralized instance. This meant that our ideal solution would leverage Aria Suite Lifecycle as well, adding the certificate to the locker prior to replacing the certificate in product. A colleague of mine recently published a blog post showing how to rotate Aria Suite Local Account Passwords using APIs and PowerShell: https://stephanmctighe.com/2024/12/20/rotating-aria-suite-local-account-passwords-using-apis-powershell/, so I used the style/splatting method he used for consistency in this post.
Due to the varied nature of requesting/approving certificates, I did not cover the process of creating a certificate signing request using APIs for this example. However, it is possible to do this via API as well. The ‘Create CSR and Key Using POST’ can be called with a POST
operation to /lcm/locker/api/v2/certificates/csr
as described here: https://developer.broadcom.com/xapis/vmware-aria-suite-lifecycle-rest-api/8.14//lcm-15-186.eng.vmware.com/lcm/locker/api/v2/certificates/csr/post/.
Workflow
I first worked through each of these steps by creating a new collection in Bruno and stepping through each API to understand the inputs/outputs and how everything worked together. Once complete, I looked through each of the requests from Bruno and converted them to a single PowerShell script, to be able to have the end to end workflow in a single document for reference. In the sections of the post below, I’ll step through each chuck of the script and add some additional context on why each section exists and what they do.
Setting up the script
For readability and usability, I decided to have a block of variables and paths at the very start of the script. In this section, you can see Aria Suite Lifecycle hostname/credentials, and basic auth string being defined. There are then a handful of filename/paths related to the certificate, root certificate, and key needed for the certificate I created from a Windows Certificate Services deployment. We then list the name of the Aria Suite Lifecycle environment containing the product we need to update. For demonstration purposes, I created an environment named h308-logs
, which only contained a single product (Aria Operations for Logs).
# LCM connection detail
$lcmHost = 'cm-lifecycle-02.lab.enterpriseadmins.org'
$username = 'admin@local'
$password = 'VMware1!'
$authorization = "Basic $([System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("$($username):$($password)")))"
# Certificate/environment detail
$newCertificateAlias = 'h308-logs-01.lab.enterpriseadmins.org_2025-01-14'
$newCertificateFolder = 'C:\Users\bwuchner\Downloads'
$newCertificateCSR = 'CSR_h308-logs-01.lab.enterpriseadmins.org_Test.pem'
$newCertificateFile = 'CERT_h308-logs-01.cer'
$newCertificateRoot = 'CERT_rootca.cer'
$environmentName = 'h308-logs'
Reading the certificate files
Our certificate consists of multiple files.
- private key, which is at the end of the certificate signing request (CSR) file that was generated by the Aria Suite Lifecycle GUI.
- certificate file, which was obtained from our certificate authority and contains subject alternative names for our Aria Operations for Logs hostname and IP address
- The root certificate from our certificate authority. In this lab, there are no intermediate certificates required. If they were, they could be added to the
$cert
variable below.
When using Get-Content
, by default PowerShell will read one line of the file at a time. In the examples below, we join each new line with a new line character (`n
) so that the API will understand our request. Failure to do so might result in an error like parsing issue: malformed PEM data encountered
, LCM_CERTIFICATE_API_ERROR0000
, or Unknown Certificate error
.
# When we generated a CSR in the UI, before sending it to our CA, the private key is at the end of the
# CSR file. We'll read that file, loop through and find the start/end of the private key, then format
# it to send in our JSON body
$key = Get-Content "$newCertificateFolder\$newCertificateCSR"
$keyCounter = 0
$key | %{if($_ -eq '-----BEGIN PRIVATE KEY-----'){$keyStartLine=$keyCounter}; if($_ -eq '-----END PRIVATE KEY-----'){$keyEndLine=$keyCounter}; $keyCounter++ }
$key = ($key[$keyStartLine..$keyEndLine] -join "`n")
# We'll also read in our cert and concatenate each line with a new line character.
# If we have intermedate certs they can be joined in a similar way
$cert = ((Get-Content "$newCertificateFolder\$newCertificateFile") -join "`n") + "`n"
$cert += ((Get-Content "$newCertificateFolder\$newCertificateRoot") -join "`n") + "`n"
Adding the certificate to the locker
We can POST
our new certificate/key combo to the /lcm/locker/api/v2/certificates/import
API. It will return details on the certificate, such as the alias provided, the validity, and sha256/sha1 hashes. It does not return the ID of the certificate in the locker, which we’ll need in a future step. Therefore, now seemed like a good time to get the certificate by filtering for the Alias name we used in our original request.
$Splat = @{
"URI" = "https://$lcmHost/lcm/locker/api/v2/certificates/import"
"Headers" = @{
'Accept' = "*/*"
'Content-Type' = "application/json"
"Authorization" = $authorization
}
"Body" = @{
'alias' = $newCertificateAlias
'certificateChain' = $cert
'privateKey' = $key
} | ConvertTo-JSON
"Method" = "POST"
}
$NewCertPost = Invoke-RestMethod @Splat
# the newcertpost variable will have detail on our certificate, its validity, and san fields.
# we will need cert ID, so we'll make a query for it.
$Splat = @{
"URI" = "https://$lcmHost/lcm/locker/api/v2/certificates"
"Headers" = @{
'Accept' = "*/*"
'Content-Type' = "application/json"
"Authorization" = $authorization
}
"Method" = "GET"
}
$lockerCertId = ((Invoke-RestMethod @Splat).Certificates | ?{$_.alias -eq $newCertificateAlias}).vmid
Depending on what parts of this process we want to automate, it would also be possible to just get the ID of the certificate from the locker in the GUI. When we view the specific certificate, the ID is the GUID we see in the address bar, right after /lcm/locker/certificate
:
Finding the environment ID
To replace the product certificate, we’ll need to know which environment ID needs to be updated. We can find this information from the API or the GUI. We’ll start by doing a GET
operation for all environments, then filtering by the environment name variable declared at the beginning of the script.
# now that we have our new cert in the locker, we can apply it to the product
# Get Environment ID
$Splat = @{
"URI" = "https://$lcmHost/lcm/lcops/api/v2/environments?status=COMPLETED"
"Headers" = @{
'Accept' = "*/*"
'Content-Type' = "application/json"
"Authorization" = $authorization
}
"Method" = "GET"
}
$Environments = Invoke-RestMethod @Splat
# find our specific environment ID
$environmentId = ($Environments |?{$_.environmentName -eq $environmentName}).environmentId
When we are looking at our specific environment in the GUI, the ID can be found in the address bar right after /lcm/lcops/environments
:
Finding the product ID
The product ID is also needed for the certificate replacement request. After running the above code block that creates the $Environments
variable, we can see a list of product IDs using the code below. It again filters the list and selects all appliable products in our specific environment:
# we also need to know the product ID. We can get a list of product IDs for the above environment using
# the example below. In this case we only have Ops for Logs, aka vrli
# ($Environments |?{$_.environmentName -eq $environmentName}).products.id
# vrli
There isn’t a clear way that I found to easily see this product ID from the GUI. However, if you are looking at a specific product, and select … > Export Configuration > Simple, the resulting file name should contain the product ID (example: h308-logs-vrli.json
).
To make this more like a multiple-choice question, the values that I currently have across all products in my lab are listed below:
- vidm
- vra
- vrli
- vrni
- vrops
- vssc
Validating the certificate
In the section below, we are using POST
to start the pre-validate
API to make sure our certificate will work. This API will only return the request ID of the task that is created. We can view progress of the request in the GUI, using something like https://cm-lifecycle-02.lab.enterpriseadmins.org/lcm/lcops/requests/acd529f9-e8af-4c61-9d6d-14ee15730c9d
, where the value of $pevalidateRequest
is the GUID at the end of our URL. However, in our code block we also wait 30 seconds, then GET
the status of our request from the API. We need this to return COMPLETED
prior to moving on to the next step. This sample code block does not have error checking/handling as it is primarily an example of calling the APIs.
# Now that we know all the relevant IDs, we can verify our new cert will work.
$Splat = @{
"URI" = "https://$lcmHost/lcm/lcops/api/v2/environments/$environmentId/products/vrli/certificates/$lockerCertId/pre-validate"
"Headers" = @{
'Accept' = "*/*"
'Content-Type' = "application/json"
"Authorization" = $authorization
}
"Method" = "POST"
}
$prevalidateRequest = (Invoke-RestMethod @Splat).requestId
# lets confirm that our validation completed.
# we may need to wait/recheck here
Start-Sleep -Seconds 30
# Lets ask the requests API if our task is complete.
$Splat = @{
"URI" = "https://$lcmHost/lcm/request/api/v2/requests/$prevalidateRequest"
"Headers" = @{
'Accept' = "*/*"
'Content-Type' = "application/json"
"Authorization" = $authorization
}
"Method" = "GET"
}
(Invoke-RestMethod @Splat).state # we want this to return 'COMPLETED'. If it didn't we should recheck/fail/not continue.
Replacing the certificate
Assuming our pre-validate
request above completed, we can move on to the certificate replacement. We do that with a PUT
method to our certificate and provide the ID of the certificate in the locker. The PUT
only returns the request ID of our task.
# Assuming the above completed, lets keep moving and actually replace the cert.
$Splat = @{
"URI" = "https://$lcmHost/lcm/lcops/api/v2/environments/$environmentId/products/vrli/certificates/$lockerCertId"
"Headers" = @{
'Accept' = "*/*"
'Content-Type' = "application/json"
"Authorization" = $authorization
}
"Method" = "PUT"
}
$replacementRequest = (Invoke-RestMethod @Splat).requestId
Checking request status
As mentioned above, in the validating the certificate section, we can query the certificate status from the API as well. This is the same code block as used in the earlier section, only changing the value of the variable at the end of our request URI.
# Once we start the replacement we should wait a bit of time and then see if it is complete
Start-Sleep -Seconds 30
$Splat = @{
"URI" = "https://$lcmHost/lcm/request/api/v2/requests/$replacementRequest"
"Headers" = @{
'Accept' = "*/*"
'Content-Type' = "application/json"
"Authorization" = $authorization
}
"Method" = "GET"
}
(Invoke-RestMethod @Splat).state # we want this to return 'COMPLETED'. If it returns 'INPROGRESS' we may want to wait/recheck until 'COMPLETED'.
As mentioned before, we can view the status of our request in the GUI as well. The URL would be https://cm-lifecycle-02.lab.enterpriseadmins.org/lcm/lcops/requests/acd529f9-e8af-4c61-9d6d-14ee15730c9d
, where the value of $replacementRequest
is the GUID at the end of our URL. Alternatively, we could look in the requests tab for the request name of VRLI in Environment h308-logs - Replace Certificate
.
Follow up tasks
After replacing a certificate, it is always a good idea to verify that the new certificate is trusted in various other products. For example, if you are using CFAPI to forward logs to this Aria Operations for Logs instance, you should check the source systems to make sure they trust this new certificate. In addition, Aria Operations and Aria Operations for Logs can be integrated. From the Aria Operations integration, check and confirm that Aria Operations for Logs is trusted after completing this change. This is not specific to the API, just a reminder to ensure that new certificates are trusted, whether or not they are replaced in the GUI or using the API.
Conclusion
In this post, we’ve explored how to automate the replacement of an SSL certificate in Aria Operations for Logs using the Aria Suite Lifecycle API. By leveraging PowerShell and the API’s various endpoints, we can streamline the process of managing certificates across Aria Suite environments, ensuring better security and consistency.
Remember, while the steps outlined here focus on certificate replacement, this workflow can also be adapted for other automation tasks within Aria Suite Lifecycle. As with any automation effort, it’s important to test thoroughly in a controlled environment and validate that all systems are properly configured and trust the updated certificates.
Whether you’re managing a single Aria Operations for Logs instance or multiple clusters, automating tasks like certificate replacement can significantly reduce manual effort and minimize downtime. Please continue to explore further API capabilities to enhance your operational efficiency and security posture!