Update
This commit is contained in:
parent
78de133e16
commit
11ab190ebc
6 changed files with 83 additions and 50 deletions
13
README.md
13
README.md
|
@ -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
|
||||||
|
|
|
@ -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):
|
if __name__ == '__main__':
|
||||||
def authenticate(self, request):
|
import argparse
|
||||||
serializer = TokenSerializer(data=request.data)
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
if serializer.is_valid():
|
parser = argparse.ArgumentParser(description='ungleichotp-client')
|
||||||
print("trying to save... {}".format(serializer))
|
parser.add_argument('-n', '--name', help="Name (for verification)", required=True)
|
||||||
user, token = serializer.save()
|
parser.add_argument('-r', '--realm', help="Realm (for verification)", required=True)
|
||||||
else:
|
parser.add_argument('-t', '--token', help="Token (for verification)", required=True)
|
||||||
raise exceptions.AuthenticationFailed()
|
|
||||||
|
|
||||||
return (user, token)
|
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")
|
||||||
|
|
28
ungleichotpclient/README.md
Normal file
28
ungleichotpclient/README.md
Normal 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()
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue