Skip to content

Commit

Permalink
ADAL to MSAL move for cli>=2.30.0 (#4468)
Browse files Browse the repository at this point in the history
* ADAL to MSAL move for cli>=2.30.0

* nit

* nit

* nit

* add comment

* addressing review comments

* linting fixes

* linter fixes

* linter nit

* add license for new file

* update release date

* add token condition

* Update src/connectedk8s/azext_connectedk8s/_utils.py

Co-authored-by: Xing Zhou <[email protected]>

Co-authored-by: Siri Teja Reddy Kasireddy <[email protected]>
Co-authored-by: Xing Zhou <[email protected]>
  • Loading branch information
3 people authored Mar 14, 2022
1 parent 2ce2a17 commit f13c495
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 143 deletions.
5 changes: 5 additions & 0 deletions src/connectedk8s/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
Release History
===============
1.2.5
++++++

* Using MSAL based auth for CLI version >= 2.30.0

1.2.4
++++++

Expand Down
135 changes: 135 additions & 0 deletions src/connectedk8s/azext_connectedk8s/_clientproxyutils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import sys
import os
import platform
import base64
import json
import requests
import yaml
import time
import azext_connectedk8s._constants as consts
from base64 import b64encode, b64decode
from azure.cli.core._profile import Profile
from azure.cli.core import telemetry
from azure.cli.core.azclierror import CLIInternalError
from psutil import process_iter, NoSuchProcess, AccessDenied, ZombieProcess, net_connections
from knack.log import get_logger
logger = get_logger(__name__)


def check_if_port_is_open(port):
try:
connections = net_connections(kind='inet')
for tup in connections:
if int(tup[3][1]) == int(port):
return True
except Exception as e:
telemetry.set_exception(exception=e, fault_type=consts.Port_Check_Fault_Type,
summary='Failed to check if port is in use.')
if platform.system() != 'Darwin':
logger.info("Failed to check if port is in use. " + str(e))
return False
return False


def close_subprocess_and_raise_cli_error(proc_subprocess, msg):
proc_subprocess.terminate()
raise CLIInternalError(msg)


def check_if_csp_is_running(clientproxy_process):
if clientproxy_process.poll() is None:
return True
else:
return False


def make_api_call_with_retries(uri, data, method, tls_verify, fault_type, summary, cli_error, clientproxy_process):
for i in range(consts.API_CALL_RETRIES):
try:
response = requests.request(method, uri, json=data, verify=tls_verify)
return response
except Exception as e:
time.sleep(5)
if i != consts.API_CALL_RETRIES - 1:
pass
else:
telemetry.set_exception(exception=e, fault_type=fault_type, summary=summary)
close_subprocess_and_raise_cli_error(clientproxy_process, cli_error + str(e))


def fetch_pop_publickey_kid(api_server_port, clientproxy_process):
requestbody = {}
poppublickey_uri = f'https://localhost:{api_server_port}/identity/poppublickey'
# Needed to prevent skip tls warning from printing to the console
original_stderr = sys.stderr
f = open(os.devnull, 'w')
sys.stderr = f

get_publickey_response = make_api_call_with_retries(poppublickey_uri, requestbody, "get", False, consts.Get_PublicKey_Info_Fault_Type,
'Failed to fetch public key info from clientproxy',
"Failed to fetch public key info from client proxy", clientproxy_process)

sys.stderr = original_stderr
publickey_info = json.loads(get_publickey_response.text)
kid = publickey_info['publicKey']['kid']

return kid


def fetch_and_post_at_to_csp(cmd, api_server_port, tenantId, kid, clientproxy_process):
req_cnfJSON = {"kid": kid, "xms_ksl": "sw"}
req_cnf = base64.urlsafe_b64encode(json.dumps(req_cnfJSON).encode('utf-8')).decode('utf-8')

# remove padding '=' character
if req_cnf[len(req_cnf) - 1] == '=':
req_cnf = req_cnf[:-1]

token_data = {"token_type": "pop", "key_id": kid, "req_cnf": req_cnf}
profile = Profile(cli_ctx=cmd.cli_ctx)
try:
credential, _, _ = profile.get_login_credentials(subscription_id=profile.get_subscription()["id"], resource=consts.KAP_1P_Server_App_Scope)
accessToken = credential.get_token(consts.KAP_1P_Server_App_Scope, data=token_data)
jwtToken = accessToken.token
except Exception as e:
telemetry.set_exception(exception=e, fault_type=consts.Post_AT_To_ClientProxy_Failed_Fault_Type,
summary='Failed to fetch access token using the PoP public key sent by client proxy')
close_subprocess_and_raise_cli_error(clientproxy_process, 'Failed to post access token to client proxy' + str(e))

jwtTokenData = {"accessToken": jwtToken, "serverId": consts.KAP_1P_Server_AppId, "tenantID": tenantId, "kid": kid}
post_at_uri = f'https://localhost:{api_server_port}/identity/at'
# Needed to prevent skip tls warning from printing to the console
original_stderr = sys.stderr
f = open(os.devnull, 'w')
sys.stderr = f
post_at_response = make_api_call_with_retries(post_at_uri, jwtTokenData, "post", False, consts.PublicKey_Export_Fault_Type, 'Failed to post access token to client proxy', "Failed to post access token to client proxy", clientproxy_process)

sys.stderr = original_stderr
return post_at_response


def insert_token_in_kubeconfig(data, token):
b64kubeconfig = data['kubeconfigs'][0]['value']
decoded_kubeconfig_str = b64decode(b64kubeconfig).decode("utf-8")
dict_yaml = yaml.safe_load(decoded_kubeconfig_str)
dict_yaml['users'][0]['user']['token'] = token
kubeconfig = yaml.dump(dict_yaml).encode("utf-8")
b64kubeconfig = b64encode(kubeconfig).decode("utf-8")
return b64kubeconfig


def check_process(processName):
'''
Check if there is any running process that contains the given name processName.
'''
for proc in process_iter():
try:
if proc.name().startswith(processName):
return True
except (NoSuchProcess, AccessDenied, ZombieProcess):
pass
return False
12 changes: 9 additions & 3 deletions src/connectedk8s/azext_connectedk8s/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,20 @@
CC_Provider_Namespace_Not_Registered_Fault_Type = "Connected Cluster Provider MS.K8 namespace not registered"
Default_Namespace_Does_Not_Exist_Fault_Type = "The default namespace defined in the kubeconfig doesn't exist on the kubernetes cluster."
ClusterConnect_Not_Present_Fault_Type = "cluster-connect-feature-unavailable"
CLIENT_PROXY_VERSION = '1.3.018101'
KAP_1P_Server_App_Scope = "6256c85f-0aad-4d50-b960-e6e9b21efe35/.default"
KAP_1P_Server_AppId = "6256c85f-0aad-4d50-b960-e6e9b21efe35"
Get_PublicKey_Info_Fault_Type = 'Error while fetching the PoP publickey information from client proxy'
PoP_Public_Key_Expried_Fault_Type = 'The PoP public key used to generate the at has expired'
Post_AT_To_ClientProxy_Failed_Fault_Type = 'Failed to post access token to client proxy'
AZ_CLI_ADAL_TO_MSAL_MIGRATE_VERSION = '2.30.0'
CLIENT_PROXY_VERSION = '1.3.018802'
API_SERVER_PORT = 47011
CLIENT_PROXY_PORT = 47010
CLIENTPROXY_CLIENT_ID = '04b07795-8ddb-461a-bbee-02f9e1bf7b46'
API_CALL_RETRIES = 12
DEFAULT_REQUEST_TIMEOUT = 10 # seconds
RELEASE_DATE_WINDOWS = 'release16-12-21'
RELEASE_DATE_LINUX = 'release16-12-21'
RELEASE_DATE_WINDOWS = 'release04-03-22'
RELEASE_DATE_LINUX = 'release04-03-22'
CSP_REFRESH_TIME = 300
# URL constants
CSP_Storage_Url = "https://k8sconnectcsp.azureedge.net"
Expand Down
25 changes: 25 additions & 0 deletions src/connectedk8s/azext_connectedk8s/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
from azext_connectedk8s._client_factory import _resource_client_factory, _resource_providers_client
import azext_connectedk8s._constants as consts
from kubernetes import client as kube_client
from azure.cli.core import get_default_cli
from packaging import version
from azure.cli.core.azclierror import CLIInternalError, ClientRequestError, ArgumentUsageError, ManualInterrupt, AzureResponseError, AzureInternalError, ValidationError

logger = get_logger(__name__)
Expand Down Expand Up @@ -444,3 +446,26 @@ def validate_node_api_response(api_instance, node_api_response):
return None
else:
return node_api_response


def az_cli(args_str):
args = args_str.split()
cli = get_default_cli()
cli.invoke(args, out_file=open(os.devnull, 'w'))
if cli.result.result:
return cli.result.result
elif cli.result.error:
raise Exception(cli.result.error)
return True


def is_cli_using_msal_auth():
response_cli_version = az_cli("version --output json")
try:
cli_version = response_cli_version['azure-cli']
except Exception as ex:
raise CLIInternalError("Unable to decode the az cli version installed: {}".format(str(ex)))
if version.parse(cli_version) >= version.parse(consts.AZ_CLI_ADAL_TO_MSAL_MIGRATE_VERSION):
return True
else:
return False
3 changes: 1 addition & 2 deletions src/connectedk8s/azext_connectedk8s/azext_metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"name": "connectedk8s",
"version": "1.2.1",
"azext.minCliCoreVersion": "2.16.0",
"azext.maxCliCoreVersion": "2.29.0"
"azext.minCliCoreVersion": "2.16.0"
}
Loading

0 comments on commit f13c495

Please sign in to comment.