Support for CAA levels

This commit is contained in:
Jay Lee
2022-04-07 00:48:40 +00:00
parent 0a03fbb82e
commit 119538c10c
4 changed files with 287 additions and 1 deletions

View File

@ -55,6 +55,7 @@ from gam import auth
from gam import controlflow from gam import controlflow
from gam import display from gam import display
from gam import fileutils from gam import fileutils
from gam.gapi import caa as gapi_caa
from gam.gapi import calendar as gapi_calendar from gam.gapi import calendar as gapi_calendar
from gam.gapi import cloudidentity as gapi_cloudidentity from gam.gapi import cloudidentity as gapi_cloudidentity
from gam.gapi import cbcm as gapi_cbcm from gam.gapi import cbcm as gapi_cbcm
@ -848,7 +849,7 @@ def _getSvcAcctData():
controlflow.system_error_exit(6, None) controlflow.system_error_exit(6, None)
GM_Globals[GM_OAUTH2SERVICE_JSON_DATA] = json.loads(json_string) GM_Globals[GM_OAUTH2SERVICE_JSON_DATA] = json.loads(json_string)
jwt_apis = ['chat'] # APIs which can handle OAuthless JWT tokens jwt_apis = ['chat', 'accesscontextmanager'] # APIs which can handle OAuthless JWT tokens
def getSvcAcctCredentials(scopes, act_as, api=None): def getSvcAcctCredentials(scopes, act_as, api=None):
try: try:
_getSvcAcctData() _getSvcAcctData()
@ -11374,6 +11375,8 @@ def ProcessGAMCommand(args):
gapi_directory_printers.create() gapi_directory_printers.create()
elif argument in ['chatmessage']: elif argument in ['chatmessage']:
gapi_chat.create_message() gapi_chat.create_message()
elif argument in ['caalevel']:
gapi_caa.create_access_level()
else: else:
controlflow.invalid_argument_exit(argument, 'gam create') controlflow.invalid_argument_exit(argument, 'gam create')
sys.exit(0) sys.exit(0)
@ -11438,6 +11441,8 @@ def ProcessGAMCommand(args):
gapi_directory_printers.update() gapi_directory_printers.update()
elif argument in ['chatmessage']: elif argument in ['chatmessage']:
gapi_chat.update_message() gapi_chat.update_message()
elif argument in ['caalevel']:
gapi_caa.update_access_level()
else: else:
controlflow.invalid_argument_exit(argument, 'gam update') controlflow.invalid_argument_exit(argument, 'gam update')
sys.exit(0) sys.exit(0)
@ -11578,6 +11583,8 @@ def ProcessGAMCommand(args):
gapi_chromepolicy.delete_policy() gapi_chromepolicy.delete_policy()
elif argument == 'chatmessage': elif argument == 'chatmessage':
gapi_chat.delete_message() gapi_chat.delete_message()
elif argument == 'caalevel':
gapi_caa.delete_access_level()
else: else:
controlflow.invalid_argument_exit(argument, 'gam delete') controlflow.invalid_argument_exit(argument, 'gam delete')
sys.exit(0) sys.exit(0)
@ -11699,6 +11706,8 @@ def ProcessGAMCommand(args):
gapi_chat.print_spaces() gapi_chat.print_spaces()
elif argument in ['chatmembers']: elif argument in ['chatmembers']:
gapi_chat.print_members() gapi_chat.print_members()
elif argument in ['caalevels']:
gapi_caa.print_access_levels()
else: else:
controlflow.invalid_argument_exit(argument, 'gam print') controlflow.invalid_argument_exit(argument, 'gam print')
sys.exit(0) sys.exit(0)

244
src/gam/gapi/caa.py Normal file
View File

