|
|
7ef9df |
|
|
|
7ef9df |
from __future__ import unicode_literals, absolute_import
|
|
|
7ef9df |
|
|
|
7ef9df |
import base64
|
|
|
7ef9df |
import urllib.parse
|
|
|
7ae3bf |
import os
|
|
|
7ae3bf |
import tempfile
|
|
|
7ae3bf |
import subprocess
|
|
|
7ae3bf |
import shutil
|
|
|
7ef9df |
from http.client import HTTPConnection
|
|
|
7ef9df |
|
|
|
7ef9df |
import flask
|
|
|
7ef9df |
from werkzeug.datastructures import Headers
|
|
|
7ef9df |
|
|
|
7ef9df |
import pagure.exceptions
|
|
|
7ef9df |
import pagure.lib.query
|
|
|
7ef9df |
import pagure.lib.login
|
|
|
7ef9df |
import pagure.utils
|
|
|
7ef9df |
|
|
|
7ef9df |
|
|
|
7ef9df |
print("proxy loaded")
|
|
|
7ef9df |
|
|
|
7ef9df |
PROXYPLUGIN = flask.Blueprint("proxyplugin", __name__)
|
|
|
7ef9df |
HOST = None
|
|
|
7ef9df |
PORT = None
|
|
|
7ef9df |
|
|
|
7ef9df |
|
|
|
7ae3bf |
def convert_ssl_to_ssh(sslcert):
|
|
|
7ae3bf |
""" Extract RSA key from certificate and represent in in ssh-rsa format
|
|
|
7ae3bf |
"""
|
|
|
7ae3bf |
sslcert = str(sslcert).strip()
|
|
|
7ae3bf |
if not sslcert:
|
|
|
7ae3bf |
return None
|
|
|
7ae3bf |
tmpdirname = tempfile.mkdtemp()
|
|
|
7ae3bf |
|
|
|
7ae3bf |
sslcert_filename = os.path.join(tmpdirname, "ssl.crt")
|
|
|
7ae3bf |
rsapem_filename = os.path.join(tmpdirname, "public-rsa.pem")
|
|
|
7ae3bf |
with open(sslcert_filename, "w") as stream:
|
|
|
7ae3bf |
stream.write(sslcert)
|
|
|
7ae3bf |
|
|
|
7ae3bf |
# extract PEM RSA public key
|
|
|
7ae3bf |
cmd = ["/usr/bin/openssl", "x509", "-in", sslcert_filename, "-pubkey", "-noout", "-out", rsapem_filename]
|
|
|
7ae3bf |
proc = subprocess.Popen(
|
|
|
7ae3bf |
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
|
|
|
7ae3bf |
stdout, stderr = proc.communicate()
|
|
|
7ae3bf |
if proc.returncode != 0:
|
|
|
7ae3bf |
print("openssl STDOUT: %s", stdout)
|
|
|
7ae3bf |
print("openssl STDERR: %s", stderr)
|
|
|
7ae3bf |
shutil.rmtree(tmpdirname)
|
|
|
7ae3bf |
return None
|
|
|
7ae3bf |
|
|
|
7ae3bf |
# make ssh-ras from PEM RSA public key
|
|
|
7ae3bf |
cmd = ["/usr/bin/ssh-keygen", "-f", rsapem_filename, "-i", "-mPKCS8"]
|
|
|
7ae3bf |
proc = subprocess.Popen(
|
|
|
7ae3bf |
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
|
|
|
7ae3bf |
stdout, stderr = proc.communicate()
|
|
|
7ae3bf |
if proc.returncode != 0:
|
|
|
7ae3bf |
print("ssh-keygen STDOUT: %s", stdout)
|
|
|
7ae3bf |
print("ssh-keygen STDERR: %s", stderr)
|
|
|
7ae3bf |
shutil.rmtree(tmpdirname)
|
|
|
7ae3bf |
return None
|
|
|
7ae3bf |
|
|
|
7ae3bf |
shutil.rmtree(tmpdirname)
|
|
|
7ae3bf |
sshrsa = stdout.decode("utf-8")
|
|
|
7ae3bf |
|
|
|
7ae3bf |
return sshrsa
|
|
|
7ae3bf |
|
|
|
7ae3bf |
|
|
|
7ef9df |
@PROXYPLUGIN.route("/repos/<path:_>", methods=["GET", "POST"])</path:_>
|
|
|
7ef9df |
def repos(_):
|
|
|
7ef9df |
""" Proxy to git http backend.
|
|
|
7ef9df |
"""
|
|
|
7ef9df |
|
|
|
7ef9df |
print("repos called")
|
|
|
7ef9df |
|
|
|
7ef9df |
env = flask.request.environ
|
|
|
7ef9df |
session = flask.g.session
|
|
|
7ef9df |
if hasattr(flask.g, "fas_user"):
|
|
|
7ef9df |
print("user logged in by cookie")
|
|
|
7ef9df |
return forbidden() # disallow cookie authorization
|
|
|
7ef9df |
|
|
|
7ef9df |
# read login and password
|
|
|
7ef9df |
login = None
|
|
|
7ef9df |
password = None
|
|
|
7ef9df |
if 'HTTP_AUTHORIZATION' in env:
|
|
|
7ef9df |
try:
|
|
|
7ef9df |
authtype, credentials = str(env['HTTP_AUTHORIZATION']).split()
|
|
|
7ef9df |
assert authtype.lower() == 'basic'
|
|
|
7ef9df |
login, password = base64.b64decode(credentials).decode('utf8').split(':')
|
|
|
7ef9df |
except Exception:
|
|
|
7ef9df |
print("wrong auth format")
|
|
|
7ef9df |
return unauthorized()
|
|
|
7ae3bf |
elif 'PAGURE_SSL_CLIENT_CERT' in env:
|
|
|
7ae3bf |
sslcert = str(urllib.parse.unquote(env['PAGURE_SSL_CLIENT_CERT']))
|
|
|
7ae3bf |
|
|
|
7ef9df |
# verify password
|
|
|
7ef9df |
if login:
|
|
|
7ef9df |
user_obj = pagure.lib.query.search_user(session, username = login)
|
|
|
7ef9df |
if not user_obj or user_obj.token:
|
|
|
7ef9df |
print("wrong user")
|
|
|
7ef9df |
return unauthorized()
|
|
|
7ef9df |
success = False
|
|
|
7ef9df |
try:
|
|
|
7ef9df |
success = pagure.lib.login.check_password(
|
|
|
7ef9df |
password,
|
|
|
7ef9df |
user_obj.password,
|
|
|
7ef9df |
seed = pagure.config.config.get("PASSWORD_SEED", None) )
|
|
|
7ef9df |
except pagure.exceptions.PagureException:
|
|
|
7ef9df |
pass
|
|
|
7ef9df |
if not success:
|
|
|
7ef9df |
print("wrong password")
|
|
|
7ef9df |
return unauthorized()
|
|
|
7ae3bf |
elif sslcert:
|
|
|
7ae3bf |
#print("received sslcert:", sslcert)
|
|
|
7ae3bf |
ssh_rsa = convert_ssl_to_ssh(sslcert)
|
|
|
7ae3bf |
if ssh_rsa:
|
|
|
7ae3bf |
#print("converted to ssh-rsa:", ssh_rsa)
|
|
|
7ae3bf |
ssh_short_key = pagure.lib.query.is_valid_ssh_key(ssh_rsa)
|
|
|
7ae3bf |
if ssh_short_key:
|
|
|
7ae3bf |
ssh_search_key = ssh_short_key.split(" ")[1]
|
|
|
7ae3bf |
#print("ssh-rsa hash:", ssh_search_key)
|
|
|
7ae3bf |
key = pagure.lib.query.find_ssh_key(session, ssh_search_key, None)
|
|
|
7ae3bf |
if key and key.user and not key.user.token:
|
|
|
7ae3bf |
login = key.user.username
|
|
|
7ae3bf |
print("login by sslcert:", login, ssh_search_key)
|
|
|
7ae3bf |
|
|
|
7ef9df |
# read path
|
|
|
7ef9df |
path = env["REQUEST_URI"].split('?')[0].split('/')
|
|
|
7ef9df |
print(path)
|
|
|
7ef9df |
if len(path) < 3 or '.' in path or '..' in path:
|
|
|
7ef9df |
print("bad path")
|
|
|
7ef9df |
return forbidden()
|
|
|
7ef9df |
if path.pop(0) != '':
|
|
|
7ef9df |
print("no leading slash")
|
|
|
7ef9df |
return forbidden()
|
|
|
7ef9df |
if path.pop(0) != 'repos':
|
|
|
7ef9df |
print("not repos")
|
|
|
7ef9df |
return forbidden()
|
|
|
7ef9df |
|
|
|
7ef9df |
# read reponame
|
|
|
7ef9df |
fork = False
|
|
|
7ae3bf |
docs = False
|
|
|
7ef9df |
repouser = None
|
|
|
7ef9df |
namespace = None
|
|
|
7ef9df |
reponame = path.pop(0)
|
|
|
7ae3bf |
if reponame == 'docs':
|
|
|
7ae3bf |
if len(path) < 1:
|
|
|
7ae3bf |
print("bad url format")
|
|
|
7ae3bf |
return forbidden()
|
|
|
7ae3bf |
docs = True
|
|
|
7ae3bf |
reponame = path.pop(0)
|
|
|
7ef9df |
if reponame == 'fork':
|
|
|
7ef9df |
if len(path) < 2:
|
|
|
7ef9df |
print("bad url format")
|
|
|
7ef9df |
return forbidden()
|
|
|
7ef9df |
fork = True
|
|
|
7ef9df |
repouser = path.pop(0)
|
|
|
7ef9df |
reponame = path.pop(0)
|
|
|
7ef9df |
if not reponame.endswith('.git'):
|
|
|
7ef9df |
if not path:
|
|
|
7ef9df |
print("bad url format")
|
|
|
7ef9df |
return forbidden()
|
|
|
7ef9df |
namespace = reponame
|
|
|
7ef9df |
reponame = path.pop(0)
|
|
|
7ef9df |
if not reponame.endswith('.git'):
|
|
|
7ef9df |
print("bad url format")
|
|
|
7ef9df |
return forbidden()
|
|
|
7ef9df |
reponame = reponame[:-4]
|
|
|
7ef9df |
|
|
|
7ef9df |
# get repo
|
|
|
7ef9df |
repo = pagure.lib.query.get_authorized_project(
|
|
|
7ef9df |
session = session,
|
|
|
7ef9df |
project_name = reponame,
|
|
|
7ef9df |
user = repouser,
|
|
|
7ef9df |
namespace = namespace,
|
|
|
7ef9df |
asuser = login )
|
|
|
7ef9df |
if not repo:
|
|
|
7ef9df |
print("no repo [%s][%s][%s][%s]" % (str(fork), repouser, namespace, reponame))
|
|
|
7ef9df |
return forbidden() if login else unauthorized()
|
|
|
7ef9df |
|
|
|
7ef9df |
# check access mode
|
|
|
7ef9df |
method = env["REQUEST_METHOD"]
|
|
|
7ef9df |
getvars = urllib.parse.parse_qs(env["QUERY_STRING"])
|
|
|
7ef9df |
readwrite = env["REQUEST_METHOD"] != 'GET'
|
|
|
7ef9df |
if method == 'GET' and getvars.get('service', list()).count('git-receive-pack'):
|
|
|
7ef9df |
readwrite = True
|
|
|
7ef9df |
elif method == 'POST' and path and path[0] == 'git-upload-pack':
|
|
|
7ef9df |
readwrite = False
|
|
|
7ef9df |
|
|
|
7ef9df |
# check write access
|
|
|
7ef9df |
if readwrite:
|
|
|
7ef9df |
print("write [%s]" % str(login))
|
|
|
7ef9df |
if readwrite and (not login or not pagure.utils.is_repo_committer(repo, login, session)):
|
|
|
7ef9df |
print("no write access")
|
|
|
7ef9df |
return forbidden() if login else unauthorized()
|
|
|
7ef9df |
|
|
|
7ef9df |
print("pass to proxy")
|
|
|
7ef9df |
return httpproxy(HOST, PORT, login)
|
|
|
7ef9df |
|
|
|
7ef9df |
|
|
|
7ef9df |
def badgateway():
|
|
|
7ef9df |
return flask.Response('502 Bad Gateway', status = '502 Bad Gateway')
|
|
|
7ef9df |
def forbidden():
|
|
|
7ef9df |
return flask.Response('403 Forbidden', status = '403 Forbidden')
|
|
|
7ef9df |
def unauthorized():
|
|
|
7ef9df |
return flask.Response(
|
|
|
7ef9df |
'401 Unauthorized',
|
|
|
7ef9df |
status = '401 Unauthorized',
|
|
|
7ef9df |
headers = Headers([('WWW-Authenticate', 'Basic ream="Authorization area", charset="UTF-8"')]) )
|
|
|
7ef9df |
|
|
|
7ef9df |
|
|
|
7ef9df |
def reader(data, connection = None):
|
|
|
7ae3bf |
count = 0
|
|
|
7ef9df |
while True:
|
|
|
7ef9df |
chunk = data.read(4096)
|
|
|
7ef9df |
if not chunk: break
|
|
|
7ae3bf |
count += len(chunk)
|
|
|
7ef9df |
yield chunk
|
|
|
7ef9df |
if connection:
|
|
|
7ef9df |
connection.close()
|
|
|
7ae3bf |
print('reply done %d' % count)
|
|
|
7ae3bf |
else:
|
|
|
7ae3bf |
print('query done %d' % count)
|
|
|
7ef9df |
|
|
|
7ef9df |
|
|
|
7ef9df |
def httpproxy(host, port, login):
|
|
|
7ef9df |
assert(host)
|
|
|
7ef9df |
assert(port)
|
|
|
7ef9df |
|
|
|
7ef9df |
skip_headers = {
|
|
|
7ef9df |
'connection',
|
|
|
7ef9df |
'keep-alive',
|
|
|
7ef9df |
'proxy-authenticate',
|
|
|
7ef9df |
'proxy-authorization',
|
|
|
7ef9df |
'proxy-connection',
|
|
|
7ef9df |
'te',
|
|
|
7ef9df |
'trailers',
|
|
|
7ef9df |
'transfer-encoding',
|
|
|
7ef9df |
'upgrade',
|
|
|
7ae3bf |
'authorization',
|
|
|
7ae3bf |
'www-authenticate',
|
|
|
7ae3bf |
'gzip' }
|
|
|
7ef9df |
|
|
|
7ef9df |
env = flask.request.environ
|
|
|
7ef9df |
|
|
|
7ef9df |
# input method
|
|
|
7ef9df |
method = env["REQUEST_METHOD"]
|
|
|
7ae3bf |
print(method)
|
|
|
7ef9df |
|
|
|
7ef9df |
# gather input headers
|
|
|
7ef9df |
header_prefix = 'HTTP_'
|
|
|
7ef9df |
proxy_headers = dict()
|
|
|
7ef9df |
for k, v in env.items():
|
|
|
7ef9df |
if k.startswith(header_prefix):
|
|
|
7ef9df |
kk = k[len(header_prefix):].lower().replace('_', '-')
|
|
|
7ef9df |
if not kk in skip_headers:
|
|
|
7ef9df |
proxy_headers[kk] = v
|
|
|
7ae3bf |
if 'CONTENT_TYPE' in env and env['CONTENT_TYPE']:
|
|
|
7ef9df |
proxy_headers['content-type'] = env['CONTENT_TYPE']
|
|
|
7ef9df |
try:
|
|
|
7ef9df |
length = int(env['CONTENT_LENGTH'])
|
|
|
7ef9df |
if length: proxy_headers['content-length'] = str(length)
|
|
|
7ef9df |
except Exception:
|
|
|
7ef9df |
pass
|
|
|
7ef9df |
proxy_headers['host'] = host
|
|
|
7ef9df |
if login:
|
|
|
7ef9df |
proxy_headers['pagure-user'] = login
|
|
|
7ae3bf |
#print(proxy_headers)
|
|
|
7ef9df |
|
|
|
7ef9df |
# input uri
|
|
|
7ef9df |
uri = env["REQUEST_URI"]
|
|
|
7ae3bf |
print(uri)
|
|
|
7ef9df |
|
|
|
7ef9df |
# input body
|
|
|
7ef9df |
body = reader(env['wsgi.input'])
|
|
|
7ef9df |
|
|
|
7ef9df |
connection = None
|
|
|
7ef9df |
try:
|
|
|
7ef9df |
connection = HTTPConnection(host = host, port = port)
|
|
|
7ef9df |
connection.request(
|
|
|
7ef9df |
method,
|
|
|
7ef9df |
uri,
|
|
|
7ef9df |
body = body,
|
|
|
7ef9df |
headers = proxy_headers )
|
|
|
7ef9df |
except Exception as e:
|
|
|
7ef9df |
return badgateway()
|
|
|
7ef9df |
|
|
|
7ef9df |
response = connection.getresponse()
|
|
|
7ef9df |
headers = list()
|
|
|
7ef9df |
for k, v in response.getheaders():
|
|
|
7ef9df |
if not k.lower() in skip_headers:
|
|
|
7ef9df |
headers.append( (k, v) )
|
|
|
7ef9df |
|
|
|
7ef9df |
return flask.Response(
|
|
|
7ef9df |
reader(response, connection),
|
|
|
7ef9df |
str(response.status) + ' ' + str(response.reason),
|
|
|
7ef9df |
Headers(headers) )
|