问题描述
在Azure Key Vault中,我们可以从Azure门户中下载证书PEM文件到本地。 可以通过OpenSSL把PFX文件转换到PEM文件。然后用TXT方式查看内容,操作步骤如下图:
OpenSSL转换命令为:
openssl pkcs12 -in "C:\Users\Downloads\mykeyvault01-cscert01-20220316.pfx" -nokeys -out "C:\tool\xd12.pem"
当然,Azure也提供了通过PowerShell或CLI命令来下载PEM文件,操作为:
**az keyvault certificate download** --vault-name vault -n cert-name -f cert.pem
(Source : https://docs.microsoft.com/en-us/cli/azure/keyvault/certificate?view=azure-cli-latest#az-keyvault-certificate-download)
那么,如何可以通过Python SDK的代码获取到PEM文件呢?
问题解答
查看 Python SDK Certificate中公布出来的接口,并没有 Export, Download 的方法。** Python Azure Key Vault SDK 中并没有可以直接下载PEM文件的方法。**
Azure SDK For Python KeyVault -- CertificateClient Class : https://docs.microsoft.com/en-us/python/api/azure-keyvault-certificates/azure.keyvault.certificates.aio.certificateclient?view=azure-python#methods
所以使用原始的SDK Methods方法不可行。
寻找解决方案
通过对CLI (az keyvault certificate download)指令的研究,发现CLI使用的是python代码执行的Get Certificates 操作,实质上是调用的Key Vault的**REST API: **
**Get Certificate: **https://docs.microsoft.com/en-us/rest/api/keyvault/certificates/get-certificate/get-certificate#getcertificate
DEBUG az 指令:
az keyvault certificate download --vault-name mykeyvault01 -n cscert01 -f cert2.pem --debug
C:\Users>az keyvault certificate download --vault-name mykeyvault01 -n cscert01 -f cert2.pem --debug
cli.knack.cli: Command arguments: ['keyvault', 'certificate', 'download', '--vault-name', 'mykeyvault01', '-n', 'cscert01', '-f', 'cert2.pem', '--debug']
cli.knack.cli: __init__ debug log:
Enable color in terminal.
Init colorama.
cli.knack.cli: Event: Cli.PreExecute []
cli.knack.cli: Event: CommandParser.OnGlobalArgumentsCreate [<function CLILogging.on_global_arguments at 0x033452F8>, <function OutputProducer.on_global_arguments at 0x034F0190>, <function CLIQuery.on_global_arguments at 0x03607D60>]
cli.knack.cli: Event: CommandInvoker.OnPreCommandTableCreate []
cli.azure.cli.core: Modules found from index for 'keyvault': ['azure.cli.command_modules.keyvault']
cli.azure.cli.core: Loading command modules:
cli.azure.cli.core: Name Load Time Groups Commands
cli.azure.cli.core: keyvault 0.038 19 117 cli.azure.cli.core: Total (1) 0.038 19 117 cli.azure.cli.core: These extensions are not installed and will be skipped: ['azext_ai_examples', 'azext_next']
cli.azure.cli.core: Loading extensions:
cli.azure.cli.core: Name Load Time Groups Commands Directory
cli.azure.cli.core: Total (0) 0.000 0 0 cli.azure.cli.core: Loaded 19 groups, 117 commands.
cli.azure.cli.core: Found a match in the command table.
cli.azure.cli.core: Raw command : keyvault certificate download
cli.azure.cli.core: Command table: keyvault certificate download
cli.knack.cli: Event: CommandInvoker.OnPreCommandTableTruncate [<function AzCliLogging.init_command_file_logging at 0x039701D8>]
cli.azure.cli.core.azlogging: metadata file logging enabled - writing logs to 'C:\Users\.azure\commands\2022-03-16.14-46-58.keyvault_certificate_download.21860.log'.
az_command_data_logger: command args: keyvault certificate download --vault-name {} -n {} -f {} --debug
cli.knack.cli: Event: CommandInvoker.OnPreArgumentLoad [<function register_global_subscription_argument.<locals>.add_subscription_parameter at 0x039B6268>, <function register_global_query_examples_argument.<locals>.register_query_examples at 0x039D8928>]
cli.knack.cli: Event: CommandInvoker.OnPostArgumentLoad []
cli.knack.cli: Event: CommandInvoker.OnPostCommandTableCreate [<function register_ids_argument.<locals>.add_ids_arguments at 0x039D8970>, <function register_cache_arguments.<locals>.add_cache_arguments at 0x039D8A00>]
cli.knack.cli: Event: CommandInvoker.OnCommandTableLoaded []
cli.knack.cli: Event: CommandInvoker.OnPreParseArgs []
cli.knack.cli: Event: CommandInvoker.OnPostParseArgs [<function OutputProducer.handle_output_argument at 0x034F01D8>, <function CLIQuery.handle_query_parameter at 0x03607DA8>, <function register_global_query_examples_argument.<locals>.handle_example_parameter at 0x039D88E0>, <function register_ids_argument.<locals>.parse_ids_arguments at 0x039D89B8>]
msrest.universal_http.requests: Configuring retry: max_retries=4, backoff_factor=0.8, max_backoff=90 msrest.service_client: Accept header absent and forced to application/json
msrest.universal_http: Configuring redirects: allow=True, max=30 msrest.universal_http: Configuring request: timeout=100, verify=True, cert=None
msrest.universal_http: Configuring proxies: ''
msrest.universal_http: Evaluate proxies against ENV settings: True urllib3.connectionpool: Starting new HTTPS connection (1): mykeyvault01.vault.azure.cn:443 urllib3.connectionpool: https://mykeyvault01.vault.azure.cn:443 "GET /certificates/cscert01/?api-version=7.0 HTTP/1.1" 401 97 cli.azure.cli.core._profile: Profile.get_raw_token invoked with resource='https://vault.azure.cn', subscription=None, tenant=None
cli.azure.cli.core.util: attempting to read file C:\Users\.azure\accessTokens.json as utf-8-sig
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - Authority:Performing instance discovery: ...
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - Authority:Performing static instance discovery
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - Authority:Authority validated via static instance discovery
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - TokenRequest:Getting token from cache with refresh if necessary.
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - CacheDriver:finding with query keys: {'_clientId': '...', 'userId': '...'}
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - CacheDriver:Looking for potential cache entries: {'_clientId': '...', 'userId': '...'}
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - CacheDriver:Found 9 potential entries.
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - CacheDriver:Resource specific token found.
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - CacheDriver:Returning token from cache lookup, AccessTokenId: b'wR5KoJYE=', RefreshTokenId: b'5VNpWgDCg4ydvuf2PeWxGaph/r7KMelGLXVY+jLV89s='
urllib3.connectionpool: https://mykeyvault01.vault.azure.cn:443 "GET /certificates/cscert01/?api-version=7.0 HTTP/1.1" 200 2114 cli.knack.cli: Event: CommandInvoker.OnTransformResult [<function _resource_group_transform at 0x039A5C88>, <function _x509_from_base64_to_hex_transform at 0x039A5CD0>]
cli.knack.cli: Event: CommandInvoker.OnFilterResult []
cli.knack.cli: Event: Cli.SuccessfulExecute []
cli.knack.cli: Event: Cli.PostExecute [<function AzCliLogging.deinit_cmd_metadata_logging at 0x039702F8>]
az_command_data_logger: exit code: 0 cli.__main__: Command ran in 3.206 seconds (init: 0.394, invoke: 2.812)
telemetry.save: Save telemetry record of length 3005 in cache
telemetry.check: Negative: The C:\Users\.azure\telemetry.txt was modified at 2022-03-16 14:43:00.945931, which in less than 600.000000 s
如以上黄色高亮内容, download指令调用了 "GET /certificates/cscert01/?api-version=7.0 HTTP/1.1" 200 2114,且返回的HTTP Code为200 成功。所以当我们单独对get certificates接口请求时,在返回结果中,发现cer属性值就是证书PEM格式内容。通过Postman发送请求并验证结果:
那么,通过Python Azure SDK的 certificate_client.get_certificate方法,获取到certificate对象后,其中包含的cer值,是否也是PEM的内容呢?
我们通过下面的代码来进行验证:
from azure.identity import DefaultAzureCredential
from azure.keyvault.certificates import CertificateClient
import ssl
credential = DefaultAzureCredential()
certificate_client = CertificateClient(vault_url=https://yourkeyvaultname.vault.azure.cn/, credential=credential)
certificate = certificate_client.get_certificate("your certificate name") print(certificate.name) print(certificate.properties.version) print(certificate.policy.issuer_name) print(str(certificate.cer)) # Convert the certificate to PEM format
cert_PEM = ssl.DER_cert_to_PEM_cert(certificate.cer); print("Certificate in PEM format:"); print(cert_PEM);
是的,在certificate对象中,cer就是我们证书的内容,但是由于格式为DER,所以使用了SSL包中的 DER_cert_to_PEM_cert 方法完成转换,最终得到PEM文件内容。
在 python代码中获取到PEM内容后,剩下的部分就是把内容输出到 .pem 文件即可。
##接上一段代码
filename = 'mypem1.pem'
# Open the file in append mode and append the new content in file_object
with open(filename, 'a') as file_object:
file_object.write(cert_PEM) print("output the PEM file End!")
注意:在创建 certificate_client对象时,需要使用credential,而以上代码使用的是默认的DefaultAzureCredential(),在执行代码的本机中,已经配置了如下环境变量:
- AZURE_TENANT_ID
- AZURE_CLIENT_ID
- AZURE_CLIENT_SECRET
使用 ClientSecretCredential 认证方式后,代码修改如下:
import os
from azure.keyvault.certificates import CertificateClient
import ssl
from azure.identity import ClientSecretCredential
from msrestazure.azure_cloud import AZURE_CHINA_CLOUD
print('AZURE_TENANT_ID:' +os.environ["AZURE_TENANT_ID"])
print('AZURE_CLIENT_ID:' +os.environ["AZURE_CLIENT_ID"])
print('AZURE_CLIENT_SECRET:' +os.environ["AZURE_CLIENT_SECRET"])
credential = ClientSecretCredential (client_id=os.environ["AZURE_CLIENT_ID"],client_secret=os.environ["AZURE_CLIENT_SECRET"],tenant_id=os.environ["AZURE_TENANT_ID"],cloud_environment=AZURE_CHINA_CLOUD,china=True)
certificate_client = CertificateClient(vault_url="https://yourkeyvault.vault.azure.cn/", credential=credential)
certificate = certificate_client.get_certificate("your certificate name") print(certificate.name) print(certificate.properties.version) print(certificate.policy.issuer_name) print(str(certificate.cer)) # Convert the certificate to PEM format
cert_PEM = ssl.DER_cert_to_PEM_cert(certificate.cer); print("Certificate in PEM format:"); print(cert_PEM);
filename = 'mypem2.pem'
# Open the file in append mode and append the new content in file_object
with open(filename, 'a') as file_object:
file_object.write(cert_PEM) print("output the PEM file End!")
参考文档
Retrieve a Certificate:https://docs.microsoft.com/en-us/python/api/overview/azure/keyvault-certificates-readme?view=azure-python#retrieve-a-certificate
Der_cert_to_pem_cert Function Of Ssl Module In Python: https://pythontic.com/ssl/ssl-module/der_cert_to_pem_cert
A certificate bundle consists of a certificate (X509) plus its attributes: https://docs.microsoft.com/en-us/rest/api/keyvault/certificates/get-certificate/get-certificate#certificatebundle
当在复杂的环境中面临问题,格物之道需:浊而静之徐清,安以动之徐生。 云中,恰是如此!
标签: Azure Developer, Azure Key Vault, 下载证书PEM文件, az keyvault certificate download, certificate_client.get_certificate, ClientSecretCredential