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

graph: support custom key identifier #7913

Merged
merged 5 commits into from
Nov 29, 2018
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
3 changes: 3 additions & 0 deletions src/command_modules/azure-cli-acs/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

Release History
===============
2.3.12
++++++
* Minor fixes

2.3.11
++++++
Expand Down
4 changes: 2 additions & 2 deletions src/command_modules/azure-cli-acs/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
logger.warn("Wheel is not available, disabling bdist_wheel hook")
cmdclass = {}

VERSION = "2.3.11"
VERSION = "2.3.12"
CLASSIFIERS = [
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
Expand All @@ -33,7 +33,7 @@
'azure-mgmt-authorization==0.50.0',
'azure-mgmt-compute==4.3.1',
'azure-mgmt-containerservice==4.2.2',
'azure-graphrbac==0.52.0',
'azure-graphrbac==0.53.0',
'azure-cli-core',
'paramiko>=2.0.8',
'pyyaml>=3.13',
Expand Down
4 changes: 4 additions & 0 deletions src/command_modules/azure-cli-ams/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Release History
===============
0.3.1
+++++
* Minor fixes

0.3.0
+++++
* New command groups added:
Expand Down
4 changes: 2 additions & 2 deletions src/command_modules/azure-cli-ams/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
logger.warn("Wheel is not available, disabling bdist_wheel hook")
cmdclass = {}

VERSION = "0.3.0"
VERSION = "0.3.1"

# The full list of classifiers is available at
# https://pypi.python.org/pypi?%3Aaction=list_classifiers
Expand All @@ -34,7 +34,7 @@
DEPENDENCIES = [
'azure-cli-core',
'azure-mgmt-media==1.0.1',
'azure-graphrbac==0.52.0'
'azure-graphrbac==0.53.0'
]

with open('README.rst', 'r', encoding='utf-8') as f:
Expand Down
4 changes: 4 additions & 0 deletions src/command_modules/azure-cli-keyvault/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Release History
===============
2.2.8
+++++
* Minor fixes

2.2.7
+++++
* bug fix: do not drop legit exceptions
Expand Down
4 changes: 2 additions & 2 deletions src/command_modules/azure-cli-keyvault/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
logger.warn("Wheel is not available, disabling bdist_wheel hook")
cmdclass = {}

VERSION = "2.2.7"
VERSION = "2.2.8"

# The full list of classifiers is available at
# https://pypi.python.org/pypi?%3Aaction=list_classifiers
Expand All @@ -36,7 +36,7 @@
DEPENDENCIES = [
'azure-mgmt-keyvault==1.1.0',
'azure-keyvault==1.1.0',
'azure-graphrbac==0.52.0',
'azure-graphrbac==0.53.0',
'azure-cli-core',
'pyOpenSSL'
]
Expand Down
4 changes: 4 additions & 0 deletions src/command_modules/azure-cli-lab/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Release History
===============
0.1.4
+++++
* Minor fixes

0.1.3
+++++
* Minor changes
Expand Down
4 changes: 2 additions & 2 deletions src/command_modules/azure-cli-lab/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
logger.warn("Wheel is not available, disabling bdist_wheel hook")
cmdclass = {}

VERSION = "0.1.3"
VERSION = "0.1.4"
CLASSIFIERS = [
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
Expand All @@ -29,7 +29,7 @@

DEPENDENCIES = [
'azure-cli-core',
'azure-graphrbac==0.52.0',
'azure-graphrbac==0.53.0',
'azure-mgmt-devtestlabs==2.2.0'
]

Expand Down
4 changes: 4 additions & 0 deletions src/command_modules/azure-cli-role/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Release History
===============
2.1.11
++++++
* support custom identifier for password credential

2.1.10
++++++
* `ad app update`: address a failure caused by updating immutable credentials
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def load_arguments(self, _):
c.argument('required_resource_accesses', type=validate_file_or_dict,
help="resource scopes and roles the application requires access to. Should be in manifest json format. See examples below for details")
c.argument('native_app', arg_type=get_three_state_flag(), help="an application which can be installed on a user's device or computer")
c.argument('credential_description', help="the description of the password")

with self.argument_context('ad app owner list') as c:
c.argument('identifier', options_list=['--id'], help='identifier uri, application id, or object id of the application')
Expand Down Expand Up @@ -80,6 +81,7 @@ def load_arguments(self, _):
c.argument('create_cert', action='store_true', arg_group='Credential')
c.argument('keyvault', arg_group='Credential')
c.argument('append', action='store_true', help='Append the new credential instead of overwriting.')
c.argument('credential_description', help="the description of the password", arg_group='Credential')

with self.argument_context('ad app credential reset') as c:
c.argument('name', options_list=['--id'], help='identifier uri, application id, or object id')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,41 @@ def transform_assignment_list(result):
('Scope', r['scope'])]) for r in result]


