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

[AKS] Support use custom kubelet identity #3249

Merged
merged 7 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from 5 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 linter_exclusions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ aks create:
enable_pod_identity_with_kubenet:
rule_exclusions:
- option_length_too_long
assign_kubelet_identity:
rule_exclusions:
- option_length_too_long
aks enable-addons:
parameters:
appgw_watch_namespace:
Expand Down
7 changes: 6 additions & 1 deletion src/aks-preview/azext_aks_preview/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,10 @@
short-summary: Using managed identity to manage cluster resource group. Default value is true, you can explicitly specify "--client-id" and "--secret" to disable managed identity.
- name: --assign-identity
type: string
short-summary: (PREVIEW) Specify an existing user assigned identity to manage cluster resource group.
short-summary: Specify an existing user assigned identity to manage cluster resource group.
- name: --assign-kubelet-identity
type: string
short-summary: Specify an existing user assigned identity for kubelet's usage, which is typically used to pull image from ACR.
- name: --api-server-authorized-ip-ranges
type: string
short-summary: Comma seperated list of authorized apiserver IP ranges. Set to 0.0.0.0/32 to restrict apiserver traffic to node pools.
Expand Down Expand Up @@ -359,6 +362,8 @@
text: az aks create -g MyResourceGroup -n MyManagedCluster --tags "foo=bar" "baz=qux"
- name: Create a kubernetes cluster with EncryptionAtHost enabled.
text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-encryption-at-host
- name: Create a kubernetes cluster with custom control plane identity and kubelet identity.
text: az aks create -g MyResourceGroup -n MyManagedCluster --assign-identity <control-plane-identity-resource-id> --assign-kubelet-identity <kubelet-identity-resource-id>

