Add simple auth based on the client certificate and pkgdb API.

This commit is contained in:
Jan Kaluza
2016-07-07 14:35:47 +02:00
parent ad4bcc846e
commit 36faac7040
5 changed files with 204 additions and 2 deletions

61
cacert.pem Normal file
View File

@@ -0,0 +1,61 @@
-----BEGIN CERTIFICATE-----
MIIK6zCCBt+gAwIBAgIJAMXcvWMyB9ZeMA0GCSqGSIb3DQEBBQUAMIGxMQswCQYD
VQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAcTB1JhbGVp
Z2gxFzAVBgNVBAoTDkZlZG9yYSBQcm9qZWN0MRowGAYDVQQLExFGZWRvcmEgUHJv
amVjdCBDQTEaMBgGA1UEAxMRRmVkb3JhIFByb2plY3QgQ0ExJjAkBgkqhkiG9w0B
CQEWF2FkbWluQGZlZG9yYXByb2plY3Qub3JnMB4XDTA4MDgyMDE0NDkxNloXDTE4
MDgxODE0NDkxNlowgbExCzAJBgNVBAYTAlVTMRcwFQYDVQQIEw5Ob3J0aCBDYXJv
bGluYTEQMA4GA1UEBxMHUmFsZWlnaDEXMBUGA1UEChMORmVkb3JhIFByb2plY3Qx
GjAYBgNVBAsTEUZlZG9yYSBQcm9qZWN0IENBMRowGAYDVQQDExFGZWRvcmEgUHJv
amVjdCBDQTEmMCQGCSqGSIb3DQEJARYXYWRtaW5AZmVkb3JhcHJvamVjdC5vcmcw
ggQWMA0GCSqGSIb3DQEBAQUAA4IEAwAwggP+AoID9QDIH2F1s0y5V7xBc2tHlXOA
H7999QZ76BU1qtDg4g4k2KyYTG7Gk5eNnJntbpYtRNPL0bQymJIhcfkMCER+UOfv
mum6hrwYSrb0ehsIP1mY9QXdJnlvA1ViXMpZy74byaue9Rn+9GOaOtRWv9dZ5/j4
Wf9JDOt7TzgFfTPZrtasqlSaOicWJuAKyp2SkQup3I0fTtM4/LpR6BY+dDr7ud9d
LTukkGuOPnNx1pxKkuN0jKYwZjwUcQHlRUNF5xrARU5youYSD7ReWdJsZkirJ0W2
dZkUQaIUm55v3p4soMYnbPeJFoAbSJkqSCPI4c/ex/Xr1xp3dXvd0vi9K+w8tvw1
Q3XUvQxum97dbcM7Sw3gRfpFy6K3Up+xXaEnMDGhX31zQAHFTP/P7N+CWNwLg57r
EmuYVfP31b6qsyvuLnpMqe0fYRNWOiJYMALPyRT15RSFGaLyKevqqzR5DFmHQI2C
wl5UFsmBK4LJWqaxE/shuNWEx70BzRYOnPgPr3ohXKBLLxZZtVSlEh+N5FW07Y7T
LkzFGxc0uArsi6EsA9AS0rGJ7FOqMNctvQoR3UFPh5bkXMHgz7aunrB1n5x5rmHk
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
YPF6KxAYMs2ema7PGl2D
-----END CERTIFICATE-----

View File

@@ -10,3 +10,9 @@ rpms_default_repository = git://pkgs.stg.fedoraproject.org/rpms/
rpms_allow_repository = False
rpms_default_cache = http://pkgs.stg.fedoraproject.org/repo/pkgs/
rpms_allow_cache = False
ssl_certificate_file=server.crt
ssl_certificate_key_file=server.key
ssl_ca_certificate_file=cacert.pem
pkgdb_api_url=https://admin.stg.fedoraproject.org/pkgdb/api

17
rida.py
View File

