Initial support for delegated admin service accounts (DASA)

Google now allows GCP service accounts to be granted delegated admin status for a G Suite domain.
To use this, admins can grant the service account email address delegated admin rights in the admin console
and then set some environment variables for GAM to use:

OAUTHFILE=oauth2service.json
GA_DOMAIN=example.com   # your primary domain name in Google
CUSTOMER_ID=1d80dfc     # admin.google.com > Account > Account settings > Customer ID
This commit is contained in:
Jay Lee
2020-09-10 11:25:59 -04:00
parent 630abbd0fc
commit 244398e096
5 changed files with 135 additions and 66 deletions

View File

@ -64,6 +64,7 @@ from gam.gapi.directory import mobiledevices as gapi_directory_mobiledevices
from gam.gapi.directory import orgunits as gapi_directory_orgunits
from gam.gapi.directory import privileges as gapi_directory_privileges
from gam.gapi.directory import resource as gapi_directory_resource
from gam.gapi.directory import roles as gapi_directory_roles
from gam.gapi import siteverification as gapi_siteverification
from gam.gapi import errors as gapi_errors
from gam.gapi import reports as gapi_reports
@ -843,14 +844,14 @@ def getOauth2TxtStorageCredentials():
return None
def getValidOauth2TxtCredentials(force_refresh=False):
def getValidOauth2TxtCredentials(force_refresh=False, api=None):
"""Gets OAuth2 credentials which are guaranteed to be fresh and valid."""
try:
credentials = auth.get_admin_credentials()
credentials = auth.get_admin_credentials(api)
except gam.auth.oauth.InvalidCredentialsFileError:
doRequestOAuth() # Make a new request which should store new creds.
return getValidOauth2TxtCredentials(force_refresh=force_refresh)
return getValidOauth2TxtCredentials(force_refresh=force_refresh,
api=api)
if credentials.expired or force_refresh:
request = transport.create_request()
credentials.refresh(request)
@ -922,7 +923,7 @@ def getService(api, http):
def buildGAPIObject(api):
GM_Globals[GM_CURRENT_API_USER] = None
credentials = getValidOauth2TxtCredentials()
credentials = getValidOauth2TxtCredentials(api=getAPIVersion(api)[0])
credentials.user_agent = GAM_INFO
http = transport.AuthorizedHttp(
credentials, transport.create_http(cache=GM_Globals[GM_CACHE_DIR]))
@ -1647,7 +1648,8 @@ def doCreateAdmin():
', '.join(['customer', 'org_unit']),
body['scopeType'])
if body['scopeType'] == 'ORG_UNIT':
orgUnit, orgUnitId = gapi_directory_orgunits.getOrgUnitId(sys.argv[6], cd)
orgUnit, orgUnitId = gapi_directory_orgunits.getOrgUnitId(
sys.argv[6], cd)
body['orgUnitId'] = orgUnitId[3:]
scope = f'ORG_UNIT {orgUnit}'
else:
@ -1659,37 +1661,6 @@ def doCreateAdmin():
body=body)
def doPrintAdminRoles():
cd = buildGAPIObject('directory')
todrive = False
titles = [
'roleId', 'roleName', 'roleDescription', 'isSuperAdminRole',
'isSystemRole'
]
fields = f'nextPageToken,items({",".join(titles)})'
csvRows = []
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam print adminroles')
roles = gapi.get_all_pages(cd.roles(),
'list',
'items',
customer=GC_Values[GC_CUSTOMER_ID],
fields=fields)
for role in roles:
role_attrib = {}
for key, value in list(role.items()):
role_attrib[key] = value
csvRows.append(role_attrib)
display.write_csv_file(csvRows, titles, 'Admin Roles', todrive)
def doPrintAdmins():
cd = buildGAPIObject('directory')
roleId = None
@ -1731,7 +1702,9 @@ def doPrintAdmins():
admin_attrib['role'] = role_from_roleid(value)
elif key == 'orgUnitId':
value = f'id:{value}'
admin_attrib['orgUnit'] = gapi_directory_orgunits.orgunit_from_orgunitid(value)
admin_attrib[
'orgUnit'] = gapi_directory_orgunits.orgunit_from_orgunitid(
value)
admin_attrib[key] = value
csvRows.append(admin_attrib)
display.write_csv_file(csvRows, titles, 'Admins', todrive)
@ -6517,7 +6490,8 @@ def getUserAttributes(i, cd, updateCmd):
body['agreedToTerms'] = getBoolean(sys.argv[i + 1], myarg)
i += 2
elif myarg in ['org', 'ou']:
body['orgUnitPath'] = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1], pathOnly=True)
body['orgUnitPath'] = gapi_directory_orgunits.getOrgUnitItem(
sys.argv[i + 1], pathOnly=True)
i += 2
elif myarg in ['language', 'languages']:
i += 1
@ -8799,6 +8773,7 @@ def doGetUserInfo(user_email=None):
'list',
'groups',
userKey=user_email,
customer=GC_Values[GC_CUSTOMER_ID],
fields='groups(name,email),nextPageToken',
throw_reasons=throw_reasons)
if groups:
@ -9108,7 +9083,8 @@ def doUndeleteUser():
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg in ['ou', 'org']:
orgUnit = gapi_directory_orgunits.makeOrgUnitPathAbsolute(sys.argv[i + 1])
orgUnit = gapi_directory_orgunits.makeOrgUnitPathAbsolute(
sys.argv[i + 1])
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam undelete user')
@ -9911,7 +9887,8 @@ def getUsersToModify(entity_type=None,
users = []
for member in members:
if ((not groupUserMembersOnly and not includeDerivedMembership) or
(member['type'] == 'USER')) and gapi_directory_groups._checkMemberRoleIsSuspended(
(member['type'] == 'USER')
) and gapi_directory_groups._checkMemberRoleIsSuspended(
member, validRoles, checkSuspended):
users.append(member.get('email', member['id']))
elif entity_type in ['cigroup']:
@ -9934,15 +9911,15 @@ def getUsersToModify(entity_type=None,
)
page_message = gapi.got_total_items_msg(f'{member_type_message}',
'...')
members = gapi.get_all_pages(
ci.groups().memberships(),
members = gapi.get_all_pages(ci.groups().memberships(),
'list',
'memberships',
page_message=page_message,
parent=parent,
fields=fields)
if member_type:
members = gapi_cloudidentity_groups.filter_members_to_roles(members, [member_type])
members = gapi_cloudidentity_groups.filter_members_to_roles(
members, [member_type])
users = []
for member in members:
users.append(member['memberKey']['id'])
@ -11225,6 +11202,8 @@ def ProcessGAMCommand(args):
doCreateAlertFeedback()
elif argument in ['gcpfolder']:
createGCPFolder()
elif argument in ['adminrole']:
gapi_directory_roles.create()
else:
controlflow.invalid_argument_exit(argument, 'gam create')
sys.exit(0)
@ -11458,7 +11437,7 @@ def ProcessGAMCommand(args):
elif argument == 'admins':
doPrintAdmins()
elif argument in ['roles', 'adminroles']:
doPrintAdminRoles()
gapi_directory_roles.print_()
elif argument in ['guardian', 'guardians']:
doPrintShowGuardians(True)
elif argument in ['matters', 'vaultmatters']:

View File

@ -1,5 +1,11 @@
"""Authentication/Credentials general purpose and convenience methods."""
import json
import os
import time
from google.auth.jwt import Credentials as JWTCredentials
from gam.auth import oauth
from gam.var import _FN_OAUTH2_TXT
from gam.var import GC_OAUTH2_TXT
@ -20,7 +26,16 @@ def get_admin_credentials_filename():
return DEFAULT_OAUTH_STORAGE_FILE
def get_admin_credentials():
def get_admin_credentials(api=None):
"""Gets oauth.Credentials that are authenticated as the domain's admin user."""
credential_file = get_admin_credentials_filename()
if not os.path.isfile(credential_file):
raise oauth.InvalidCredentialsFileError
with open(credential_file, 'r') as f:
creds_data = json.load(f)
if 'token' in creds_data:
return oauth.Credentials.from_credentials_file(credential_file)
elif 'private_key' in creds_data:
audience = f'https://{api}.googleapis.com/'
return JWTCredentials.from_service_account_info(creds_data,
audience=audience)

View File

@ -404,7 +404,7 @@ def getOrgUnitId(orgUnit, cd=None):
def buildOrgUnitIdToNameMap():
cd = buildGAPIObject('directory')
cd = gapi_directory.build()
result = gapi.call(cd.orgunits(),
'list',
customerId=GC_Values[GC_CUSTOMER_ID],
@ -420,5 +420,3 @@ def orgunit_from_orgunitid(orgunitid):
if not GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME]:
buildOrgUnitIdToNameMap()
return GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME].get(orgunitid, orgunitid)

View File

@ -3,6 +3,7 @@ from gam import display
from gam import gapi
from gam.gapi import directory as gapi_directory
def flatten_privilege_list(privs, parent=None):
flat_privs = []
for priv in privs:
@ -10,18 +11,22 @@ def flatten_privilege_list(privs, parent=None):
if parent:
priv['parent'] = parent
if priv.get('childPrivileges'):
children = flatten_privilege_list(priv['childPrivileges'], parent=priv['privilegeName'])
priv['children'] = ' '.join([child['privilegeName'] for child in children])
del(priv['childPrivileges'])
children = flatten_privilege_list(priv['childPrivileges'],
parent=priv['privilegeName'])
priv['children'] = ' '.join(
[child['privilegeName'] for child in children])
del (priv['childPrivileges'])
flat_privs = flat_privs + children
flat_privs.append(priv)
return flat_privs
def print_():
def print_(return_only=False):
cd = gapi_directory.build()
privs = gapi.call(cd.privileges(), 'list',
privs = gapi.call(cd.privileges(),
'list',
customer=GC_Values[GC_CUSTOMER_ID])
privs = flatten_privilege_list(privs.get('items', []))
if return_only:
return privs
display.print_json(privs)

View File

@ -0,0 +1,72 @@
import sys
from gam.var import GC_Values, GC_CUSTOMER_ID
from gam import display
from gam import gapi
from gam.gapi import directory as gapi_directory
from gam.gapi.directory import privileges as gapi_directory_privileges
def create():
cd = gapi_directory.build()
body = {'privileges': []}
all_privileges = gapi_directory_privileges.print_(return_only=True)
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'privileges':
privs = sys.argv[i + 1]
if privs == 'all':
body['rolePrivileges'] = all_privileges
elif privs == 'all_ou':
body['rolePrivileges'] = [
p for p in all_privileges if p.get('isOuScopable')
]
else:
# Known broken, need to get serviceName in here also...
body['rolePrivileges'] = [{
'privilegeName': p
} for p in sys.argv[i + 1].split(',')]
i += 2
elif myarg == 'name':
body['roleName'] = sys.argv[i + 1]
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam create adminrole')
print(f'Creating role {body["roleName"]}')
gapi.call(cd.roles(),
'insert',
customer=GC_Values[GC_CUSTOMER_ID],
body=body)
def print_():
cd = gapi_directory.build()
todrive = False
titles = [
'roleId', 'roleName', 'roleDescription', 'isSuperAdminRole',
'isSystemRole'
]
fields = f'nextPageToken,items({",".join(titles)})'
csvRows = []
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam print adminroles')
roles = gapi.get_all_pages(cd.roles(),
'list',
'items',
customer=GC_Values[GC_CUSTOMER_ID],
fields=fields)
for role in roles:
role_attrib = {}
for key, value in list(role.items()):
role_attrib[key] = value
csvRows.append(role_attrib)
display.write_csv_file(csvRows, titles, 'Admin Roles', todrive)