Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable better certificate importing for Key Vault #2754

Merged
merged 1 commit into from Apr 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -91,6 +91,32 @@
--image debian --secrets "$vm_secrets"
"""

helps['keyvault certificate import'] = """
type: command
long-summary: >
Import a certificate into Key Vault. Certificates can also be used as a secrets in provisioned virtual machines.
examples:
- name: Create a service principal with a certificate, add the certificate to Key Vault and provision a VM with that certificate.
text: >
az group create -g my-group -l westus \n

service_principal=$(az ad sp create-for-rbac --create-cert) \n

cert_file=$(echo $service_principal | jq .fileWithCertAndPrivateKey -r) \n

az keyvault create -g my-group -n vaultname \n

az keyvault certificate import --vault-name vaultname -n cert_file \n

secrets=$(az keyvault secret list-versions --vault-name vaultname \\
-n cert1 --query "[?attributes.enabled].id" -o tsv)

vm_secrets=$(az vm format-secret -s "$secrets") \n

az vm create -g group-name -n vm-name --admin-username deploy \\
--image debian --secrets "$vm_secrets"
"""

helps['keyvault certificate pending'] = """
type: group
short-summary: Manage pending certificate creation operations.
Expand Down
Expand Up @@ -24,7 +24,7 @@
from azure.keyvault.generated.models import \
(KeyAttributes, SecretAttributes, CertificateAttributes)
from azure.cli.command_modules.keyvault._validators import \
(datetime_type, base64_encoded_certificate_type,
(datetime_type, certificate_type,
get_attribute_validator,
vault_base_url_type, validate_key_import_source,
validate_key_type, validate_key_ops, validate_policy_permissions,
Expand Down Expand Up @@ -155,15 +155,16 @@ def register_attributes_argument(scope, name, attr_class, create=False):

register_cli_argument('keyvault certificate', 'certificate_version', options_list=('--version', '-v'), help='The certificate version. If omitted, uses the latest version.', default='', required=False, completer=get_keyvault_version_completion_list('certificate'))
register_attributes_argument('keyvault certificate create', 'certificate', CertificateAttributes, True)
register_attributes_argument('keyvault certificate import', 'certificate', CertificateAttributes, True)
register_attributes_argument('keyvault certificate set-attributes', 'certificate', CertificateAttributes)
register_cli_argument('keyvault certificate set-attributes', 'expires', ignore_type)
register_cli_argument('keyvault certificate set-attributes', 'not_before', ignore_type)

for item in ['create', 'set-attributes', 'import']:
register_cli_argument('keyvault certificate {}'.format(item), 'certificate_policy', options_list=('--policy', '-p'), help='JSON encoded policy defintion. Use @{file} to load from a file.', type=get_json_object)

register_cli_argument('keyvault certificate import', 'base64_encoded_certificate', options_list=('--file', '-f'), completer=FilesCompleter(), help='PKCS12 file or PEM file containing the certificate and private key.', type=base64_encoded_certificate_type)
register_cli_argument('keyvault certificate import', 'certificate_data', options_list=('--file', '-f'), completer=FilesCompleter(), help='PKCS12 file or PEM file containing the certificate and private key.', type=certificate_type)
register_cli_argument('keyvault certificate import', 'password', help="If the private key in certificate is encrypted, the password used for encryption.")
register_extra_cli_argument('keyvault certificate import', 'disabled', help='Import the certificate in disabled state.', **three_state_flag())

register_cli_argument('keyvault certificate download', 'file_path', options_list=('--file', '-f'), type=file_type, completer=FilesCompleter(), help='File to receive the binary certificate contents.')
register_cli_argument('keyvault certificate download', 'encoding', options_list=('--encoding', '-e'), help='How to store base64 certificate contents in file.', **enum_choice_list(certificate_file_encoding_values))
Expand Down
Expand Up @@ -210,15 +210,11 @@ def _load_certificate_as_bytes(file_name):
# ARGUMENT TYPES


def base64_encoded_certificate_type(string):
def certificate_type(string):
""" Loads file and outputs contents as base64 encoded string. """
with open(string, 'rb') as f:
import os
with open(os.path.expanduser(string), 'rb') as f:
cert_data = f.read()
try:
# for PEM files (including automatic endline conversion for Windows)
cert_data = cert_data.decode('utf-8').replace('\r\n', '\n')
except UnicodeDecodeError:
cert_data = binascii.b2a_base64(cert_data).decode('utf-8')
return cert_data


Expand Down
Expand Up @@ -28,6 +28,7 @@
cli_command(__name__, 'keyvault delete-policy', custom_path.format('delete_policy'), factory)



cli_generic_update_command(__name__,
'keyvault update',
mgmt_path.format('VaultsOperations.get'),
Expand Down Expand Up @@ -61,7 +62,7 @@
cli_keyvault_data_plane_command('keyvault certificate show', base_client_path.format('KeyVaultClient.get_certificate'))
cli_keyvault_data_plane_command('keyvault certificate delete', convenience_path.format('KeyVaultClient.delete_certificate'))
cli_keyvault_data_plane_command('keyvault certificate set-attributes', base_client_path.format('KeyVaultClient.update_certificate'))
cli_keyvault_data_plane_command('keyvault certificate import', convenience_path.format('KeyVaultClient.import_certificate'))
cli_keyvault_data_plane_command('keyvault certificate import', custom_path.format('import_certificate'))
cli_keyvault_data_plane_command('keyvault certificate download', custom_path.format('download_certificate'))

cli_keyvault_data_plane_command('keyvault key list', convenience_path.format('KeyVaultClient.get_keys'))
Expand Down
Expand Up @@ -542,6 +542,75 @@ def create_certificate(client, vault_base_url, certificate_name, certificate_pol
create_certificate.__doc__ = KeyVaultClient.create_certificate.__doc__


def _asn1_to_iso8601(asn1_date):
import dateutil
return dateutil.parser.parse(asn1_date.decode('utf-8'))


def import_certificate(client, vault_base_url, certificate_name, certificate_data,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that this logic should be moved to azure.keyvault.key_vault_client.py (the convenience wrapper) so that it benefits all Python developers and not just CLI users.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but I'm hesitant to take that up right now.

disabled=False, password=None, certificate_policy=None, tags=None):
import binascii

x509 = None
content_type = None
try:
x509 = crypto.load_certificate(crypto.FILETYPE_PEM, certificate_data)
# if we get here, we know it was a PEM file
content_type = 'application/x-pem-file'
try:
# for PEM files (including automatic endline conversion for Windows)
certificate_data = certificate_data.decode('utf-8').replace('\r\n', '\n')
except UnicodeDecodeError:
certificate_data = binascii.b2a_base64(certificate_data).decode('utf-8')
except (ValueError, crypto.Error):
pass

if not x509:
try:
if password:
x509 = crypto.load_pkcs12(certificate_data, password).get_certificate()
else:
x509 = crypto.load_pkcs12(certificate_data).get_certificate()
content_type = 'application/x-pkcs12'
certificate_data = binascii.b2a_base64(certificate_data).decode('utf-8')
except crypto.Error:
raise CLIError('We could not parse the provided certificate as .pem or .pfx. Please verify the certificate with OpenSSL.') # pylint: disable=line-too-long

not_before, not_after = None, None

if x509.get_notBefore():
not_before = _asn1_to_iso8601(x509.get_notBefore())

if x509.get_notAfter():
not_after = _asn1_to_iso8601(x509.get_notAfter())

cert_attrs = CertificateAttributes(
enabled=not disabled,
not_before=not_before,
expires=not_after)

if certificate_policy:
secret_props = certificate_policy.get('secret_properties')
if secret_props:
secret_props['content_type'] = content_type
elif certificate_policy and not secret_props:
certificate_policy['secret_properties'] = SecretProperties(content_type=content_type)
else:
certificate_policy = CertificatePolicy(
secret_properties=SecretProperties(content_type=content_type))

logger.info("Starting 'keyvault certificate import'")
result = client.import_certificate(vault_base_url=vault_base_url,
certificate_name=certificate_name,
base64_encoded_certificate=certificate_data,
certificate_attributes=cert_attrs,
certificate_policy=certificate_policy,
tags=tags,
password=password)
logger.info("Finished 'keyvault certificate import'")
return result


def download_certificate(client, vault_base_url, certificate_name, file_path,
encoding='binary', certificate_version=''):
""" Download a certificate from a KeyVault. """
Expand Down