This commit is contained in:
Nico Schottelius 2018-12-24 20:28:21 +01:00
parent 78de133e16
commit 11ab190ebc
6 changed files with 83 additions and 50 deletions

View file

@ -6,7 +6,7 @@ made for micro services.
The basic idea is that every micro service has a (long term) triple The basic idea is that every micro service has a (long term) triple
constisting of (name, realm, seed) and creates time based tokens. constisting of (name, realm, seed) and creates time based tokens.
It basically revamps Kerberos in a simple way into the web area. This basically revamps Kerberos in a simple way into the web area.
ungleichotp has been created and is maintained by [ungleich](https://ungleich.ch/). ungleichotp has been created and is maintained by [ungleich](https://ungleich.ch/).
@ -24,6 +24,17 @@ This repository contains three components:
* ungleichotp-client: a sample implementation of an ungleichotp client * ungleichotp-client: a sample implementation of an ungleichotp client
### Overview client ###
An application that lets you enter the triple (realm, name, token) and
responds with OK / NOTOK.
How to use:
## Setup instructions ## ## Setup instructions ##
This is a standard django project and thus can be easily setup using This is a standard django project and thus can be easily setup using

View file

@ -1,64 +1,75 @@
from django.contrib.auth.models import User
from rest_framework import authentication, exceptions, serializers
import urllib.request
import pyotp import pyotp
import json import json
import urllib.request
import urllib.error
# For parsing class UngleichOTPClient(object):
class TokenSerializer(serializers.Serializer):
name = serializers.CharField(max_length=128)
token = serializers.CharField(max_length=128)
realm = serializers.CharField(max_length=128)
token_name = 'token' token_name = 'token'
name_name = 'name' name_name = 'name'
realm_name = 'realm' realm_name = 'realm'
def __init__(self, name, realm, seed, serverurl, *args, **kwargs): def __init__(self, name, realm, seed, serverurl):
self.name = name self.name = name
self.realm = realm self.realm = realm
self.seed = seed self.seed = seed
self.serverurl = serverurl self.serverurl = serverurl
super(serializers.Serializer, self).__init__(*args, **kwargs) def verify(self, name, realm, token):
def save(self):
to_send = {} to_send = {}
# Client credentials to be verified # Client credentials to be verified
to_send['verifytoken'] = self.validated_data.get(self.token_name) to_send['verifyname'] = name
to_send['verifyname'] = self.validated_data.get(self.name_name) to_send['verifyrealm'] = realm
to_send['verifyrealm'] = self.validated_data.get(self.real_name) to_send['verifytoken'] = token
# Our credentials # Our credentials
to_send['token'] = pyotp.TOTP(self.seed) to_send['token'] = pyotp.TOTP(self.seed).now()
to_send['name'] = self.name to_send['name'] = self.name
to_send['realm'] = self.realm to_send['realm'] = self.realm
data = json.dumps(to_send).encode("utf-8") data = json.dumps(to_send).encode("utf-8")
req = urllib.request.Request(url=serverurl, req = urllib.request.Request(url=self.serverurl,
data=data, data=data,
headers={'Content-Type': 'application/json'}, headers={'Content-Type': 'application/json'},
method='POST') method='POST')
f = urllib.request.urlopen(req) f = urllib.request.urlopen(req)
if not f.status == 200: if f.status == 200:
raise exceptions.AuthenticationFailed()
return True return True
return False
class OTPAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
serializer = TokenSerializer(data=request.data)
if serializer.is_valid(): if __name__ == '__main__':
print("trying to save... {}".format(serializer)) import argparse
user, token = serializer.save() import os
else: import sys
raise exceptions.AuthenticationFailed()
return (user, token) parser = argparse.ArgumentParser(description='ungleichotp-client')
parser.add_argument('-n', '--name', help="Name (for verification)", required=True)
parser.add_argument('-r', '--realm', help="Realm (for verification)", required=True)
parser.add_argument('-t', '--token', help="Token (for verification)", required=True)
args = parser.parse_args(sys.argv[1:])
UNGLEICHOTP={}
for env in ['UNGLEICHOTPREALM', 'UNGLEICHOTPNAME', 'UNGLEICHOTPSEED', 'UNGLEICHOTPSERVER' ]:
if not env in os.environ:
raise Exception("Required environment variable missing: {}".format(env))
client = UngleichOTPClient(os.environ['UNGLEICHOTPNAME'],
os.environ['UNGLEICHOTPREALM'],
os.environ['UNGLEICHOTPSEED'],
os.environ['UNGLEICHOTPSERVER'])
try:
client.verify(args.name, args.realm, args.token)
except urllib.error.HTTPError as e:
print("Failed to verify: {}".format(e))
print("Verify ok")

View file

@ -0,0 +1,28 @@
## Run
Use your favorite uwsgi configuration or
```
UNGLEICHOTPSERVER=https://your-server-address/
UNGLEICHOTPREALM=admin \
UNGLEICHOTPNAME=sampleapp \
UNGLEICHOTPSEED=CEKXVG3235PO2HDW \
python manage.py runserver
```
This triple will be used to connect to the
## Use ##
curl
def index(request):
answer = {}
answer['token'] = pyotp.TOTP(settings.UNGLEICHOTP['UNGLEICHOTPSEED']).now()
answer['name'] = settings.UNGLEICHOTP['UNGLEICHOTPNAME']
answer['realm'] = settings.UNGLEICHOTP['UNGLEICHOTPREALM']
return HttpResponse(json.dumps(answer))
elif request.method == 'POST':
do_something_else()

View file

@ -8,5 +8,4 @@ from .models import OTPSeed
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
# admin.site.register(OTPSeed, UserAdmin)
admin.site.register(OTPSeed) admin.site.register(OTPSeed)

View file

@ -9,7 +9,7 @@ from django.http import JsonResponse
from otpauth.serializer import VerifySerializer, OTPSerializer from otpauth.serializer import VerifySerializer, OTPSerializer
from otpauth.models import OTPSeed from otpauth.models import OTPSeed
#
class OTPVerifyViewSet(viewsets.ModelViewSet): class OTPVerifyViewSet(viewsets.ModelViewSet):
serializer_class = OTPSerializer serializer_class = OTPSerializer
queryset = OTPSeed.objects.all() queryset = OTPSeed.objects.all()

View file

@ -1,19 +1,3 @@
"""ungleichotp URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin from django.contrib import admin
from django.urls import path from django.urls import path
from django.conf.urls import url, include from django.conf.urls import url, include