Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/azure-cli-core/azure/cli/core/commands/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

def validate_tags(ns):
''' Extracts multiple space-separated tags in key[=value] format '''
if ns.tags is not None:
if isinstance(ns.tags, list):
tags_dict = {}
for item in ns.tags:
tags_dict.update(validate_tag(item))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ def get_token(server, resource, scope): # pylint: disable=unused-argument
except ClientRequestError as ex:
if 'Failed to establish a new connection' in str(ex.inner_exception):
raise CLIError('Max retries exceeded attempting to connect to vault. '
'Try flushing your DNS cache or try again later.')
'The vault may not exist or you may need to flush your DNS cache '
'and try again later.')
raise CLIError(ex)

command_module_map[name] = module_name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@

from azure.mgmt.keyvault.models.key_vault_management_client_enums import \
(SkuName, KeyPermissions, SecretPermissions, CertificatePermissions)
from azure.cli.core.commands.parameters import (
get_resource_name_completion_list, resource_group_name_type,
tags_type, ignore_type, enum_choice_list)
from azure.cli.core.commands import \
(register_cli_argument, register_extra_cli_argument, CliArgumentType)
import azure.cli.core.commands.arm # pylint: disable=unused-import
from azure.cli.core.commands.parameters import (
get_resource_name_completion_list, resource_group_name_type,
tags_type, ignore_type, enum_choice_list)
from azure.cli.core._profile import Profile
from azure.cli.core._util import get_json_object
from azure.cli.command_modules.keyvault.keyvaultclient import KeyVaultClient, KeyVaultAuthentication

from azure.cli.command_modules.keyvault.keyvaultclient.generated.models.key_vault_client_enums import \
(JsonWebKeyOperation)
Expand All @@ -26,17 +28,49 @@
validate_key_type, validate_key_ops, validate_policy_permissions,
validate_principal, validate_resource_group_name,
validate_x509_certificate_chain,
process_certificate_cancel_namespace)
process_certificate_cancel_namespace,
process_secret_set_namespace,
secret_text_encoding_values, secret_binary_encoding_values)

# COMPLETERS

def _get_token(server, resource, scope): # pylint: disable=unused-argument
return Profile().get_login_credentials(resource)[0]._token_retriever() # pylint: disable=protected-access

def get_keyvault_name_completion_list(resource_name):
def completer(prefix, action, parsed_args, **kwargs): # pylint: disable=unused-argument
client = KeyVaultClient(KeyVaultAuthentication(_get_token)) # pylint: disable=redefined-variable-type
func_name = 'get_{}s'.format(resource_name)
vault = parsed_args.vault_base_url
items = []
for y in list(getattr(client, func_name)(vault)):
id_val = getattr(y, 'id', None) or getattr(y, 'kid', None)
items.append(id_val.rsplit('/', 1)[1])
return items
return completer

def get_keyvault_version_completion_list(resource_name):

def completer(prefix, action, parsed_args, **kwargs): # pylint: disable=unused-argument
client = KeyVaultClient(KeyVaultAuthentication(_get_token)) # pylint: disable=redefined-variable-type
func_name = 'get_{}_versions'.format(resource_name)
vault = parsed_args.vault_base_url
name = getattr(parsed_args, '{}_name'.format(resource_name))
items = []
for y in list(getattr(client, func_name)(vault, name)):
id_val = getattr(y, 'id', None) or getattr(y, 'kid', None)
items.append(id_val.rsplit('/', 1)[1])
return items
return completer

# CUSTOM CHOICE LISTS

key_permission_values = ', '.join([x.value for x in KeyPermissions])
secret_permission_values = ', '.join([x.value for x in SecretPermissions])
certificate_permission_values = ', '.join([x.value for x in CertificatePermissions])
json_web_key_op_values = ', '.join([x.value for x in JsonWebKeyOperation])
secret_file_encoding_values = ['utf8', 'utf16le', 'ucs2', 'ascii']
secret_file_decoding_values = ['base64', 'hex']
certificate_file_encoding_values = ['base64']
secret_encoding_values = secret_text_encoding_values + secret_binary_encoding_values
certificate_file_encoding_values = ['binary', 'string']

