Skip to content

Commit

Permalink
[KeyVault] Onboard KeyVault Round 1 (Azure#1037)
Browse files Browse the repository at this point in the history
* Initial KeyVault data plane commands and management plane revisions.

* Code review comments.

* Bump to wake up CI.

* Bump to wake up CI.

* Fix imports.

* Updates to setup.py

* Updates to setup.py

* Update vault_base_url
  • Loading branch information
tjprescott authored Oct 10, 2016
1 parent 4133de3 commit d72cfaa
Show file tree
Hide file tree
Showing 111 changed files with 8,222 additions and 915 deletions.
120 changes: 77 additions & 43 deletions azure-cli.pyproj

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
adal==0.4.1
applicationinsights==0.10.0
argcomplete==1.3.0
azure==2.0.0rc6
azure-mgmt-trafficmanager==0.30.0rc6
azure-mgmt-dns==0.30.0rc6
colorama==0.3.7
jmespath
mock==1.3.0
Expand Down
10 changes: 7 additions & 3 deletions src/azure-cli-core/azure/cli/core/_azure_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,26 @@ class ENDPOINT_URLS: #pylint: disable=too-few-public-methods,old-style-class,no-
MANAGEMENT = 'management'
ACTIVE_DIRECTORY_AUTHORITY = 'active_directory_authority'
ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID = 'active_directory_graph_resource_id'
KEY_VAULT = 'key_vault'

_environments = {
ENV_DEFAULT: {
ENDPOINT_URLS.MANAGEMENT: 'https://management.core.windows.net/',
ENDPOINT_URLS.ACTIVE_DIRECTORY_AUTHORITY : 'https://login.microsoftonline.com',
ENDPOINT_URLS.ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID: 'https://graph.windows.net/'
ENDPOINT_URLS.ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID: 'https://graph.windows.net/',
ENDPOINT_URLS.KEY_VAULT: 'https://vault.azure.net'
},
ENV_CHINA: {
ENDPOINT_URLS.MANAGEMENT: 'https://management.core.chinacloudapi.cn/',
ENDPOINT_URLS.ACTIVE_DIRECTORY_AUTHORITY: 'https://login.chinacloudapi.cn',
ENDPOINT_URLS.ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID: 'https://graph.chinacloudapi.cn/'
ENDPOINT_URLS.ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID: 'https://graph.chinacloudapi.cn/',
ENDPOINT_URLS.KEY_VAULT: 'https://vault.azure.cn'
},
ENV_US_GOVERNMENT: {
ENDPOINT_URLS.MANAGEMENT: 'https://management.core.usgovcloudapi.net/',
ENDPOINT_URLS.ACTIVE_DIRECTORY_AUTHORITY: 'https://login.microsoftonline.com',
ENDPOINT_URLS.ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID: 'https://graph.windows.net/'
ENDPOINT_URLS.ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID: 'https://graph.windows.net/',
ENDPOINT_URLS.KEY_VAULT: 'https://vault.usgovcloudapi.net'
}
}

Expand Down
21 changes: 14 additions & 7 deletions src/azure-cli-core/azure/cli/core/_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import json
import os.path
import errno

from enum import Enum

import adal
from azure.cli.core._session import ACCOUNT
from azure.cli.core._util import CLIError, get_file_json
Expand Down Expand Up @@ -63,15 +66,19 @@ def _delete_file(file_path):
if e.errno != errno.ENOENT:
raise

class CredentialType(Enum): # pylint: disable=too-few-public-methods
management = get_env()[ENDPOINT_URLS.MANAGEMENT]
rbac = get_env()[ENDPOINT_URLS.ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID]
keyvault = get_env()[ENDPOINT_URLS.KEY_VAULT]

class Profile(object):
def __init__(self, storage=None, auth_ctx_factory=None):
self._storage = storage or ACCOUNT
factory = auth_ctx_factory or _AUTH_CTX_FACTORY
self._creds_cache = CredsCache(factory)
self._subscription_finder = SubscriptionFinder(factory, self._creds_cache.adal_token_cache)
env = get_env()
self._management_resource_uri = env[ENDPOINT_URLS.MANAGEMENT]
self._graph_resource_uri = env[ENDPOINT_URLS.ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID]
self.env = get_env()
self._management_resource_uri = self.env[ENDPOINT_URLS.MANAGEMENT]

def find_subscriptions_on_login(self, #pylint: disable=too-many-arguments
interactive,
Expand Down Expand Up @@ -218,18 +225,18 @@ def get_subscription(self, subscription_id=None):
raise CLIError("Please run 'az account set' to select active account.")
return result[0]

def get_login_credentials(self, for_graph_client=False, subscription_id=None):
def get_login_credentials(self, credential_type=CredentialType.management,
subscription_id=None):
account = self.get_subscription(subscription_id)
user_type = account[_USER_ENTITY][_USER_TYPE]
username_or_sp_id = account[_USER_ENTITY][_USER_NAME]
resource = self._graph_resource_uri if for_graph_client else self._management_resource_uri
if user_type == _USER:
token_retriever = lambda: self._creds_cache.retrieve_token_for_user(
username_or_sp_id, account[_TENANT_ID], resource)
username_or_sp_id, account[_TENANT_ID], credential_type.value)
auth_object = AdalAuthentication(token_retriever)
else:
token_retriever = lambda: self._creds_cache.retrieve_token_for_service_principal(
username_or_sp_id, resource)
username_or_sp_id, credential_type.value)
auth_object = AdalAuthentication(token_retriever)

