# ungleichotp # ungleich-otp is a full blown authentication and authorisation service made for micro services. The basic idea is that every micro service has a (long term) triple constisting of (name, realm, seed) and creates time based tokens. It basically revamps Kerberos in a simple way into the web area. ungleichotp has been created and is maintained by [ungleich](https://ungleich.ch/). Related documentation: * [Python pyotp](https://pyotp.readthedocs.io/) * [RFC6238, TOTP](https://tools.ietf.org/html/rfc6238) * [RFC4120, Kerberos](https://tools.ietf.org/html/rfc4120) ## Overview This repository contains three components: * ungleichotp-server: the reference implementation of the ungleichotp server * ungleichotp-client: a sample implementation of an ungleichotp client ## Setup instructions ## This is a standard django project and thus can be easily setup using ``` pip install -r requirements.txt ``` To bootstrap the application, you need your very first trusted seed to access the application. You can generate it using ``` to be filled in ``` After that, you can run the application using ``` python manage.py runserver ``` The usual instructions on how to setup an https proxy should be followed. ## Realms ## Access is granting/denied based on realms. There are two reserved realms, all other realms can be used by the users: ### Reserved realms Conceptually the realms "ungleich-admin" and "ungleich-auth" are reserved for higher priviliged applications. Usually there is only 1 entry in ungleich-admin that is used to bootstrap and manage ungleich-otp. All micro services that are trusted to authenticate another micro service should have an entry in the ungleich-auth realm, which allows them to verify a token of somebody else. | Name | Capabilities | |------------------+--------------------------------------------| | ungleich-admin | authenticate, create, delete, list, update | | ungleich-auth | authenticate | | all other realms | NO ACCESS | ## Environment / Configuration (unfinished) - POSTGRES_USERNAME - SECRET_KEY -- random (?) ## Random notes / stuff (unfinished) django.db.backends.postgresql django.contrib.admin ``` DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'mydatabase', 'USER': 'mydatabaseuser', 'PASSWORD': 'mypassword', 'HOST': '127.0.0.1', 'PORT': '5432', } } ``` Custom auth ``` from django.contrib.auth.models import User from rest_framework import authentication from rest_framework import exceptions class ExampleAuthentication(authentication.BaseAuthentication): def authenticate(self, request): username = request.META.get('X_USERNAME') if not username: return None try: user = User.objects.get(username=username) except User.DoesNotExist: raise exceptions.AuthenticationFailed('No such user') return (user, None) ``` Custom user Don’t forget to point AUTH_USER_MODEL to it. Do this before creating any migrations or running manage.py migrate for the first time. ### To document * Login via username password interactively * Login via name/realm/token rest ## The library (ungleichotp) ## The server (ungleichotp-server) ## The sample client (ungleichotp-client) The included client application is a Django application that makes use of an ungleichotp-server to authenticate requests. ### Usage * Ensure that the ungleichotp-server is running and reachable ## Integrating ungleichotp ### In Django ### In other frameworks In general, you will need to implement the following into your app for resources that need to have an authenticated user: #### Retrieve name, realm and token from the request This is application specific. In the sample Django rest framework, we use JSON to retrieve this values: ``` { "name": "info@ungleich.ch", "token": "947732", "realm": "ungleich-admin", "otherdata": "..." } ``` #### Send name, realm and token from the request to the ungleichotp-server Post a JSON object to /ungleichotp/verify that contains the following elements: Request JSON object: ``` { version: "1", name: "your-name", realm: "your-realm", token: "current time based token", verifyname: "name that wants to be authenticated", verifyrealm: "realm that wants to be authenticated", verifytoken: "token that wants to be authenticated", } ``` Response JSON object: Either HTTP 200 with ``` { status: "OK", } ``` OR return code 403: * If token for authenticating is wrong, you get ``` {"detail":"Incorrect authentication credentials."} ``` * If token that is being verified is wrong, you get ``` {"detail":"You do not have permission to perform this action."} ``` #### Authorize the request From the ungleichotp-server, you get a validated information that a name on a realm authenticated successfully. The associated permissions ("authorization") is application specific and needs to be decided by your application. ## Limitations ## * Name, Realm and seed are hard coded to 128 bytes length. This can be changed, if necessary. * Only python3 support for ungleichotp ## TODOs - [x] (server) Serialize / input request - [x] (server) Make seed read only - [x] (server) Implement registering of new entries - [x] (server) OTPSerializer: allow to read seed for admin - [x] (server) Implement deleting entry - [x] (server) Include verify in ModelSerializer - [x] (server) Map name+realm == User (?) - name == name@realm - password is used for admin login (?) - seed - custom auth method - [n] (server) Try to fake username for django based on name+realm (?) - No need - [n] (server) maybe overwrite get_username() - No need - [x] (server) Use Custom authentication - needs to have a user! - [x] (server) Implement creating new "User" by POST / Model based - [n] (server) Remove hard coded JSON in /verify (no - good enough for the moment) - [x] (server) Fully rename server from ungleichotp to ungleichotpserver - [ ] (security) Ensure that only the right realms can verify - [ ] (security) Ensure that only the right realms can manage - [ ] (doc) Add proper documentation - [ ] (server) Add tests for verify - [ ] (server) Add tests for authentication - [ ] (server) move totp constants into settings - [ ] (server) move field lengths into settings - [ ] (server) Document how admin vs. rest works - [ ] (server, client) Make settings adjustable by environment - k8s/docker compatible - [ ] (server, client) Read DB from outside (?) (fallback to sqlite) - [x] (client) Establish auth using urllib - [ ] (client) Bootstrap Django + DRF (including an object for CRUD) - [ ] (client) Add custom authentication / remote auth - [ ] (client) Show case: any realm vs. specific realm - [ ] (library) Write a "client library" that can use ungleichotp - [ ] (library) extract generic parts from server - [ ] (library) upload to pypi ## Changelog ### 0.6, 2018-11-18 * Reuse TokenSerializer for VerifySerializer logic ### 0.5, 2018-11-18 * Require authentication on all rest endpoints by token