# KEY ATTRIBUTE PARAMETER REGISTRATION

Expand Down Expand Up @@ -75,11 +109,13 @@ def register_attributes_argument(scope, name, attr_class, create=False):
register_cli_argument('keyvault set-policy', 'certificate_permissions', metavar='PERM', nargs='*', help='Space separated list. Possible values: {}'.format(certificate_permission_values), arg_group='Permission')

for item in ['key', 'secret', 'certificate']:
register_cli_argument('keyvault {}'.format(item), '{}_name'.format(item), options_list=('--name', '-n'), help='Name of the {}.'.format(item), id_part='child_name')
register_cli_argument('keyvault {}'.format(item), '{}_name'.format(item), options_list=('--name', '-n'), help='Name of the {}.'.format(item), id_part='child_name', completer=get_keyvault_name_completion_list(item))
register_cli_argument('keyvault {}'.format(item), 'vault_base_url', vault_name_type, type=vault_base_url_type, id_part=None)
# TODO: Fix once service side issue is fixed that there is no way to list pending certificates
register_cli_argument('keyvault certificate pending', 'certificate_name', options_list=('--name', '-n'), help='Name of the pending certificate.', id_part='child_name', completer=None)

register_cli_argument('keyvault key', 'key_ops', options_list=('--ops',), nargs='*', help='Space separated list of permitted JSON web key operations. Possible values: {}'.format(json_web_key_op_values), validator=validate_key_ops, type=str.lower)
register_cli_argument('keyvault key', 'key_version', options_list=('--version', '-v'), help='The key version. If omitted, uses the latest version.', default='', required=False)
register_cli_argument('keyvault key', 'key_version', options_list=('--version', '-v'), help='The key version. If omitted, uses the latest version.', default='', required=False, completer=get_keyvault_version_completion_list('key'))

for item in ['create', 'import']:
register_cli_argument('keyvault key {}'.format(item), 'destination', options_list=('--protection', '-p'), choices=['software', 'hsm'], help='Specifies the type of key protection.', validator=validate_key_type, type=str.lower)
Expand All @@ -98,12 +134,20 @@ def register_attributes_argument(scope, name, attr_class, create=False):

register_attributes_argument('keyvault key set-attributes', 'key', KeyAttributes)

register_cli_argument('keyvault secret', 'secret_version', options_list=('--version', '-v'), help='The secret version. If omitted, uses the latest version.', default='', required=False)
register_cli_argument('keyvault secret', 'secret_version', options_list=('--version', '-v'), help='The secret version. If omitted, uses the latest version.', default='', required=False, completer=get_keyvault_version_completion_list('secret'))

register_cli_argument('keyvault secret set', 'content_type', options_list=('--description',), help='Description of the secret contents (e.g. password, connection string, etc)')
register_attributes_argument('keyvault secret set', 'secret', SecretAttributes, create=True)
register_cli_argument('keyvault secret set', 'value', options_list=('--value',), help="Plain text secret value. Cannot be used with '--file' or '--encoding'", required=False, arg_group='Content Source')
register_extra_cli_argument('keyvault secret set', 'file_path', options_list=('--file', '-f'), help="Source file for secret. Use in conjunction with '--encoding'", arg_group='Content Source')
register_extra_cli_argument('keyvault secret set', 'encoding', options_list=('--encoding', '-e'), help='Source file encoding. The value is saved as a tag (file-encoding=<val>) and used during download to automtically encode the resulting file.', default='utf-8', validator=process_secret_set_namespace, arg_group='Content Source', **enum_choice_list(secret_encoding_values))

register_attributes_argument('keyvault secret set-attributes', 'secret', SecretAttributes)

