def check_sa(service): sa = get_sa(service) echo(f"Associated service account: {sa}") result( f"Associated service account is not the default service account", details="Ensure a custom service account is associated to the service", success=("compute-" not in sa), )
def check_bucket(media_bucket): header("Object storage checks") sapi = build("storage", "v1") try: bucket = sapi.buckets().get(bucket=media_bucket).execute() result( f"Storage bucket {bucket['name']} exists in {bucket['location']}") except googleapiclient.errors.HttpError as e: result(f"Storage bucket error {e}", success=False)
def get_secret(project, secret_name): sm = sml.SecretManagerServiceClient() # using static library secret_path = f"projects/{project}/secrets/{secret_name}/versions/latest" try: payload = sm.access_secret_version( name=secret_path).payload.data.decode("UTF-8") return payload except exceptions.PermissionDenied as e: result(f"Secret error: {e}", success=False) return ""
def check_database(project, service, secrets): header("Database checks") database_name = service["spec"]["template"]["metadata"]["annotations"][ "run.googleapis.com/cloudsql-instances"] echo(f"Associated database: {database_name}") _, dbregion, dbinstance = database_name.split(":") result( f"Associated database instance matches secret connection URL instance", success=(secrets["dbinstance"] == database_name), ) dbapi = build("sqladmin", "v1beta4") instance = dbapi.instances().get(project=project, instance=dbinstance).execute() result( f"Instance exists: {instance['name']}, running {instance['databaseVersion']}" ) database = (dbapi.databases().get(project=project, instance=dbinstance, database=secrets["dbname"]).execute()) result( f"Database exists: {database['name']}, collation {database['collation']}" ) users = dbapi.users().list(project=project, instance=dbinstance).execute() result( f"User exists: {secrets['dbuser']}", details=users["items"], success=(secrets["dbuser"] in [user["name"] for user in users["items"]]), )
def check_roles(service, project): sa = f"serviceAccount:{get_sa(service)}" crm = build("cloudresourcemanager", "v1") iam = crm.projects().getIamPolicy(resource=f"{project}").execute() required_roles = ["roles/run.admin", "roles/cloudsql.client"] member_roles = [b["role"] for b in iam["bindings"] if sa in b["members"]] for role in required_roles: result( f"SA has {role}", details=f"Ensure SA has {role}", success=(role in member_roles), )
def check_bindings(service, project): sa = get_sa(service) echo(f"Associated service account (SA): {sa}") success = True crm = build("cloudresourcemanager", "v1") iam = crm.projects().getIamPolicy(resource=f"{project}").execute() for binding in iam["bindings"]: if binding["role"] == "roles/owner": for member in binding["members"]: if member == sa: success = False result( "SA doesn't have Owner role", details="Remove service account from having Owner role", success=success, )
def check_envvars(project, service): envvars = service["spec"]["template"]["spec"]["containers"][0]["env"] current_host = [x["value"] for x in envvars if x["name"] == "CURRENT_HOST"] if not current_host: result( "CURRENT_HOST envvar not found", details="Check the service environment variables.", success=False, ) else: host = current_host[0] service_host = service["status"]["url"] if host == service_host: result(f"CURRENT_HOST set to service URL ({host}).") else: result( f"CURRENT_HOST ({host}) and service URL ({service_host}) don't match.", success=False)
def check_run(service): header(f"Service configuration checks") sn = service["metadata"]["name"] result(f"Service {sn} exists")
def check_secrets(values): header("Settings checks") for key in ["DATABASE_URL", "GS_BUCKET_NAME", "SECRET_KEY"]: result(f"{key} is defined", success=(key in values.keys()))
def check_unicodex(project, service): header("Deployed service checks") fixture_code = "1F44B" fixture_slug = f"/u/{fixture_code}" login_slug = "/admin/login/?next=/admin/" model_admin_slug = "/admin/unicodex/codepoint/" if "url" not in service["status"].keys(): message = service["status"]["conditions"][0]["message"] result(f"Service does not have a deployed URL: {message}", success=False) else: url = service["status"]["url"] echo(f"Service deployment URL: {url}") try: response = httpx.get(url, timeout=30) except httpx.ReadTimeout as e: result(e, success=False) return print(cleanhtml(response.text)) if response.status_code == 200: result("Index page loaded successfully") else: result(f"Index page returns an error: {response.status_code}", success=False) if "Unicodex" in response.text: result("Index page contains 'Unicodex'") else: result("Index page does not contain the string 'Unicodex'", success=False) fixture = httpx.get(url + fixture_slug) print(cleanhtml(fixture.text)) admin = httpx.get(url + login_slug) if not admin.is_error: result(f"Django admin returns status okay ({admin.status_code})") else: result(f"Django admin returns an error: {admin.status_code}", success=False) if "Log in" in admin.text and "Django administration" in admin.text: result("Django admin login screen successfully loaded") else: result("Django admin login not found", success=False, details=admin.text) headers = {"Referer": url} with httpx.Client(headers=headers, follow_redirects=True, timeout=30) as client: # Login admin_username = get_secret(project, "SUPERUSER") admin_password = get_secret(project, "SUPERPASS") header("Test Django Admin") client.get(url + login_slug) response = client.post( url + login_slug, data={ "username": admin_username, "password": admin_password, "csrfmiddlewaretoken": client.cookies["csrftoken"], }, ) assert not response.is_error assert "Site administration" in response.text assert "Codepoints" in response.text result(f"Django Admin logged in") # Try admin action response = client.post( url + model_admin_slug, data={ "action": "generate_designs", "_selected_action": 1, "csrfmiddlewaretoken": client.cookies["csrftoken"], }, ) assert not response.is_error assert "Imported vendor versions" in response.text result(f"Django Admin action completed") # check updated feature response = client.get(url + f"/u/{fixture_code}") assert fixture_code in response.text assert "Android" in response.text result(f"Django Admin action verified") print(cleanhtml(response.text))
def check_deploy(project, service_name, region, secret_name): click.secho(f"🛠Checking {service_name} in {region} in {project}", bold=True) header(f"Service configuration checks") api = build("run", "v1") fqname = f"projects/{project}/locations/{region}/services/{service_name}" service = api.projects().locations().services().get(name=fqname).execute() sn = service["metadata"]["name"] result(f"Service {sn} exists") sa = service["spec"]["template"]["spec"]["serviceAccountName"] echo(f"Associated service account: {sa}") ### result( f"Associated service account is not default", details="Ensure a custom service account is associated to the service", success=("compute-" not in sa), ) ### success = True crm = build("cloudresourcemanager", "v1") iam = crm.projects().getIamPolicy(resource=f"{project}").execute() for binding in iam["bindings"]: if binding["role"] == "roles/owner": echo("Checking roles/owner bindings") for member in binding["members"]: echo(member, indent="> ") if member == sa: success = True result( "Associated service account permissions aren't owner", details="Ensure the service account isn't using owner permissions", success=success, ) ### header("Deployed service checks") url = service["status"]["url"] echo(f"Service deployment URL: {url}") page = httpx.get(url) if page.status_code == 200: result("Index page loaded successfully") else: result(f"Index page returns an error: {page.status_code}", success=False) if "Unicodex" in page.text: result("Index page contains 'Unicodex'") else: result("Index page does not contain the string 'Unicodex'", success=False) admin = httpx.get(url + "/admin") if admin.status_code == 200: result("Django admin returns status 200") else: result(f"Django admin returns an error: {page.status_code}", success=False) if "Log in" in admin.text and "Django administration" in admin.text: result("Django admin login screen successfully loaded") else: result("Django admin login not found", success=False, details=admin.text) ### header("Secret checks") sm = sml.SecretManagerServiceClient() # using static library secret_path = f"projects/{project}/secrets/{secret_name}/versions/latest" payload = sm.access_secret_version(name=secret_path).payload.data.decode("UTF-8") result(f"Secret {secret_path} exist") # https://github.com/theskumar/python-dotenv#in-memory-filelikes values = dotenv_values(stream=StringIO(payload)) for key in ["DATABASE_URL", "GS_BUCKET_NAME", "SECRET_KEY"]: result(f"{key} is defined", success=(key in values.keys())) secret_dburl = urlparse(values["DATABASE_URL"]) secret_dbuser = secret_dburl.netloc.split(":")[0] secret_dbinstance, secret_dbname = secret_dburl.path.split("/")[3:] media_bucket = values["GS_BUCKET_NAME"] ### header("Object storage checks") sapi = build("storage", "v1") bucket = sapi.buckets().get(bucket=media_bucket).execute() result(f"Storage bucket {bucket['name']} exists in {bucket['location']}") # TODO check bucket permissions. ### header("Database checks") database_name = service["spec"]["template"]["metadata"]["annotations"][ "run.googleapis.com/cloudsql-instances" ] echo(f"Associated database: {database_name}") _, dbregion, dbinstance = database_name.split(":") result( f"Associated database instance matches secret connection URL instance", success=(secret_dbinstance == database_name), ) dbapi = build("sqladmin", "v1beta4") instance = dbapi.instances().get(project=project, instance=dbinstance).execute() result( f"Instance exists: {instance['name']}, running {instance['databaseVersion']}" ) database = ( dbapi.databases() .get(project=project, instance=dbinstance, database=secret_dbname) .execute() ) result(f"Database exists: {database['name']}, collation {database['collation']}") users = dbapi.users().list(project=project, instance=dbinstance).execute() result( f"User exists: {secret_dbuser}", details=users["items"], success=(secret_dbuser in [user["name"] for user in users["items"]]), ) # All checks complete; show results. summary()