return (auth_object,
Expand Down
2 changes: 2 additions & 0 deletions src/azure-cli-core/azure/cli/core/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ def execute(self, unexpanded_argv):
self.session['command'] = expanded_arg.command
try:
_validate_arguments(expanded_arg)
except CLIError:
raise
except: # pylint: disable=bare-except
err = sys.exc_info()[1]
getattr(expanded_arg, '_parser', self.parser).validation_error(str(err))
Expand Down
4 changes: 2 additions & 2 deletions src/azure-cli-core/azure/cli/core/tests/test_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import mock
from azure.mgmt.resource.subscriptions.models import (SubscriptionState, Subscription,
SubscriptionPolicies, spendingLimit)
from azure.cli.core._profile import Profile, CredsCache, SubscriptionFinder
from azure.cli.core._profile import Profile, CredsCache, SubscriptionFinder, CredentialType
from azure.cli.core._azure_env import ENV_DEFAULT

class Test_Profile(unittest.TestCase):
Expand Down Expand Up @@ -253,7 +253,7 @@ def test_get_login_credentials_for_graph_client(self, mock_get_token, mock_read_
False, ENV_DEFAULT)
profile._set_subscriptions(consolidated)
#action
cred, _, tenant_id = profile.get_login_credentials(for_graph_client=True)
cred, _, tenant_id = profile.get_login_credentials(credential_type=CredentialType.rbac)
_, _ = cred._token_retriever()
#verify
mock_get_token.assert_called_once_with(mock.ANY, self.user1, self.tenant_id,
Expand Down
1 change: 0 additions & 1 deletion src/azure-cli-core/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
'adal>=0.4.1',
'applicationinsights',
'argcomplete>=1.3.0',
'azure==2.0.0rc6',
'azure-mgmt-trafficmanager==0.30.0rc6',
'azure-mgmt-dns==0.30.0rc6',
'colorama',
Expand Down
1 change: 0 additions & 1 deletion src/command_modules/azure-cli-iot/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
azure==2.0.0rc6
azure-mgmt-iothub==0.1.0
1 change: 0 additions & 1 deletion src/command_modules/azure-cli-iot/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
]

DEPENDENCIES = [
'azure==2.0.0rc6',
'azure-mgmt-iothub==0.1.0',
'azure-cli-core',
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
import azure.cli.command_modules.keyvault.custom
import azure.cli.command_modules.keyvault._params
import azure.cli.command_modules.keyvault.generated

import azure.cli.command_modules.keyvault.convenience
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#---------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
#---------------------------------------------------------------------------------------------

import base64

from msrest.paging import Paged
from msrest.exceptions import ValidationError
from msrestazure.azure_operation import AzureOperationPoller

from azure.cli.core.commands import command_table, CliCommand, LongRunningOperation
from azure.cli.core.commands._introspection import \
(extract_full_summary_from_signature, extract_args_from_signature)
from azure.cli.core._profile import Profile, CredentialType
from azure.cli.core._util import CLIError

from azure.cli.command_modules.keyvault.convenience import KeyVaultClient
from azure.cli.command_modules.keyvault.keyvaultclient.models import KeyVaultErrorException

def _encode_hex(item):
""" Recursively crawls the object structure and converts bytes or bytearrays to base64
encoded strings. """
if isinstance(item, list):
return [_encode_hex(x) for x in item]
elif hasattr(item, '__dict__'):
for key, val in item.__dict__.items():
item.__dict__[key] = _encode_hex(val)
return item
elif isinstance(item, bytes) or isinstance(item, bytearray):
return base64.b64encode(item).decode('utf-8')
else:
return item

def _create_key_vault_command(name, operation, transform_result, table_transformer):

def _execute_command(kwargs):

try:
client = KeyVaultClient(
Profile().get_login_credentials(credential_type=CredentialType.keyvault)[0])
result = operation(client, **kwargs)

# apply results transform if specified
if transform_result:
return _encode_hex(transform_result(result))

# otherwise handle based on return type of results
if isinstance(result, AzureOperationPoller):
return _encode_hex(LongRunningOperation('Starting {}'.format(name))(result))
elif isinstance(result, Paged):
try:
return _encode_hex(list(result))
except TypeError:
# TODO: Workaround for an issue in either KeyVault server-side or msrest
# See https://github.com/Azure/autorest/issues/1309
return []
else:
return _encode_hex(result)
except (ValidationError, KeyVaultErrorException) as ex:
try:
raise CLIError(ex.inner_exception.error.message)
except AttributeError:
raise CLIError(ex)

name = ' '.join(name.split())
cmd = CliCommand(name, _execute_command, table_transformer=table_transformer)
cmd.description = extract_full_summary_from_signature(operation)
cmd.arguments.update(extract_args_from_signature(operation))
return cmd

def cli_keyvault_data_plane_command(
name, operation, transform=None, table_transformer=None):
""" Registers an Azure CLI KeyVault Data Plane command. These commands must respond to a
challenge from the service when they make requests. """
command = _create_key_vault_command(name, operation, transform, table_transformer)
command_table[command.name] = command
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,30 @@
type: group
short-summary: Safeguard and maintain control of keys, secrets, and certificates.
"""

helps['keyvault create'] = """
type: command
short-summary: Create a new Key Vault.
long-summary: "Default permissions are created for the current user unless the --no-self-perms
flag is specified."
"""

helps['keyvault delete'] = """
type: command
short-summary: Delete a Key Vault.
"""

helps['keyvault list'] = """
type: command
short-summary: List Key Vaults within a subscription or resource group.
"""

helps['keyvault show'] = """
type: command
short-summary: Show details of a Key Vault.
"""

helps['keyvault update'] = """
type: command
short-summary: Update properties of a Key Vault.
"""
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,98 @@
#---------------------------------------------------------------------------------------------

# pylint: disable=line-too-long
from azure.mgmt.keyvault.models.key_vault_management_client_enums import (SkuName)
import json

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,
name_type, enum_choice_list)
from azure.cli.core.commands import register_cli_argument
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.command_modules.keyvault._validators import (process_policy_namespace, process_set_policy_perms_namespace)
from azure.cli.command_modules.keyvault.keyvaultclient.models.key_vault_client_enums import \
(JsonWebKeyOperation)
from azure.cli.command_modules.keyvault.keyvaultclient.models import \
(KeyAttributes, SecretAttributes, CertificateAttributes)
from azure.cli.command_modules.keyvault._validators import \
(datetime_type,
get_attribute_validator,
vault_base_url_type, validate_key_import_source,
validate_key_type, validate_key_ops, validate_policy_permissions,
validate_principal, validate_resource_group_name)

# 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])

# KEY ATTRIBUTE PARAMETER REGISTRATION

def register_attributes_argument(scope, name, attr_class, create=False):
register_cli_argument(scope, '{}_attributes'.format(name), ignore_type, validator=get_attribute_validator(name, attr_class, create))
if create:
register_extra_cli_argument(scope, 'disabled', action='store_true', help='Create {} in disabled state.'.format(name))
else:
register_extra_cli_argument(scope, 'enabled', default=None, choices=['true', 'false'], help='Enable the {}.'.format(name))
register_extra_cli_argument(scope, 'expires', default=None, help='Expiration UTC datetime (Y-m-d\'T\'H:M\'Z\').', type=datetime_type)
register_extra_cli_argument(scope, 'not_before', default=None, help='Key not usable before the provided UTC datetime (Y-m-d\'T\'H:M\'Z\').', type=datetime_type)

register_cli_argument('keyvault', 'vault_name', arg_type=name_type, help='Name of the key vault', completer=get_resource_name_completion_list('Microsoft.KeyVault/vaults'), id_part='name')
# ARGUMENT DEFINITIONS

vault_name_type = CliArgumentType(help='Name of the key vault.', options_list=('--vault-name',), completer=get_resource_name_completion_list('Microsoft.KeyVault/vaults'), id_part=None)

# PARAMETER REGISTRATIONS

register_cli_argument('keyvault', 'resource_group_name', resource_group_name_type, id_part=None, required=False, help='Proceed only if Key Vault belongs to the specified resource group.', validator=validate_resource_group_name)
register_cli_argument('keyvault', 'vault_name', vault_name_type, options_list=('--name', '-n'))
register_cli_argument('keyvault', 'object_id', help='a GUID that identifies the principal that will receive permissions')
register_cli_argument('keyvault', 'spn', help='name of a service principal that will receive permissions')
register_cli_argument('keyvault', 'upn', help='name of a user principal that will receive permissions')
register_cli_argument('keyvault', 'tags', tags_type)

register_cli_argument('keyvault create', 'resource_group_name', resource_group_name_type, completer=None, validator=None)
register_cli_argument('keyvault create', 'vault_name', completer=None)
register_cli_argument('keyvault create', 'sku', **enum_choice_list(SkuName))
register_cli_argument('keyvault create', 'no_self_perms', action='store_true', help="If specified, don't add permissions for the current user in the new vault")

register_cli_argument('keyvault set-policy', 'object_id', validator=process_policy_namespace)
register_cli_argument('keyvault delete-policy', 'object_id', validator=process_policy_namespace)
# TODO Validate perms_to_keys and perms_to_secrets when enums are added in keyvault SDK
register_cli_argument('keyvault set-policy', 'perms_to_keys', nargs='*', validator=process_set_policy_perms_namespace, help='Permissions to keys')
register_cli_argument('keyvault set-policy', 'perms_to_secrets', nargs='*', help='Permissions to secrets')
register_cli_argument('keyvault list', 'resource_group_name', resource_group_name_type, validator=None)

register_cli_argument('keyvault delete-policy', 'object_id', validator=validate_principal)
register_cli_argument('keyvault set-policy', 'key_permissions', metavar='PERM', nargs='*', help='Space separated list. Possible values: {}'.format(key_permission_values), arg_group='Permission', validator=validate_policy_permissions)
register_cli_argument('keyvault set-policy', 'secret_permissions', metavar='PERM', nargs='*', help='Space separated list. Possible values: {}'.format(secret_permission_values), arg_group='Permission')
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), 'vault_base_url', vault_name_type, type=vault_base_url_type, id_part=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.')

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)
register_cli_argument('keyvault key {}'.format(item), 'disabled', action='store_true', help='Create key in disabled state.')
register_cli_argument('keyvault key {}'.format(item), 'key_size', options_list=('--size',), type=int)
register_cli_argument('keyvault key {}'.format(item), 'expires', default=None, help='Expiration UTC datetime (Y-m-d\'T\'H:M\'Z\').', type=datetime_type)
register_cli_argument('keyvault key {}'.format(item), 'not_before', default=None, help='Key not usable before the provided UTC datetime (Y-m-d\'T\'H:M\'Z\').', type=datetime_type)

register_cli_argument('keyvault key import', 'pem_file', help='PEM file containing the key to be imported.', arg_group='Key Source', validator=validate_key_import_source)
register_cli_argument('keyvault key import', 'pem_password', help='Password of PEM file.', arg_group='Key Source')
register_cli_argument('keyvault key import', 'byok_file', help='BYOK file containing the key to be imported. Must not be password protected.', arg_group='Key Source')

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.')

register_attributes_argument('keyvault secret set', 'secret', SecretAttributes, create=True)
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.')

for item in ['create', 'set-attributes']:
register_attributes_argument('keyvault certificate {}'.format(item), 'certificate', CertificateAttributes, item == 'create')
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=json.loads)
Loading

0 comments on commit d72cfaa

Please sign in to comment.