register_cli_argument('keyvault certificate', 'certificate_version', options_list=('--version', '-v'), help='The certificate version. If omitted, uses the latest version.', default='', required=False)
register_cli_argument('keyvault secret download', 'file_path', options_list=('--file', '-f'), help='File to receive the secret contents.')
register_cli_argument('keyvault secret download', 'encoding', options_list=('--encoding', '-e'), help="Encoding of the destination file. By default, will look for the 'file-encoding' tag on the secret. Otherwise will assume 'utf-8'.", default=None, **enum_choice_list(secret_encoding_values))

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)
Expand All @@ -112,6 +156,9 @@ def register_attributes_argument(scope, name, attr_class, create=False):

register_cli_argument('keyvault certificate import', 'base64_encoded_certificate', options_list=('--file', '-f'), help='PKCS12 file or PEM file containing the certificate and private key.', type=base64_encoded_certificate_type)

register_cli_argument('keyvault certificate download', 'file_path', options_list=('--file', '-f'), 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))

register_cli_argument('keyvault certificate pending merge', 'x509_certificates', options_list=('--file', '-f'), help='File containing the certificate or certificate chain to merge.', validator=validate_x509_certificate_chain)
register_attributes_argument('keyvault certificate pending merge', 'certificate', CertificateAttributes, True)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
from azure.mgmt.keyvault.models.key_vault_management_client_enums import \
(KeyPermissions, SecretPermissions, CertificatePermissions)

from azure.cli.command_modules.keyvault.keyvaultclient.generated.models.key_vault_client_enums \
import JsonWebKeyOperation

from azure.cli.core.commands.client_factory import get_mgmt_service_client
from azure.cli.core.commands.arm import parse_resource_id
from azure.cli.core.commands.validators import validate_tags
from azure.cli.core._util import CLIError

from azure.cli.command_modules.keyvault.keyvaultclient.generated.models.key_vault_client_enums \
import JsonWebKeyOperation

secret_text_encoding_values = ['utf-8', 'utf-16le', 'utf-16be', 'ascii']
secret_binary_encoding_values = ['base64', 'hex']

def _extract_version(item_id):
return item_id.split('/')[-1]

Expand All @@ -28,6 +32,57 @@ def _extract_version(item_id):
def process_certificate_cancel_namespace(namespace):
namespace.cancellation_requested = True

def process_secret_set_namespace(namespace):

validate_tags(namespace)

content = namespace.value
file_path = namespace.file_path
encoding = namespace.encoding
tags = namespace.tags or {}

use_error = CLIError("incorrect usage: [Required] --value VALUE | --file PATH")

if (content and file_path) or (not content and not file_path):
raise use_error

encoding = encoding or 'utf-8'
if file_path:
if encoding in secret_text_encoding_values:
with open(file_path, 'r') as f:
try:
content = f.read()
except UnicodeDecodeError:
raise CLIError("Unable to decode file '{}' with '{}' encoding.".format(
file_path, encoding))
encoded_str = content
encoded = content.encode(encoding)
decoded = encoded.decode(encoding)
elif encoding == 'base64':
with open(file_path, 'rb') as f:
content = f.read()
try:
encoded = base64.encodebytes(content)
except AttributeError:
encoded = base64.encodestring(content) # pylint: disable=deprecated-method
encoded_str = encoded.decode('utf-8')
decoded = base64.b64decode(encoded_str)
elif encoding == 'hex':
with open(file_path, 'rb') as f:
content = f.read()
encoded = binascii.b2a_hex(content)
encoded_str = encoded.decode('utf-8')
decoded = binascii.unhexlify(encoded_str)

if content != decoded:
raise CLIError("invalid encoding '{}'".format(encoding))

content = encoded_str

tags.update({'file-encoding': encoding})
namespace.tags = tags
namespace.value = content

# PARAMETER NAMESPACE VALIDATORS

def get_attribute_validator(name, attribute_class, create=False):
Expand Down
Loading