Files
vmware-tools/vCert-6.1.1-20260401/operation/generate_report.py
T
2026-06-23 11:27:54 +08:00

433 lines
18 KiB
Python

# Copyright (c) 2024-2025 Broadcom. All Rights Reserved.
# The term "Broadcom" refers to Broadcom Inc.
# and/or its subsidiaries.
import datetime
import os
import pathlib
import re
from OpenSSL.crypto import load_crl, dump_crl, FILETYPE_PEM, FILETYPE_TEXT
from lib import certificate_utils as certutil
from lib import vcdb
from lib import vmdir
from lib import vecs
from lib.certificate_utils import get_x509_certificate
from lib.constants import (
VCERT_DESC, VMCAM_CERT_FILE_PATH, RBD_CERT_FILE_PATH, RHTTPPROXY_CONFIG_FILE_PATH,
VMCA_CERT_FILE_PATH, REPORT_FILE_PATH
)
from lib.environment import Environment
from lib.exceptions import CommandExecutionError
from lib.console import init_env_colormap, print_text_warning, print_text_error
from lib.host_utils import get_file_contents
from lib.services import is_service_running
from lib.text_utils import TextFilter
from operation.common import get_vcenter_extensions
def write_report(file, text, end='\n', sep=' '):
print(text, file=file, end=end, sep=sep)
print(text, end=end, sep=sep)
def generate_header():
if not is_service_running('vmware-vpostgres'):
print_text_error(
'The vPostgres service is stopped!\n'
'Please ensure this service is running before generating a certificate report.\n'
'Hint: Check the number of CRL entries in VECS')
raise CommandExecutionError('vPostgres is not running')
report_path = pathlib.Path(REPORT_FILE_PATH)
if not os.path.exists(report_path.parent):
# this may necessary for remote execution on a restricted environment
report_file_path = str(pathlib.Path('/tmp').joinpath(report_path.relative_to('/')))
else:
report_file_path = REPORT_FILE_PATH
env = Environment.get_environment()
try:
file = open(report_file_path, 'w')
set_report_file(file)
except IOError as e:
error_message = "Failed to create report file {}: {}".format(REPORT_FILE_PATH, e)
print_text_error(error_message)
raise CommandExecutionError(error_message)
env.set_value('COLORS_DISABLED', True)
init_env_colormap()
write_report(file, '=' * 130)
write_report(file, 'SSL Certificate Report')
write_report(file, VCERT_DESC)
write_report(file, "Host: {}".format(env.get_value('HOSTNAME')))
write_report(file, "Date: {}".format(datetime.datetime.now().ctime()))
write_report(file, "Version: {}".format(env.get_value('VC_VERSION')))
write_report(file, "Build: {}".format(env.get_value('VC_BUILD')))
write_report(file, "Machine ID: {}".format(env.get_value('MACHINE_ID')))
write_report(file, "PNID: {}".format(env.get_value('PNID')))
write_report(file, "Certificate Management Mode: {}".format(vcdb.get_certificate_management_mode()))
write_report(file, '=' * 130)
def report_certificate_details(file, pem_cert, options=None):
x509_cert = certutil.get_x509_certificate(pem_cert)
sign_alg = x509_cert.get_signature_algorithm().decode('utf-8')
subject_kid = certutil.get_subject_keyid(x509_cert)
extensions = certutil.get_certificate_extensions(x509_cert)
report_certificate_basic(file, x509_cert)
write_report(file, " Signature Algorithm: {}".format(sign_alg))
write_report(file, " Subject Key Identifier: {}".format(subject_kid))
write_report(file, ' Authority Key Identifier:')
auth_kid_list = get_authority_keyid_list(x509_cert)
if auth_kid_list:
for auth_key in auth_kid_list:
write_report(file, " |_{}".format(auth_key))
else:
akid = certutil.get_authority_keyid(x509_cert)
if akid:
write_report(file, " |_keyid:{}".format(akid))
write_report(file, ' Key Usage:')
key_usage = extensions.get('keyUsage')
if key_usage:
for ku in key_usage.split(', '):
write_report(file, " |_{}".format(ku))
write_report(file, ' Extended Key Usage:')
ext_key_usage = extensions.get('extendedKeyUsage')
if ext_key_usage:
for ku in ext_key_usage.splitlines():
write_report(file, " |_{}".format(ku))
write_report(file, ' Subject Alternative Name entries:')
san = extensions.get('subjectAltName')
if san:
for entry in san.split(', '):
if re.match('^[A-Za-z]+: ', entry):
entry = re.sub('^([A-Za-z]+:) ', '\\1', entry)
write_report(file, " |_{}".format(entry))
write_report(file, ' Other Information:')
write_report(file, " |_Is a Certificate Authority: {}"
.format('Yes' if certutil.is_ca_certificate(x509_cert) else 'No'))
auth_keyid_alt = certutil.get_authority_keyid(x509_cert, allow_alternate=True)
subject_kids_in_vecs = get_subject_keyids_in_vecs()
subject_kids_in_vmdir = get_subject_keyids_in_vmdir()
found_in_vecs = auth_keyid_alt in subject_kids_in_vecs
found_in_vmdir = auth_keyid_alt in subject_kids_in_vmdir
if not found_in_vmdir and not found_in_vecs:
status = "No{}".format(' (Self-Signed)' if certutil.is_self_signed_certificate(x509_cert)
else '')
elif found_in_vmdir and not found_in_vecs:
status = 'Yes, in VMware Directory'
elif not found_in_vmdir and found_in_vecs:
status = 'Yes, in VECS'
else:
status = 'Yes, in both'
write_report(file, " |_Issuing CA in VMware Directory/VECS: {}".format(status))
if options:
if 'checkCurrentMachineSSLUsage' in options:
report_current_machine_ssl_usage(file)
if 'checkCurrentExtensionThumbprints' in options:
report_current_extension_thumbprints(file)
def report_certificate_basic(file, x509_cert):
subject_dn, issuer_dn = certutil.get_subject_and_issuer_dn(x509_cert)
output_format = '%b %e %H:%M:%S %Y GMT'
start_date = certutil.get_certificate_start_date(x509_cert).strftime(output_format)
end_date = certutil.get_certificate_end_date(x509_cert).strftime(output_format)
fingerprint = certutil.get_certificate_fingerprint(x509_cert)
write_report(file, " Issuer: {}".format(issuer_dn))
write_report(file, " Subject: {}".format(subject_dn))
write_report(file, " Not Before: {}".format(start_date))
write_report(file, " Not After : {}".format(end_date))
write_report(file, " SHA1 Fingerprint: {}".format(fingerprint))
def report_current_machine_ssl_usage(file):
machine_cert = certutil.get_certificate_from_host('localhost', 443)
machine_cert_fingerprint = certutil.get_certificate_fingerprint(get_x509_certificate(machine_cert)) \
if machine_cert else ''
write_report(file, " |_Current certificate used by the reverse proxy: {}"
.format(machine_cert_fingerprint))
vpxd_cert = certutil.get_certificate_from_host('localhost', 8089)
vpxd_cert_fingerprint = certutil.get_certificate_fingerprint(get_x509_certificate(vpxd_cert)) \
if vpxd_cert else ''
write_report(file, " |_Current certificate used by vCenter (vpxd) : {}"
.format(vpxd_cert_fingerprint))
def report_current_extension_thumbprints(file):
vcenter_extensions = get_vcenter_extensions()
max_len = len(max(vcenter_extensions, key=len))
write_report(file, ' |_Thumbprints in VCDB for extensions that should use the vpxd-extension certificate')
for extension in vcenter_extensions:
thumbprint = vcdb.get_extension_thumbprint(extension)
write_report(file, " |_{:<{}}: {}".format(extension, max_len, thumbprint))
def report_crl_details(file, pem_text):
crl = load_crl(FILETYPE_PEM, pem_text)
output = dump_crl(FILETYPE_TEXT, crl).decode('utf-8')
issuer = TextFilter(output).contain('Issuer:').cut('Issuer:', [1]).get_text().strip()
if issuer.startswith('/'):
issuer = re.sub('/([A-Z]+=)', ', \\1', issuer[1:])
last_update = TextFilter(output).contain('Last Update:').cut('Update:', [1]).get_text().strip()
next_update = TextFilter(output).contain('Next Update:').cut('Update:', [1]).get_text().strip()
sign_alg = TextFilter(output).contain('Signature Algorithm:').head(1).cut('Algorithm:', [1]).get_text().strip()
write_report(file, " Issuer: {}".format(issuer))
write_report(file, " Last Update: {}".format(last_update))
write_report(file, " Next Update: {}".format(next_update))
write_report(file, " Signature Algorithm: {}".format(sign_alg))
def get_authority_keyid_list(x509_cert):
"""
Get Authority KeyId list from X509 certificate
:param x509_cert: X509Certificate object
:return: list of as keyId:, or DirName:.../serial:
"""
extensions = certutil.get_certificate_extensions(x509_cert)
akid = extensions.get('authorityKeyIdentifier')
if akid:
return TextFilter(akid).match('^keyid:.*|^DirName:.*|^serial:.*').get_lines()
else:
return []
def get_subject_keyids_in_vecs(pem_certs=None):
env = Environment.get_environment()
subject_kids = env.get_value('SUBJECT_KEYIDS_IN_VECS')
if subject_kids is not None:
return subject_kids
if pem_certs is None:
pem_certs, _ = vecs.get_all_ca_certificates()
subject_kids = []
for pem_cert in pem_certs:
x509_cert = get_x509_certificate(pem_cert)
subject_kids.append(certutil.get_subject_keyid(x509_cert, allow_alternate=True))
env.set_value('SUBJECT_KEYIDS_IN_VECS', subject_kids)
return subject_kids
def get_subject_keyids_in_vmdir(pem_certs=None):
env = Environment.get_environment()
subject_kids = env.get_value('SUBJECT_KEYIDS_IN_VMDIR')
if subject_kids is not None:
return subject_kids
if pem_certs is None:
pem_certs, _ = vmdir.get_all_ca_certificates()
subject_kids = []
for pem_cert in pem_certs:
x509_cert = get_x509_certificate(pem_cert)
subject_kids.append(certutil.get_subject_keyid(x509_cert, allow_alternate=True))
env.set_value('SUBJECT_KEYIDS_IN_VMDIR', subject_kids)
return subject_kids
def generate_report_ca_certs_in_vmdir():
file = get_report_file()
write_report(file, 'VMware Directory Certificates')
write_report(file, ' CA Certificates')
pem_certs, aliases = vmdir.get_all_ca_certificates()
get_subject_keyids_in_vmdir(pem_certs)
get_subject_keyids_in_vecs()
for pem_cert, alias in zip(pem_certs, aliases):
write_report(file, " CN(id): {}".format(alias))
report_certificate_details(file, pem_cert)
def generate_report_ca_certs_in_vecs():
file = get_report_file()
write_report(file, 'VECS Certificates')
get_subject_keyids_in_vecs()
get_subject_keyids_in_vmdir()
for store in vecs.get_store_list():
if store == 'APPLMGMT_PASSWORD':
continue
write_report(file, " Store: {}".format(store))
pem_certs, aliases = vecs.get_all_certificates(store)
for pem_text, alias in zip(pem_certs, aliases):
write_report(file, " Alias: {}".format(alias))
if store == 'TRUSTED_ROOT_CRLS':
report_crl_details(file, pem_text)
else:
if store == 'MACHINE_SSL_CERT' and alias == '__MACHINE_CERT':
options = ['checkCurrentMachineSSLUsage']
elif store == 'vpxd-extension' and alias == 'vpxd-extension':
options = ['checkCurrentExtensionThumbprints']
else:
options = None
report_certificate_details(file, pem_text, options)
def generate_report_solution_user_certs_in_vmdir():
file = get_report_file()
write_report(file, ' Service Principal (Solution User) Certificates')
solution_users = vmdir.get_solution_users()
machine_id = Environment.get_environment().get_value('MACHINE_ID')
for solution_user in solution_users:
write_report(file, " Service Principal: {}-{}".format(solution_user, machine_id))
pem_cert = vmdir.get_solution_user_certificate(solution_user)
report_certificate_details(file, pem_cert)
def generate_report_sts_tenant_certs():
file = get_report_file()
write_report(file, ' Single Sign-On Secure Token Service Certificates')
tenant_certs = vmdir.get_sts_tenant_certificates()
for tenant in tenant_certs.keys():
certs = tenant_certs[tenant]
for cert in certs:
x509_cert = get_x509_certificate(cert)
cert_type = 'CA Certificate' if certutil.is_ca_certificate(x509_cert) else 'Signing Certificate'
write_report(file, " {} {}".format(tenant, cert_type))
report_certificate_details(file, cert)
def generate_report_certs_in_filesystem():
file = get_report_file()
write_report(file, 'Filesystem Certificates')
write_report(file, ' VMCA Certificate')
write_report(file, " Certificate: {}".format(VMCAM_CERT_FILE_PATH))
pem_cert = get_file_contents(VMCA_CERT_FILE_PATH)
report_certificate_details(file, pem_cert)
write_report(file, ' Authentication Proxy Certificate')
write_report(file, " Certificate: {}".format(VMCAM_CERT_FILE_PATH))
pem_cert = get_file_contents(VMCAM_CERT_FILE_PATH)
report_certificate_details(file, pem_cert)
write_report(file, ' Auto Deploy CA Certificate')
write_report(file, " Certificate: {}".format(RBD_CERT_FILE_PATH))
pem_cert = get_file_contents(RBD_CERT_FILE_PATH)
report_certificate_details(file, pem_cert)
config = get_file_contents(RHTTPPROXY_CONFIG_FILE_PATH)
cert_file = TextFilter(config).match('.*<clientCAListFile>.*</clientCAListFile>.*')\
.set_options(invert_match=True).contain('<!--').get_text().strip()
if cert_file:
write_report(file, ' Smart Card Whitelist Certificates')
cert_file = TextFilter(config).cut('clientCAListFile', [1])[1:-2].strip()
file_content = get_file_contents(cert_file)
certs = TextFilter(file_content).match_block(
'-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----',
True).get_lines()
for index, cert in enumerate(certs, 1):
write_report(file, " Certificate {}".format(index))
report_certificate_details(file, cert)
def generate_report_ca_certs_sca():
file = get_report_file()
certs = vmdir.get_smart_card_issuing_ca_certs()
if certs:
write_report(file, ' Smart Card Issuing CA Certificates')
for index, cert in enumerate(certs, 1):
write_report(file, " Smart Card Issuing CA {}".format(index))
report_certificate_details(file, certutil.build_pem_certificate(cert))
def generate_report_ca_certs_ad_ldaps():
file = get_report_file()
identity_sources = vmdir.get_identity_sources()
certs = []
for source in identity_sources:
if source['type'] == 'AD over LDAP':
certs.extend(source['certificates'])
if certs:
write_report(file, ' AD Over LDAPS Domain Controller Certificates')
for index, cert in enumerate(certs, 1):
write_report(file, " Certificate {}".format(index))
report_certificate_details(file, certutil.build_pem_certificate(cert))
def generate_report_ssl_trust_anchors():
file = get_report_file()
write_report(file, 'Lookup Service Registration Trust Anchors')
endpoints = vmdir.get_all_lookup_service_endpoints()
trust_anchors = get_ssl_trust_anchors(endpoints)
for index, cert in enumerate(trust_anchors, 1):
write_report(file, " Endpoint Certificate {}".format(index))
service_ids, uris = get_service_ids_and_uri_by_certificate(endpoints, cert)
report_trust_anchor_details(file, certutil.build_pem_certificate(cert), service_ids, uris)
def get_ssl_trust_anchors(endpoints):
trust_anchors = []
for endpoint in endpoints:
endpoint['certificates'] = []
for cert in sum([endpoint['vmwLKUPEndpointSslTrust'], endpoint['vmwLKUPSslTrustAnchor']], []):
pem_cert = certutil.build_pem_certificate(cert)
endpoint['certificates'].append(pem_cert)
if pem_cert not in trust_anchors:
trust_anchors.append(pem_cert)
return trust_anchors
def get_service_ids_and_uri_by_certificate(endpoints, cert):
service_ids = []
uris = []
for endpoint in endpoints:
if cert in endpoint['certificates']:
service_id = endpoint['dn'].split(',')[1].replace('cn=', '')
if service_id not in service_ids:
service_ids.append(service_id)
uri = endpoint['vmwLKUPURI']
if uri not in uris:
uris.append(endpoint['vmwLKUPURI'])
return sorted(service_ids), sorted(uris)
def report_trust_anchor_details(file, pem_cert, service_ids, uris):
x509_cert = get_x509_certificate(pem_cert)
report_certificate_basic(file, x509_cert)
write_report(file, ' Service IDs:')
for service_id in service_ids:
service_type = vmdir.get_endpoint_service_type(service_id)
write_report(file, " |_{} ({})".format(service_id, service_type))
write_report(file, ' Endpoints:')
for uri in uris:
write_report(file, " |_{}".format(uri))
def set_report_file(file):
env = Environment.get_environment()
env.set_value('REPORT_FILE_DESC', file)
def get_report_file():
env = Environment.get_environment()
return env.get_value('REPORT_FILE_DESC')
def finalize_report():
file = get_report_file()
if file is not None:
file.close()
set_report_file(None)
env = Environment.get_environment()
env.set_value('COLORS_DISABLED', False)
init_env_colormap()
print()
print_text_warning("Certificate report is available at {}".format(REPORT_FILE_PATH))