mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-29 18:31:38 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a92a07f9c0 | ||
|
|
42ed5509ee | ||
|
|
a6582503f2 | ||
|
|
7aecb889d2 | ||
|
|
c273f87cc7 | ||
|
|
76d00c993a | ||
|
|
013b47e6e7 | ||
|
|
9f1e9934ff | ||
|
|
48b218bd9c | ||
|
|
af5baa4f3a | ||
|
|
a2cf38d904 | ||
|
|
185522d943 | ||
|
|
a42e4dd080 | ||
|
|
3a5486889f | ||
|
|
1a1f100902 | ||
|
|
c67b214298 | ||
|
|
3ad1d5c661 | ||
|
|
13400d9bde | ||
|
|
048e8dfef5 | ||
|
|
aaf7a89192 | ||
|
|
e3ee9135ff | ||
|
|
a774fc0beb | ||
|
|
f3429bd537 | ||
|
|
37876acfda | ||
|
|
2a6dd0d1a2 | ||
|
|
b0626dd37a | ||
|
|
ed0ed8d7fc | ||
|
|
d67d999930 | ||
|
|
ac79cff6b9 | ||
|
|
50aadc6ea7 | ||
|
|
9036d114ed | ||
|
|
75c19104ae |
15
.github/workflows/build.yml
vendored
15
.github/workflows/build.yml
vendored
@@ -12,7 +12,7 @@ defaults:
|
|||||||
working-directory: src
|
working-directory: src
|
||||||
|
|
||||||
env:
|
env:
|
||||||
OPENSSL_CONFIG_OPTS: no-fips
|
OPENSSL_CONFIG_OPTS: no-fips --api=3.0.0
|
||||||
OPENSSL_INSTALL_PATH: ${{ github.workspace }}/bin/ssl
|
OPENSSL_INSTALL_PATH: ${{ github.workspace }}/bin/ssl
|
||||||
OPENSSL_SOURCE_PATH: ${{ github.workspace }}/src/openssl
|
OPENSSL_SOURCE_PATH: ${{ github.workspace }}/src/openssl
|
||||||
PYTHON_INSTALL_PATH: ${{ github.workspace }}/bin/python
|
PYTHON_INSTALL_PATH: ${{ github.workspace }}/bin/python
|
||||||
@@ -103,7 +103,7 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
bin.tar.xz
|
bin.tar.xz
|
||||||
src/cpython
|
src/cpython
|
||||||
key: gam-${{ matrix.jid }}-20230208
|
key: gam-${{ matrix.jid }}-20230314
|
||||||
|
|
||||||
- name: Untar Cache archive
|
- name: Untar Cache archive
|
||||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||||
@@ -144,12 +144,6 @@ jobs:
|
|||||||
sudo apt-get -qq --yes update
|
sudo apt-get -qq --yes update
|
||||||
sudo apt-get -qq --yes install swig libpcsclite-dev
|
sudo apt-get -qq --yes install swig libpcsclite-dev
|
||||||
|
|
||||||
#- name: MacOS remove Homebrew
|
|
||||||
# if: runner.os == 'macOS'
|
|
||||||
# run: |
|
|
||||||
# # remove everything except the libraries needed by yubikey-manager
|
|
||||||
# brew uninstall $(brew list | grep -v 'pcre\|swig\|pcsc-lite')
|
|
||||||
|
|
||||||
- name: MacOS install tools
|
- name: MacOS install tools
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
run: |
|
run: |
|
||||||
@@ -175,7 +169,7 @@ jobs:
|
|||||||
staticx: ${{ matrix.staticx }}
|
staticx: ${{ matrix.staticx }}
|
||||||
run: |
|
run: |
|
||||||
echo "We are running on ${RUNNER_OS}"
|
echo "We are running on ${RUNNER_OS}"
|
||||||
LD_LIBRARY_PATH="${OPENSSL_INSTALL_PATH}/lib:${PYTHON_INSTALL_PATH}/lib"
|
LD_LIBRARY_PATH="${OPENSSL_INSTALL_PATH}/lib:${PYTHON_INSTALL_PATH}/lib:/usr/local/lib"
|
||||||
if [[ "${arch}" == "Win64" ]]; then
|
if [[ "${arch}" == "Win64" ]]; then
|
||||||
PYEXTERNALS_PATH="amd64"
|
PYEXTERNALS_PATH="amd64"
|
||||||
PYBUILDRELEASE_ARCH="x64"
|
PYBUILDRELEASE_ARCH="x64"
|
||||||
@@ -298,7 +292,7 @@ jobs:
|
|||||||
rm -rf ${GITHUB_WORKSPACE}/bin/ssl-darwin64-arm64
|
rm -rf ${GITHUB_WORKSPACE}/bin/ssl-darwin64-arm64
|
||||||
echo "LDFLAGS=-L${OPENSSL_INSTALL_PATH}/lib" >> $GITHUB_ENV
|
echo "LDFLAGS=-L${OPENSSL_INSTALL_PATH}/lib" >> $GITHUB_ENV
|
||||||
echo "CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1" >> $GITHUB_ENV
|
echo "CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1" >> $GITHUB_ENV
|
||||||
echo "CFLAGS=-I${OPENSSL_INSTALL_PATH}/include -arch arm64 -arch x86_64" >> $GITHUB_ENV
|
echo "CFLAGS=-I${OPENSSL_INSTALL_PATH}/include -arch arm64 -arch x86_64 ${CFLAGS}" >> $GITHUB_ENV
|
||||||
echo "ARCHFLAGS=-arch x86_64 -arch arm64" >> $GITHUB_ENV
|
echo "ARCHFLAGS=-arch x86_64 -arch arm64" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
cd "${GITHUB_WORKSPACE}/src/openssl-${openssl_archs}"
|
cd "${GITHUB_WORKSPACE}/src/openssl-${openssl_archs}"
|
||||||
@@ -310,6 +304,7 @@ jobs:
|
|||||||
if: matrix.goal == 'build'
|
if: matrix.goal == 'build'
|
||||||
run: |
|
run: |
|
||||||
"${OPENSSL_INSTALL_PATH}/bin/openssl" version
|
"${OPENSSL_INSTALL_PATH}/bin/openssl" version
|
||||||
|
"${OPENSSL_INSTALL_PATH}/bin/openssl" version -f
|
||||||
file "${OPENSSL_INSTALL_PATH}/bin/openssl"
|
file "${OPENSSL_INSTALL_PATH}/bin/openssl"
|
||||||
|
|
||||||
- name: Get latest stable Python source
|
- name: Get latest stable Python source
|
||||||
|
|||||||
@@ -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 [yubikeyserialnumber <Number>]
|
||||||
|
gam rotate sakey yubikey yubikey_pin yubikey_slot AUTHENTICATION yubikeyserialnumber <Number>
|
||||||
|
|
||||||
|
gam create [gcpserviceaccount|signjwtserviceaccount]
|
||||||
|
gam enable apis [auto|manual]
|
||||||
|
|
||||||
gam whatis <EmailItem>
|
gam whatis <EmailItem>
|
||||||
|
|
||||||
<ResoldCustomerAttribute> ::=
|
<ResoldCustomerAttribute> ::=
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -588,7 +589,7 @@ def SetGlobalVariables():
|
|||||||
GM_Globals[GM_ENABLEDASA_TXT] = os.path.join(
|
GM_Globals[GM_ENABLEDASA_TXT] = os.path.join(
|
||||||
GC_Values[GC_CONFIG_DIR], FN_ENABLEDASA_TXT)
|
GC_Values[GC_CONFIG_DIR], FN_ENABLEDASA_TXT)
|
||||||
if not GC_Values[GC_NO_UPDATE_CHECK]:
|
if not GC_Values[GC_NO_UPDATE_CHECK]:
|
||||||
doGAMCheckForUpdates()
|
doGAMCheckForUpdates(forceCheck=0)
|
||||||
|
|
||||||
# domain must be set and customer_id must be set and != my_customer when enable_dasa = true
|
# domain must be set and customer_id must be set and != my_customer when enable_dasa = true
|
||||||
if GC_Values[GC_ENABLE_DASA]:
|
if GC_Values[GC_ENABLE_DASA]:
|
||||||
@@ -635,8 +636,10 @@ TIME_OFFSET_UNITS = [('day', 86400), ('hour', 3600), ('minute', 60),
|
|||||||
|
|
||||||
|
|
||||||
def getLocalGoogleTimeOffset(testLocation='admin.googleapis.com'):
|
def getLocalGoogleTimeOffset(testLocation='admin.googleapis.com'):
|
||||||
|
# If local time is well off, it breaks https because the server certificate
|
||||||
|
# will be seen as too old or new and thus invalid; http doesn't have that issue.
|
||||||
# Try with http first, if time is close (<MAX_LOCAL_GOOGLE_TIME_OFFSET seconds),
|
# Try with http first, if time is close (<MAX_LOCAL_GOOGLE_TIME_OFFSET seconds),
|
||||||
# retry with https
|
# retry with https as it should be OK
|
||||||
badhttp = transport.create_http()
|
badhttp = transport.create_http()
|
||||||
for prot in ['http', 'https']:
|
for prot in ['http', 'https']:
|
||||||
localUTC = datetime.datetime.now(datetime.timezone.utc)
|
localUTC = datetime.datetime.now(datetime.timezone.utc)
|
||||||
@@ -645,6 +648,12 @@ def getLocalGoogleTimeOffset(testLocation='admin.googleapis.com'):
|
|||||||
badhttp.request(f'{prot}://' + testLocation, 'HEAD')[0]['date'])
|
badhttp.request(f'{prot}://' + testLocation, 'HEAD')[0]['date'])
|
||||||
except (httplib2.ServerNotFoundError, RuntimeError, ValueError) as e:
|
except (httplib2.ServerNotFoundError, RuntimeError, ValueError) as e:
|
||||||
controlflow.system_error_exit(4, str(e))
|
controlflow.system_error_exit(4, str(e))
|
||||||
|
except httplib2.socks.HTTPError as e:
|
||||||
|
# If user has specified an HTTPS proxy, the http request will probably fail as httplib2
|
||||||
|
# turns a GET into a CONNECT which is not valid for an http address
|
||||||
|
if prot == 'http':
|
||||||
|
continue
|
||||||
|
handleServerError(e)
|
||||||
offset = remainder = int(abs((localUTC - googleUTC).total_seconds()))
|
offset = remainder = int(abs((localUTC - googleUTC).total_seconds()))
|
||||||
if offset < MAX_LOCAL_GOOGLE_TIME_OFFSET and prot == 'http':
|
if offset < MAX_LOCAL_GOOGLE_TIME_OFFSET and prot == 'http':
|
||||||
continue
|
continue
|
||||||
@@ -659,7 +668,7 @@ def getLocalGoogleTimeOffset(testLocation='admin.googleapis.com'):
|
|||||||
return (offset, nicetime)
|
return (offset, nicetime)
|
||||||
|
|
||||||
|
|
||||||
def doGAMCheckForUpdates(forceCheck=False):
|
def doGAMCheckForUpdates(forceCheck=0):
|
||||||
|
|
||||||
def _gamLatestVersionNotAvailable():
|
def _gamLatestVersionNotAvailable():
|
||||||
if forceCheck:
|
if forceCheck:
|
||||||
@@ -702,6 +711,8 @@ def doGAMCheckForUpdates(forceCheck=False):
|
|||||||
print(
|
print(
|
||||||
f'Version Check:\n Current: {current_version}\n Latest: {latest_version}'
|
f'Version Check:\n Current: {current_version}\n Latest: {latest_version}'
|
||||||
)
|
)
|
||||||
|
if forceCheck < 0:
|
||||||
|
sys.exit(1 if latest_version > current_version else 0)
|
||||||
if latest_version <= current_version:
|
if latest_version <= current_version:
|
||||||
fileutils.write_file(GM_Globals[GM_LAST_UPDATE_CHECK_TXT],
|
fileutils.write_file(GM_Globals[GM_LAST_UPDATE_CHECK_TXT],
|
||||||
str(now_time),
|
str(now_time),
|
||||||
@@ -782,9 +793,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)
|
||||||
@@ -821,14 +832,18 @@ def checkConnection():
|
|||||||
controlflow.system_error_exit(3, createYellowText('Some hosts failed to connect! Please follow the recommendations for those hosts to correct any issues and try again.'))
|
controlflow.system_error_exit(3, createYellowText('Some hosts failed to connect! Please follow the recommendations for those hosts to correct any issues and try again.'))
|
||||||
|
|
||||||
def doGAMVersion(checkForArgs=True):
|
def doGAMVersion(checkForArgs=True):
|
||||||
force_check = extended = simple = timeOffset = False
|
forceCheck = 0
|
||||||
|
extended = simple = timeOffset = False
|
||||||
testLocation = 'admin.googleapis.com'
|
testLocation = 'admin.googleapis.com'
|
||||||
if checkForArgs:
|
if checkForArgs:
|
||||||
i = 2
|
i = 2
|
||||||
while i < len(sys.argv):
|
while i < len(sys.argv):
|
||||||
myarg = sys.argv[i].lower().replace('_', '')
|
myarg = sys.argv[i].lower().replace('_', '')
|
||||||
if myarg == 'check':
|
if myarg == 'check':
|
||||||
force_check = True
|
forceCheck = 1
|
||||||
|
i += 1
|
||||||
|
elif myarg == 'checkrc':
|
||||||
|
forceCheck = -1
|
||||||
i += 1
|
i += 1
|
||||||
elif myarg == 'simple':
|
elif myarg == 'simple':
|
||||||
simple = True
|
simple = True
|
||||||
@@ -868,8 +883,8 @@ def doGAMVersion(checkForArgs=True):
|
|||||||
(testLocation, nicetime))
|
(testLocation, nicetime))
|
||||||
if offset > MAX_LOCAL_GOOGLE_TIME_OFFSET:
|
if offset > MAX_LOCAL_GOOGLE_TIME_OFFSET:
|
||||||
controlflow.system_error_exit(4, 'Please fix your system time.')
|
controlflow.system_error_exit(4, 'Please fix your system time.')
|
||||||
if force_check:
|
if forceCheck:
|
||||||
doGAMCheckForUpdates(forceCheck=True)
|
doGAMCheckForUpdates(forceCheck)
|
||||||
if extended:
|
if extended:
|
||||||
print(ssl.OPENSSL_VERSION)
|
print(ssl.OPENSSL_VERSION)
|
||||||
libs = ['cryptography',
|
libs = ['cryptography',
|
||||||
@@ -925,14 +940,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 +953,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 +970,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,8 +1100,9 @@ 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
|
||||||
|
if not credentials:
|
||||||
credentials = getValidOauth2TxtCredentials(api=getAPIVersion(api)[0])
|
credentials = getValidOauth2TxtCredentials(api=getAPIVersion(api)[0])
|
||||||
credentials.user_agent = GAM_INFO
|
credentials.user_agent = GAM_INFO
|
||||||
httpObj = transport.AuthorizedHttp(
|
httpObj = transport.AuthorizedHttp(
|
||||||
@@ -1295,7 +1318,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,9 +1340,7 @@ 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...'
|
||||||
)
|
)
|
||||||
@@ -1346,6 +1369,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 +7183,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,
|
||||||
@@ -7743,7 +7805,7 @@ def doUpdateProjects():
|
|||||||
_grantRotateRights(iam, sa_email, sa_email)
|
_grantRotateRights(iam, sa_email, sa_email)
|
||||||
|
|
||||||
|
|
||||||
def _generatePrivateKeyAndPublicCert(client_id, key_size, b64enc_pub=True):
|
def _generatePrivateKeyAndPublicCert(client_id, key_size, b64enc_pub=True, validity_hours=0):
|
||||||
print(' Generating new private key...')
|
print(' Generating new private key...')
|
||||||
private_key = rsa.generate_private_key(public_exponent=65537,
|
private_key = rsa.generate_private_key(public_exponent=65537,
|
||||||
key_size=key_size,
|
key_size=key_size,
|
||||||
@@ -7760,9 +7822,15 @@ def _generatePrivateKeyAndPublicCert(client_id, key_size, b64enc_pub=True):
|
|||||||
builder = builder.issuer_name(
|
builder = builder.issuer_name(
|
||||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, client_id)]))
|
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, client_id)]))
|
||||||
# Gooogle seems to enforce the not before date strictly. Set the not before
|
# Gooogle seems to enforce the not before date strictly. Set the not before
|
||||||
# date to be UTC one hour ago should cover any clock skew.
|
# date to be UTC two minutes ago which should cover any clock skew.
|
||||||
builder = builder.not_valid_before(datetime.datetime.utcnow() - datetime.timedelta(hours=1))
|
now = datetime.datetime.utcnow()
|
||||||
# Google uses 12/31/9999 date for end time
|
builder = builder.not_valid_before(now - datetime.timedelta(minutes=2))
|
||||||
|
# Google defaults to 12/31/9999 date for end time if there's no
|
||||||
|
# policy to restrict key age
|
||||||
|
if validity_hours:
|
||||||
|
expires = now + datetime.timedelta(hours=validity_hours) - datetime.timedelta(minutes=2)
|
||||||
|
builder = builder.not_valid_after(expires)
|
||||||
|
else:
|
||||||
builder = builder.not_valid_after(datetime.datetime(9999, 12, 31, 23, 59))
|
builder = builder.not_valid_after(datetime.datetime(9999, 12, 31, 23, 59))
|
||||||
builder = builder.serial_number(x509.random_serial_number())
|
builder = builder.serial_number(x509.random_serial_number())
|
||||||
builder = builder.public_key(public_key)
|
builder = builder.public_key(public_key)
|
||||||
@@ -7824,12 +7892,12 @@ 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',
|
||||||
name=name,
|
name=name,
|
||||||
|
fields='*',
|
||||||
keyTypes=keyTypes)
|
keyTypes=keyTypes)
|
||||||
if not keys:
|
if not keys:
|
||||||
print('No keys')
|
print('No keys')
|
||||||
@@ -7844,11 +7912,60 @@ def doShowServiceAccountKeys():
|
|||||||
display.print_json(keys)
|
display.print_json(keys)
|
||||||
|
|
||||||
|
|
||||||
|
def getYubiKeySerialNumber(new_data, serial_number):
|
||||||
|
try:
|
||||||
|
new_data['yubikey_serial_number'] = int(serial_number)
|
||||||
|
except ValueError:
|
||||||
|
controlflow.system_error_exit(
|
||||||
|
3,
|
||||||
|
'yubikey_serial_number must be a number')
|
||||||
|
|
||||||
|
def doResetYubiKeyPIV():
|
||||||
|
new_data = {}
|
||||||
|
i = 3
|
||||||
|
while i < len(sys.argv):
|
||||||
|
myarg = sys.argv[i].lower().replace('_', '')
|
||||||
|
if myarg == 'yubikeyserialnumber':
|
||||||
|
getYubiKeySerialNumber(new_data, sys.argv[i+1])
|
||||||
|
i += 2
|
||||||
|
else:
|
||||||
|
controlflow.invalid_argument_exit(myarg, 'gam yubikey resetpiv')
|
||||||
|
yk = yubikey.YubiKey(new_data)
|
||||||
|
yk.serial_number = yk.get_serial_number()
|
||||||
|
yk.reset_piv()
|
||||||
|
|
||||||
|
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,
|
||||||
client_id=None):
|
client_id=None):
|
||||||
local_key_size = 2048
|
local_key_size = 2048
|
||||||
|
validity_hours = 0
|
||||||
mode = 'retainexisting'
|
mode = 'retainexisting'
|
||||||
body = {}
|
body = {}
|
||||||
if iam:
|
if iam:
|
||||||
@@ -7899,12 +8016,10 @@ def doCreateOrRotateServiceAccountKeys(iam=None,
|
|||||||
new_data['yubikey_pin'] = input('Enter your YubiKey PIN: ')
|
new_data['yubikey_pin'] = input('Enter your YubiKey PIN: ')
|
||||||
i += 1
|
i += 1
|
||||||
elif myarg == 'yubikeyserialnumber':
|
elif myarg == 'yubikeyserialnumber':
|
||||||
try:
|
getYubiKeySerialNumber(new_data, sys.argv[i+1])
|
||||||
new_data['yubikey_serial_number'] = int(sys.argv[i+1])
|
i += 2
|
||||||
except ValueError:
|
elif myarg == 'validityhours':
|
||||||
controlflow.system_error_exit(
|
validity_hours = int(sys.argv[i + 1])
|
||||||
3,
|
|
||||||
'yubikey_serial_number must be a number')
|
|
||||||
i += 2
|
i += 2
|
||||||
elif myarg in ['retainnone', 'retainexisting', 'replacecurrent']:
|
elif myarg in ['retainnone', 'retainexisting', 'replacecurrent']:
|
||||||
mode = myarg
|
mode = myarg
|
||||||
@@ -7926,7 +8041,7 @@ def doCreateOrRotateServiceAccountKeys(iam=None,
|
|||||||
elif local_key_size:
|
elif local_key_size:
|
||||||
# Generate private key locally, store in file
|
# Generate private key locally, store in file
|
||||||
new_data['private_key'], publicKeyData = _generatePrivateKeyAndPublicCert(
|
new_data['private_key'], publicKeyData = _generatePrivateKeyAndPublicCert(
|
||||||
sa_name, local_key_size)
|
sa_name, local_key_size, validity_hours=validity_hours)
|
||||||
new_data['key_type'] = 'default'
|
new_data['key_type'] = 'default'
|
||||||
for key in list(new_data):
|
for key in list(new_data):
|
||||||
if key.startswith('yubikey_'):
|
if key.startswith('yubikey_'):
|
||||||
@@ -11564,6 +11679,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)
|
||||||
@@ -11802,6 +11919,8 @@ def ProcessGAMCommand(args):
|
|||||||
argument = sys.argv[2].lower()
|
argument = sys.argv[2].lower()
|
||||||
if argument in ['browsertoken', 'browserokens']:
|
if argument in ['browsertoken', 'browserokens']:
|
||||||
gapi_cbcm.revoketoken()
|
gapi_cbcm.revoketoken()
|
||||||
|
else:
|
||||||
|
controlflow.invalid_argument_exit(argument, 'gam revoke')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif command in ['close', 'reopen']:
|
elif command in ['close', 'reopen']:
|
||||||
# close and reopen will have to be split apart if either takes a new argument
|
# close and reopen will have to be split apart if either takes a new argument
|
||||||
@@ -11966,6 +12085,8 @@ def ProcessGAMCommand(args):
|
|||||||
argument = sys.argv[2].lower()
|
argument = sys.argv[2].lower()
|
||||||
if argument in ['browser', 'browsers']:
|
if argument in ['browser', 'browsers']:
|
||||||
gapi_cbcm.move()
|
gapi_cbcm.move()
|
||||||
|
else:
|
||||||
|
controlflow.invalid_argument_exit(argument, 'gam move')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif command in ['oauth', 'oauth2']:
|
elif command in ['oauth', 'oauth2']:
|
||||||
argument = sys.argv[2].lower()
|
argument = sys.argv[2].lower()
|
||||||
@@ -12063,6 +12184,8 @@ def ProcessGAMCommand(args):
|
|||||||
argument = sys.argv[2].lower()
|
argument = sys.argv[2].lower()
|
||||||
if argument in ['isinvitable', 'userinvitation', 'userinvitations']:
|
if argument in ['isinvitable', 'userinvitation', 'userinvitations']:
|
||||||
gapi_cloudidentity_userinvitations.check()
|
gapi_cloudidentity_userinvitations.check()
|
||||||
|
else:
|
||||||
|
controlflow.invalid_argument_exit(argument, 'gam check')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif command in ['cancelwipe', 'wipe', 'approve', 'block', 'sync']:
|
elif command in ['cancelwipe', 'wipe', 'approve', 'block', 'sync']:
|
||||||
target = sys.argv[2].lower().replace('_', '')
|
target = sys.argv[2].lower().replace('_', '')
|
||||||
@@ -12082,6 +12205,8 @@ def ProcessGAMCommand(args):
|
|||||||
gapi_cloudidentity_devices.approve_user()
|
gapi_cloudidentity_devices.approve_user()
|
||||||
elif command == 'block':
|
elif command == 'block':
|
||||||
gapi_cloudidentity_devices.block_user()
|
gapi_cloudidentity_devices.block_user()
|
||||||
|
else:
|
||||||
|
controlflow.invalid_argument_exit(target, f'gam {command}')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif command in ['issuecommand', 'getcommand']:
|
elif command in ['issuecommand', 'getcommand']:
|
||||||
target = sys.argv[2].lower().replace('_', '')
|
target = sys.argv[2].lower().replace('_', '')
|
||||||
@@ -12090,13 +12215,22 @@ def ProcessGAMCommand(args):
|
|||||||
gapi_directory_cros.issue_command()
|
gapi_directory_cros.issue_command()
|
||||||
elif command == 'getcommand':
|
elif command == 'getcommand':
|
||||||
gapi_directory_cros.get_command()
|
gapi_directory_cros.get_command()
|
||||||
|
else:
|
||||||
|
controlflow.invalid_argument_exit(target, f'gam {command}')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif command in ['yubikey']:
|
elif command in ['yubikey']:
|
||||||
action = sys.argv[2].lower().replace('_', '')
|
action = sys.argv[2].lower().replace('_', '')
|
||||||
if action == 'resetpiv':
|
if action == 'resetpiv':
|
||||||
yk = yubikey.YubiKey()
|
doResetYubiKeyPIV()
|
||||||
yk.serial_number = yk.get_serial_number()
|
else:
|
||||||
yk.reset_piv()
|
controlflow.invalid_argument_exit(action, f'gam yubikey')
|
||||||
|
sys.exit(0)
|
||||||
|
elif command == 'enable':
|
||||||
|
enable_what = sys.argv[2].lower().replace('_', '')
|
||||||
|
if enable_what in ['api', 'apis']:
|
||||||
|
enable_apis()
|
||||||
|
else:
|
||||||
|
controlflow.invalid_argument_exit(enable_what, 'gam enable')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
users = getUsersToModify()
|
users = getUsersToModify()
|
||||||
command = sys.argv[3].lower()
|
command = sys.argv[3].lower()
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import os
|
|||||||
|
|
||||||
from google.auth.jwt import Credentials as JWTCredentials
|
from google.auth.jwt import Credentials as JWTCredentials
|
||||||
|
|
||||||
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
|
||||||
@@ -28,7 +28,6 @@ def get_admin_credentials_filename():
|
|||||||
# some custom name in it. Otherwise, just use the default name.
|
# some custom name in it. Otherwise, just use the default name.
|
||||||
if GC_Values[GC_ENABLE_DASA]:
|
if GC_Values[GC_ENABLE_DASA]:
|
||||||
return GC_Values[GC_OAUTH2SERVICE_JSON] if GC_Values[GC_OAUTH2SERVICE_JSON] else _FN_OAUTH2SERVICE_JSON
|
return GC_Values[GC_OAUTH2SERVICE_JSON] if GC_Values[GC_OAUTH2SERVICE_JSON] else _FN_OAUTH2SERVICE_JSON
|
||||||
else:
|
|
||||||
return GC_Values[GC_OAUTH2_TXT] if GC_Values[GC_OAUTH2_TXT] else _FN_OAUTH2_TXT
|
return GC_Values[GC_OAUTH2_TXT] if GC_Values[GC_OAUTH2_TXT] else _FN_OAUTH2_TXT
|
||||||
|
|
||||||
|
|
||||||
@@ -40,17 +39,22 @@ 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':
|
||||||
return JWTCredentials.from_service_account_info(creds_data,
|
return JWTCredentials.from_service_account_info(creds_data,
|
||||||
audience=audience)
|
audience=audience)
|
||||||
elif key_type == 'yubikey':
|
if key_type == 'yubikey':
|
||||||
yksigner = yubikey.YubiKey(creds_data)
|
yksigner = yubikey.YubiKey(creds_data)
|
||||||
return JWTCredentials._from_signer_and_info(yksigner,
|
return JWTCredentials._from_signer_and_info(yksigner,
|
||||||
creds_data,
|
creds_data,
|
||||||
audience=audience)
|
audience=audience)
|
||||||
|
if 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:
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ MESSAGE_LOCAL_SERVER_SUCCESS = ('The authentication flow has completed. You may'
|
|||||||
' close this browser window and return to GAM.')
|
' close this browser window and return to GAM.')
|
||||||
|
|
||||||
MESSAGE_AUTHENTICATION_COMPLETE = ('\nThe authentication flow has completed.\n')
|
MESSAGE_AUTHENTICATION_COMPLETE = ('\nThe authentication flow has completed.\n')
|
||||||
|
MESSAGE_AUTHENTICATION_FAILED = ('\nThe authentication flow failed, reissue command')
|
||||||
|
|
||||||
|
|
||||||
class CredentialsError(Exception):
|
class CredentialsError(Exception):
|
||||||
@@ -629,15 +630,22 @@ class _ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
|
|||||||
print(MESSAGE_CONSOLE_AUTHORIZATION_PROMPT.format(url=d['auth_url']))
|
print(MESSAGE_CONSOLE_AUTHORIZATION_PROMPT.format(url=d['auth_url']))
|
||||||
user_input.start()
|
user_input.start()
|
||||||
userInput = False
|
userInput = False
|
||||||
while True:
|
alive = 2
|
||||||
|
while alive > 0:
|
||||||
sleep(0.1)
|
sleep(0.1)
|
||||||
if not http_client.is_alive():
|
if not http_client.is_alive():
|
||||||
|
if 'code' in d:
|
||||||
user_input.terminate()
|
user_input.terminate()
|
||||||
break
|
break
|
||||||
elif not user_input.is_alive():
|
alive -= 1
|
||||||
|
if not user_input.is_alive():
|
||||||
userInput = True
|
userInput = True
|
||||||
|
if 'code' in d:
|
||||||
http_client.terminate()
|
http_client.terminate()
|
||||||
break
|
break
|
||||||
|
alive -= 1
|
||||||
|
if 'code' not in d:
|
||||||
|
controlflow.system_error_exit(8, MESSAGE_AUTHENTICATION_FAILED)
|
||||||
while True:
|
while True:
|
||||||
code = d['code']
|
code = d['code']
|
||||||
if code.startswith('http'):
|
if code.startswith('http'):
|
||||||
|
|||||||
89
src/gam/auth/signjwt.py
Normal file
89
src/gam/auth/signjwt.py
Normal 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
|
||||||
@@ -87,6 +87,9 @@ class YubiKey():
|
|||||||
def get_serial_number(self):
|
def get_serial_number(self):
|
||||||
try:
|
try:
|
||||||
devices = list_all_devices()
|
devices = list_all_devices()
|
||||||
|
if not devices:
|
||||||
|
msg = f'Could not find any YubiKey'
|
||||||
|
controlflow.system_error_exit(3, msg)
|
||||||
if self.serial_number:
|
if self.serial_number:
|
||||||
for (device, info) in devices:
|
for (device, info) in devices:
|
||||||
if info.serial == self.serial_number:
|
if info.serial == self.serial_number:
|
||||||
|
|||||||
@@ -408,7 +408,7 @@ def update_policy():
|
|||||||
f'{expected_enums}, got {value}'
|
f'{expected_enums}, got {value}'
|
||||||
controlflow.system_error_exit(8, msg)
|
controlflow.system_error_exit(8, msg)
|
||||||
elif vtype in ['TYPE_LIST']:
|
elif vtype in ['TYPE_LIST']:
|
||||||
value = value.split(',')
|
value = value.split(',') if value else []
|
||||||
if myarg == 'chrome.users.chromebrowserupdates' and \
|
if myarg == 'chrome.users.chromebrowserupdates' and \
|
||||||
cased_field == 'targetVersionPrefixSetting':
|
cased_field == 'targetVersionPrefixSetting':
|
||||||
mg = re.compile(r'^([a-z]+)-(\d+)$').match(value)
|
mg = re.compile(r'^([a-z]+)-(\d+)$').match(value)
|
||||||
|
|||||||
@@ -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.42'
|
GAM_VERSION = '6.52'
|
||||||
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'
|
||||||
|
|||||||
Reference in New Issue
Block a user