From: Daniel Lenski Date: Wed, 13 May 2020 21:29:41 +0000 (-0700) Subject: add test-fortinet-login.py X-Git-Tag: v8.20~325^2~32 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=092c4a590f265691407cbed9a270519f28e63e53;p=users%2Fdwmw2%2Fopenconnect.git add test-fortinet-login.py Often easier to prototype HTTPS-based authentication flows in Python, since they're so fiddly and arbitary. So I copied `test-f5-login.py` to `test-fortinet-login.py`. Currently only handles basic username-and-password auth, no 2FA: ``` usage: test-fortinet-login.py [-h] [-v] [-u USERNAME] [-p PASSWORD] [-r REALM] [-c CERT] [--key KEY] [--no-verify] endpoint [extra [extra ...]] positional arguments: endpoint Fortinet server (or complete URL, e.g. https://forti.vpn.com/remote/login) extra Extra field to pass to include in the login query string (e.g. "foo=bar") optional arguments: -h, --help show this help message and exit -v, --verbose --no-verify Ignore invalid server certificate Login credentials: -u USERNAME, --username USERNAME Username (will prompt if unspecified) -p PASSWORD, --password PASSWORD Password (will prompt if unspecified) -r REALM, --realm REALM Realm (empty if unspecified) -c CERT, --cert CERT PEM file containing client certificate (and optionally private key) --key KEY PEM file containing client private key (if not included in same file as certificate) ``` Signed-off-by: Daniel Lenski --- diff --git a/test-fortinet-login.py b/test-fortinet-login.py new file mode 100755 index 00000000..631bcb62 --- /dev/null +++ b/test-fortinet-login.py @@ -0,0 +1,94 @@ +#!/usr/bin/python3 + +from __future__ import print_function +from sys import stderr, version_info +import requests +import argparse +import getpass +from shlex import quote +if (version_info >= (3, 0)): + from urllib.parse import urlparse, urlencode + raw_input = input + import http.client as httplib +else: + from urlparse import urlparse + import httplib + +p = argparse.ArgumentParser() +p.add_argument('-v', '--verbose', default=0, action='count') +p.add_argument('endpoint', help='Fortinet server (or complete URL, e.g. https://forti.vpn.com/remote/login)') +p.add_argument('extra', nargs='*', help='Extra field to pass to include in the login query string (e.g. "foo=bar")') +g = p.add_argument_group('Login credentials') +g.add_argument('-u','--username', help='Username (will prompt if unspecified)') +g.add_argument('-p','--password', help='Password (will prompt if unspecified)') +g.add_argument('-r','--realm', default='', help='Realm (empty if unspecified)') +g.add_argument('-c','--cert', help='PEM file containing client certificate (and optionally private key)') +g.add_argument('--key', help='PEM file containing client private key (if not included in same file as certificate)') +p.add_argument('--no-verify', dest='verify', action='store_false', default=True, help='Ignore invalid server certificate') +args = p.parse_args() + +if args.verbose > 1: + httplib.HTTPConnection.debuglevel = 1 + +extra = dict(x.split('=', 1) for x in args.extra) +endpoint = urlparse(('https://{}/remote/login'.format(args.endpoint) if '//' not in args.endpoint else args.endpoint), 'https:') + +if args.cert and args.key: + cert = (args.cert, args.key) +elif args.cert: + cert = (args.cert, None) +elif args.key: + p.error('--key specified without --cert') +else: + cert = None + +s = requests.Session() +s.cert = cert +s.verify = args.verify +s.headers['User-Agent'] = 'Open AnyConnect VPN Agent v8.x' # seems to result in a more machine-parseable response format than 'Mozilla/5.0 SV1' +s.headers['Accept-Encoding'] = 'identity' # these servers appear to bork 'Transfer-Encoding: chunked' + +print("Initial GET /remote/login to populate cookies...", file=stderr) +res = s.get(endpoint.geturl(), allow_redirects=False) + +# Send login credentials +if args.username == None: + args.username = raw_input('Username: ') +if args.password == None: + args.password = getpass.getpass('Password: ') +data=dict(username=args.username, credential=args.password, realm=args.realm, + ajax=1, redir='%2Fremote%2Findex', just_logged_in=1, + **extra) +print("POST /remote/logincheck to submit login credentials...", file=stderr) +res = s.post(endpoint._replace(path='/remote/logincheck').geturl(), data=data) + +res.raise_for_status() + +# Build openconnect --cookie argument from the result: +url = urlparse(res.url) +if any(c.name=='SVPNCOOKIE' and c.value for c in s.cookies): + cookie = next(c.value for c in s.cookies if c.name=='SVPNCOOKIE') + if args.verbose: + if cert: + cert_and_key = ' \\\n ' + ' '.join('%s "%s"' % (opt, quote(fn)) for opt, fn in zip(('-c','-k'), cert) if fn) + else: + cert_and_key = '' + + print(''' +Extracted connection cookie. Use this to connect: + + echo %s | openconnect --protocol=fortinet %s --cookie-on-stdin %s + +''' % (quote(cookie), cert_and_key, quote(endpoint.netloc)), file=stderr) + + varvals = { + 'HOST': quote(url.netloc), + 'COOKIE': quote(cookie), + } + print('\n'.join('%s=%s' % pair for pair in varvals.items())) + +# Just print the result +else: + if args.verbose: + print(res.headers, file=stderr) + print(res.text)