From 7ae3bf8a7c654844ed142e99c8ebe35b5f49a884 Mon Sep 17 00:00:00 2001 From: Ivan Mahonin Date: Dec 20 2019 08:56:21 +0000 Subject: git auth by SSL client cert --- diff --git a/proxyplugin.py b/proxyplugin.py index a83722e..4a1ab1b 100644 --- a/proxyplugin.py +++ b/proxyplugin.py @@ -3,6 +3,10 @@ from __future__ import unicode_literals, absolute_import import base64 import urllib.parse +import os +import tempfile +import subprocess +import shutil from http.client import HTTPConnection import flask @@ -21,6 +25,47 @@ HOST = None PORT = None +def convert_ssl_to_ssh(sslcert): + """ Extract RSA key from certificate and represent in in ssh-rsa format + """ + sslcert = str(sslcert).strip() + if not sslcert: + return None + tmpdirname = tempfile.mkdtemp() + + sslcert_filename = os.path.join(tmpdirname, "ssl.crt") + rsapem_filename = os.path.join(tmpdirname, "public-rsa.pem") + with open(sslcert_filename, "w") as stream: + stream.write(sslcert) + + # extract PEM RSA public key + cmd = ["/usr/bin/openssl", "x509", "-in", sslcert_filename, "-pubkey", "-noout", "-out", rsapem_filename] + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) + stdout, stderr = proc.communicate() + if proc.returncode != 0: + print("openssl STDOUT: %s", stdout) + print("openssl STDERR: %s", stderr) + shutil.rmtree(tmpdirname) + return None + + # make ssh-ras from PEM RSA public key + cmd = ["/usr/bin/ssh-keygen", "-f", rsapem_filename, "-i", "-mPKCS8"] + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) + stdout, stderr = proc.communicate() + if proc.returncode != 0: + print("ssh-keygen STDOUT: %s", stdout) + print("ssh-keygen STDERR: %s", stderr) + shutil.rmtree(tmpdirname) + return None + + shutil.rmtree(tmpdirname) + sshrsa = stdout.decode("utf-8") + + return sshrsa + + @PROXYPLUGIN.route("/repos/", methods=["GET", "POST"]) def repos(_): """ Proxy to git http backend. @@ -45,7 +90,9 @@ def repos(_): except Exception: print("wrong auth format") return unauthorized() - + elif 'PAGURE_SSL_CLIENT_CERT' in env: + sslcert = str(urllib.parse.unquote(env['PAGURE_SSL_CLIENT_CERT'])) + # verify password if login: user_obj = pagure.lib.query.search_user(session, username = login) @@ -63,7 +110,20 @@ def repos(_): if not success: print("wrong password") return unauthorized() - + elif sslcert: + #print("received sslcert:", sslcert) + ssh_rsa = convert_ssl_to_ssh(sslcert) + if ssh_rsa: + #print("converted to ssh-rsa:", ssh_rsa) + ssh_short_key = pagure.lib.query.is_valid_ssh_key(ssh_rsa) + if ssh_short_key: + ssh_search_key = ssh_short_key.split(" ")[1] + #print("ssh-rsa hash:", ssh_search_key) + key = pagure.lib.query.find_ssh_key(session, ssh_search_key, None) + if key and key.user and not key.user.token: + login = key.user.username + print("login by sslcert:", login, ssh_search_key) + # read path path = env["REQUEST_URI"].split('?')[0].split('/') print(path) @@ -79,9 +139,16 @@ def repos(_): # read reponame fork = False + docs = False repouser = None namespace = None reponame = path.pop(0) + if reponame == 'docs': + if len(path) < 1: + print("bad url format") + return forbidden() + docs = True + reponame = path.pop(0) if reponame == 'fork': if len(path) < 2: print("bad url format") @@ -143,13 +210,17 @@ def unauthorized(): def reader(data, connection = None): + count = 0 while True: chunk = data.read(4096) if not chunk: break + count += len(chunk) yield chunk if connection: connection.close() - print('done') + print('reply done %d' % count) + else: + print('query done %d' % count) def httpproxy(host, port, login): @@ -166,12 +237,15 @@ def httpproxy(host, port, login): 'trailers', 'transfer-encoding', 'upgrade', - 'authorization' } + 'authorization', + 'www-authenticate', + 'gzip' } env = flask.request.environ # input method method = env["REQUEST_METHOD"] + print(method) # gather input headers header_prefix = 'HTTP_' @@ -181,7 +255,7 @@ def httpproxy(host, port, login): kk = k[len(header_prefix):].lower().replace('_', '-') if not kk in skip_headers: proxy_headers[kk] = v - if 'CONTENT_TYPE' in env: + if 'CONTENT_TYPE' in env and env['CONTENT_TYPE']: proxy_headers['content-type'] = env['CONTENT_TYPE'] try: length = int(env['CONTENT_LENGTH']) @@ -191,10 +265,11 @@ def httpproxy(host, port, login): proxy_headers['host'] = host if login: proxy_headers['pagure-user'] = login - print(proxy_headers) + #print(proxy_headers) # input uri uri = env["REQUEST_URI"] + print(uri) # input body body = reader(env['wsgi.input'])