@@ -36,9 +36,10 @@ This is the implementation of the orchestrator's public RESTful API.
# TODO: Emit messages about module submission.
from flask import Flask, request
from rida import config, database, messaging
from rida import config, database, messaging, auth
import json
import modulemd
import ssl
app = Flask(__name__)
app.config.from_envvar("RIDA_SETTINGS", silent=True)
@@ -50,6 +51,12 @@ db = database.Database()
@app.route("/rida/module-builds/", methods=["POST"])
def submit_build():
"""Handles new module build submissions."""
username = auth.is_packager(conf.pkgdb_api_url)
if not username:
return ("You must use your Fedora certificate when submitting"
" new build", 403)
try:
r = json.loads(request.data.decode('utf-8'))
except:
@@ -126,4 +133,10 @@ def query_build(id):
return "No such module found.", 404
if __name__ == "__main__":
app.run()
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
ssl_ctx.load_cert_chain(conf.ssl_certificate_file,
conf.ssl_certificate_key_file)
ssl_ctx.verify_mode = ssl.CERT_OPTIONAL
ssl_ctx.load_verify_locations(cafile=conf.ssl_ca_certificate_file)
app.run(request_handler=auth.ClientCertRequestHander, ssl_context=ssl_ctx)

80
rida/auth.py Normal file
View File

@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2016 Red Hat, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Written by Jan Kaluza <jkaluza@redhat.com>
"""Auth system based on the client certificate and FAS account"""
from flask import Flask, request
from werkzeug.serving import WSGIRequestHandler
import requests
import json
class ClientCertRequestHander(WSGIRequestHandler):
"""
WSGIRequestHandler subclass adding SSL_CLIENT_CERT_* variables
to `request.environ` dict when the client certificate is set and
is signed by CA configured in `conf.ssl_ca_certificate_file`.
"""
def make_environ(self):
environ = WSGIRequestHandler.make_environ(self)
try:
cert = self.request.getpeercert(False)
except:
cert = None
if cert and "subject" in cert:
for keyval in cert["subject"]:
key, val = keyval[0]
environ["SSL_CLIENT_CERT_" + key] = val
return environ
def is_packager(pkgdb_api_url):
"""
Returns the username of user associated with current request by checking
client cert's commonName and pkgdb database API.
When user is not a packager (is not in pkgdb), returns None.
"""
if not "SSL_CLIENT_CERT_commonName" in request.environ:
return None
username = request.environ["SSL_CLIENT_CERT_commonName"]
acl_url = pkgdb_api_url + "/packager/package/" + username
resp = requests.get(acl_url)
try:
resp.raise_for_status()
except:
return None
try:
r = json.loads(resp.content.decode('utf-8'))
except:
return None
if r["output"] == "ok":
return username
return None

View File

@@ -56,6 +56,12 @@ def from_file(filename=None):
conf.rpms_allow_repository = default.getboolean("rpms_allow_repository")
conf.rpms_default_cache = default.get("rpms_default_cache")
conf.rpms_allow_cache = default.getboolean("rpms_allow_cache")
conf.ssl_certificate_file = default.get("ssl_certificate_file")
conf.ssl_certificate_key_file = default.get("ssl_certificate_key_file")
conf.ssl_ca_certificate_file = default.get("ssl_ca_certificate_file")
conf.pkgdb_api_url = default.get("pkgdb_api_url")
return conf
class Config(object):
@@ -72,6 +78,10 @@ class Config(object):
self._rpms_allow_repository = False
self._rpms_default_cache = ""
self._rpms_allow_cache = False
self._ssl_certificate_file = ""
self._ssl_certificate_key_file = ""
self._ssl_ca_certificate_file = ""
self._pkgdb_api_url = ""
@property
def system(self):
@@ -170,3 +180,35 @@ class Config(object):
if not isinstance(b, bool):
raise TypeError("rpms_allow_cache must be a bool.")
self._rpms_allow_cache = b
@property
def ssl_certificate_file(self):
return self._ssl_certificate_file
@ssl_certificate_file.setter
def ssl_certificate_file(self, s):
self._ssl_certificate_file = str(s)
@property
def ssl_ca_certificate_file(self):
return self._ssl_ca_certificate_file
@ssl_ca_certificate_file.setter
def ssl_ca_certificate_file(self, s):
self._ssl_ca_certificate_file = str(s)
@property
def ssl_certificate_key_file(self):
return self._ssl_certificate_key_file
@ssl_certificate_key_file.setter
def ssl_certificate_key_file(self, s):
self._ssl_certificate_key_file = str(s)
@property
def pkgdb_api_url(self):
return self._pkgdb_api_url
@pkgdb_api_url.setter
def pkgdb_api_url(self, s):
self._pkgdb_api_url = str(s)