def transform_graph_objects_with_cred(result):
# here we will convert utf16 encoded custom key id back to the plain text
# we will handle single object from "show" cmd, object list from "list" cmd, and cred object itself
if not result:
return result
from msrest.paging import Paged
from azure.graphrbac.models import PasswordCredential

def _patch_creds(creds):
for c in creds:
custom_key_id = getattr(c, 'custom_key_identifier', None)
if custom_key_id:
try:
c.custom_key_identifier = custom_key_id.decode('utf-16')
except Exception: # pylint: disable=broad-except
c.custom_key_identifier = None
return creds

singular = False
if isinstance(result, Paged):
result = list(result)

if not isinstance(result, list):
singular = True
result = [result]

for r in result:
if getattr(r, 'password_credentials', None):
_patch_creds(r.password_credentials)

if isinstance(r, PasswordCredential):
_patch_creds([r])
return result[0] if singular else result


def graph_err_handler(ex):
from azure.graphrbac.models import GraphErrorException
if isinstance(ex, GraphErrorException):
Expand Down Expand Up @@ -88,7 +123,7 @@ def load_command_table(self, _):
g.custom_command('list-changelogs', 'list_role_assignment_change_logs')

with self.command_group('ad app', client_factory=get_graph_client_applications, resource_type=PROFILE_TYPE,
exception_handler=graph_err_handler) as g:
exception_handler=graph_err_handler, transform=transform_graph_objects_with_cred) as g:
g.custom_command('create', 'create_application')
g.custom_command('delete', 'delete_application')
g.custom_command('list', 'list_apps')
Expand All @@ -109,7 +144,8 @@ def load_command_table(self, _):
g.custom_command('add', 'add_application_owner')
g.custom_command('remove', 'remove_application_owner')

with self.command_group('ad sp', resource_type=PROFILE_TYPE, exception_handler=graph_err_handler) as g:
with self.command_group('ad sp', resource_type=PROFILE_TYPE, exception_handler=graph_err_handler,
transform=transform_graph_objects_with_cred) as g:
g.custom_command('create', 'create_service_principal')
g.custom_command('delete', 'delete_service_principal')
g.custom_command('list', 'list_sps', client_factory=get_graph_client_service_principals)
Expand All @@ -119,7 +155,7 @@ def load_command_table(self, _):
g.custom_command('list', 'list_service_principal_owners')

# RBAC related
with self.command_group('ad sp', exception_handler=graph_err_handler) as g:
with self.command_group('ad sp', exception_handler=graph_err_handler, transform=transform_graph_objects_with_cred) as g:
g.custom_command('create-for-rbac', 'create_service_principal_for_rbac')
g.custom_command('credential reset', 'reset_service_principal_credential')
g.custom_command('credential list', 'list_service_principal_credentials')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from __future__ import print_function