@ -0,0 +1,244 @@
import string
import sys
import googleapiclient.errors
import gam
from gam.var import *
from gam import controlflow
from gam import display
from gam import fileutils
from gam import gapi
from gam import utils
from gam.gapi import errors as gapi_errors
from gam.gapi import cloudresourcemanager as gapi_crm
THROW_REASONS = [gapi_errors.ErrorReason.FOUR_O_THREE]
def build():
return gam.buildGAPIServiceObject('accesscontextmanager',
act_as=None)
def get_access_policy(caa=None):
if not caa:
build()
parent = gapi_crm.get_org_id()
try:
aps = gapi.get_all_pages(caa.accessPolicies(),
'list',
'accessPolicies',
throw_reasons=THROW_REASONS,
parent=parent,
fields='accessPolicies/name')
except googleapiclient.errors.HttpError:
controlflow.system_error_exit(2, 'Your service account needs the Access Context Manager Reader or Editor role for your organization.')
if len(aps) != 1:
print(f'expected 1 access policy, got {len(aps)}.')
return
return aps[0]['name']
def print_access_levels():
caa = build()
ap_name = get_access_policy(caa)
try:
levels = gapi.get_all_pages(caa.accessPolicies().accessLevels(),
'list',
'accessLevels',
throw_reasons=THROW_REASONS,
parent=ap_name,
accessLevelFormat='CEL', fields='*')
except googleapiclient.errors.HttpError:
controlflow.system_error_exit(2, 'Your service account needs the Access Context Manager Reader or Editor role for your organization.')
for level in levels:
display.print_json(level)
print()
def build_os_constraints(constraints):
consts_obj = []
constraints = constraints.upper().split(',')
valid_os_types = ['DESKTOP_MAC', 'DESKTOP_WINDOWS', 'DESKTOP_LINUX', 'DESKTOP_CHROMEOS', 'ANDROID', 'IOS']
for constraint in constraints:
new_const = {}
new_const['osType'], new_const['minimumVersion'] = constraint.split(':')
if new_const['osType'] == 'VERIFIED_DESKTOP_CHROME_OS':
new_const['osType'] = 'DESKTOP_CHROME_OS'
new_const['requireVerifiedChromeOs'] = True
if new_const['osType'] not in valid_os_types:
controlflow.system_error_exit(2, f'expected os type of {", ".join(valid_os_types)} got {new_const["osType"]}')
consts_obj.append(new_const)
return consts_obj
def build_device_policy(i, schemas):
device_policy = {}
while True:
myarg = sys.argv[i].replace('_', '').lower()
if myarg == 'requirescreenlock':
device_policy['requireScreenLock'] = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg == 'allowedencryptionstatuses':
allowed_statuses = gapi.get_enum_values_minus_unspecified(schemas["DevicePolicy"]["properties"]["allowedEncryptionStatuses"]["items"]["enum"])
device_policy['allowedEncryptionStatuses'] = sys.argv[i+1].upper().split(',')
for status in device_policy['allowedEncryptionStatuses']:
if status not in allowed_statuses:
controlflow.system_error_exit(2, f'expected encryption status of {", ".join(allowed_statuses)} got {status}')
i += 2
elif myarg == 'osconstraints':
device_policy['osConstraints'] = build_os_constraints(sys.argv[i+1])
i += 2
elif myarg == 'alloweddevicemanagementlevels':
allowed_levels = gapi.get_enum_values_minus_unspecified(schemas["DevicePolicy"]["properties"]["allowedDeviceManagementLevels"]["items"]["enum"])
device_policy['allowedDeviceManagementLevels'] = sys.argv[i+1].upper().split(',')
for level in device_policy['allowedDeviceManagementLevels']:
if level == 'ADVANCED':
level == 'COMPLETE'
if level not in allowed_levels:
controlflow.system_error_exit(2, f'expected device management level of {", ".join(allowed_levels)} got {level}')
i += 2
elif myarg == 'requireadminapproval':
device_policy['requireAdminApproval'] = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg == 'requirecorpowned':
device_policy['requireCorpOwned'] = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg == 'enddevicepolicy':
i += 1
break
else:
controlflow.invalid_argument_exit(myarg, 'gam create/update caalevel')
return i, device_policy
def build_condition(i, schemas):
condition = {}
while True:
myarg = sys.argv[i].replace('_', '').lower()
if myarg == 'ipsubnetworks':
condition['ipSubnetworks'] = sys.argv[i+1].split(',')
i += 2
elif myarg == 'devicepolicy':
i += 1
i, condition['devicePolicy'] = build_device_policy(i, schemas)
elif myarg == 'requiredaccesslevels':
condition['requiredaccesslevels'] = sys.argv[i+1].split(',')
i += 2
elif myarg == 'negate':
condition['negate'] = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg == 'members':
condition['members'] = sys.argv[i+1].split(',')
i += 2
elif myarg == 'regions':
condition['regions'] = sys.argv[i+1].split(',')
i += 2
elif myarg == 'endcondition':
i += 1
break
else:
controlflow.invalid_argument_exit(myarg, 'gam create/update caalevel')
return i, condition
def build_basic_level(i, schemas):
basic_level = {'conditions': []}
valid_functions = gapi.get_enum_values_minus_unspecified(schemas['BasicLevel']['properties']['combiningFunction']['enum'])
while i < len(sys.argv):
myarg = sys.argv[i].replace('_', '').lower()
if myarg == 'combiningfunction':
combiningFunction = sys.argv[i+1].upper()
if combiningFunction not in valid_functions:
controlflow.system_error_exit(2, f'expected combining function of {",".join(valid_functions)} got {combiningFunction}')
basic_level['combiningFunction'] = combiningFunction
i += 2
elif myarg == 'condition':
i += 1
i, condition = build_condition(i, schemas)
basic_level['conditions'].append(condition)
else:
controlflow.invalid_argument_exit(myarg, 'gam create/update caalevel')
return i, basic_level
def create_access_level():
caa = build()
ap_name = get_access_policy(caa)
title = sys.argv[3].replace(' ', '_')
custom = {'expr': {'expression': sys.argv[4], 'title': 'expr'}}
allowed_title_chars = string.ascii_letters + string.digits + '_'
name = ''.join([c for c in title if c in allowed_title_chars])[:49]
name = f'{ap_name}/accessLevels/{name}'
body = {
'name': name,
'title': title,
}
i = 4
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'basic':
schemas = caa._rootDesc['schemas']
i += 1
i, body['basic'] = build_basic_level(i, schemas)
elif myarg == 'custom':
body['custom'] = {'expr': {'expression': sys.argv[i+1], 'title': 'expr'}}
i += 2
else:
controlflow.invalid_argument_exit(myarg, 'gam create caalevel')
print(f'Creating access level {name}...')
try:
gapi.call(caa.accessPolicies().accessLevels(),
'create',
throw_reasons=THROW_REASONS,
parent=ap_name,
body=body)
except googleapiclient.errors.HttpError:
controlflow.system_error_exit(2, 'Your service account needs the Access Context Manager Editor role for your organization.')
def update_access_level():
caa = build()
name = sys.argv[3]
if not name.startswith('accessPolicies/'):
ap_name = get_access_policy(caa)
name = f'{ap_name}/accessLevels/{name}'
body = {}
i = 4
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'basic':
schemas = caa._rootDesc['schemas']
i += 1
i, body['basic'] = build_basic_level(i, schemas)
elif myarg == 'custom':
body['custom'] = {'expr': {'expression': sys.argv[i+1], 'title': 'expr'}}
i += 2
else:
controlflow.invalid_argument_exit(myarg, 'gam update caalevel')
updateMask = ','.join(body.keys())
print(f'Updating access level {name}...')
try:
gapi.call(caa.accessPolicies().accessLevels(),
'patch',
throw_reasons=THROW_REASONS,
name=name,
updateMask=updateMask,
body=body)
except googleapiclient.errors.HttpError:
controlflow.system_error_exit(2, 'Your service account needs the Access Context Manager Editor role for your organization.')
def delete_access_level():
caa = build()
name = sys.argv[3]
if not name.startswith('accessPolicies/'):
ap_name = get_access_policy(caa)
name = f'{ap_name}/accessLevels/{name}'
print('Deleting access level {name}...')
try:
gapi.call(caa.accessPolicies().accessLevels(),
'delete',
name=name)
except googleapiclient.errors.HttpError:
controlflow.system_error_exit(2, 'Your service account needs the Access Context Manager Editor role for your organization.')

