''' Executes an arbitrary github gist on GCloudShell in ephimeral mode, no traces left in the docker containers Every restart of the Google Cloud Shell the machine appears to be a completely different one (ip, mac) Specs: 2x vCPU Intel Xeon @ 2.6 GHz 1GB Ram ''' # text.get_attribute("value") return the text content of a text field from qwiklabs.get_google_account import get_google_account from utils import global_vars, browser_manager import random import time from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium import webdriver class Shell(): def __init__(self): print("[GCShell] Created a new GShell instance ") Shell.TEXT_AREA_XPATH = '/html/body/cloud-shell-root/div/docked/stacked-layout/div[1]/div/devshell/terminal-container/div/xterm-terminal-tab/div/xterm-terminal/div/div/div/div[2]/div[1]/textarea' Shell.XTERM_SCREEN_XPATH = '/html/body/cloud-shell-root/div/docked/stacked-layout/div[1]/devshell/terminal-container/div/xterm-terminal-tab/div/xterm-terminal/div/div/div/div[2]' self.account_specs = get_google_account() self.driver = self.account_specs[3] self.account = (self.account_specs[0], self.account_specs[1]) self.gcs_link = self.account_specs[2] self.started = False def start(self): if self.account_specs is False: print("[GCShell] No account was available, can't start!") return # If using a test account, use the provided gsc link if global_vars.TEST_ACCOUNT is False: while (browser_manager.waitForElement(self.driver, By.XPATH, '/html/body/main/div/ql-drawer-container/ql-drawer-content/ql-drawer-container/ql-drawer[1]/ql-lab-control-panel') is False): print("[GCShell] Page not ready, waiting...") # get the shadow_dom element and click it panel = self.driver.find_element_by_xpath('/html/body/main/div/ql-drawer-container/ql-drawer-content/ql-drawer-container/ql-drawer[1]/ql-lab-control-panel') shadow1 = browser_manager.expand_shadow_element(self.driver, panel) open_shell = shadow1.find_element_by_css_selector('div:nth-child(2) > div > ql-button') print("[GCShell] Opening shell") open_shell.click() time.sleep(5) # Now switch to the second tab self.driver.switch_to.window(self.driver.window_handles[1]) #apparently clicking in selenium doesn't switch window else: print("[GCShell] Using Google Test Account") self.driver.get(self.gcs_link) time.sleep(5) oldPage = False #The email may be already filled in, just in case delete it and type it again while browser_manager.waitForElement(self.driver, By.CSS_SELECTOR, '#identifierId') is False: if browser_manager.waitForElement(self.driver, By.CSS_SELECTOR, '#Email') is not False: oldPage = True break print("[GCShell] Page not ready, waiting...") time.sleep(5) # print("Got old page?", oldPage) print("[GCShell] Inserting credentials") if oldPage: email = self.driver.find_element_by_css_selector('#Email') email.send_keys(Keys.CONTROL, 'a') email.send_keys(Keys.BACKSPACE) browser_manager.inputText(self.driver, By.CSS_SELECTOR, '#Email', self.account[0]) browser_manager.clickButton(self.driver, By.CSS_SELECTOR, '#next') time.sleep(5) #password is always empty, no need to empty the textbox first browser_manager.inputText(self.driver, By.CSS_SELECTOR, '#password', self.account[1]) browser_manager.clickButton(self.driver, By.CSS_SELECTOR, '#submit') else: email = self.driver.find_element_by_css_selector('#identifierId') email.send_keys(Keys.CONTROL, 'a') email.send_keys(Keys.BACKSPACE) browser_manager.inputText(self.driver, By.CSS_SELECTOR, '#identifierId', self.account[0]) browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[1]/div[1]/div[2]/div/div[2]/div/div/div[2]/div/div[2]/div/div[1]/div/div/button') time.sleep(5) #password is always empty, no need to empty the textbox first browser_manager.inputText(self.driver, By.XPATH, '//*[@id="password"]/div[1]/div/div[1]/input', self.account[1]) browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[1]/div[1]/div[2]/div/div[2]/div/div/div[2]/div/div[2]/div/div[1]/div/div/button') # Login Done: Accept the EULA browser_manager.clickButton(self.driver, By.XPATH, '//*[@id="accept"]') time.sleep(5) # Now we are in GCP. We need to accept some stuff (country, terms of use) # The form accepts keyboard input and anticipates it proposing a country. Make up a random string, send it. It will be out country # Select country button #there's no country starting with an 'x' alphabet = 'abcdefghijklmnopqrstuwyz' letters = 3 country = "" for i in range(0, letters): country += alphabet[random.randint(0, len(alphabet)-1)] #country += '\n' # print(country) browser_manager.inputText(self.driver, By.XPATH, '/html/body/div[3]/div[3]/div/mat-dialog-container/xap-deferred-loader-outlet/ng-component/mat-dialog-content/form/cfc-tos-checkboxes/form/div[1]/cfc-loader/div/mat-form-field/div/div[1]/div[3]/ace-select', country, interval=0, after_delay=2) #accept terms of use browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[3]/div[3]/div/mat-dialog-container/xap-deferred-loader-outlet/ng-component/mat-dialog-content/form/cfc-tos-checkboxes/form/div[2]/mat-checkbox/label/span[1]') #finally agree browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[3]/div[3]/div/mat-dialog-container/xap-deferred-loader-outlet/ng-component/mat-dialog-actions/ace-progress-button/div[1]/button') #wait for the page to load time.sleep(20) # Now open GCS, clicking the button browser_manager.clickButton(self.driver, By.XPATH, '//*[@id="pcc-devshell-container"]/xap-deferred-loader-outlet/pcc-platform-bar-devshell-button/pcc-platform-bar-button/button') time.sleep(10) # Sometimes another accept pops out browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[3]/div[3]/div/mat-dialog-container/xap-deferred-loader-outlet/ng-component/mat-dialog-content/form/cfc-tos-checkboxes/form/div[1]/cfc-loader/div/mat-form-field/div/div[1]/div[3]/ace-select') # Sometimes there's even an accept button inside the iframe, where the command area should be self.switch_to_shell_iframe() browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div/div[2]/div/mat-dialog-container/dialog-overlay/div[5]/modal-action/button') #wait for the machine to boot time.sleep(20) self.started = True # Done. This is ours to command now! # reboot to get a fresh machine (we are in ephimeral mode) def reboot(self): print("[GCShell] Going down for reboot NOW!") self.switch_to_shell_iframe() browser_manager.clickButton(self.driver, By.XPATH, '/html/body/cloud-shell-root/div/docked/stacked-layout/div[1]/devshell/devshell-toolbar/csh-header/mat-toolbar/csh-header-buttons/more-button/button') browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[3]/div[2]/div/div/div/button[1]') browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[3]/div[2]/div/mat-dialog-container/dialog-overlay/div[5]/modal-action[1]/button') rebooted = False t = 0 #wait for it to actually reboot while not rebooted: time.sleep(5) t += 5 try: self.get_text_area() rebooted = True except: print("[GCShell]{} seconds passed, VM still hasn't fully rebooted".format(t)) print("[GCShell] VM fully rebooted") def toggle_ephimeral(self): print("[GCShell] Toggling ephimeral mode...") #open menu browser_manager.clickButton(self.driver, By.XPATH, '/html/body/cloud-shell-root/div/docked/stacked-layout/div[1]/devshell/devshell-toolbar/csh-header/mat-toolbar/csh-header-buttons/more-button/button') #click ephimeral mode option browser_manager.clickButton(self.driver, By.XPATH, '//*[@id="mat-menu-panel-10"]/div/button[6]') #this only appears when enabling. Click to enable browser_manager.clickButton(self.driver, By.XPATH, '//*[@id="mat-slide-toggle-1"]') #This confirms enabling or disabling, depending if it's already enabled or not browser_manager.clickButton(self.driver, By.XPATH, '//*[@id="mat-dialog-0"]/dialog-overlay/div[5]/modal-action[1]/button') print("[GCShell] Toggled ephimeral mode...") # select the text area. This is where we can input commands # (assumes the window is still open) def switch_to_shell_iframe(self): #sometimes we're still in the iframe, so switching to main content is needed self.driver.switch_to.default_content() #switch to shell iframe frame = self.driver.find_element_by_xpath('/html/body/pan-shell/pcc-shell/cfc-panel-container/div/div/cfc-panel[1]/div[1]/div/div[3]/cfc-panel-container/div/div/cfc-panel/div/div/cfc-panel-container/div/div/cfc-panel[2]/div/div[1]/pcc-cloud-shell-wrapper/xap-deferred-loader-outlet/pcc-cloud-shell/div/div[2]/iframe') self.driver.switch_to.frame(frame) return frame def get_text_area(self): if self.started: self.switch_to_shell_iframe() return self.driver.find_element_by_xpath(Shell.TEXT_AREA_XPATH) return False def execute_command(self, command, stop_current=False): text_area = self.get_text_area() if text_area is False: print("[GCShell] No account were available, can't execute cmd!") return if stop_current: text_area.send_keys(Keys.CONTROL, 'c') time.sleep(5) #wait in case it takes a bit of time to stop browser_manager.inputText_no_find(self.driver, text_area, command + '\n', interval=0) def execute_python_custom_script(self, gist): gist.execute(self) # we use our own "language" to add information to the script (e.g. how much to wait before a command is complete) def execute_script_from_local(self, gist_path): print("[GCShell] Executing gist from", gist_path) with open(gist_path, "r") as file: for line in file.readlines(): line = line.strip().rstrip() print("[GCShell] Executing specific line:", line) # that's a comment, ignore by skipping to the next line if( line.startswith("#") ): print("[GCShell] Line was a comment, skipping") else: i1 = line.find('.') i2 = line.find('.',1) sleeptime = int(line[i1+1:i2]) command = line[i2+1:] print("[GCShell] Executing command:", line, "for", sleeptime, "seconds") self.execute_command(command, False) time.sleep(sleeptime) # take a raw text file from the url (github, pastebin possibly), run it using curl def execute_raw_gist(self, gist_url, arguments): arguments_str = "" for a in arguments: arguments_str += " " arguments_str += a command = gist_url + " " + arguments_str self.execute_command(command, stop_current=True)