225 lines
5.7 KiB
Python
Executable File
225 lines
5.7 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
import argparse
|
|
from oauth2client import client
|
|
import certclient
|
|
import os
|
|
from urllib import parse
|
|
import requests
|
|
from http import server
|
|
import socket
|
|
import ssl
|
|
import subprocess
|
|
import tempfile
|
|
|
|
|
|
parser = argparse.ArgumentParser(description='oauthproxy')
|
|
parser.add_argument(
|
|
'--allowed-domain',
|
|
dest='allowed_domain',
|
|
action='store',
|
|
required=True)
|
|
parser.add_argument(
|
|
'--api-key',
|
|
dest='api_key',
|
|
action='store',
|
|
required=True)
|
|
parser.add_argument(
|
|
'--ca-cert',
|
|
dest='ca_cert',
|
|
action='store',
|
|
required=True)
|
|
parser.add_argument(
|
|
'--certserver-ca-cert',
|
|
dest='certserver_ca_cert',
|
|
action='store',
|
|
required=True)
|
|
parser.add_argument(
|
|
'--certserver-client-cert',
|
|
dest='certserver_client_cert',
|
|
action='store',
|
|
required=True)
|
|
parser.add_argument(
|
|
'--certserver-client-key',
|
|
dest='certserver_client_key',
|
|
action='store',
|
|
required=True)
|
|
parser.add_argument(
|
|
'--certserver',
|
|
dest='certserver',
|
|
action='store',
|
|
required=True)
|
|
parser.add_argument(
|
|
'--client-secrets',
|
|
dest='client_secrets',
|
|
action='store',
|
|
required=True)
|
|
parser.add_argument(
|
|
'--export-password',
|
|
dest='export_password',
|
|
action='store',
|
|
required=True)
|
|
parser.add_argument(
|
|
'--listen-host',
|
|
dest='listen_host',
|
|
action='store',
|
|
default='::')
|
|
parser.add_argument(
|
|
'--listen-port',
|
|
dest='listen_port',
|
|
type=int,
|
|
action='store',
|
|
default=443)
|
|
parser.add_argument(
|
|
'--server-key',
|
|
dest='server_key',
|
|
action='store',
|
|
required=True)
|
|
parser.add_argument(
|
|
'--server-cert',
|
|
dest='server_cert',
|
|
action='store',
|
|
required=True)
|
|
parser.add_argument(
|
|
'--subject',
|
|
dest='subject',
|
|
action='store',
|
|
required=True)
|
|
FLAGS = parser.parse_args()
|
|
|
|
|
|
class HTTPServer6(server.HTTPServer):
|
|
address_family = socket.AF_INET6
|
|
|
|
|
|
class OAuthProxy(object):
|
|
|
|
def __init__(self, listen_host, listen_port, server_key, server_cert, client_secrets, api_key, allowed_domain, subject, ca_cert, export_password, certclient):
|
|
self._client_secrets = client_secrets
|
|
self._api_key = api_key
|
|
self._allowed_domain = allowed_domain
|
|
self._subject = subject
|
|
self._ca_cert = ca_cert
|
|
self._export_password = export_password
|
|
self._certclient = certclient
|
|
|
|
HANDLERS = {
|
|
'/': self._ServeRedirect,
|
|
'/oauth2callback': self._OAuth2Callback,
|
|
}
|
|
|
|
class RequestHandler(server.BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
self.parsed_url = parse.urlparse(self.path)
|
|
try:
|
|
HANDLERS[self.parsed_url.path](self)
|
|
except KeyError:
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
|
|
self._httpd = HTTPServer6((listen_host, listen_port), RequestHandler)
|
|
self._httpd.socket = ssl.wrap_socket(
|
|
self._httpd.socket,
|
|
keyfile=server_key,
|
|
certfile=server_cert,
|
|
server_side=True)
|
|
self._httpd.socket.settimeout(5.0)
|
|
|
|
def Serve(self):
|
|
self._httpd.serve_forever()
|
|
|
|
def _GetFlow(self, req):
|
|
return_url = ''.join([
|
|
'https://',
|
|
req.headers['Host'],
|
|
'/oauth2callback',
|
|
])
|
|
return client.flow_from_clientsecrets(
|
|
self._client_secrets,
|
|
login_hint=self._allowed_domain,
|
|
scope='https://www.googleapis.com/auth/userinfo.email',
|
|
redirect_uri=return_url)
|
|
|
|
def _GetCert(self, email):
|
|
with tempfile.TemporaryDirectory() as td:
|
|
key_path = os.path.join(td, 'key.pem')
|
|
subprocess.check_call([
|
|
'openssl', 'ecparam', '-genkey',
|
|
'-name', 'secp384r1',
|
|
'-out', key_path,
|
|
])
|
|
csr_path = os.path.join(td, 'csr.pem')
|
|
proc = subprocess.Popen([
|
|
'openssl', 'req', '-new',
|
|
'-key', key_path,
|
|
'-subj', self._subject.replace('EMAIL', email),
|
|
],
|
|
stdout=subprocess.PIPE)
|
|
csr = proc.stdout.read()
|
|
cert = self._certclient.Request(csr)
|
|
proc = subprocess.Popen([
|
|
'openssl', 'pkcs12', '-export',
|
|
'-inkey', key_path,
|
|
'-certfile', self._ca_cert,
|
|
'-passout', self._export_password,
|
|
],
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE)
|
|
proc.stdin.write(cert.encode('ascii'))
|
|
proc.stdin.close()
|
|
ret = proc.stdout.read()
|
|
assert proc.wait() == 0
|
|
return ret
|
|
|
|
def _ServeRedirect(self, req):
|
|
req.send_response(302)
|
|
req.send_header('Location', self._GetFlow(req).step1_get_authorize_url())
|
|
req.end_headers()
|
|
|
|
def _OAuth2Callback(self, req):
|
|
qs = parse.parse_qs(req.parsed_url.query)
|
|
credentials = self._GetFlow(req).step2_exchange(qs['code'][0])
|
|
result = requests.get(
|
|
'https://www.googleapis.com/plus/v1/people/me?%s' % parse.urlencode({
|
|
'key': self._api_key,
|
|
'access_token': credentials.access_token,
|
|
}))
|
|
emails = [
|
|
x['value']
|
|
for x in result.json()['emails']
|
|
if x['type'] == 'account'
|
|
]
|
|
email = emails[0]
|
|
assert email.endswith('@%s' % self._allowed_domain)
|
|
result = self._GetCert(email)
|
|
req.send_response(200)
|
|
req.send_header('Content-Type', 'application/x-pkcs12')
|
|
req.send_header('Content-Disposition', 'attachment; filename=%s.pfx' % email)
|
|
req.end_headers()
|
|
req.wfile.write(result)
|
|
|
|
|
|
def main():
|
|
client = certclient.CertClient(
|
|
FLAGS.certserver,
|
|
FLAGS.certserver_ca_cert,
|
|
FLAGS.certserver_client_cert,
|
|
FLAGS.certserver_client_key)
|
|
server = OAuthProxy(
|
|
FLAGS.listen_host,
|
|
FLAGS.listen_port,
|
|
FLAGS.server_key,
|
|
FLAGS.server_cert,
|
|
FLAGS.client_secrets,
|
|
FLAGS.api_key,
|
|
FLAGS.allowed_domain,
|
|
FLAGS.subject,
|
|
FLAGS.ca_cert,
|
|
FLAGS.export_password,
|
|
client)
|
|
server.Serve()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|