View File

@ -0,0 +1,31 @@
import string
import sys
import googleapiclient.errors
import gam
from gam.var import *
from gam import controlflow
from gam import display
from gam import fileutils
from gam import gapi
from gam import utils
from gam.gapi import errors as gapi_errors
from gam.gapi.directory import customer as gapi_directory_customer
def build():
return gam.buildGAPIServiceObject('cloudresourcemanager',
act_as=None)
def get_org_id():
gapi_directory_customer.setTrueCustomerId()
crm = build()
query = f'directorycustomerid:{GC_Values[GC_CUSTOMER_ID]}'
orgs = gapi.get_all_pages(crm.organizations(),
'search',
'organizations',
query=query)
if len(orgs) < 1:
controlflow.system_error_exit(2, 'Your service account needs permission to read org id')
return orgs[0]['name']

View File

@ -1,3 +1,4 @@
accesscontextmanager.googleapis.com.
admin.googleapis.com admin.googleapis.com
alertcenter.googleapis.com alertcenter.googleapis.com
calendar-json.googleapis.com calendar-json.googleapis.com
@ -6,6 +7,7 @@ chromemanagement.googleapis.com
chromepolicy.googleapis.com chromepolicy.googleapis.com
classroom.googleapis.com classroom.googleapis.com
cloudidentity.googleapis.com cloudidentity.googleapis.com
cloudresourcemanager.googleapis.com
contacts.googleapis.com contacts.googleapis.com
drive.googleapis.com drive.googleapis.com
driveactivity.googleapis.com driveactivity.googleapis.com