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

[Network] Add support for setting identity in Application-Gateway #10119

Merged
merged 17 commits into from
Sep 4, 2019
4 changes: 3 additions & 1 deletion src/azure-cli/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ Fix #10286: Unable to delete network rules.

**Network**

* network watcher flow-log: Fix #8132. Support `--interval` to set traffic analysis interval value.
* az network watcher flow-log: Fix #8132. Support `--interval` to set traffic analysis interval value.
* az network application-gateway identity: Fix #10073 and #8244 Add support for setting identity in application-gateway.
* az network application-gateway ssl-cert: Fix #8244. Add support for setting key vault id in application-gateway ssl-cert.

**Policy**

Expand Down
30 changes: 30 additions & 0 deletions src/azure-cli/azure/cli/command_modules/network/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,36 @@
-n MyHttpSettings --probe MyNewProbe
"""

helps['network application-gateway identity'] = """
type: group
short-summary: Manage the managed service identity of an application gateway.
"""

helps['network application-gateway identity assign'] = """
type: command
short-summary: Assign a managed service identity to an application-gateway
examples:
- name: Assign an identity to the application gateway
text: az network application-gateway identity assign -g MyResourceGroup --gateway-name ag1 \\
--identity /subscriptions/*-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id1
"""

helps['network application-gateway identity remove'] = """
type: command
short-summary: Remove the managed service identity of an application-gateway
examples:
- name: Remove an identity to the application gateway
text: az network application-gateway identity remove -g MyResourceGroup --gateway-name ag1
"""

helps['network application-gateway identity show'] = """
type: command
short-summary: Show the managed service identity of an application-gateway
examples:
- name: Show an identity to the application gateway
text: az network application-gateway identity show -g MyResourceGroup --gateway-name ag1
"""

helps['network application-gateway list'] = """
type: command
short-summary: List application gateways.
Expand Down
10 changes: 9 additions & 1 deletion src/azure-cli/azure/cli/command_modules/network/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
WafConfigExclusionAction, validate_express_route_peering, validate_virtual_hub,
validate_express_route_port, bandwidth_validator_factory,
get_header_configuration_validator, validate_nat_gateway, validate_match_variables,
validate_waf_policy, get_subscription_list_validator, validate_frontend_ip_configs)
validate_waf_policy, get_subscription_list_validator, validate_frontend_ip_configs,
validate_application_gateway_identity)
from azure.mgmt.trafficmanager.models import MonitorProtocol, ProfileStatus
from azure.cli.command_modules.network._completers import (
subnet_completion_list, get_lb_subresource_completion_list, get_ag_subresource_completion_list,
Expand Down Expand Up @@ -109,6 +110,9 @@ def load_arguments(self, _):
c.argument('custom_error_pages', min_api='2018-08-01', nargs='+', help='Space-separated list of custom error pages in `STATUS_CODE=URL` format.', validator=validate_custom_error_pages)
c.argument('firewall_policy', options_list='--waf-policy', min_api='2018-12-01', help='Name or ID of a web application firewall (WAF) policy.', validator=validate_waf_policy)

with self.argument_context('network application-gateway', arg_group='Identity') as c:
c.argument('user_assigned_identity', options_list='--identity', help="Name or ID of the ManagedIdentity Resource", validator=validate_application_gateway_identity)

with self.argument_context('network application-gateway', arg_group='Network') as c:
c.argument('virtual_network_name', virtual_network_name_type)
c.argument('private_ip_address')
Expand Down Expand Up @@ -138,6 +142,7 @@ def load_arguments(self, _):
c.argument('cert_password', help='The certificate password')
c.argument('http_settings_port', help='The HTTP settings port.')
c.argument('servers', ag_servers_type)
c.argument('key_vault_secret_id', help="Secret Id of (base-64 encoded unencrypted pfx) 'Secret' or 'Certificate' object stored in Azure KeyVault. You need enable soft delete for keyvault to use this feature.", is_preview=True)
Copy link
Contributor

Choose a reason for hiding this comment

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

What kind of message does server return when no identity assigned or no access policy set?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

identity should be set. The server will check the existence of the identity


with self.argument_context('network application-gateway update', arg_group=None) as c:
c.argument('sku', default=None)
Expand Down Expand Up @@ -271,6 +276,7 @@ def load_arguments(self, _):
with self.argument_context('network application-gateway ssl-cert') as c:
c.argument('cert_data', options_list='--cert-file', type=file_type, completer=FilesCompleter(), help='The path to the PFX certificate file.', validator=validate_ssl_cert)
c.argument('cert_password', help='Certificate password.')
c.argument('key_vault_secret_id', help="Secret Id of (base-64 encoded unencrypted pfx) 'Secret' or 'Certificate' object stored in Azure KeyVault.", is_preview=True)

with self.argument_context('network application-gateway ssl-policy') as c:
c.argument('clear', action='store_true', help='Clear SSL policy.')
Expand Down Expand Up @@ -357,6 +363,8 @@ def load_arguments(self, _):
with self.argument_context('network application-gateway rule', min_api='2017-06-01') as c:
c.argument('redirect_config', help='The name or ID of the redirect configuration to use with the created rule.')

with self.argument_context('network application-gateway identity', min_api='2019-04-01') as c:
c.argument('application_gateway_name', app_gateway_name_type)
# endregion

# region ApplicationGatewayWAFPolicies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ def _build_frontend_ip_config(cmd, name, public_ip_id=None, subnet_id=None, priv
return frontend_ip_config


# pylint: disable=too-many-locals
# pylint: disable=too-many-locals, too-many-statements
def build_application_gateway_resource(cmd, name, location, tags, sku_name, sku_tier, capacity, servers, frontend_port,
private_ip_address, private_ip_allocation, cert_data, cert_password,
private_ip_address, private_ip_allocation,
cert_data, cert_password, key_vault_secret_id,
cookie_based_affinity, http_settings_protocol, http_settings_port,
http_listener_protocol, routing_rule_type, public_ip_id, subnet_id,
connection_draining_timeout, enable_http2, min_capacity, zones,
custom_error_pages, firewall_policy, max_capacity):
custom_error_pages, firewall_policy, max_capacity,
user_assigned_identity):

# set the default names
frontend_ip_name = 'appGatewayFrontendIP'
Expand Down Expand Up @@ -90,6 +92,15 @@ def _ag_subresource_id(_type, name):
if cert_password:
ssl_cert['properties']['password'] = "[parameters('certPassword')]"

if key_vault_secret_id:
http_listener['properties'].update({'SslCertificate': {'id': ssl_cert_id}})
ssl_cert = {
'name': ssl_cert_name,
'properties': {
'keyVaultSecretId': key_vault_secret_id,
}
}

backend_http_settings = {
'name': http_settings_name,
'properties': {
Expand Down Expand Up @@ -172,6 +183,17 @@ def _ag_subresource_id(_type, name):
}
if cmd.supported_api_version(min_api='2018-08-01'):
ag.update({'zones': zones})
if user_assigned_identity and cmd.supported_api_version(min_api='2018-12-01'):
ag.update(
{
"identity": {
"type": "UserAssigned",
"userAssignedIdentities": {
user_assigned_identity: {}
}
}
}
)
return ag


Expand Down
18 changes: 17 additions & 1 deletion src/azure-cli/azure/cli/command_modules/network/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,13 @@ def validate_cert(namespace):

def validate_ssl_cert(namespace):
params = [namespace.cert_data, namespace.cert_password]
if all([not x for x in params]):
if all([not x for x in params]) and not namespace.key_vault_secret_id:
# no cert supplied -- use HTTP
if not namespace.frontend_port:
namespace.frontend_port = 80
else:
if namespace.key_vault_secret_id:
return
# cert supplied -- use HTTPS
if not all(params):
raise CLIError(
Expand Down Expand Up @@ -263,6 +265,19 @@ def validate_dns_record_type(namespace):
return


def validate_application_gateway_identity(cmd, namespace):
from msrestazure.tools import is_valid_resource_id, resource_id

if namespace.user_assigned_identity and not is_valid_resource_id(namespace.user_assigned_identity):
namespace.user_assigned_identity = resource_id(
subscription=get_subscription_id(cmd.cli_ctx),
resource_group=namespace.resource_group_name,
namespace='Microsoft.ManagedIdentity',
type='userAssignedIdentities',
name=namespace.user_assigned_identity
)


def validate_express_route_peering(cmd, namespace):
from msrestazure.tools import is_valid_resource_id, resource_id
circuit = namespace.circuit_name
Expand Down Expand Up @@ -795,6 +810,7 @@ def process_ag_create_namespace(cmd, namespace):
validate_tags(namespace)
validate_custom_error_pages(namespace)
validate_waf_policy(cmd, namespace)
validate_application_gateway_identity(cmd, namespace)


def process_auth_create_namespace(cmd, namespace):
Expand Down
5 changes: 5 additions & 0 deletions src/azure-cli/azure/cli/command_modules/network/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ def _make_singular(value):
g.custom_command('set', 'set_ag_waf_config_2016_09_01', max_api='2016-09-01', supports_no_wait=True)
g.custom_show_command('show', 'show_ag_waf_config')
g.custom_command('list-rule-sets', 'list_ag_waf_rule_sets', min_api='2017-03-01', client_factory=cf_application_gateways, table_transformer=transform_waf_rule_sets_table_output)

with self.command_group('network application-gateway identity', command_type=network_ag_sdk, min_api='2018-12-01') as g:
g.custom_command('assign', 'assign_ag_identity', supports_no_wait=True)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you want to make it support --no-wait? It is a long-running operation?

g.custom_command('remove', 'remove_ag_identity', supports_no_wait=True)
g.custom_show_command('show', 'show_ag_identity')
# endregion

# region ApplicationGatewayWAFPolicy
Expand Down
59 changes: 51 additions & 8 deletions src/azure-cli/azure/cli/command_modules/network/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def list_network_watchers(cmd, resource_group_name=None):
# pylint: disable=too-many-locals
def create_application_gateway(cmd, application_gateway_name, resource_group_name, location=None,
tags=None, no_wait=False, capacity=2,
cert_data=None, cert_password=None,
cert_data=None, cert_password=None, key_vault_secret_id=None,
frontend_port=None, http_settings_cookie_based_affinity='disabled',
http_settings_port=80, http_settings_protocol='Http',
routing_rule_type='Basic', servers=None,
Expand All @@ -132,7 +132,8 @@ def create_application_gateway(cmd, application_gateway_name, resource_group_nam
virtual_network_name=None, vnet_address_prefix='10.0.0.0/16',
public_ip_address_type=None, subnet_type=None, validate=False,
connection_draining_timeout=0, enable_http2=None, min_capacity=None, zones=None,
custom_error_pages=None, firewall_policy=None, max_capacity=None):
custom_error_pages=None, firewall_policy=None, max_capacity=None,
user_assigned_identity=None):
from azure.cli.core.util import random_string
from azure.cli.core.commands.arm import ArmTemplateBuilder
from azure.cli.command_modules.network._template_builder import (
Expand Down Expand Up @@ -180,11 +181,11 @@ def create_application_gateway(cmd, application_gateway_name, resource_group_nam

app_gateway_resource = build_application_gateway_resource(
cmd, application_gateway_name, location, tags, sku, sku_tier, capacity, servers, frontend_port,
private_ip_address, private_ip_allocation, cert_data, cert_password,
private_ip_address, private_ip_allocation, cert_data, cert_password, key_vault_secret_id,
http_settings_cookie_based_affinity, http_settings_protocol, http_settings_port,
http_listener_protocol, routing_rule_type, public_ip_id, subnet_id,
connection_draining_timeout, enable_http2, min_capacity, zones, custom_error_pages,
firewall_policy, max_capacity)
firewall_policy, max_capacity, user_assigned_identity)
app_gateway_resource['dependsOn'] = ag_dependencies
master_template.add_variable(
'appGwID',
Expand Down Expand Up @@ -361,6 +362,45 @@ def update_ag_http_listener(cmd, instance, parent, item_name, frontend_ip=None,
return parent


def assign_ag_identity(cmd, resource_group_name, application_gateway_name,
user_assigned_identity, no_wait=False):
ncf = network_client_factory(cmd.cli_ctx).application_gateways
ag = ncf.get(resource_group_name, application_gateway_name)
ManagedServiceIdentity, ManagedServiceIdentityUserAssignedIdentitiesValue = \
cmd.get_models('ManagedServiceIdentity', 'ManagedServiceIdentityUserAssignedIdentitiesValue')
user_assigned_indentity_instance = ManagedServiceIdentityUserAssignedIdentitiesValue()

user_assigned_identities_instance = dict()

user_assigned_identities_instance[user_assigned_identity] = user_assigned_indentity_instance

identity_instance = ManagedServiceIdentity(
type="UserAssigned",
user_assigned_identities=user_assigned_identities_instance
)
ag.identity = identity_instance

return sdk_no_wait(no_wait, ncf.create_or_update, resource_group_name, application_gateway_name, ag)


def remove_ag_identity(cmd, resource_group_name, application_gateway_name, no_wait=False):
ncf = network_client_factory(cmd.cli_ctx).application_gateways
ag = ncf.get(resource_group_name, application_gateway_name)
if ag.identity is None:
logger.warning("This command will be ignored. The identity doesn't exist.")
ag.identity = None

return sdk_no_wait(no_wait, ncf.create_or_update, resource_group_name, application_gateway_name, ag)


def show_ag_identity(cmd, resource_group_name, application_gateway_name):
ncf = network_client_factory(cmd.cli_ctx).application_gateways
ag = ncf.get(resource_group_name, application_gateway_name)
if ag.identity is None:
raise CLIError("Please first use 'az network application-gateway identity assign` to init the identity.")
return ag.identity


def create_ag_backend_http_settings_collection(cmd, resource_group_name, application_gateway_name, item_name, port,
probe=None, protocol='http', cookie_based_affinity=None, timeout=None,
no_wait=False, connection_draining_timeout=0,
Expand Down Expand Up @@ -710,23 +750,26 @@ def update_ag_request_routing_rule(cmd, instance, parent, item_name, address_poo
return parent


def create_ag_ssl_certificate(cmd, resource_group_name, application_gateway_name, item_name, cert_data,
cert_password, no_wait=False):
def create_ag_ssl_certificate(cmd, resource_group_name, application_gateway_name, item_name, cert_data=None,
cert_password=None, key_vault_secret_id=None, no_wait=False):
ApplicationGatewaySslCertificate = cmd.get_models('ApplicationGatewaySslCertificate')
ncf = network_client_factory(cmd.cli_ctx)
ag = ncf.application_gateways.get(resource_group_name, application_gateway_name)
new_cert = ApplicationGatewaySslCertificate(
name=item_name, data=cert_data, password=cert_password)
name=item_name, data=cert_data, password=cert_password, key_vault_secret_id=key_vault_secret_id)
upsert_to_collection(ag, 'ssl_certificates', new_cert, 'name')
return sdk_no_wait(no_wait, ncf.application_gateways.create_or_update,
resource_group_name, application_gateway_name, ag)


def update_ag_ssl_certificate(instance, parent, item_name, cert_data=None, cert_password=None):
def update_ag_ssl_certificate(instance, parent, item_name,
cert_data=None, cert_password=None, key_vault_secret_id=None):
if cert_data is not None:
instance.data = cert_data
if cert_password is not None:
instance.password = cert_password
if key_vault_secret_id is not None:
instance.key_vault_secret_id = key_vault_secret_id
return parent


Expand Down
Loading