From f98232cbbbbe583ce812f0df9b705ef7fe74d159 Mon Sep 17 00:00:00 2001 From: Patrick Lee <31744877+patricklee2@users.noreply.github.com> Date: Fri, 18 Jan 2019 11:48:00 -0800 Subject: [PATCH 1/3] webapp ssh, remove extra info text --- .../azure/cli/command_modules/appservice/custom.py | 2 +- .../azure/cli/command_modules/appservice/tunnel.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py index 1b735573dc3..d40d6bb9b7b 100644 --- a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py +++ b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py @@ -2315,7 +2315,7 @@ def _start_tunnel(tunnel_server): def _start_ssh(host_name, port, user_name): - subprocess.call("ssh -o StrictHostKeyChecking=no {}@{} -p {}".format(user_name, host_name, port), shell=True) + subprocess.call("ssh -q -o StrictHostKeyChecking=no {}@{} -p {}".format(user_name, host_name, port), shell=True) def ssh_webapp(cmd, resource_group_name, name, slot=None): # pylint: disable=too-many-statements diff --git a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/tunnel.py b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/tunnel.py index 15c663d2f11..109e45a4816 100644 --- a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/tunnel.py +++ b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/tunnel.py @@ -54,7 +54,7 @@ def __init__(self, local_addr, local_port, remote_addr, remote_user_name, remote self.sock.bind((self.local_addr, self.local_port)) if self.local_port == 0: self.local_port = self.sock.getsockname()[1] - logger.warning('Auto-selecting port: %s', self.local_port) + logger.info('Auto-selecting port: %s', self.local_port) logger.info('Finished initialization') def create_basic_auth(self): @@ -69,7 +69,7 @@ def is_port_open(self): if sock.connect_ex(('', self.local_port)) == 0: logger.info('Port %s is NOT open', self.local_port) else: - logger.warning('Port %s is open', self.local_port) + logger.info('Port %s is open', self.local_port) is_port_open = True return is_port_open @@ -117,7 +117,7 @@ def _listen(self): logger.info('Websocket tracing enabled') websocket.enableTrace(True) else: - logger.warning('Websocket tracing disabled, use --verbose flag to enable') + logger.info('Websocket tracing disabled, use --verbose flag to enable') websocket.enableTrace(False) self.ws = create_connection(host, sockopt=((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),), @@ -133,11 +133,11 @@ def _listen(self): debugger_thread.start() web_socket_thread.start() logger.info('Both debugger and websocket threads started...') - logger.warning('Successfully connected to local server..') + logger.info('Successfully connected to local server..') debugger_thread.join() web_socket_thread.join() logger.info('Both debugger and websocket threads stopped...') - logger.warning('Stopped local server..') + logger.info('Stopped local server..') def _listen_to_web_socket(self, client, ws_socket, index): while True: @@ -173,7 +173,7 @@ def _listen_to_client(self, client, ws_socket, index): ws_socket.send_binary(responseData) logger.info('Done sending to websocket, index: %s', index) else: - logger.warning('Client disconnected %s', index) + logger.info('Client disconnected %s', index) client.close() ws_socket.close() break From ba68500977cb8809ec8d0c4dc47f74028aa3ea0c Mon Sep 17 00:00:00 2001 From: Patrick Lee <31744877+patricklee2@users.noreply.github.com> Date: Fri, 18 Jan 2019 17:54:01 -0800 Subject: [PATCH 2/3] webapp ssh, wait for connections --- .../cli/command_modules/appservice/custom.py | 47 ++++++++++--------- .../cli/command_modules/appservice/tunnel.py | 8 ++-- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py index d40d6bb9b7b..f9e0020ce67 100644 --- a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py +++ b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py @@ -2253,11 +2253,11 @@ def _ping_scm_site(cmd, resource_group, name): requests.get(scm_url + '/api/settings', headers=authorization) -def _check_for_ready_tunnel(tunnel_server): - return tunnel_server.is_port_set_to_default() +def is_webapp_up(tunnel_server): + return tunnel_server.is_webapp_up() -def create_tunnel(cmd, resource_group_name, name, port=None, slot=None): +def create_tunnel_and_session(cmd, resource_group_name, name, port=None, slot=None): webapp = show_webapp(cmd, resource_group_name, name, slot) is_linux = webapp.reserved if not is_linux: @@ -2281,15 +2281,16 @@ def create_tunnel(cmd, resource_group_name, name, port=None, slot=None): tunnel_server = TunnelServer('', port, host_name, profile_user_name, profile_user_password) _ping_scm_site(cmd, resource_group_name, name) + _wait_for_webapp(tunnel_server) + t = threading.Thread(target=_start_tunnel, args=(tunnel_server,)) t.daemon = True t.start() - _wait_for_tunnel(tunnel_server, False) - logger.warning("SSH is available ( username: %s, password: %s )", ssh_user_name, ssh_user_password) + #_wait_for_tunnel(tunnel_server) - s = threading.Thread(target=_start_ssh, - args=('localhost', tunnel_server.get_port(), ssh_user_name)) + s = threading.Thread(target=_start_ssh_session, + args=('localhost', tunnel_server.get_port(), ssh_user_name, ssh_user_password)) s.daemon = True s.start() @@ -2297,25 +2298,29 @@ def create_tunnel(cmd, resource_group_name, name, port=None, slot=None): time.sleep(5) -def _wait_for_tunnel(tunnel_server, print_warnings): - if not _check_for_ready_tunnel(tunnel_server): - if print_warnings: - logger.warning('Tunnel is not ready yet, please wait (may take up to 1 minute)') - while True: - time.sleep(1) - if print_warnings: - logger.warning('.') - if _check_for_ready_tunnel(tunnel_server): - break +def _wait_for_webapp(tunnel_server): + tries = 0 + while True: + if is_webapp_up(tunnel_server): + break + if tries == 0: + logger.warning('Webapp is not ready yet, please wait') + tries = tries + 1 + logger.warning('.') + time.sleep(1) def _start_tunnel(tunnel_server): - _wait_for_tunnel(tunnel_server, True) tunnel_server.start_server() -def _start_ssh(host_name, port, user_name): - subprocess.call("ssh -q -o StrictHostKeyChecking=no {}@{} -p {}".format(user_name, host_name, port), shell=True) +def _start_ssh_session(host_name, port, user_name, user_password): + logger.warning("SSH username: %s, password: %s ", user_name, user_password) + while True: + #exit_code = subprocess.call("ssh -q -c aes128-cbc,3des-cbc,aes256-cbc -o StrictHostKeyChecking=no {}@{} -p {}".format(user_name, host_name, port), shell=True) + #if exit_code in [0, 130]: # success or ctrl+C + # break + time.sleep(1) def ssh_webapp(cmd, resource_group_name, name, slot=None): # pylint: disable=too-many-statements @@ -2323,4 +2328,4 @@ def ssh_webapp(cmd, resource_group_name, name, slot=None): # pylint: disable=to if platform.system() == "Windows": raise CLIError('webapp ssh is only supported on linux and mac') else: - create_tunnel(cmd, resource_group_name, name, port=None, slot=slot) + create_tunnel_and_session(cmd, resource_group_name, name, port=None, slot=slot) diff --git a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/tunnel.py b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/tunnel.py index 109e45a4816..da108a156fd 100644 --- a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/tunnel.py +++ b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/tunnel.py @@ -54,7 +54,7 @@ def __init__(self, local_addr, local_port, remote_addr, remote_user_name, remote self.sock.bind((self.local_addr, self.local_port)) if self.local_port == 0: self.local_port = self.sock.getsockname()[1] - logger.info('Auto-selecting port: %s', self.local_port) + logger.warning('Auto-selecting port: %s', self.local_port) logger.info('Finished initialization') def create_basic_auth(self): @@ -73,7 +73,7 @@ def is_port_open(self): is_port_open = True return is_port_open - def is_port_set_to_default(self): + def is_webapp_up(self): import certifi import urllib3 try: @@ -97,10 +97,10 @@ def is_port_set_to_default(self): msg = r.read().decode('utf-8') logger.info('Status response message: %s', msg) if 'FAIL' in msg.upper(): - logger.warning('WARNING - Remote debugging may not be setup properly. Reponse content: %s', msg) + logger.info('WARNING - Remote debugging may not be setup properly. Reponse content: %s', msg) + return False if '2222' in msg: return True - return False def _listen(self): self.sock.listen(100) From 69996036c38a96b145c455ab47dfffa801da3175 Mon Sep 17 00:00:00 2001 From: Patrick Lee <31744877+patricklee2@users.noreply.github.com> Date: Tue, 22 Jan 2019 15:27:37 -0800 Subject: [PATCH 3/3] passwordless --- .../azure-cli-appservice/HISTORY.rst | 1 + .../cli/command_modules/appservice/custom.py | 41 ++++++++++++++----- .../command_modules/appservice/fabric_license | 22 ++++++++++ .../cli/command_modules/appservice/tunnel.py | 40 +++++++++--------- .../azure-cli-appservice/setup.py | 2 + 5 files changed, 77 insertions(+), 29 deletions(-) create mode 100644 src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/fabric_license diff --git a/src/command_modules/azure-cli-appservice/HISTORY.rst b/src/command_modules/azure-cli-appservice/HISTORY.rst index 69c1adcb6c4..cfbaa56ccc9 100644 --- a/src/command_modules/azure-cli-appservice/HISTORY.rst +++ b/src/command_modules/azure-cli-appservice/HISTORY.rst @@ -3,6 +3,7 @@ Release History =============== * functionapp: add support for app insights on functionapp create +* webapp: bugfixes for webapp ssh 0.2.11 ++++++ diff --git a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py index f9e0020ce67..d55cdd95f9c 100644 --- a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py +++ b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py @@ -4,7 +4,6 @@ # -------------------------------------------------------------------------------------------- from __future__ import print_function -import subprocess import threading import time @@ -21,6 +20,9 @@ import sys import OpenSSL.crypto +from fabric import Connection + + from knack.prompting import prompt_pass, NoTTYException from knack.util import CLIError from knack.log import get_logger @@ -2287,8 +2289,6 @@ def create_tunnel_and_session(cmd, resource_group_name, name, port=None, slot=No t.daemon = True t.start() - #_wait_for_tunnel(tunnel_server) - s = threading.Thread(target=_start_ssh_session, args=('localhost', tunnel_server.get_port(), ssh_user_name, ssh_user_password)) s.daemon = True @@ -2304,7 +2304,9 @@ def _wait_for_webapp(tunnel_server): if is_webapp_up(tunnel_server): break if tries == 0: - logger.warning('Webapp is not ready yet, please wait') + logger.warning('Connection is not ready yet, please wait') + if tries == 60: + raise CLIError("Timeout Error, Unable to establish a connection") tries = tries + 1 logger.warning('.') time.sleep(1) @@ -2314,13 +2316,32 @@ def _start_tunnel(tunnel_server): tunnel_server.start_server() -def _start_ssh_session(host_name, port, user_name, user_password): - logger.warning("SSH username: %s, password: %s ", user_name, user_password) +def _start_ssh_session(hostname, port, username, password): + tries = 0 while True: - #exit_code = subprocess.call("ssh -q -c aes128-cbc,3des-cbc,aes256-cbc -o StrictHostKeyChecking=no {}@{} -p {}".format(user_name, host_name, port), shell=True) - #if exit_code in [0, 130]: # success or ctrl+C - # break - time.sleep(1) + try: + c = Connection(host=hostname, + port=port, + user=username, + # connect_timeout=60*10, + connect_kwargs={"password": password}) + break + except Exception as ex: # pylint: disable=broad-except + logger.info(ex) + if tries == 0: + logger.warning('Connection is not ready yet, please wait') + if tries == 60: + raise CLIError("Timeout Error, Unable to establish a connection") + tries = tries + 1 + logger.warning('.') + time.sleep(1) + try: + c.run('cat /etc/motd', pty=True) + c.run('source /etc/profile; /bin/ash', pty=True) + except Exception as ex: # pylint: disable=broad-except + logger.info(ex) + finally: + c.close() def ssh_webapp(cmd, resource_group_name, name, slot=None): # pylint: disable=too-many-statements diff --git a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/fabric_license b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/fabric_license new file mode 100644 index 00000000000..aa3e5a4f739 --- /dev/null +++ b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/fabric_license @@ -0,0 +1,22 @@ +Copyright (c) 2018 Jeff Forcier. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/tunnel.py b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/tunnel.py index da108a156fd..ff136dbba3b 100644 --- a/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/tunnel.py +++ b/src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/tunnel.py @@ -54,7 +54,7 @@ def __init__(self, local_addr, local_port, remote_addr, remote_user_name, remote self.sock.bind((self.local_addr, self.local_port)) if self.local_port == 0: self.local_port = self.sock.getsockname()[1] - logger.warning('Auto-selecting port: %s', self.local_port) + logger.info('Auto-selecting port: %s', self.local_port) logger.info('Finished initialization') def create_basic_auth(self): @@ -101,6 +101,7 @@ def is_webapp_up(self): return False if '2222' in msg: return True + return False def _listen(self): self.sock.listen(100) @@ -108,7 +109,7 @@ def _listen(self): basic_auth_string = self.create_basic_auth() while True: self.client, _address = self.sock.accept() - self.client.settimeout(1800) + self.client.settimeout(60 * 20) host = 'wss://{}{}'.format(self.remote_addr, '.scm.azurewebsites.net/AppServiceTunnel/Tunnel.ashx') basic_auth_header = 'Authorization: Basic {}'.format(basic_auth_string) cli_logger = get_logger() # get CLI logger which has the level set through command lines @@ -124,6 +125,7 @@ def _listen(self): class_=TunnelWebSocket, header=[basic_auth_header], sslopt={'cert_reqs': ssl.CERT_NONE}, + timeout=60 * 20, enable_multithread=True) logger.info('Websocket, connected status: %s', self.ws.connected) index = index + 1 @@ -140,8 +142,8 @@ def _listen(self): logger.info('Stopped local server..') def _listen_to_web_socket(self, client, ws_socket, index): - while True: - try: + try: + while True: logger.info('Waiting for websocket data, connection status: %s, index: %s', ws_socket.connected, index) data = ws_socket.recv() logger.info('Received websocket data: %s, index: %s', data, index) @@ -152,17 +154,17 @@ def _listen_to_web_socket(self, client, ws_socket, index): client.sendall(response) logger.info('Done sending to debugger, index: %s', index) else: - logger.info('Client disconnected!, index: %s', index) - client.close() - ws_socket.close() break - except: - client.close() - ws_socket.close() + except Exception as ex: # pylint: disable=broad-except + logger.info(ex) + finally: + logger.info('Client disconnected!, index: %s', index) + client.close() + ws_socket.close() def _listen_to_client(self, client, ws_socket, index): - while True: - try: + try: + while True: logger.info('Waiting for debugger data, index: %s', index) buf = bytearray(4096) nbytes = client.recv_into(buf, 4096) @@ -173,14 +175,14 @@ def _listen_to_client(self, client, ws_socket, index): ws_socket.send_binary(responseData) logger.info('Done sending to websocket, index: %s', index) else: - logger.info('Client disconnected %s', index) - client.close() - ws_socket.close() break - except: - traceback.print_exc(file=sys.stdout) - client.close() - ws_socket.close() + except Exception as ex: # pylint: disable=broad-except + logger.info(ex) + logger.warning("Connection Timed Out") + finally: + logger.info('Client disconnected %s', index) + client.close() + ws_socket.close() def start_server(self): self._listen() diff --git a/src/command_modules/azure-cli-appservice/setup.py b/src/command_modules/azure-cli-appservice/setup.py index 12f63b84352..d8fe38f697b 100644 --- a/src/command_modules/azure-cli-appservice/setup.py +++ b/src/command_modules/azure-cli-appservice/setup.py @@ -38,6 +38,8 @@ # v1.17 breaks on wildcard cert https://github.com/shazow/urllib3/issues/981 'urllib3[secure]>=1.18', 'xmltodict', + 'fabric>=2.4', + 'cryptography<2.5', 'pyOpenSSL', 'six', 'vsts-cd-manager<1.1.0',