mirror of
https://github.com/os-mnemo/pan-globalprotect-okta
synced 2026-07-31 15:54:36 +02:00
Implement MFA selection priority (by mfa_order, by line number, by value).
This commit is contained in:
@@ -3,6 +3,8 @@ vpn_url = https://vpn.example.com
|
|||||||
okta_url = https://example.okta.com
|
okta_url = https://example.okta.com
|
||||||
username = myuser
|
username = myuser
|
||||||
password = mypass
|
password = mypass
|
||||||
|
# mfa_order = totp sms
|
||||||
|
sms.okta = 0
|
||||||
totp.okta = ABCDEFGHIJKLMNOP
|
totp.okta = ABCDEFGHIJKLMNOP
|
||||||
totp.google = ABCDEFGHIJKLMNOP
|
totp.google = ABCDEFGHIJKLMNOP
|
||||||
gateway = Manual ny1-gw.example.com
|
gateway = Manual ny1-gw.example.com
|
||||||
|
|||||||
+67
-42
@@ -97,8 +97,10 @@ def parse_form(html):
|
|||||||
def load_conf(cf):
|
def load_conf(cf):
|
||||||
conf = {}
|
conf = {}
|
||||||
keys = ['vpn_url', 'username', 'password', 'okta_url']
|
keys = ['vpn_url', 'username', 'password', 'okta_url']
|
||||||
|
line_nr = 0
|
||||||
with io.open(cf, 'r', encoding='utf-8') as fp:
|
with io.open(cf, 'r', encoding='utf-8') as fp:
|
||||||
for rline in fp:
|
for rline in fp:
|
||||||
|
line_nr += 1
|
||||||
line = rline.strip()
|
line = rline.strip()
|
||||||
mx = re.match('^\s*([^=\s]+)\s*=\s*(.*?)\s*(?:#\s+.*)?\s*$', line)
|
mx = re.match('^\s*([^=\s]+)\s*=\s*(.*?)\s*(?:#\s+.*)?\s*$', line)
|
||||||
if mx:
|
if mx:
|
||||||
@@ -109,6 +111,7 @@ def load_conf(cf):
|
|||||||
if re.match('^{0}.*{0}$'.format(q), v):
|
if re.match('^{0}.*{0}$'.format(q), v):
|
||||||
v = v[1:-1]
|
v = v[1:-1]
|
||||||
conf[k] = v
|
conf[k] = v
|
||||||
|
conf['{0}.line'.format(k)] = line_nr
|
||||||
for k, v in os.environ.items():
|
for k, v in os.environ.items():
|
||||||
k = k.lower()
|
k = k.lower()
|
||||||
if k.startswith('gp_'):
|
if k.startswith('gp_'):
|
||||||
@@ -129,6 +132,29 @@ def load_conf(cf):
|
|||||||
conf['debug'] = conf.get('debug', '').lower() in ['1', 'true']
|
conf['debug'] = conf.get('debug', '').lower() in ['1', 'true']
|
||||||
return conf
|
return conf
|
||||||
|
|
||||||
|
def mfa_priority(conf, ftype, fprovider):
|
||||||
|
if ftype == 'token:software:totp':
|
||||||
|
ftype = 'totp'
|
||||||
|
if ftype not in ['totp', 'sms']:
|
||||||
|
return 0
|
||||||
|
mfa_order = conf.get('mfa_order', '')
|
||||||
|
if ftype in mfa_order:
|
||||||
|
priority = (10 - mfa_order.index(ftype)) * 100
|
||||||
|
else:
|
||||||
|
priority = 0
|
||||||
|
value = conf.get('{0}.{1}'.format(ftype, fprovider))
|
||||||
|
if ftype == 'sms':
|
||||||
|
if not (value or '').lower() in ['1', 'true']:
|
||||||
|
value = None
|
||||||
|
line_nr = conf.get('{0}.{1}.line'.format(ftype, fprovider), 0)
|
||||||
|
if value is None:
|
||||||
|
priority += 0
|
||||||
|
elif len(value) == 0:
|
||||||
|
priority += (128 - line_nr)
|
||||||
|
else:
|
||||||
|
priority += (512 - line_nr)
|
||||||
|
return priority
|
||||||
|
|
||||||
def paloalto_prelogin(conf, s):
|
def paloalto_prelogin(conf, s):
|
||||||
log('prelogin request')
|
log('prelogin request')
|
||||||
r = s.get('{0}/global-protect/prelogin.esp'.format(conf.get('vpn_url')))
|
r = s.get('{0}/global-protect/prelogin.esp'.format(conf.get('vpn_url')))
|
||||||
@@ -219,53 +245,52 @@ def okta_mfa(conf, s, j):
|
|||||||
'id': factor_id,
|
'id': factor_id,
|
||||||
'type': factor_type,
|
'type': factor_type,
|
||||||
'provider': provider,
|
'provider': provider,
|
||||||
|
'priority': mfa_priority(conf, factor_type, provider),
|
||||||
'url': factor_url})
|
'url': factor_url})
|
||||||
dbg(conf.get('debug'), 'factors', factors)
|
dbg(conf.get('debug'), 'factors', factors)
|
||||||
if len(factors) == 0:
|
if len(factors) == 0:
|
||||||
err('no factors found')
|
err('no factors found')
|
||||||
totp_factors = [x for x in factors if x.get('type') == 'token:software:totp']
|
for f in sorted(factors, key=lambda x: x.get('priority', 0), reverse=True):
|
||||||
dbg(conf.get('debug'), 'topt_factors', totp_factors)
|
print(f)
|
||||||
if len(totp_factors) == 0:
|
ftype = f.get('type')
|
||||||
err('no totp factors found')
|
if ftype == 'token:software:totp':
|
||||||
return okta_mfa_totp(conf, s, totp_factors, state_token)
|
r = okta_mfa_totp(conf, s, f, state_token)
|
||||||
|
elif ftype == 'sms':
|
||||||
|
r = okta_mfa_sms(conf, s, f, state_token)
|
||||||
|
else:
|
||||||
|
r = None
|
||||||
|
if r is not None:
|
||||||
|
return r
|
||||||
|
err('no factors processed')
|
||||||
|
|
||||||
def okta_mfa_totp(conf, s, factors, state_token):
|
def okta_mfa_totp(conf, s, factor, state_token):
|
||||||
for factor in factors:
|
provider = factor.get('provider', '')
|
||||||
provider = factor.get('provider', '')
|
secret = conf.get('totp.{0}'.format(provider), '') or ''
|
||||||
secret = conf.get('totp.{0}'.format(provider))
|
code = None
|
||||||
if secret is None:
|
if len(secret) == 0:
|
||||||
order = 2
|
code = input('{0} TOTP: '.format(provider)).strip()
|
||||||
elif len(secret) == 0:
|
else:
|
||||||
order = 1
|
import pyotp
|
||||||
else:
|
totp = pyotp.TOTP(secret)
|
||||||
order = 0
|
code = totp.now()
|
||||||
factor['order'] = order
|
code = code or ''
|
||||||
for factor in sorted(factors, key=lambda x: x.get('order', 0)):
|
if len(code) == 0:
|
||||||
provider = factor.get('provider', '')
|
return None
|
||||||
secret = conf.get('totp.{0}'.format(provider), '') or ''
|
data = {
|
||||||
code = None
|
'factorId': factor.get('id'),
|
||||||
if len(secret) == 0:
|
'stateToken': state_token,
|
||||||
code = input('{0} TOTP: '.format(provider)).strip()
|
'passCode': code
|
||||||
else:
|
}
|
||||||
import pyotp
|
log('mfa {0} totp request'.format(provider))
|
||||||
totp = pyotp.TOTP(secret)
|
r = s.post(factor.get('url'), headers=hdr_json(), data=json.dumps(data))
|
||||||
code = totp.now()
|
if r.status_code != 200:
|
||||||
code = code or ''
|
err('okta mfa request failed. {0}'.format(reprr(r)))
|
||||||
if len(code) == 0:
|
dbg(conf.get('debug'), 'mfa.response', r.status_code, r.text)
|
||||||
continue
|
j = parse_rjson(r)
|
||||||
data = {
|
return j.get('sessionToken', '').strip()
|
||||||
'factorId': factor.get('id'),
|
|
||||||
'stateToken': state_token,
|
def okta_mfa_sms(conf, s, factor, state_token):
|
||||||
'passCode': code
|
return None
|
||||||
}
|
|
||||||
log('mfa {0} totp request'.format(provider))
|
|
||||||
r = s.post(factor.get('url'), headers=hdr_json(), data=json.dumps(data))
|
|
||||||
if r.status_code != 200:
|
|
||||||
err('okta mfa request failed. {0}'.format(reprr(r)))
|
|
||||||
dbg(conf.get('debug'), 'mfa.response', r.status_code, r.text)
|
|
||||||
j = parse_rjson(r)
|
|
||||||
return j.get('sessionToken', '').strip()
|
|
||||||
err('no totp was processed')
|
|
||||||
|
|
||||||
def okta_redirect(conf, s, session_token, redirect_url):
|
def okta_redirect(conf, s, session_token, redirect_url):
|
||||||
data = {
|
data = {
|
||||||
|
|||||||
Reference in New Issue
Block a user