Module framework.project

Source code
import os
import time
import sys

import google.auth
from googleapiclient import discovery

from .config import cfg
from .cloudhelpers import iam


# Permissions to check if account has owner access
check_permissions = [
    'iam.roles.create', 'iam.roles.delete', 'iam.roles.get', 'iam.roles.list', 'iam.roles.undelete', 'iam.roles.update', 'iam.serviceAccounts.actAs', 'iam.serviceAccounts.create', 'iam.serviceAccounts.delete', 'iam.serviceAccounts.get', 'iam.serviceAccounts.getIamPolicy', 'iam.serviceAccounts.list', 'iam.serviceAccounts.setIamPolicy', 'iam.serviceAccounts.update', 'logging.logs.delete', 'logging.logs.list', 'resourcemanager.projects.createBillingAssignment', 'resourcemanager.projects.delete', 'resourcemanager.projects.deleteBillingAssignment', 'resourcemanager.projects.get', 'resourcemanager.projects.getIamPolicy', 'resourcemanager.projects.setIamPolicy', 'resourcemanager.projects.undelete', 'resourcemanager.projects.update', 'resourcemanager.projects.updateLiens', 'storage.buckets.create', 'storage.buckets.delete', 'storage.buckets.list', 'serviceusage.services.enable'
]


def test_application_default_credentials(tctf_project=None):
    '''Tests to make sure the Thunder CTF config project and gcloud CLI project are the same, and that the application default credentials give owner access to the project.

    Parameters:
        tctf_project (str, optional): Overrides Thunder CTF config project id. If not supplied, the project will be read from "core/framework/config/project.txt"
    '''
    # Query user to delete environment variable
    if 'GOOGLE_APPLICATION_CREDENTIALS' in os.environ:
        if 'y' == input(f'GOOGLE_APPLICATION_CREDENTIALS is set, meaning the application default credentials will use a service account. '
                        'Unless the service account has owner access, the command will fail.'
                        'Would you like to unset GOOGLE_APPLICATION_CREDENTIALS? [y/n] ').lower().strip()[0]:
            del os.environ['GOOGLE_APPLICATION_CREDENTIALS']
    # Try to extract application default credentials
    try:
        credentials, project_id = google.auth.default()
    except google.auth.exceptions.DefaultCredentialsError:
        exit('Application default credentials not set. To set credentials, run:\n'
             '  gcloud auth application-default login')

    # Make sure application default project is the same as the project in thunder ctf config
    if not tctf_project:
        tctf_project = cfg.get_project()
    if not project_id:
        exit('You must the set the gcloud cli project: \n'
             '  gcloud config set project [project-id]')
    if not tctf_project == project_id:
        exit('gcloud CLI project ID is not equal to Thunder CTF config project ID'
             f'gcloud CLI project id: {project_id if not project_id=="" else "None"}\n'
             f'Thunder CTF project id: {tctf_project if not tctf_project=="" else "None"}.\n'
             'To change gcloud cli project, run:\n'
             '  gcloud config set project=[project-id]\n'
             'To change the Thunder CTF project, run:\n'
             '  python3 thunder.py activate_project [project-id]')
    # Build api object
    crm_api = discovery.build('cloudresourcemanager',
                              'v1', credentials=credentials)
    # Check if credentials have permissions
    response = crm_api.projects().testIamPermissions(resource=project_id, body={
        'permissions': check_permissions}).execute()
    if 'permissions' in response:
        if len(response['permissions']) == len(check_permissions):
            return True
    # If credentials don't have necessary permissions, exit
    exit(f'Application default account should have owner role on project {project_id}.\n'
         'make sure you spelled the project ID correctly and the account')


def setup_project():
    '''Enables necessary Google Cloud APIs, 
        gives the deployment manager owner permission on the gcloud cli config project, 
        and adds the default-allow-http firewall rule.'''
    credentials, project_id = google.auth.default()
    # Build api object
    crm_api = discovery.build('cloudresourcemanager',
                              'v1', credentials=credentials)
    # Get project number
    project_num = crm_api.projects().get(
        projectId=project_id).execute()['projectNumber']
    # Build api object
    services_api = discovery.build(
        'serviceusage', 'v1', credentials=credentials)
    # Enable apis
    apis = [
        'cloudapis.googleapis.com',
        'cloudfunctions.googleapis.com',
        'cloudresourcemanager.googleapis.com',
        'compute.googleapis.com',
        'datastore.googleapis.com',
        'iam.googleapis.com',
        'iamcredentials.googleapis.com',
        'logging.googleapis.com',
        'deploymentmanager.googleapis.com',
        'storage-api.googleapis.com',
        'storage-component.googleapis.com'
    ]
    request_body = {'serviceIds': apis}
    op_name = services_api.services().batchEnable(
        parent=f'projects/{project_num}', body=request_body).execute()['name']
    _wait_for_api_op(op_name, services_api)
    # Set deployment manager service account as owner
    iam.set_account_iam(
        f'{project_num}@cloudservices.gserviceaccount.com', ['roles/owner'])

    # Add the default-allow-http firewall rule
    firewall_body = {'allowed':
                 [{'IPProtocol': 'tcp',
                   'ports': ['80']}],
                 'direction': 'INGRESS',
                 'disabled': False,
                 'logConfig': {
                     'enable': False},
                 'name': 'default-allow-http',
                 'sourceRanges': ['0.0.0.0/0'],
                 'targetTags': ['http-server']}
    compute_api = discovery.build('compute', 'v1', credentials=credentials)
    compute_api.firewalls().insert(project=project_id, body=firewall_body).execute()


