Rework certificate parts. Use different client certificates.

Reshape and reword configuration file.
This commit is contained in:
Andris Raugulis
2020-05-23 01:06:52 +00:00
parent 3e6b63285c
commit 9eea095bda
2 changed files with 64 additions and 43 deletions
+35 -24
View File
@@ -1,38 +1,49 @@
# --- general
debug = 0 debug = 0
vpn_url = https://vpn.example.com
#vpn_url_cert = vpn_url.cert
okta_url = https://example.okta.com okta_url = https://example.okta.com
#okta_url_cert = okta_url.cert vpn_url = https://vpn.example.com
#gateway = NEWYORK1-GW # optional gateway name to choose # "gateway_url" forces authentication with specific gateway.
#gateway_url = https://ny1-gw.my.lan # optional gateway url to authenticate #gateway_url = https://ny1-gw.my.lan
another_dance = 0 # second round of authentication # "another_dance" (0/1) defines whether to do second authentication, i.e.,
# after authentication with portal, do another authentication with gateway.
#another_dance = 0
username = myuser username = myuser
password = mypass password = mypass
#client_cert = path-to-myusers-client-cert-as-unencrypted-pem-file.pem # "gateway" is a preferred gateway selection, which portal asks for.
gateway = NEWYORK1-GW
#openconnect_certs = path-to-a-writeable-filename-in-which-the-script-will-collect-all-involved-server-certs-if-not-set-a-temp-file-is-used-instead
# --- multi-factor authentication
#mfa_order = totp sms #mfa_order = totp sms
sms.okta = 0 #sms.okta = 0
#totp.okta = ABCDEFGHIJKLMNOP #totp.okta = ABCDEFGHIJKLMNOP
#totp.google = ABCDEFGHIJKLMNOP #totp.google = ABCDEFGHIJKLMNOP
#totp.symantec = ABCDEFGHIJKLMNOP #totp.symantec = ABCDEFGHIJKLMNOP
execute = 0 # execute openconnect command # --- certificates
openconnect_cmd = sudo openconnect # command to run openconnect # "certs" is (optional) path to certificate file, which will be overwritten
openconnect_args = # optional arguments to openconnect # and stored with all certificates encountered (in config and portal data).
# by default, "certs" is not specified, so a temporary file will be created.
# "openconnect_fmt" is automatically detected by running "openconnect_cmd" -V
# otherwise, it can be manually formatted what exactly pipe to openconect:
# - supports <cookie>, <gateway_name>, etc
# - other characters are provided as-is
# #
# - for openconnect 8.05+ # any "*_cert" is (optional) path to readable and unencrypted certificate
#openconnect_fmt = <cookie><gateway_name><cookie> # file, which will be appended to "certs". four specific settings exists:
# - for openconnect 8.00 - 8.04 # "vpn_url_cert" and "okta_url_cert" are used to verify relevant URLs.
#openconnect_fmt = <cookie><gateway_name> # "vpn_cli_cert" and "okta_cli_cert" are used as client certificate.
#
#certs = certs.pem
#vpn_url_cert = vpn.pem
#okta_url_cert = okta.pem
#vpn_cli_cert = vpn_cli.pem
#okta_cli_cert = okta_cli.pem
# --- execution
# "execute" (0/1) defines if openconnect (as specified with "openconnect_cmd"
# and "openconnect_args") is executed after OKTA authentication dance.
execute = 0
openconnect_cmd = sudo openconnect
openconnect_args =
# "openconnect_fmt" is manually specified format what to pipe to openconect.
# supports <cookie>, <username>, etc. other characters are provided as-is
#openconnect_fmt = <cookie><cookie>
+28 -18
View File
@@ -198,7 +198,7 @@ class Conf(object):
self._lines = {} # type: Dict[str, int] self._lines = {} # type: Dict[str, int]
self.debug = False self.debug = False
self.vpn_url = '' # for reassignment self.vpn_url = '' # for reassignment
self.certs = '' # for type self.certs = '' # for filename
def __getattr__(self, name): def __getattr__(self, name):
# type: (str) -> str # type: (str) -> str
@@ -220,10 +220,11 @@ class Conf(object):
if not cert: if not cert:
return return
if not self.certs: if not self.certs:
if 'openconnect_certs' in self._store: if 'certs' in self._store:
self.certs_fh = io.open(self._store['openconnect_certs'], 'wb') self.certs_fh = io.open(self._store['certs'], 'wb')
else: else:
self.certs_fh = tempfile.NamedTemporaryFile() self.certs_fh = tempfile.NamedTemporaryFile(prefix='gpvpn_', delete=False)
log('using temporary file {0} for storing certificates'.format(self.certs_fh.name))
self.certs = self.certs_fh.name self.certs = self.certs_fh.name
self.certs_fh.write(cert) self.certs_fh.write(cert)
@@ -271,12 +272,16 @@ class Conf(object):
conf._store['username'] = input('username: ').strip() conf._store['username'] = input('username: ').strip()
if len(conf._store.get('password', '').strip()) == 0: if len(conf._store.get('password', '').strip()) == 0:
conf._store['password'] = getpass.getpass('password: ').strip() conf._store['password'] = getpass.getpass('password: ').strip()
for cert_name in ['vpn_url_cert', 'okta_url_cert', 'client_cert']: for k in conf._store.keys():
if not k.endswith('_cert'):
continue
cert_file = conf._store.get(k, '').strip() cert_file = conf._store.get(k, '').strip()
if cert_file: if cert_file:
cert_file = os.path.expandvars(os.path.expanduser(cert_file)) cert_file = os.path.expandvars(os.path.expanduser(cert_file))
if not os.path.exists(cert_file): if not os.path.exists(cert_file):
err('configured "{0}" file "{1}" does not exist'.format(cert_name, cert_file)) err('configured "{0}" file "{1}" does not exist'.format(k, cert_file))
if k.endswith('_cli_cert'):
continue
with io.open(cert_file, 'rb') as fp: with io.open(cert_file, 'rb') as fp:
conf.add_cert(fp.read()) conf.add_cert(fp.read())
for k in keys: for k in keys:
@@ -391,15 +396,15 @@ def paloalto_prelogin(conf, s, gateway_url=None):
# 2nd round or direct gateway: use gateway # 2nd round or direct gateway: use gateway
log('prelogin request [gateway_url]') log('prelogin request [gateway_url]')
url = '{0}/ssl-vpn/prelogin.esp'.format(gateway_url) url = '{0}/ssl-vpn/prelogin.esp'.format(gateway_url)
verify = conf.get_cert('vpn_url', True)
else:
# 1st round: use portal
log('prelogin request [vpn_url]')
url = '{0}/global-protect/prelogin.esp'.format(conf.vpn_url)
if conf.certs: if conf.certs:
verify = conf.certs verify = conf.certs
else: else:
verify = True verify = True
else:
# 1st round: use portal
log('prelogin request [vpn_url]')
url = '{0}/global-protect/prelogin.esp'.format(conf.vpn_url)
verify = conf.get_cert('vpn_url', True)
_, _h, c = send_req(conf, s, 'prelogin', url, {}, get=True, verify=verify) _, _h, c = send_req(conf, s, 'prelogin', url, {}, get=True, verify=verify)
x = parse_xml(c) x = parse_xml(c)
saml_req = x.find('.//saml-request') saml_req = x.find('.//saml-request')
@@ -683,9 +688,12 @@ def okta_redirect(conf, s, session_token, redirect_url):
log('okta redirect form request [vpn_url]') log('okta redirect form request [vpn_url]')
purl, pexp = parse_url(form_url), parse_url(conf.vpn_url) purl, pexp = parse_url(form_url), parse_url(conf.vpn_url)
if purl != pexp: if purl != pexp:
# NOTE: redirect to nearest (geo) portal/gateway without any prior knowledge # NOTE: redirect to nearest (geo) gateway without any prior knowledge
warn('{0}: unexpected url found {1} != {2}'.format('redirect form', purl, pexp)) warn('{0}: unexpected url found {1} != {2}'.format('redirect form', purl, pexp))
_, h, c = send_req(conf, s, 'redirect form', form_url, form_data) verify = True # type: Union[str, bool]
if conf.certs:
verify = conf.certs
_, h, c = send_req(conf, s, 'redirect form', form_url, form_data, verify=verify)
else: else:
_, h, c = send_req(conf, s, 'redirect form', form_url, form_data, _, h, c = send_req(conf, s, 'redirect form', form_url, form_data,
expected_url=conf.vpn_url, verify=conf.get_cert('vpn_url', True)) expected_url=conf.vpn_url, verify=conf.get_cert('vpn_url', True))
@@ -757,7 +765,7 @@ def okta_saml_2(conf, s, gateway_url, saml_xml):
url, data = parse_form(xhtml) url, data = parse_form(xhtml)
dbg_form(conf, 'okta.saml request(2)', data) dbg_form(conf, 'okta.saml request(2)', data)
log('okta redirect form request (2) [gateway]') log('okta redirect form request (2) [gateway]')
verify = False # type: Union[str, bool] verify = True # type: Union[str, bool]
if conf.certs: if conf.certs:
verify = conf.certs verify = conf.certs
_, h, c = send_req(conf, s, 'okta redirect form (2)', url, data, _, h, c = send_req(conf, s, 'okta redirect form (2)', url, data,
@@ -842,8 +850,8 @@ def main():
s = requests.Session() s = requests.Session()
s.headers['User-Agent'] = 'PAN GlobalProtect' s.headers['User-Agent'] = 'PAN GlobalProtect'
if conf.client_cert: if conf.okta_cli_cert:
s.cert = conf.client_cert s.cert = conf.okta_cli_cert
if args.list_gateways: if args.list_gateways:
log('listing gateways') log('listing gateways')
@@ -910,11 +918,13 @@ def main():
cmd = conf.openconnect_cmd or 'openconnect' cmd = conf.openconnect_cmd or 'openconnect'
cmd += ' --protocol=gp -u \'{0}\'' cmd += ' --protocol=gp -u \'{0}\''
cmd += ' --usergroup {1}' cmd += ' --usergroup {1}'
if conf.client_cert: if conf.vpn_cli_cert:
cmd += ' --certificate=\'{0}\''.format(conf.client_cert) cmd += ' --certificate=\'{0}\''.format(conf.vpn_cli_cert)
if conf.certs: if conf.certs:
cmd += ' --cafile=\'{0}\''.format(conf.certs) cmd += ' --cafile=\'{0}\''.format(conf.certs)
cmd += ' --passwd-on-stdin ' + conf.openconnect_args + ' \'{2}\'' cmd += ' --passwd-on-stdin ' + conf.openconnect_args + ' \'{2}\''
if conf.certs:
cmd += '; rm -f \'{0}\''.format(conf.certs)
cmd = cmd.format(username, cookie_type, cmd = cmd.format(username, cookie_type,
gateway_url if conf.get_bool('another_dance') else conf.vpn_url) gateway_url if conf.get_bool('another_dance') else conf.vpn_url)