Compare commits

..

16 Commits
v6.40 ... v6.50

Author SHA1 Message Date
Ross Scroggs
aaf7a89192 Fix documenataion error (#1604) 2023-02-24 09:45:22 -05:00
Jay Lee
e3ee9135ff Update build.yml 2023-02-23 16:30:23 -05:00
Ross Scroggs
a774fc0beb GCP cleanup (#1602) 2023-02-23 11:44:52 -05:00
Jay Lee
f3429bd537 Update build.yml 2023-02-23 08:50:03 -05:00
Jay Lee
37876acfda Update var.py 2023-02-23 08:17:22 -05:00
Jay Lee
2a6dd0d1a2 fix building iamcredentials 2023-02-22 17:30:10 +00:00
Jay Lee
b0626dd37a improve on gam enable apis 2023-02-17 22:07:36 +00:00
Jay Lee
ed0ed8d7fc fix Id 2023-02-17 20:33:47 +00:00
Jay Lee
d67d999930 enable APIs command for signjwt 2023-02-17 20:32:29 +00:00
Jay Lee
ac79cff6b9 create signjwtserviceaccount 2023-02-17 19:39:02 +00:00
Jay Lee
50aadc6ea7 allow forcing OAuth for service account 2023-02-17 15:40:36 +00:00
Jay Lee
9036d114ed signjwt key_type for key-less service account auth 2023-02-17 15:17:01 +00:00
Jay Lee
75c19104ae fix ipv6 with checkconn 2023-02-15 17:22:34 +00:00
Jay Lee
d9b7f88287 6.42 - build shared drive restrictions dynamically 2023-02-13 21:51:41 +00:00
Jay Lee
ae28c09560 6.41 - fixes #1600 2023-02-11 13:40:59 +00:00
Jay Lee
6ffc738a51 Update gam-install.sh 2023-02-11 08:12:35 -05:00
6 changed files with 209 additions and 36 deletions

View File

@@ -873,7 +873,7 @@ Specify a collection of Users by directly specifying them or by specifying items
<UserMultiAttribute> <UserMultiAttribute>
gam checkconnection gam checkconnection
gam version [check|checkrc|simple|extended] [timeoffset] [location <HostName>] gam version [check|simple|extended] [timeoffset] [location <HostName>]
gam help gam help
gam batch <FileName>|- [charset <Charset>] gam batch <FileName>|- [charset <Charset>]
@@ -911,6 +911,12 @@ gam oauth|oauth2 refresh
gam <UserTypeEntity> check serviceaccount [scope|scopes <APIScopeURLList>] gam <UserTypeEntity> check serviceaccount [scope|scopes <APIScopeURLList>]
gam yubikey [resetpiv]
gam rotate sakey yubikey yubikey_pin yubikey_slot AUTHENTICATION yubikeypin <String> yubikeyserialnumber <String>
gam create [gcpserviceaccount|signjwtserviceaccount]
gam enable apis [auto|manual]
gam whatis <EmailItem> gam whatis <EmailItem>
<ResoldCustomerAttribute> ::= <ResoldCustomerAttribute> ::=

View File

@@ -28,7 +28,7 @@ upgrade_only=false
gamversion="latest" gamversion="latest"
adminuser="" adminuser=""
regularuser="" regularuser=""
gam_glibc_vers="2.35" gam_glibc_vers="2.31"
while getopts "hd:a:o:b:lp:u:r:v:" OPTION while getopts "hd:a:o:b:lp:u:r:v:" OPTION
do do

View File

@@ -51,6 +51,7 @@ from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID from cryptography.x509.oid import NameOID
import gam.auth.oauth import gam.auth.oauth
from gam.auth import signjwt
from gam import auth from gam import auth
from gam import controlflow from gam import controlflow
from gam import display from gam import display
@@ -782,9 +783,9 @@ def checkConnection():
success_count = 0 success_count = 0
for host in hosts: for host in hosts:
try_count += 1 try_count += 1
ip = socket.gethostbyname(host) ip = socket.getaddrinfo(host, None)[0][-1][0] # works with ipv6
check_line = f'Checking {host} ({ip}) ({try_count}/{host_count})...' check_line = f'Checking {host} ({ip}) ({try_count}/{host_count})...'
sys.stdout.write(f'{check_line:<80}') sys.stdout.write(f'{check_line:<100}')
sys.stdout.flush() sys.stdout.flush()
try: try:
httpc.request(f'https://{host}/', 'HEAD', headers=headers) httpc.request(f'https://{host}/', 'HEAD', headers=headers)
@@ -925,14 +926,12 @@ 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', def getSvcAcctCredentials(scopes, act_as, api=None, force_oauth=False):
'cloudresourcemanager',
'accesscontextmanager'] # APIs which can handle OAuthless JWT tokens
def getSvcAcctCredentials(scopes, act_as, api=None):
try: try:
_getSvcAcctData() _getSvcAcctData()
sign_method = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA].get('key_type', 'default') sign_method = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA].get('key_type', 'default')
if act_as or api not in jwt_apis: if act_as or force_oauth:
# DwD means we need to go about things differently...
if sign_method == 'default': if sign_method == 'default':
credentials = google.oauth2.service_account.Credentials.from_service_account_info( credentials = google.oauth2.service_account.Credentials.from_service_account_info(
GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]) GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
@@ -940,6 +939,10 @@ def getSvcAcctCredentials(scopes, act_as, api=None):
yksigner = yubikey.YubiKey(GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]) yksigner = yubikey.YubiKey(GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
credentials = google.oauth2.service_account.Credentials._from_signer_and_info(yksigner, credentials = google.oauth2.service_account.Credentials._from_signer_and_info(yksigner,
GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]) GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
elif sign_method == 'signjwt':
sjsigner = signjwt.SignJwt(GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
credentials = signjwt.Credentials._from_signer_and_info(sjsigner.sign,
GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
credentials = credentials.with_scopes(scopes) credentials = credentials.with_scopes(scopes)
if act_as: if act_as:
credentials = credentials.with_subject(act_as) credentials = credentials.with_subject(act_as)
@@ -953,6 +956,11 @@ def getSvcAcctCredentials(scopes, act_as, api=None):
credentials = JWTCredentials._from_signer_and_info(yksigner, credentials = JWTCredentials._from_signer_and_info(yksigner,
GM_Globals[GM_OAUTH2SERVICE_JSON_DATA], GM_Globals[GM_OAUTH2SERVICE_JSON_DATA],
audience=audience) audience=audience)
elif sign_method == 'signjwt':
sjsigner = signjwt.SignJwt(GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
credentials = signjwt.JWTCredentials._from_signer_and_info(sjsigner,
GM_Globals[GM_OAUTH2SERVICE_JSON_DATA],
audience=audience)
credentials.project_id = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]['project_id'] credentials.project_id = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]['project_id']
GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID] = GM_Globals[ GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID] = GM_Globals[
GM_OAUTH2SERVICE_JSON_DATA]['client_id'] GM_OAUTH2SERVICE_JSON_DATA]['client_id']
@@ -1078,9 +1086,10 @@ def getService(api, httpObj):
controlflow.invalid_json_exit(disc_file) controlflow.invalid_json_exit(disc_file)
def buildGAPIObject(api): def buildGAPIObject(api, credentials=None):
GM_Globals[GM_CURRENT_API_USER] = None GM_Globals[GM_CURRENT_API_USER] = None
credentials = getValidOauth2TxtCredentials(api=getAPIVersion(api)[0]) if not credentials:
credentials = getValidOauth2TxtCredentials(api=getAPIVersion(api)[0])
credentials.user_agent = GAM_INFO credentials.user_agent = GAM_INFO
httpObj = transport.AuthorizedHttp( httpObj = transport.AuthorizedHttp(
credentials, transport.create_http(cache=GM_Globals[GM_CACHE_DIR])) credentials, transport.create_http(cache=GM_Globals[GM_CACHE_DIR]))
@@ -1295,7 +1304,9 @@ def doCheckServiceAccount(users):
# We are explicitly not doing DwD here, just confirming service account can auth # We are explicitly not doing DwD here, just confirming service account can auth
auth_error = '' auth_error = ''
try: try:
credentials = getSvcAcctCredentials([USERINFO_EMAIL_SCOPE], None) credentials = getSvcAcctCredentials([USERINFO_EMAIL_SCOPE],
None,
force_oauth=True)
request = transport.create_request() request = transport.create_request()
credentials.refresh(request) credentials.refresh(request)
sa_token_info = gapi.call(oa2, sa_token_info = gapi.call(oa2,
@@ -1315,12 +1326,10 @@ def doCheckServiceAccount(users):
'Invalid private key in oauth2service.json. Please delete the file and then\nrecreate with "gam create project" or "gam use project"' 'Invalid private key in oauth2service.json. Please delete the file and then\nrecreate with "gam create project" or "gam use project"'
) )
key_type = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA].get('key_type', 'default') key_type = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA].get('key_type', 'default')
if key_type == 'yubikey': if key_type == 'default':
printPassFail('Skipping age check. YubiKey rotation not necessary.', test_pass)
else:
print( print(
'Checking key age. Google recommends rotating keys on a routine basis...' 'Checking key age. Google recommends rotating keys on a routine basis...'
) )
try: try:
iam = buildGAPIServiceObject('iam', None) iam = buildGAPIServiceObject('iam', None)
project = GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID] project = GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID]
@@ -1346,6 +1355,8 @@ def doCheckServiceAccount(users):
key_days = 'UNKNOWN' key_days = 'UNKNOWN'
print('Unable to check key age, please run "gam update project"') print('Unable to check key age, please run "gam update project"')
printPassFail(f'Key is {key_days} days old', key_age_result) printPassFail(f'Key is {key_days} days old', key_age_result)
else:
printPassFail(f'Skipping age check. {key_type} rotation not necessary.', test_pass)
if not check_scopes: if not check_scopes:
for _, scopes in list(API_SCOPE_MAPPING.items()): for _, scopes in list(API_SCOPE_MAPPING.items()):
for scope in scopes: for scope in scopes:
@@ -7158,6 +7169,43 @@ def getGAMProjectFile(filepath):
return c.decode(UTF8) return c.decode(UTF8)
def enable_apis():
a_or_m = None
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg in ['auto', 'manual']:
a_or_m = myarg[0]
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam enable apis')
GAMProjectAPIs = getGAMProjectFile('project-apis.txt').splitlines()
try:
_, projectId = google.auth.default()
except google.auth.exceptions.DefaultCredentialsError as e:
projectId = input('Please enter your project ID: ')
while a_or_m not in ['a', 'm']:
a_or_m = input('Do you want to enable projects [a]utomatically or [m]anually? (a/m): ').strip().lower()
if a_or_m in ['a', 'm']:
break
print('Please enter A or M....')
if a_or_m == 'a':
login_hint = _getValidateLoginHint()
_, httpObj = getCRMService(login_hint)
enableGAMProjectAPIs(GAMProjectAPIs,
httpObj,
projectId=projectId,
checkEnabled=True)
else:
chunk_size = 20
print('Using an account with project access, please use ALL of these URLs to enable 20 APIs at a time:\n\n')
for chunk in range(0, len(GAMProjectAPIs), chunk_size):
apiid = ",".join(GAMProjectAPIs[chunk:chunk+chunk_size])
url = f'https://console.cloud.google.com/apis/enableflow?apiid={apiid}&project={projectId}'
print(f' {url}\n\n')
def enableGAMProjectAPIs(GAMProjectAPIs, def enableGAMProjectAPIs(GAMProjectAPIs,
httpObj, httpObj,
projectId, projectId,
@@ -7456,10 +7504,12 @@ def _getProjects(crm, pfilter):
crm.projects(), crm.projects(),
'get', 'get',
name=f'projects/{pfilter}', name=f'projects/{pfilter}',
throw_reasons=[gapi_errors.ErrorReason.BAD_REQUEST])] throw_reasons=[gapi_errors.ErrorReason.BAD_REQUEST,
gapi_errors.ErrorReason.FOUR_O_THREE])]
except gapi_errors.GapiBadRequestError as e: except gapi_errors.GapiBadRequestError as e:
controlflow.system_error_exit(2, f'Project: {pfilter}, {str(e)}') controlflow.system_error_exit(2, f'Project: {pfilter}, {str(e)}')
except googleapiclient.errors.HttpError:
return []
PROJECTID_PATTERN = re.compile(r'^[a-z][a-z0-9-]{4,28}[a-z0-9]$') PROJECTID_PATTERN = re.compile(r'^[a-z][a-z0-9-]{4,28}[a-z0-9]$')
PROJECTID_FORMAT_REQUIRED = '[a-z][a-z0-9-]{4,28}[a-z0-9]' PROJECTID_FORMAT_REQUIRED = '[a-z][a-z0-9-]{4,28}[a-z0-9]'
@@ -7822,8 +7872,7 @@ def doShowServiceAccountKeys():
else: else:
controlflow.invalid_argument_exit(myarg, 'gam show sakeys') controlflow.invalid_argument_exit(myarg, 'gam show sakeys')
name = f'projects/-/serviceAccounts/{GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID]}' name = f'projects/-/serviceAccounts/{GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID]}'
currentPrivateKeyId = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA][ currentPrivateKeyId = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA].get('private_key_id')
'private_key_id']
keys = gapi.get_items(iam.projects().serviceAccounts().keys(), keys = gapi.get_items(iam.projects().serviceAccounts().keys(),
'list', 'list',
'keys', 'keys',
@@ -7842,6 +7891,32 @@ def doShowServiceAccountKeys():
display.print_json(keys) display.print_json(keys)
def create_signjwt_serviceaccount():
i = 3
if i < len(sys.argv):
controlflow.invalid_argument_exit(sys.argv[i], f'gam create {sys.argv[2]}')
_checkForExistingProjectFiles()
sa_info = {
'type': 'service_account',
'key_type': 'signjwt',
'token_uri': 'https://oauth2.googleapis.com/token'
}
try:
creds, sa_info['project_id'] = google.auth.default()
except google.auth.exceptions.DefaultCredentialsError as e:
controlflow.system_error_exit(2, e)
request = transport.create_request()
creds.refresh(request)
sa_info['client_email'] = creds.service_account_email
oa2 = buildGAPIObjectNoAuthentication('oauth2')
token_info = gapi.call(oa2, 'tokeninfo', access_token=creds.token)
sa_info['client_id'] = token_info['issued_to']
sa_output = json.dumps(sa_info, indent=4, sort_keys=True)
fileutils.write_file(GC_Values[GC_OAUTH2SERVICE_JSON],
sa_output,
continue_on_error=False)
def doCreateOrRotateServiceAccountKeys(iam=None, def doCreateOrRotateServiceAccountKeys(iam=None,
project_id=None, project_id=None,
client_email=None, client_email=None,
@@ -8173,20 +8248,14 @@ def doCreateSharedDrive(users):
print(f'Created Shared Drive {body["name"]} with id {result["id"]}') print(f'Created Shared Drive {body["name"]} with id {result["id"]}')
TEAMDRIVE_RESTRICTIONS_MAP = {
'adminmanagedrestrictions': 'adminManagedRestrictions',
'copyrequireswriterpermission': 'copyRequiresWriterPermission',
'domainusersonly': 'domainUsersOnly',
'teammembersonly': 'teamMembersOnly',
}
def doUpdateSharedDrive(users): def doUpdateSharedDrive(users):
i, driveId = getSharedDriveId(5) i, driveId = getSharedDriveId(5)
body = {} body = {}
useDomainAdminAccess = False useDomainAdminAccess = False
change_hide = None change_hide = None
orgUnit = None orgUnit = None
_, d = buildDrive3GAPIObject(_get_admin_email())
restrictions_map = {r.lower(): r for r in d._rootDesc['schemas']['Drive']['properties']['restrictions']['properties'].keys()}
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '') myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'name': if myarg == 'name':
@@ -8212,19 +8281,15 @@ def doUpdateSharedDrive(users):
elif myarg == 'asadmin': elif myarg == 'asadmin':
useDomainAdminAccess = True useDomainAdminAccess = True
i += 1 i += 1
# elif myarg in ['ou', 'org', 'orgunit']:
# body['orgUnitId'] = gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])
# i += 2
elif myarg in ['hidden']: elif myarg in ['hidden']:
if getBoolean(sys.argv[i+1], myarg): if getBoolean(sys.argv[i+1], myarg):
change_hide = 'hide' change_hide = 'hide'
else: else:
change_hide = 'unhide' change_hide = 'unhide'
i += 2 i += 2
elif myarg in TEAMDRIVE_RESTRICTIONS_MAP: elif myarg in restrictions_map:
body.setdefault('restrictions', {}) body.setdefault('restrictions', {})
body['restrictions'][ body['restrictions'][restrictions_map[myarg]] = getBoolean(
TEAMDRIVE_RESTRICTIONS_MAP[myarg]] = getBoolean(
sys.argv[i + 1], myarg) sys.argv[i + 1], myarg)
i += 2 i += 2
else: else:
@@ -11572,6 +11637,8 @@ def ProcessGAMCommand(args):
gapi_chat.create_message() gapi_chat.create_message()
elif argument in ['caalevel']: elif argument in ['caalevel']:
gapi_caa.create_access_level() gapi_caa.create_access_level()
elif argument in ['gcpserviceaccount', 'signjwtserviceaccount']:
create_signjwt_serviceaccount()
else: else:
controlflow.invalid_argument_exit(argument, 'gam create') controlflow.invalid_argument_exit(argument, 'gam create')
sys.exit(0) sys.exit(0)
@@ -12106,6 +12173,11 @@ def ProcessGAMCommand(args):
yk.serial_number = yk.get_serial_number() yk.serial_number = yk.get_serial_number()
yk.reset_piv() yk.reset_piv()
sys.exit(0) sys.exit(0)
elif command == 'enable':
enable_what = sys.argv[2].lower().replace('_', '')
if enable_what in ['api', 'apis']:
enable_apis()
sys.exit(0)
users = getUsersToModify() users = getUsersToModify()
command = sys.argv[3].lower() command = sys.argv[3].lower()
if command == 'print' and len(sys.argv) == 4: if command == 'print' and len(sys.argv) == 4:

View File

@@ -9,6 +9,7 @@ import gam
from gam import utils from gam import utils
from gam.auth import oauth from gam.auth import oauth
from gam.auth import signjwt
from gam.var import _FN_OAUTH2_TXT from gam.var import _FN_OAUTH2_TXT
from gam.var import _FN_OAUTH2SERVICE_JSON from gam.var import _FN_OAUTH2SERVICE_JSON
from gam.var import GC_OAUTH2_TXT from gam.var import GC_OAUTH2_TXT
@@ -40,7 +41,7 @@ def get_admin_credentials(api=None):
with open(credential_file) as f: with open(credential_file) as f:
creds_data = json.load(f) creds_data = json.load(f)
# Validate that enable DASA matches content of authorization file # Validate that enable DASA matches content of authorization file
if GC_Values[GC_ENABLE_DASA] and 'private_key_id' in creds_data: if GC_Values[GC_ENABLE_DASA] and 'key_type' in creds_data:
audience = f'https://{api}.googleapis.com/' audience = f'https://{api}.googleapis.com/'
key_type = creds_data.get('key_type', 'default') key_type = creds_data.get('key_type', 'default')
if key_type == 'default': if key_type == 'default':
@@ -51,6 +52,11 @@ def get_admin_credentials(api=None):
return JWTCredentials._from_signer_and_info(yksigner, return JWTCredentials._from_signer_and_info(yksigner,
creds_data, creds_data,
audience=audience) audience=audience)
elif key_type == 'signjwt':
sjsigner = signjwt.SignJwt(creds_data)
return signjwt.JWTCredentials._from_signer_and_info(sjsigner,
creds_data,
audience=audience)
elif not GC_Values[GC_ENABLE_DASA] and 'token' in creds_data: elif not GC_Values[GC_ENABLE_DASA] and 'token' in creds_data:
return oauth.Credentials.from_credentials_file(credential_file) return oauth.Credentials.from_credentials_file(credential_file)
else: else:

89
src/gam/auth/signjwt.py Normal file
View File

@@ -0,0 +1,89 @@
''' Use Google Application Default Credentials '''
import datetime
import json
import google.auth
from google.auth._helpers import datetime_to_secs, scopes_to_string, utcnow
import google.oauth2.service_account
import gam
from gam import controlflow
from gam import gapi
from gam import transport
from gam.var import GM_Globals, GM_CACHE_DIR
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
class JWTCredentials(google.auth.jwt.Credentials):
''' Class used for DASA '''
def _make_jwt(self):
now = utcnow()
lifetime = datetime.timedelta(seconds=self._token_lifetime)
expiry = now + lifetime
payload = {
"iss": self._issuer,
"sub": self._subject,
"iat": datetime_to_secs(now),
"exp": datetime_to_secs(expiry),
}
if self._audience:
payload["aud"] = self._audience
payload.update(self._additional_claims)
jwt = self._signer.sign(payload)
return jwt, expiry
class Credentials(google.oauth2.service_account.Credentials):
''' Class used for DwD '''
def _make_authorization_grant_assertion(self):
now = utcnow()
lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
expiry = now + lifetime
payload = {
"iat": datetime_to_secs(now),
"exp": datetime_to_secs(expiry),
"iss": self._service_account_email,
"aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT,
"scope": scopes_to_string(self._scopes or ()),
}
payload.update(self._additional_claims)
# The subject can be a user email for domain-wide delegation.
if self._subject:
payload.setdefault("sub", self._subject)
token = self._signer(payload)
return token
class SignJwt(google.auth.crypt.Signer):
''' Signer class for SignJWT '''
def __init__(self, service_account_info):
self.service_account_email = service_account_info['client_email']
self.name = f'projects/-/serviceAccounts/{self.service_account_email}'
self._key_id = None
@property # type: ignore
def key_id(self):
return self._key_id
def sign(self, message):
''' Call IAM Credentials SignJWT API to get our signed JWT '''
try:
credentials, _ = google.auth.default()
except google.auth.exceptions.DefaultCredentialsError as e:
controlflow.system_error_exit(2, e)
httpObj = transport.AuthorizedHttp(
credentials,
transport.create_http(cache=GM_Globals[GM_CACHE_DIR]))
iamc = gam.getService('iamcredentials', httpObj)
response = gapi.call(iamc.projects().serviceAccounts(),
'signJwt',
name=self.name,
body={'payload': json.dumps(message)})
signed_jwt = response.get('signedJwt')
return signed_jwt

View File

@@ -8,7 +8,7 @@ import platform
import re import re
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>' GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
GAM_VERSION = '6.40' GAM_VERSION = '6.50'
GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
GAM_URL = 'https://jaylee.us/gam' GAM_URL = 'https://jaylee.us/gam'