GShellAutomator/Automator/shell/shell.py

255 lines
12 KiB
Python

'''
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)