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
from werkzeug.datastructures import Headers
import pagure.exceptions
import pagure.lib.query
import pagure.lib.login
import pagure.utils
print("proxy loaded")
PROXYPLUGIN = flask.Blueprint("proxyplugin", __name__)
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/<path:_>", methods=["GET", "POST"])
def repos(_):
""" Proxy to git http backend.
"""
print("repos called")
env = flask.request.environ
session = flask.g.session
if hasattr(flask.g, "fas_user"):
print("user logged in by cookie")
return forbidden() # disallow cookie authorization
# read login and password
login = None
password = None
if 'HTTP_AUTHORIZATION' in env:
try:
authtype, credentials = str(env['HTTP_AUTHORIZATION']).split()
assert authtype.lower() == 'basic'
login, password = base64.b64decode(credentials).decode('utf8').split(':')
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)
if not user_obj or user_obj.token:
print("wrong user")
return unauthorized()
success = False
try:
success = pagure.lib.login.check_password(
password,
user_obj.password,
seed = pagure.config.config.get("PASSWORD_SEED", None) )
except pagure.exceptions.PagureException:
pass
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)
if len(path) < 3 or '.' in path or '..' in path:
print("bad path")
return forbidden()
if path.pop(0) != '':
print("no leading slash")
return forbidden()
if path.pop(0) != 'repos':
print("not repos")
return forbidden()
# 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")
return forbidden()
fork = True
repouser = path.pop(0)
reponame = path.pop(0)
if not reponame.endswith('.git'):
if not path:
print("bad url format")
return forbidden()
namespace = reponame
reponame = path.pop(0)
if not reponame.endswith('.git'):
print("bad url format")
return forbidden()
reponame = reponame[:-4]
# get repo
repo = pagure.lib.query.get_authorized_project(
session = session,
project_name = reponame,
user = repouser,
namespace = namespace,
asuser = login )
if not repo:
print("no repo [%s][%s][%s][%s]" % (str(fork), repouser, namespace, reponame))
return forbidden() if login else unauthorized()
# check access mode
method = env["REQUEST_METHOD"]
getvars = urllib.parse.parse_qs(env["QUERY_STRING"])
readwrite = env["REQUEST_METHOD"] != 'GET'
if method == 'GET' and getvars.get('service', list()).count('git-receive-pack'):
readwrite = True
elif method == 'POST' and path and path[0] == 'git-upload-pack':
readwrite = False
# check write access
if readwrite:
print("write [%s]" % str(login))
if readwrite and (not login or not pagure.utils.is_repo_committer(repo, login, session)):
print("no write access")
return forbidden() if login else unauthorized()
print("pass to proxy")
return httpproxy(HOST, PORT, login)
def badgateway():
return flask.Response('502 Bad Gateway', status = '502 Bad Gateway')
def forbidden():
return flask.Response('403 Forbidden', status = '403 Forbidden')
def unauthorized():
return flask.Response(
'401 Unauthorized',
status = '401 Unauthorized',
headers = Headers([('WWW-Authenticate', 'Basic ream="Authorization area", charset="UTF-8"')]) )
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('reply done %d' % count)
else:
print('query done %d' % count)
def httpproxy(host, port, login):
assert(host)
assert(port)
skip_headers = {
'connection',
'keep-alive',
'proxy-authenticate',
'proxy-authorization',
'proxy-connection',
'te',
'trailers',
'transfer-encoding',
'upgrade',
'authorization',
'www-authenticate',
'gzip' }
env = flask.request.environ
# input method
method = env["REQUEST_METHOD"]
print(method)
# gather input headers
header_prefix = 'HTTP_'
proxy_headers = dict()
for k, v in env.items():
if k.startswith(header_prefix):
kk = k[len(header_prefix):].lower().replace('_', '-')
if not kk in skip_headers:
proxy_headers[kk] = v
if 'CONTENT_TYPE' in env and env['CONTENT_TYPE']:
proxy_headers['content-type'] = env['CONTENT_TYPE']
try:
length = int(env['CONTENT_LENGTH'])
if length: proxy_headers['content-length'] = str(length)
except Exception:
pass
proxy_headers['host'] = host
if login:
proxy_headers['pagure-user'] = login
#print(proxy_headers)
# input uri
uri = env["REQUEST_URI"]
print(uri)
# input body
body = reader(env['wsgi.input'])
connection = None
try:
connection = HTTPConnection(host = host, port = port)
connection.request(
method,
uri,
body = body,
headers = proxy_headers )
except Exception as e:
return badgateway()
response = connection.getresponse()
headers = list()
for k, v in response.getheaders():
if not k.lower() in skip_headers:
headers.append( (k, v) )
return flask.Response(
reader(response, connection),
str(response.status) + ' ' + str(response.reason),
Headers(headers) )