diff options
-rw-r--r-- | auth-server-poc/requirements.txt | 34 | ||||
-rw-r--r-- | auth-server-poc/src/app.py | 39 | ||||
-rwxr-xr-x | auth-server-poc/src/authn.py (renamed from src/authn.py) | 61 | ||||
-rw-r--r-- | auth-server-poc/src/userdb.yaml | 23 | ||||
-rw-r--r-- | demo/wsgi_demo_users.yaml | 15 | ||||
-rwxr-xr-x | quickstart.sh | 5 | ||||
-rwxr-xr-x | src/main.py | 63 |
7 files changed, 164 insertions, 76 deletions
diff --git a/auth-server-poc/requirements.txt b/auth-server-poc/requirements.txt index 9927acb..fc8fc53 100644 --- a/auth-server-poc/requirements.txt +++ b/auth-server-poc/requirements.txt @@ -248,3 +248,37 @@ zipp==3.6.0 \ # via # -r requirements.txt # importlib-metadata +pyyaml==6.0 \ + --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \ + --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \ + --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \ + --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \ + --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \ + --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \ + --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \ + --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \ + --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \ + --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \ + --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \ + --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \ + --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \ + --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \ + --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \ + --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \ + --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \ + --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \ + --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \ + --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \ + --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \ + --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \ + --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \ + --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \ + --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \ + --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \ + --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \ + --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \ + --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \ + --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \ + --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \ + --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \ + --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5 diff --git a/auth-server-poc/src/app.py b/auth-server-poc/src/app.py index 443eded..c7ba0d1 100644 --- a/auth-server-poc/src/app.py +++ b/auth-server-poc/src/app.py @@ -3,40 +3,51 @@ from flask_restful import Api, Resource from flask_jwt_extended import create_access_token, JWTManager from flask_cors import CORS +import authn + app = Flask(__name__) cors = CORS( app, resources={r"/api/*": {"origins": "*"}}, expose_headers=["Content-Type", "Authorization", "X-Total-Count"], ) -api = Api(app, prefix='/api/v1.0') +api = Api(app, prefix="/api/v1.0") jwt = JWTManager(app) -PEM_PRIVATE = '/opt/auth-server-poc/cert/private.pem' -PEM_PUBLIC = '/opt/auth-server-poc/cert/public.pem' +PEM_PRIVATE = "/opt/auth-server-poc/cert/private.pem" +PEM_PUBLIC = "/opt/auth-server-poc/cert/public.pem" -app.config['JWT_PRIVATE_KEY'] = open(PEM_PRIVATE).read() -app.config['JWT_PUBLIC_KEY'] = open(PEM_PUBLIC).read() -app.config['JWT_ALGORITHM'] = 'ES256' -app.config['JWT_IDENTITY_CLAIM'] = 'sub' -app.config['JWT_ACCESS_TOKEN_EXPIRES'] = False +app.config["JWT_PRIVATE_KEY"] = open(PEM_PRIVATE).read() +app.config["JWT_PUBLIC_KEY"] = open(PEM_PUBLIC).read() +app.config["JWT_ALGORITHM"] = "ES256" +app.config["JWT_IDENTITY_CLAIM"] = "sub" +app.config["JWT_ACCESS_TOKEN_EXPIRES"] = False class AuthApi(Resource): def post(self): - additional_claims = {"type": "access", "domains": ["sunet.se"]} + + identity = request.environ.get("REMOTE_USER") + db = authn.UserDB("userdb.yaml") + additional_claims = { + "type": "access", + "read": db.read_perms(identity), + "write": db.write_perms(identity), + } + access_token = create_access_token( - identity=request.environ.get('REMOTE_USER'), + identity=identity, additional_claims=additional_claims, ) - return {'access_token': access_token}, 200 + + return {"access_token": access_token}, 200 -@app.route('/') +@app.route("/") def index(): return "<p>Username: {}</p><p>Auth type: {}</p>".format( - request.environ.get('REMOTE_USER'), request.environ.get('AUTH_TYPE') + request.environ.get("REMOTE_USER"), request.environ.get("AUTH_TYPE") ) -api.add_resource(AuthApi, '/auth') +api.add_resource(AuthApi, "/auth") diff --git a/src/authn.py b/auth-server-poc/src/authn.py index e90118a..8b32cdc 100755 --- a/src/authn.py +++ b/auth-server-poc/src/authn.py @@ -12,26 +12,24 @@ class Authz: return "{}: {}".format(self._org, self._perms) def read_p(self): - return 'r' in self._perms + return "r" in self._perms def write_p(self): - return 'w' in self._perms + return "w" in self._perms class User: - def __init__(self, username, pw, authz): + def __init__(self, username, authz): self._username = username - self._password = pw self._authz = {} for org, perms in authz.items(): self._authz[org] = Authz(org, perms) def dump(self): - return ["{}/{}: {}".format(self._username, self._password, auth.dump()) - for auth in self._authz.values()] - - def authn_p(self, pw): - return pw == self._password + return [ + "{}: {}".format(self._username, auth.dump()) + for auth in self._authz.values() + ] def orgnames(self): return [x for x in self._authz.keys()] @@ -55,58 +53,45 @@ class UserDB: def __init__(self, yamlfile): self._users = {} for u, d in yaml.safe_load(open(yamlfile)).items(): - self._users[u] = User(u, d['pw'], d['authz']) + self._users[u] = User(u, d["authz"]) def dump(self): return [u.dump() for u in self._users.values()] - def user_authn_p(self, username, password): - user = self._users.get(username) - if not user: - return False - return user.authn_p(password) - def orgs_for_user(self, username): return self._users.get(username).orgnames() - def read_perms(self, username, password): + def read_perms(self, username): user = self._users.get(username) if not user: return None - if not user.authn_p(password): - return None return user.read_perms() - def write_perms(self, username, password): + def write_perms(self, username): user = self._users.get(username) if not user: return None - if not user.authn_p(password): - return None return user.write_perms() def self_test(): - db = UserDB('userdb.yaml') + db = UserDB("userdb.yaml") print(db.dump()) - orgs = db.orgs_for_user('user3') - assert('sunet.se' in orgs) - assert('su.se' in orgs) - assert(len(orgs) == 2) - - assert(db.user_authn_p('user3', 'pw3') == True) - assert(db.user_authn_p('user3', 'wrongpw') == False) + orgs = db.orgs_for_user("user3") + assert "sunet.se" in orgs + assert "su.se" in orgs + assert len(orgs) == 2 - rp = db.read_perms('user3', 'pw3') - assert(len(rp) == 2) - assert('sunet.se' in rp) - assert('su.se' in rp) + rp = db.read_perms("user3", "pw3") + assert len(rp) == 2 + assert "sunet.se" in rp + assert "su.se" in rp - wp = db.write_perms('user3', 'pw3') - assert(len(wp) == 1) - assert('sunet.se' in wp) + wp = db.write_perms("user3", "pw3") + assert len(wp) == 1 + assert "sunet.se" in wp -if __name__ == '__main__': +if __name__ == "__main__": self_test() diff --git a/auth-server-poc/src/userdb.yaml b/auth-server-poc/src/userdb.yaml new file mode 100644 index 0000000..c55773b --- /dev/null +++ b/auth-server-poc/src/userdb.yaml @@ -0,0 +1,23 @@ +user1: + authz: + sunet.se: r + su.se: r + kth.se: r + +user2: + authz: + sunet.se: w + su.se: w + kth.se: w + +user3: + authz: + sunet.se: rw + su.se: rw + kth.se: rw + +user4: + authz: + sunet.se: rw + su.se: r + kth.se: w diff --git a/demo/wsgi_demo_users.yaml b/demo/wsgi_demo_users.yaml deleted file mode 100644 index 49c4795..0000000 --- a/demo/wsgi_demo_users.yaml +++ /dev/null @@ -1,15 +0,0 @@ -user1: - pw: pw1 - authz: - sunet.se: r - -user2: - pw: pw2 - authz: - su.se: r - -user3: - pw: pw3 - authz: - sunet.se: rw - su.se: r diff --git a/quickstart.sh b/quickstart.sh index d46a791..6e566de 100755 --- a/quickstart.sh +++ b/quickstart.sh @@ -27,7 +27,10 @@ fi # Generate a default htpasswd file with a user "usr:pwd". if [ ! -f ${DOCKER_JWT_HTPASSWD_PATH}/.htpasswd ]; then - htpasswd -b -c ${DOCKER_JWT_HTPASSWD_PATH}/.htpasswd usr pwd + htpasswd -b -c ${DOCKER_JWT_HTPASSWD_PATH}/.htpasswd user1 pwd + htpasswd -b ${DOCKER_JWT_HTPASSWD_PATH}/.htpasswd user2 pwd + htpasswd -b ${DOCKER_JWT_HTPASSWD_PATH}/.htpasswd user3 pwd + htpasswd -b ${DOCKER_JWT_HTPASSWD_PATH}/.htpasswd user4 pwd fi # Launch the containers. diff --git a/src/main.py b/src/main.py index f95a09c..9beace0 100755 --- a/src/main.py +++ b/src/main.py @@ -116,13 +116,16 @@ async def get(key=None, limit=25, skip=0, ip=None, port=None, data = [] raw_jwt = Authorize.get_raw_jwt() - if 'domains' not in raw_jwt: - return JSONResponse(content={"status": "error", - "message": "Could not find domains" + - "claim in JWT token"}, - status_code=400) + if "read" not in raw_jwt: + return JSONResponse( + content={ + "status": "error", + "message": "Could not find read claim in JWT token", + }, + status_code=400, + ) else: - domains = raw_jwt['domains'] + domains = raw_jwt["read"] for domain in domains: data.extend(get_data(key, limit, skip, ip, port, asn, domain)) @@ -135,10 +138,30 @@ async def get_key(key=None, Authorize: AuthJWT = Depends()): Authorize.jwt_required() - # TODO: Use JWT authz and check e.g. domain here + raw_jwt = Authorize.get_raw_jwt() + + if "read" not in raw_jwt: + return JSONResponse( + content={ + "status": "error", + "message": "Could not find read claim in JWT token", + }, + status_code=400, + ) + else: + allowed_domains = raw_jwt["read"] data = get_data(key) + if data["domain"] not in allowed_domains: + return JSONResponse( + content={ + "status": "error", + "message": "User not authorized to view this object", + }, + status_code=400, + ) + return JSONResponse(content={"status": "success", "docs": data}) @@ -161,12 +184,36 @@ async def delete(key, Authorize: AuthJWT = Depends()): Authorize.jwt_required() + raw_jwt = Authorize.get_raw_jwt() + + if "write" not in raw_jwt: + return JSONResponse( + content={ + "status": "error", + "message": "Could not find write claim in JWT token", + }, + status_code=400, + ) + else: + allowed_domains = raw_jwt["write"] + + data = get_data(key) + + if data["domain"] not in allowed_domains: + return JSONResponse( + content={ + "status": "error", + "message": "User not authorized to delete this object", + }, + status_code=400, + ) + if db.delete(key) is None: return JSONResponse(content={"status": "error", "message": "Document not found"}, status_code=400) - return JSONResponse(content={"status": "success", "docs": {}}) + return JSONResponse(content={"status": "success", "docs": data}) def main(standalone=False): |