def _wait_for_api_op(op_name, services_api):
    # Wait till operation finishes, giving updates every 5 seconds
    op_done = False
    t = 0
    start_time = time.time()
    time_string = ''
    while not op_done:
        time_string = f'[{int(t/60)}m {(t%60)//10}{t%10}s]'
        sys.stdout.write(
            f'\r{time_string} Enabling APIs...')
        t += 5
        while t < time.time()-start_time:
            t += 5
        time.sleep(t-(time.time()-start_time))
        # Check to see if the operation has finished
        response = services_api.operations().get(
            name=op_name).execute()
        if not 'done' in response:
            op_done = False
        else:
            op_done = response['done']
    sys.stdout.write(
        f'\r{time_string} Enabling APIs... Done\n')

Functions

def setup_project()

Enables necessary Google Cloud APIs, gives the deployment manager owner permission on the gcloud cli config project, and adds the default-allow-http firewall rule.

Source code
def setup_project():
    '''Enables necessary Google Cloud APIs, 
        gives the deployment manager owner permission on the gcloud cli config project, 
        and adds the default-allow-http firewall rule.'''
    credentials, project_id = google.auth.default()
    # Build api object
    crm_api = discovery.build('cloudresourcemanager',
                              'v1', credentials=credentials)
    # Get project number
    project_num = crm_api.projects().get(
        projectId=project_id).execute()['projectNumber']
    # Build api object
    services_api = discovery.build(
        'serviceusage', 'v1', credentials=credentials)
    # Enable apis
    apis = [
        'cloudapis.googleapis.com',
        'cloudfunctions.googleapis.com',
        'cloudresourcemanager.googleapis.com',
        'compute.googleapis.com',
        'datastore.googleapis.com',
        'iam.googleapis.com',
        'iamcredentials.googleapis.com',
        'logging.googleapis.com',
        'deploymentmanager.googleapis.com',
        'storage-api.googleapis.com',
        'storage-component.googleapis.com'
    ]
    request_body = {'serviceIds': apis}
    op_name = services_api.services().batchEnable(
        parent=f'projects/{project_num}', body=request_body).execute()['name']
    _wait_for_api_op(op_name, services_api)
    # Set deployment manager service account as owner
    iam.set_account_iam(
        f'{project_num}@cloudservices.gserviceaccount.com', ['roles/owner'])

    # Add the default-allow-http firewall rule
    firewall_body = {'allowed':
                 [{'IPProtocol': 'tcp',
                   'ports': ['80']}],
                 'direction': 'INGRESS',
                 'disabled': False,
                 'logConfig': {
                     'enable': False},
                 'name': 'default-allow-http',
                 'sourceRanges': ['0.0.0.0/0'],
                 'targetTags': ['http-server']}
    compute_api = discovery.build('compute', 'v1', credentials=credentials)
    compute_api.firewalls().insert(project=project_id, body=firewall_body).execute()
def test_application_default_credentials(tctf_project=None)

Tests to make sure the Thunder CTF config project and gcloud CLI project are the same, and that the application default credentials give owner access to the project.

Parameters

tctf_project : str, optional
Overrides Thunder CTF config project id. If not supplied, the project will be read from "core/framework/config/project.txt"
Source code
def test_application_default_credentials(tctf_project=None):
    '''Tests to make sure the Thunder CTF config project and gcloud CLI project are the same, and that the application default credentials give owner access to the project.

    Parameters:
        tctf_project (str, optional): Overrides Thunder CTF config project id. If not supplied, the project will be read from "core/framework/config/project.txt"
    '''
    # Query user to delete environment variable
    if 'GOOGLE_APPLICATION_CREDENTIALS' in os.environ:
        if 'y' == input(f'GOOGLE_APPLICATION_CREDENTIALS is set, meaning the application default credentials will use a service account. '
                        'Unless the service account has owner access, the command will fail.'
                        'Would you like to unset GOOGLE_APPLICATION_CREDENTIALS? [y/n] ').lower().strip()[0]:
            del os.environ['GOOGLE_APPLICATION_CREDENTIALS']
    # Try to extract application default credentials
    try:
        credentials, project_id = google.auth.default()
    except google.auth.exceptions.DefaultCredentialsError:
        exit('Application default credentials not set. To set credentials, run:\n'
             '  gcloud auth application-default login')

    # Make sure application default project is the same as the project in thunder ctf config
    if not tctf_project:
        tctf_project = cfg.get_project()
    if not project_id:
        exit('You must the set the gcloud cli project: \n'
             '  gcloud config set project [project-id]')
    if not tctf_project == project_id:
        exit('gcloud CLI project ID is not equal to Thunder CTF config project ID'
             f'gcloud CLI project id: {project_id if not project_id=="" else "None"}\n'
             f'Thunder CTF project id: {tctf_project if not tctf_project=="" else "None"}.\n'
             'To change gcloud cli project, run:\n'
             '  gcloud config set project=[project-id]\n'
             'To change the Thunder CTF project, run:\n'
             '  python3 thunder.py activate_project [project-id]')
    # Build api object
    crm_api = discovery.build('cloudresourcemanager',
                              'v1', credentials=credentials)
    # Check if credentials have permissions
    response = crm_api.projects().testIamPermissions(resource=project_id, body={
        'permissions': check_permissions}).execute()
    if 'permissions' in response:
        if len(response['permissions']) == len(check_permissions):
            return True
    # If credentials don't have necessary permissions, exit
    exit(f'Application default account should have owner role on project {project_id}.\n'
         'make sure you spelled the project ID correctly and the account')