""".format(sp_cache=AKS_SERVICE_PRINCIPAL_CACHE)

Expand Down
3 changes: 2 additions & 1 deletion src/aks-preview/azext_aks_preview/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
validate_taints, validate_priority, validate_eviction_policy, validate_spot_max_price, validate_acr, validate_user,
validate_load_balancer_outbound_ports, validate_load_balancer_idle_timeout, validate_nodepool_tags,
validate_nodepool_labels, validate_vnet_subnet_id, validate_pod_subnet_id, validate_max_surge, validate_assign_identity, validate_addons,
validate_pod_identity_pod_labels, validate_pod_identity_resource_name, validate_pod_identity_resource_namespace)
validate_pod_identity_pod_labels, validate_pod_identity_resource_name, validate_pod_identity_resource_namespace, validate_assign_kubelet_identity)
from ._consts import CONST_OUTBOUND_TYPE_LOAD_BALANCER, \
CONST_OUTBOUND_TYPE_USER_DEFINED_ROUTING, CONST_SCALE_SET_PRIORITY_REGULAR, CONST_SCALE_SET_PRIORITY_SPOT, \
CONST_SPOT_EVICTION_POLICY_DELETE, CONST_SPOT_EVICTION_POLICY_DEALLOCATE, \
Expand Down Expand Up @@ -127,6 +127,7 @@ def load_arguments(self, _):
c.argument('aci_subnet_name', type=str)
c.argument('enable_encryption_at_host', arg_type=get_three_state_flag(), help='Enable EncryptionAtHost.')
c.argument('enable_secret_rotation', action='store_true')
c.argument('assign_kubelet_identity', type=str, validator=validate_assign_kubelet_identity)
c.argument('yes', options_list=['--yes', '-y'], help='Do not prompt for confirmation.', action='store_true')

with self.argument_context('aks update') as c:
Expand Down
9 changes: 9 additions & 0 deletions src/aks-preview/azext_aks_preview/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,3 +468,12 @@ def validate_pod_identity_resource_namespace(namespace):
if not namespace_value:
# namespace cannot be empty
raise CLIError('--namespace is required')


def validate_assign_kubelet_identity(namespace):
if namespace.assign_kubelet_identity is not None:
if namespace.assign_kubelet_identity == '':
return
from msrestazure.tools import is_valid_resource_id
if not is_valid_resource_id(namespace.assign_kubelet_identity):
raise CLIError("--assign-kubelet-identity is not a valid Azure resource ID.")
49 changes: 47 additions & 2 deletions src/aks-preview/azext_aks_preview/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@
ManagedClusterPodIdentity,
ManagedClusterPodIdentityException,
UserAssignedIdentity,
RunCommandRequest)
RunCommandRequest,
ManagedClusterPropertiesIdentityProfileValue)
from ._client_factory import cf_resource_groups
from ._client_factory import get_auth_management_client
from ._client_factory import get_graph_rbac_management_client
Expand Down Expand Up @@ -642,6 +643,10 @@ def _get_user_assigned_identity_client_id(cli_ctx, resource_id):
return _get_user_assigned_identity(cli_ctx, resource_id).client_id


def _get_user_assigned_identity_object_id(cli_ctx, resource_id):
return _get_user_assigned_identity(cli_ctx, resource_id).principal_id


def _update_dict(dict1, dict2):
cp = dict1.copy()
cp.update(dict2)
Expand Down Expand Up @@ -1027,6 +1032,7 @@ def aks_create(cmd, # pylint: disable=too-many-locals,too-many-statements,to
enable_encryption_at_host=False,
enable_secret_rotation=False,
no_wait=False,
assign_kubelet_identity=None,
yes=False):
if not no_ssh_key:
try:
Expand Down Expand Up @@ -1327,6 +1333,22 @@ def aks_create(cmd, # pylint: disable=too-many-locals,too-many-statements,to
user_assigned_identities=user_assigned_identity
)

identity_profile = None
if assign_kubelet_identity:
if not assign_identity:
raise CLIError('--assign-kubelet-identity can only be specified when --assign-identity is specified')
kubelet_identity = _get_user_assigned_identity(cmd.cli_ctx, assign_kubelet_identity)
identity_profile = {
'kubeletidentity': ManagedClusterPropertiesIdentityProfileValue(
resource_id=assign_kubelet_identity,
client_id=kubelet_identity.client_id,
object_id=kubelet_identity.principal_id
)
}
cluster_identity_object_id = _get_user_assigned_identity_object_id(cmd.cli_ctx, assign_identity)
# ensure the cluster identity has "Managed Identity Operator" role at the scope of kubelet identity
_ensure_cluster_identity_permission_on_kubelet_identity(cmd.cli_ctx, cluster_identity_object_id, assign_kubelet_identity)

pod_identity_profile = None
if enable_pod_identity:
if not enable_managed_identity:
Expand Down Expand Up @@ -1363,7 +1385,8 @@ def aks_create(cmd, # pylint: disable=too-many-locals,too-many-statements,to
disk_encryption_set_id=node_osdisk_diskencryptionset_id,
api_server_access_profile=api_server_access_profile,
auto_upgrade_profile=auto_upgrade_profile,
pod_identity_profile=pod_identity_profile)
pod_identity_profile=pod_identity_profile,
identity_profile=identity_profile)

if node_resource_group:
mc.node_resource_group = node_resource_group
Expand Down Expand Up @@ -4076,3 +4099,25 @@ def aks_pod_identity_exception_update(cmd, client, resource_group_name, cluster_
def aks_pod_identity_exception_list(cmd, client, resource_group_name, cluster_name):
instance = client.get(resource_group_name, cluster_name)
return _remove_nulls([instance])[0]


def _ensure_cluster_identity_permission_on_kubelet_identity(cli_ctx, cluster_identity_object_id, scope):
managed_identity_operator_role = 'Managed Identity Operator'
Copy link
Member

Choose a reason for hiding this comment

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

define as consts?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done. Thanks for reviewing and reminding!

managed_identity_operator_role_id = 'f1a07417-d97a-45cb-824c-7a7467783830'

factory = get_auth_management_client(cli_ctx, scope)
assignments_client = factory.role_assignments

for i in assignments_client.list_for_scope(scope=scope, filter='atScope()'):
if i.scope.lower() != scope.lower():
continue
if not i.role_definition_id.lower().endswith(managed_identity_operator_role_id):
continue
if i.principal_id.lower() != cluster_identity_object_id.lower():
continue
# already assigned
return

if not _add_role_assignment(cli_ctx, managed_identity_operator_role, cluster_identity_object_id,
is_service_principal=False, scope=scope):
raise CLIError('Could not grant Managed Identity Operator permission to cluster identity at scope {}'.format(scope))
Loading