import base64
import datetime
import json
import re
Expand Down Expand Up @@ -643,7 +644,8 @@ def _resolve_group(client, identifier):
def create_application(cmd, display_name, homepage=None, identifier_uris=None,
available_to_other_tenants=False, password=None, reply_urls=None,
key_value=None, key_type=None, key_usage=None, start_date=None, end_date=None,
oauth2_allow_implicit_flow=None, required_resource_accesses=None, native_app=None):
oauth2_allow_implicit_flow=None, required_resource_accesses=None, native_app=None,
credential_description=None):
graph_client = _graph_client_factory(cmd.cli_ctx)
key_creds, password_creds, required_accesses = None, None, None
if native_app:
Expand All @@ -653,8 +655,8 @@ def create_application(cmd, display_name, homepage=None, identifier_uris=None,
else:
if not identifier_uris:
raise CLIError("'--identifier-uris' is required for creating an application")
password_creds, key_creds = _build_application_creds(password, key_value, key_type,
key_usage, start_date, end_date)
password_creds, key_creds = _build_application_creds(password, key_value, key_type, key_usage,
start_date, end_date, credential_description)

if required_resource_accesses:
required_accesses = _build_application_accesses(required_resource_accesses)
Expand Down Expand Up @@ -856,8 +858,8 @@ def _resolve_application(client, identifier):
return result[0].object_id if result else identifier


def _build_application_creds(password=None, key_value=None, key_type=None,
key_usage=None, start_date=None, end_date=None):
def _build_application_creds(password=None, key_value=None, key_type=None, key_usage=None,
start_date=None, end_date=None, key_description=None):
if password and key_value:
raise CLIError('specify either --password or --key-value, but not both.')

Expand All @@ -871,18 +873,21 @@ def _build_application_creds(password=None, key_value=None, key_type=None,
elif isinstance(end_date, str):
end_date = dateutil.parser.parse(end_date)

custom_key_id = None
if key_description and password:
custom_key_id = _encode_custom_key_description(key_description)

key_type = key_type or 'AsymmetricX509Cert'
key_usage = key_usage or 'Verify'

password_creds = None
key_creds = None
if password:
password_creds = [PasswordCredential(start_date=start_date, end_date=end_date,
key_id=str(_gen_guid()), value=password)]
password_creds = [PasswordCredential(start_date=start_date, end_date=end_date, key_id=str(_gen_guid()),
value=password, custom_key_identifier=custom_key_id)]
elif key_value:
key_creds = [KeyCredential(start_date=start_date, end_date=end_date,
key_id=str(_gen_guid()), value=key_value,
usage=key_usage, type=key_type)]
key_creds = [KeyCredential(start_date=start_date, end_date=end_date, key_id=str(_gen_guid()), value=key_value,
usage=key_usage, type=key_type, custom_key_identifier=custom_key_id)]

return (password_creds, key_creds)

Expand Down Expand Up @@ -1030,7 +1035,6 @@ def _process_service_principal_creds(cli_ctx, years, app_start_date, app_end_dat
public_cert_string, cert_file, cert_start_date, cert_end_date = \
_create_self_signed_cert_with_keyvault(cli_ctx, years, keyvault, cert)
elif keyvault:
import base64
# 6 - Use existing cert from KeyVault
kv_client = _get_keyvault_client(cli_ctx)
vault_base = 'https://{}{}/'.format(keyvault, cli_ctx.cloud.suffixes.keyvault_dns)
Expand Down Expand Up @@ -1112,7 +1116,8 @@ def create_service_principal_for_rbac(
password=password,
key_value=public_cert_string,
start_date=app_start_date,
end_date=app_end_date)
end_date=app_end_date,
credential_description='rbac')
# pylint: disable=no-member
app_id = aad_application.app_id

Expand Down Expand Up @@ -1252,7 +1257,6 @@ def _create_self_signed_cert(start_date, end_date): # pylint: disable=too-many-


def _create_self_signed_cert_with_keyvault(cli_ctx, years, keyvault, keyvault_cert_name): # pylint: disable=too-many-locals
import base64
import time

kv_client = _get_keyvault_client(cli_ctx)
Expand Down Expand Up @@ -1323,7 +1327,6 @@ def _try_x509_pem(cert):

def _try_x509_der(cert):
import OpenSSL.crypto
import base64
try:
cert = base64.b64decode(cert)
return OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, cert)
Expand All @@ -1341,8 +1344,8 @@ def _get_public(x509):
return stripped


def reset_service_principal_credential(cmd, name, password=None, create_cert=False,
cert=None, years=None, keyvault=None, append=False):
def reset_service_principal_credential(cmd, name, password=None, create_cert=False, cert=None, years=None,
keyvault=None, append=False, credential_description=None):
client = _graph_client_factory(cmd.cli_ctx)

# pylint: disable=no-member
Expand Down Expand Up @@ -1377,6 +1380,10 @@ def reset_service_principal_credential(cmd, name, password=None, create_cert=Fal
app_creds = None
cert_creds = None

custom_key_identifier = None
if credential_description and password:
custom_key_identifier = _encode_custom_key_description(credential_description)

if password:
app_creds = []
if append:
Expand All @@ -1385,7 +1392,8 @@ def reset_service_principal_credential(cmd, name, password=None, create_cert=Fal
start_date=app_start_date,
end_date=app_end_date,
key_id=str(_gen_guid()),
value=password
value=password,
custom_key_identifier=custom_key_identifier
))

if public_cert_string:
Expand All @@ -1398,7 +1406,8 @@ def reset_service_principal_credential(cmd, name, password=None, create_cert=Fal
value=public_cert_string,
key_id=str(_gen_guid()),
usage='Verify',
type='AsymmetricX509Cert'
type='AsymmetricX509Cert',
custom_key_identifier=custom_key_identifier
))

app_create_param = ApplicationUpdateParameters(password_credentials=app_creds, key_credentials=cert_creds)
Expand All @@ -1416,6 +1425,12 @@ def reset_service_principal_credential(cmd, name, password=None, create_cert=Fal
return result


def _encode_custom_key_description(key_description):
# utf16 is used by AAD portal. Do not change it to other random encoding
# unless you know what you are doing.
return key_description.encode('utf-16')


def _resolve_object_id(cli_ctx, assignee, fallback_to_object_id=False):
client = _graph_client_factory(cli_ctx)
result = None
Expand Down
Loading