Compare commits
56 Commits
556b8cbe50
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
741ac73e35 | ||
![]() |
bb5390928e | ||
![]() |
e9be4808ed | ||
![]() |
3b6312b596 | ||
![]() |
f31221caf0 | ||
![]() |
f1b0ace7ae | ||
![]() |
c77485c4a1 | ||
![]() |
755e3be41e | ||
![]() |
d7dd7b5947 | ||
![]() |
4b56c4bd55 | ||
![]() |
aadfe81674 | ||
![]() |
265476d24e | ||
![]() |
af6fc43067 | ||
![]() |
67ed0e0f34 | ||
![]() |
ed2e93ed8e | ||
![]() |
e9cbcbed42 | ||
![]() |
85ea999996 | ||
![]() |
9a50318740 | ||
![]() |
0af718949c | ||
![]() |
a0ee4b3d34 | ||
![]() |
e52b494759 | ||
![]() |
57ae8543f2 | ||
![]() |
8941b805d5 | ||
![]() |
05325c2905 | ||
![]() |
52c9b053be | ||
![]() |
d4e105d980 | ||
![]() |
84b5c1760f | ||
![]() |
22f3855e68 | ||
![]() |
7f434069f4 | ||
![]() |
236cadf901 | ||
![]() |
9a70ef5bb1 | ||
![]() |
c5604df1ca | ||
![]() |
2d804e85c9 | ||
![]() |
a3bd50613b | ||
![]() |
74279b1bd0 | ||
![]() |
9724c905bf | ||
![]() |
f665628b94 | ||
![]() |
10971e5e5a | ||
![]() |
518d90a66f | ||
![]() |
2319dcbfa7 | ||
![]() |
5b48e994db | ||
![]() |
389161bf83 | ||
![]() |
75133c6452 | ||
![]() |
4bfa93c3e4 | ||
![]() |
ecc2ce208d | ||
![]() |
3672b6fa59 | ||
![]() |
ccfc87937a | ||
![]() |
ce3d38f51f | ||
![]() |
695cb09150 | ||
![]() |
7b11b0c7f1 | ||
![]() |
e0b5aceb5b | ||
![]() |
041021ab35 | ||
![]() |
2b0ffb02a4 | ||
![]() |
93b5116aa0 | ||
![]() |
e45632630a | ||
![]() |
1a7307b89f |
10
eindopdracht/azure/bind/cloudinit.yaml
Normal file
10
eindopdracht/azure/bind/cloudinit.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
#cloud-config
|
||||
bootcmd:
|
||||
- echo "making directories"
|
||||
- mkdir -p /etc/bind
|
||||
- mkdir -p /var/lib/bind
|
||||
- echo "downloading files"
|
||||
- wget https://git.ventilaar.nl/ventilaar/clim/raw/branch/master/eindopdracht/testomgeving/bind/named.conf.local -O /etc/bind/named.conf.local
|
||||
- wget https://git.ventilaar.nl/ventilaar/clim/raw/branch/master/eindopdracht/testomgeving/bind/dns.mashallah.nl.zone -O /var/lib/bind/dns.mashallah.nl.zone
|
||||
packages:
|
||||
- bind9
|
63
eindopdracht/azure/kubernetes/api.yaml
Normal file
63
eindopdracht/azure/kubernetes/api.yaml
Normal file
@@ -0,0 +1,63 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: api-deployment
|
||||
labels:
|
||||
app: api-deployment
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: api-deployment
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: api-deployment
|
||||
spec:
|
||||
containers:
|
||||
- name: api-container
|
||||
image: 4grxfq/api
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 5001
|
||||
name: api-port
|
||||
env:
|
||||
- name: OPENID_SECRET
|
||||
value:
|
||||
- name: DNS_SERVER
|
||||
value: dnsns.mashallah.nl
|
||||
- name: MONGO_CONNECTIONSTRING
|
||||
value:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: api-service
|
||||
labels:
|
||||
run: api-service
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 5001
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: api-deployment
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: api-ingress
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: addon-http-application-routing
|
||||
spec:
|
||||
rules:
|
||||
- host: dnsapi.mashallah.nl
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: api-service
|
||||
port:
|
||||
number: 80
|
63
eindopdracht/azure/kubernetes/gui.yaml
Normal file
63
eindopdracht/azure/kubernetes/gui.yaml
Normal file
@@ -0,0 +1,63 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: gui-deployment
|
||||
labels:
|
||||
app: gui-deployment
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: gui-deployment
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: gui-deployment
|
||||
spec:
|
||||
containers:
|
||||
- name: gui-container
|
||||
image: 4grxfq/gui
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 5000
|
||||
name: gui-port
|
||||
env:
|
||||
- name: OPENID_SECRET
|
||||
value:
|
||||
- name: DNS_API
|
||||
value: http://api-service:80
|
||||
- name: MONGO_CONNECTIONSTRING
|
||||
value:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: gui-service
|
||||
labels:
|
||||
run: gui-service
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 5000
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: gui-deployment
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: gui-ingress
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: addon-http-application-routing
|
||||
spec:
|
||||
rules:
|
||||
- host: dnsgui.mashallah.nl
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: gui-service
|
||||
port:
|
||||
number: 80
|
117
eindopdracht/azure/terraform/main.tf
Normal file
117
eindopdracht/azure/terraform/main.tf
Normal file
File diff suppressed because one or more lines are too long
11
eindopdracht/dns_api/Dockerfile
Normal file
11
eindopdracht/dns_api/Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM alpine:latest
|
||||
RUN apk add --update py3-pip python3-dev gcc libc-dev libffi-dev
|
||||
RUN pip install --upgrade pip
|
||||
COPY requirements.txt /app/
|
||||
RUN pip install --no-cache-dir -r /app/requirements.txt
|
||||
COPY openid.py /app/
|
||||
COPY dnszone.py /app/
|
||||
COPY openid.py /app/
|
||||
COPY mango.py /app
|
||||
COPY api.py /app/
|
||||
CMD ["python3", "/app/api.py"]
|
@@ -3,9 +3,10 @@ from flask import Flask, request
|
||||
from flask_restful import reqparse, Api, Resource
|
||||
from dnszone import DnsZone
|
||||
from ipaddress import ip_address, IPv4Address
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
from eindopdracht.openid import *
|
||||
from openid import *
|
||||
import os
|
||||
from dns import resolver
|
||||
from mango import Mango
|
||||
|
||||
app = Flask(__name__)
|
||||
api = Api(app)
|
||||
@@ -15,17 +16,31 @@ parser.add_argument('name')
|
||||
parser.add_argument('value')
|
||||
parser.add_argument('type')
|
||||
|
||||
dnsserver = '192.168.66.113'
|
||||
dnsserverport = 5053
|
||||
# verander de variabelen hieronder als je de script handmatig uitvoert
|
||||
|
||||
dnsserver = os.environ.get('DNS_SERVER')
|
||||
mongo_connect = os.environ.get('MONGO_CONNECTIONSTRING')
|
||||
dnsserverport = int(os.environ.get('DNS_PORT', default=53))
|
||||
debug = bool(os.environ.get('API_DEBUG', default=False))
|
||||
|
||||
# end change
|
||||
|
||||
if dnsserver is None or mongo_connect is None:
|
||||
print('You did not set DNS_SERVER or MONGO_CONNECTIONSTRING environ')
|
||||
exit(1)
|
||||
|
||||
|
||||
def make_fqdn_check(subname, parentdomain):
|
||||
"""
|
||||
De functie moet de subdomain naam met de parent domain samenvoegen en daarna op errors controleren
|
||||
:param subname: hostname
|
||||
:param parentdomain: zone name
|
||||
:return: hostname.domain.tld in string
|
||||
"""
|
||||
def check_ipv4(ipv4): # check if given ipv4 address in string is a valid one
|
||||
try:
|
||||
if type(ip_address(ipv4)) is not IPv4Address: # als ip adress ongelig is
|
||||
raise ValueError # raise value error
|
||||
except ValueError: # geen geldige ip adres formaat
|
||||
return False
|
||||
|
||||
return True # adres correct
|
||||
|
||||
|
||||
def make_fqdn_check(subname, parentdomain): # make fqdn from given subdomain name, also check if valid
|
||||
fqdn = f'{subname}.{parentdomain}'
|
||||
if True: # do some regex checking if it made a correct fqdn, currently passing and trusting user input!!!
|
||||
return fqdn
|
||||
@@ -33,30 +48,33 @@ def make_fqdn_check(subname, parentdomain):
|
||||
return False
|
||||
|
||||
|
||||
def validate_authorization(req):
|
||||
jwt = req.get('Authorization')
|
||||
if jwt is None:
|
||||
def validate_authorization(req): # validate authorization header
|
||||
jwt = req.get('Authorization') # get Authorization header
|
||||
if jwt is None: # if not set
|
||||
return False
|
||||
|
||||
jwt = jwt.split(' ')[1]
|
||||
|
||||
if GoogleOID.check_jwt(jwt)['error'] is False:
|
||||
return True
|
||||
form, value = jwt.split(' ') # get header type and value
|
||||
|
||||
if form == "Bearer": # if bearer(openid)
|
||||
if GoogleOID.check_jwt(value)['error'] is False: # check if jwt is valid
|
||||
return True
|
||||
elif form == "Basic": # basic auth stored in db
|
||||
if mango.check_api_key(value): # check if apikey exists in db
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class domains(Resource):
|
||||
def get(self):
|
||||
def get(self): # list domains
|
||||
"""
|
||||
list all domains
|
||||
:return: {"domains": ["name":"a.b"], "error":false}
|
||||
"""
|
||||
# Vooralsnog moet je handmatig domains toevoegen aan BIND, dus dit heeft niet veel nut, hardcoded domeinen.
|
||||
if validate_authorization(request.headers) is False:
|
||||
if validate_authorization(request.headers) is False: # check authorization
|
||||
return {"error": True, "reason": "Invalid authorization header"}, 403
|
||||
|
||||
return {'domains': ['school.test'], 'error': False}
|
||||
return {'domains': ['dns.mashallah.nl'], 'error': False}
|
||||
|
||||
|
||||
class domain(Resource):
|
||||
@@ -66,7 +84,7 @@ class domain(Resource):
|
||||
:param fqdn: domain name
|
||||
:return:
|
||||
"""
|
||||
if validate_authorization(request.headers) is False:
|
||||
if validate_authorization(request.headers) is False: # check authorization
|
||||
return {"error": True, "reason": "Invalid authorization header"}, 403
|
||||
|
||||
args = parser.parse_args()
|
||||
@@ -79,21 +97,21 @@ class domain(Resource):
|
||||
if t != 'A':
|
||||
return {"error": True, "reason": "Only A records are supported"}, 400
|
||||
|
||||
fqdn = make_fqdn_check(n, dmn)
|
||||
fqdn = make_fqdn_check(n, dmn) # combine subdomain with parent domain
|
||||
if fqdn is False:
|
||||
return {"error": True, "reason": "Invalid subdomain, domain combination"}, 400
|
||||
|
||||
dns = DnsZone(dmn, dnsserver, 1, dnsserverport)
|
||||
dns = DnsZone(dmn, dnsserver, 1, dnsserverport) # setup dnszone
|
||||
|
||||
current = dns.check_address(fqdn)
|
||||
current = dns.check_address(fqdn) # check if address exists
|
||||
|
||||
if current['error'] is True:
|
||||
if 'not found' not in current['error_text']: # als er een error is wat niet een not found is
|
||||
if 'not found' not in current['error_text']: # als er een error is wat NIET een "not found" is
|
||||
return {"error": True, "reason": current['error_text']}, 500
|
||||
else: # record bestond al niet
|
||||
return {"error": True, "reason": "Record does not exist"}, 400
|
||||
|
||||
new = dns.clear_address(fqdn)
|
||||
new = dns.clear_address(fqdn) # delete record
|
||||
|
||||
if new['error'] is True: # als er een error is met het weghalen van dns regel
|
||||
return {"error": True, "reason": new['error_text']}, 500
|
||||
@@ -106,12 +124,11 @@ class domain(Resource):
|
||||
:param dmn: domain name
|
||||
:return: alle dns A records als keys
|
||||
"""
|
||||
# de dnszone library kan geen lijst met records opvragen, een andere manier uitzoeken? mongodb bijhouden?
|
||||
if validate_authorization(request.headers) is False:
|
||||
if validate_authorization(request.headers) is False: # check authorization
|
||||
return {"error": True, "reason": "Invalid authorization header"}, 403
|
||||
|
||||
dns = DnsZone(dmn, dnsserver, 1, dnsserverport)
|
||||
current = dns.list_addresses()
|
||||
dns = DnsZone(dmn, dnsserver, 1, dnsserverport) # setup dnszone
|
||||
current = dns.list_addresses() # get all a records
|
||||
|
||||
if current['error'] is True:
|
||||
return {"error": True, "reason": current['error_text']}, 500
|
||||
@@ -125,7 +142,7 @@ class domain(Resource):
|
||||
:param dmn: domain name
|
||||
:return:
|
||||
"""
|
||||
if validate_authorization(request.headers) is False:
|
||||
if validate_authorization(request.headers) is False: # check authorization
|
||||
return {"error": True, "reason": "Invalid authorization header"}, 403
|
||||
|
||||
args = parser.parse_args()
|
||||
@@ -138,10 +155,7 @@ class domain(Resource):
|
||||
if t != 'A': # als record type niet A is
|
||||
return {"error": True, "reason": "Only A records are supported"}, 400
|
||||
|
||||
try:
|
||||
if type(ip_address(v)) is not IPv4Address: # als ip adress ongelig is
|
||||
raise ValueError # raise value error
|
||||
except ValueError: # geen geldige ip adres formaat
|
||||
if check_ipv4(v) is False: # check if ip is valid
|
||||
return {"error": True, "reason": "Value is not correct"}, 400
|
||||
|
||||
fqdn = make_fqdn_check(n, dmn) # maak fqdn
|
||||
@@ -176,5 +190,10 @@ api.add_resource(domains, '/api/v1/dns/domains')
|
||||
api.add_resource(domain, '/api/v1/dns/domain/<dmn>')
|
||||
|
||||
if __name__ == '__main__':
|
||||
GoogleOID = GoogleOID()
|
||||
app.run(debug=True, host='0.0.0.0', port=5001)
|
||||
if check_ipv4(dnsserver) is False: # cant use domain in resolver so check if direct ip is given if not
|
||||
dnsserver = resolver.resolve(dnsserver, "A")[0].to_text() # try to resolve given string
|
||||
|
||||
# do not setup dns zone globally because it errors on simultaneous requests
|
||||
GoogleOID = GoogleOID() # setup google oid
|
||||
mango = Mango(mongo_connect)
|
||||
app.run(debug=debug, host='0.0.0.0', port=5001) # run werkzeug
|
||||
|
@@ -3,22 +3,23 @@ import datetime
|
||||
|
||||
|
||||
class Mango:
|
||||
def __init__(self, username, password, host, port):
|
||||
def __init__(self, connect):
|
||||
try:
|
||||
self.client = MongoClient(username=username, password=password, host=host, port=port)
|
||||
self.client = MongoClient(connect)
|
||||
self.users = self.client['dns']['users']
|
||||
self.keys = self.client['dns']['keys']
|
||||
except ConnectionError:
|
||||
print('MongoDB connection error')
|
||||
|
||||
def google_check_sso_uuid(self, uuid):
|
||||
def google_check_sso_uuid(self, uuid): # checks if uuid exist in any document
|
||||
found = self.users.find_one({"sso.google.profile.sub": uuid})
|
||||
|
||||
if found is None:
|
||||
if found is None: # None if nothing found
|
||||
return {"error": True, "reason": "User not found"}
|
||||
else:
|
||||
return {"error": False}
|
||||
|
||||
def google_update_lastlogin(self, uuid):
|
||||
def google_update_lastlogin(self, uuid): # replaces lastlogin with current time for given user
|
||||
found = self.users.find_one({"sso.google.profile.sub": uuid})
|
||||
|
||||
if found is None:
|
||||
@@ -30,7 +31,7 @@ class Mango:
|
||||
|
||||
return {"error": False}
|
||||
|
||||
def google_update_profile(self, profile):
|
||||
def google_update_profile(self, profile): # overwrites user profile with the one that google has given
|
||||
found = self.users.find_one({"sso.google.profile.sub": profile['sub']})
|
||||
|
||||
found['sso']['google']['profile'] = profile
|
||||
@@ -39,23 +40,28 @@ class Mango:
|
||||
|
||||
return {"error": False}
|
||||
|
||||
def google_get_profile(self, uuid):
|
||||
def google_get_profile(self, uuid): # returns google profile as stored in db for a given user
|
||||
found = self.users.find_one(({"sso.google.profile.sub": uuid}))
|
||||
return found['sso']['google']['profile']
|
||||
|
||||
def google_get_lastlogin(self, uuid):
|
||||
def google_get_lastlogin(self, uuid): # returns lastlogin in pretty format for given user
|
||||
found = self.users.find_one(({"sso.google.profile.sub": uuid}))
|
||||
return found['sso']['google']['lastlogin'].strftime('%A %d-%m-%Y, %H:%M:%S')
|
||||
|
||||
def google_add_new_sub(self, uuid):
|
||||
def google_add_new_sub(self, uuid): # adds new document in db with only the sub for a given uuid
|
||||
self.users.insert_one({'sso':{'google':{'profile':{"sub": str(uuid)}}}})
|
||||
|
||||
def google_overwrite_jwt(self, uuid, jwt):
|
||||
def google_overwrite_jwt(self, uuid, jwt): # overwrite jwt in db for given user
|
||||
found = self.users.find_one({"sso.google.profile.sub": uuid})
|
||||
|
||||
found['sso']['google']['jwt'] = jwt
|
||||
self.users.replace_one({"sso.google.profile.sub": uuid}, found)
|
||||
|
||||
def google_get_jwt(self, uuid):
|
||||
def google_get_jwt(self, uuid): # return jwt as stored in db for given user
|
||||
found = self.users.find_one({"sso.google.profile.sub": uuid})
|
||||
return found['sso']['google']['jwt']
|
||||
|
||||
def check_api_key(self, key): # True or False if api key exists in api keys db
|
||||
if self.keys.find_one({"key": key}):
|
||||
return True
|
||||
return False
|
@@ -2,55 +2,55 @@ class GoogleOID:
|
||||
def __init__(self):
|
||||
import requests
|
||||
import jwt
|
||||
from openid_secrets import client_secret
|
||||
import os
|
||||
client_secret = os.environ.get('OPENID_SECRET') # change this to your secret if running manually
|
||||
|
||||
if client_secret is None: # if environ not set
|
||||
print('No OPENID_SECRET environ')
|
||||
exit(1)
|
||||
|
||||
self.jwt = jwt
|
||||
self.requests = requests
|
||||
|
||||
self.settings = {'client_id': '954325872153-1v466clrtgg6h4ptt2ne5pgpb9mhilr5.apps.googleusercontent.com',
|
||||
'client_secret': client_secret,
|
||||
'callback_uri': 'http://127.0.0.1.nip.io:5000/login/gcp/callback',
|
||||
'key_server': 'https://www.googleapis.com/oauth2/v3/certs'}
|
||||
'callback_uri': 'https://dnsgui.mashallah.nl/login/gcp/callback',
|
||||
'key_server': 'https://www.googleapis.com/oauth2/v3/certs'} # global oid settings
|
||||
|
||||
def settings(self):
|
||||
def settings(self): # make it so that the settings variable is callable
|
||||
return self.settings
|
||||
|
||||
def get_token_from_code(self, code):
|
||||
def get_token_from_code(self, code): # get usable api token from oauth code
|
||||
data = {'grant_type': 'authorization_code',
|
||||
'client_id': self.settings['client_id'],
|
||||
'client_secret': self.settings['client_secret'],
|
||||
'redirect_uri': self.settings['callback_uri'],
|
||||
'code': code}
|
||||
|
||||
r = self.requests.post('https://oauth2.googleapis.com/token', data=data)
|
||||
r = self.requests.post('https://oauth2.googleapis.com/token', data=data) # exchange code for token
|
||||
|
||||
if r.status_code != 200:
|
||||
if r.status_code != 200: # if not successful
|
||||
return {'error': True, 'reason': f'Could not exchange code for access key'}
|
||||
|
||||
return {'error': False, 'data': r.json()}
|
||||
|
||||
def get_profile_information(self, token):
|
||||
def get_profile_information(self, token): # get google profile with token
|
||||
headers = {'Authorization': f'Bearer {token}'}
|
||||
|
||||
r = self.requests.get('https://openidconnect.googleapis.com/v1/userinfo', headers=headers)
|
||||
|
||||
if r.status_code != 200:
|
||||
if r.status_code != 200: # if not successful
|
||||
return {'error': True, 'reason': 'Could not get profile info'}
|
||||
|
||||
return {'error': False, 'profile': r.json()}
|
||||
|
||||
def check_jwt(self, bearer):
|
||||
"""
|
||||
Decodes JWT and checks if it is for us from google
|
||||
:param bearer: JWT token in base64 format
|
||||
:return: {'error': False, 'data': decoded jwt dict}
|
||||
"""
|
||||
jwks_client = self.jwt.PyJWKClient(self.settings['key_server'])
|
||||
signing_key = jwks_client.get_signing_key_from_jwt(bearer).key
|
||||
def check_jwt(self, bearer): # check if jwt is signed by google and valid
|
||||
jwks_client = self.jwt.PyJWKClient(self.settings['key_server']) # setup jwks client with keyserver
|
||||
signing_key = jwks_client.get_signing_key_from_jwt(bearer).key # extract signing key from jwt
|
||||
|
||||
try:
|
||||
try: # fails if jwt is not valid eg. not signed by google, expired, wrong application
|
||||
decoded = self.jwt.decode(bearer, signing_key, algorithms=["RS256"], audience=self.settings['client_id'])
|
||||
except self.jwt.exceptions.DecodeError:
|
||||
except self.jwt.exceptions.DecodeError: # catch generic error
|
||||
return {'error': True, 'reason': 'Error decoding JWT'}
|
||||
|
||||
return {'error': False, 'data': decoded}
|
8
eindopdracht/dns_api/requirements.txt
Normal file
8
eindopdracht/dns_api/requirements.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
flask
|
||||
flask_restful
|
||||
pyjwt
|
||||
pyjwt[crypto]
|
||||
dnspython
|
||||
pymongo
|
||||
|
||||
werkzeug == 2.0.3 # er zit een fout in de laatste versie die plain http post requests altijd als json interperteerd
|
BIN
eindopdracht/dns_gui/.assets/dashboard.png
Normal file
BIN
eindopdracht/dns_gui/.assets/dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
BIN
eindopdracht/dns_gui/.assets/login.png
Normal file
BIN
eindopdracht/dns_gui/.assets/login.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
10
eindopdracht/dns_gui/Dockerfile
Normal file
10
eindopdracht/dns_gui/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM alpine:latest
|
||||
RUN apk add --update py3-pip python3-dev gcc libc-dev libffi-dev
|
||||
RUN pip install --upgrade pip
|
||||
COPY requirements.txt /app/
|
||||
RUN pip install --no-cache-dir -r /app/requirements.txt
|
||||
COPY templates/ /app/templates/
|
||||
COPY gui.py /app/
|
||||
COPY mango.py /app/
|
||||
COPY openid.py /app/
|
||||
CMD ["python3", "/app/gui.py"]
|
41
eindopdracht/dns_gui/README.md
Normal file
41
eindopdracht/dns_gui/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# DNS Manager GUI
|
||||
In deze folder bevind zich alle code omtrent de web-gui voor de DNS manager.
|
||||
|
||||
## Install
|
||||
### Container
|
||||
Als je direct de container gebruikt moet je de environment variables gebruiken die op de docker page staat
|
||||
https://hub.docker.com/r/4grxfq/gui/ vergeet ook poort 5000 niet te openen naar je container
|
||||
|
||||
### Manual install
|
||||
1. verander de variabelen in gui.py die aangepast moeten worden of zet de environment variabelen in je shell
|
||||
2. run ```pip install -r /app/requirements.txt```
|
||||
3. start flask ```python3 gui.py``` de webserver luistert naar poort 5000
|
||||
|
||||
|
||||
## Screenshots
|
||||
### Index page
|
||||
Op de index page wordt er een inlog form getoont, ook worden hyperlinks getoont naar de dashboard voor als je al
|
||||
ingelogt bent, en een link naar de uitlog pad. De username en password textvakken doen niets en zijn puur cosmetisch,,
|
||||
je kunt alleen inloggen met de klop die je redirect naar de google OID login pagina.
|
||||
|
||||

|
||||
|
||||
### Dashboard page
|
||||
De dashboard page laat een basale tabel zien met alle A records die op de nameserver ingesteld staan. Bovenin wordt je
|
||||
profiel getoont zoals gekregen van google en opgeslagen in de database.
|
||||
|
||||
Met de knop "Query JWT" kan je de JWT opvragen die google mee heeft gegeven met het inloggen, deze JWT kan je gebruiken om de API handmatig met curl aan te roepen en
|
||||
is verplicht.
|
||||
|
||||
Met de Add SUB form kan je een gebruiker authorizeren om in laten te loggen. Als een gebruikter niet mag inloggen wordt
|
||||
tijdens het inloggen een error getoont met een 21 cijferig code. Die code moet in de Google UUID box worden ingevoerd
|
||||
om de desbetreffende gebruiker authorizatie te geven om in te kunnen loggen.
|
||||
|
||||
De tabel met DNS records spreekt voorzich. Alle bestaande records worden weergeven en de adressen zijn aanpasbaar.
|
||||
Wanneer er een adres aangepast moet worden is dat makkelijk te doen door de waarde aan te wijzigen en Update aan te
|
||||
klikken. Evendeels is het verwijderen van een record ook mogelijk door op Delete te klikken.
|
||||
|
||||
Een nieuwe record kan worden toegevoegd door de subdomain aan te geven met de juiste IPv4 Waarde. Momenteel zijn alleen
|
||||
A records ondersteund. Klik op Add om de record toe te voegen.
|
||||
|
||||

|
@@ -1,26 +1,36 @@
|
||||
from flask import Flask, render_template, redirect, request, url_for, session, flash
|
||||
import secrets
|
||||
import requests
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
from eindopdracht.mango import Mango
|
||||
from eindopdracht.openid import *
|
||||
from mango import Mango
|
||||
from openid import *
|
||||
import os
|
||||
|
||||
# the options below you must change if you want to use a different zone and or want to run the script manually
|
||||
|
||||
zone = 'dns.mashallah.nl'
|
||||
dns_api = os.environ.get('DNS_API')
|
||||
mongo_connection = os.environ.get('MONGO_CONNECTIONSTRING')
|
||||
debug = bool(os.environ.get('API_DEBUG', default=False)) # debug option as bool
|
||||
|
||||
# end editing
|
||||
|
||||
if dns_api is None or mongo_connection is None:
|
||||
# check if vars are not set
|
||||
print('Missing DNS or MONGO environs')
|
||||
exit(1)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'fdsfdsafadfa'
|
||||
|
||||
dns_api = 'http://localhost:5001'
|
||||
zone = 'school.test'
|
||||
app.secret_key = secrets.token_hex(22)
|
||||
|
||||
|
||||
def record_update(name, typ, value, jwt):
|
||||
def record_update(name, typ, value, jwt): # update record via api server
|
||||
data = {'name': name, 'type': typ, 'value': value}
|
||||
headers = {'Authorization': f'Bearer {jwt}'}
|
||||
|
||||
r = requests.post(f'{dns_api}/api/v1/dns/domain/{zone}', data=data, headers=headers)
|
||||
|
||||
if r.status_code != 200:
|
||||
return {'error': True, 'reason': f'Request got status code: {r.status_code}'}
|
||||
if r.status_code != 200: # if not sucessful
|
||||
return {'error': True, 'reason': f'Request got status code: {r.status_code}'} # return status code from api
|
||||
|
||||
return {'error': False}
|
||||
|
||||
@@ -29,9 +39,10 @@ def records_get(jwt):
|
||||
headers = {'Authorization': f'Bearer {jwt}'}
|
||||
|
||||
r = requests.get(f'{dns_api}/api/v1/dns/domain/{zone}', headers=headers)
|
||||
if r.status_code != 200:
|
||||
return {'error': True, 'reason': f'Request got status code: {r.status_code}'}
|
||||
return r.json()['records']
|
||||
|
||||
if r.status_code != 200: # if not sucessful
|
||||
return {'error': True, 'reason': f'Request got status code: {r.status_code}'} # return status code from api
|
||||
return r.json()['records'] # return all the records as a dict
|
||||
|
||||
|
||||
def record_delete(name, jwt):
|
||||
@@ -39,18 +50,20 @@ def record_delete(name, jwt):
|
||||
headers = {'Authorization': f'Bearer {jwt}'}
|
||||
|
||||
r = requests.delete(f'{dns_api}/api/v1/dns/domain/{zone}', data=data, headers=headers)
|
||||
if r.status_code != 200:
|
||||
return {'error': True, 'reason': f'Request got status code: {r.status_code}'}
|
||||
|
||||
if r.status_code != 200: # if not sucessful
|
||||
return {'error': True, 'reason': f'Request got status code: {r.status_code}'} # return status code from api
|
||||
|
||||
return {'error': False}
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
def index(): # base index page
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
@app.route('/login/gcp/start')
|
||||
def login_start():
|
||||
def login_start(): # client gets redirected to google login page with parameters
|
||||
nonce = secrets.token_hex(16)
|
||||
return redirect(f'https://accounts.google.com/o/oauth2/v2/auth?'
|
||||
f'response_type=code'
|
||||
@@ -61,97 +74,90 @@ def login_start():
|
||||
|
||||
|
||||
@app.route('/login/gcp/callback')
|
||||
def login_callback():
|
||||
"""
|
||||
We get parameters:(code)
|
||||
:return:
|
||||
"""
|
||||
code = request.args.get('code')
|
||||
def login_callback(): # client gets returned from google with parameters
|
||||
code = request.args.get('code') # get the code given by google
|
||||
|
||||
if code is None:
|
||||
return 'Did not get correct parameters. Missing code'
|
||||
|
||||
grant = GoogleOID.get_token_from_code(code)
|
||||
grant = GoogleOID.get_token_from_code(code) # exchange code for access token
|
||||
|
||||
if grant['error'] is True:
|
||||
return f"Could not exchange code for token: {grant['reason']}"
|
||||
|
||||
profile = GoogleOID.get_profile_information(grant['data']['access_token'])
|
||||
profile = GoogleOID.get_profile_information(grant['data']['access_token']) # get profile info from google
|
||||
|
||||
if profile['error'] is True:
|
||||
return f"Could not get profile information: {profile['reason']}"
|
||||
|
||||
if db.google_check_sso_uuid(profile['profile']['sub'])['error'] is True:
|
||||
return 'Account is unavailable for login'
|
||||
if db.google_check_sso_uuid(profile['profile']['sub'])['error'] is True: # check if user may login
|
||||
return f"Account is unavailable for login {profile['profile']['sub']}"
|
||||
|
||||
db.google_update_profile(profile['profile'])
|
||||
db.google_update_lastlogin(profile['profile']['sub'])
|
||||
db.google_overwrite_jwt(profile['profile']['sub'], grant['data']['id_token'])
|
||||
db.google_update_profile(profile['profile']) # update google profile in db
|
||||
db.google_update_lastlogin(profile['profile']['sub']) # change login date in db
|
||||
db.google_overwrite_jwt(profile['profile']['sub'], grant['data']['id_token']) # overwrite jwt in db
|
||||
|
||||
session['username'] = profile['profile']['sub']
|
||||
session['username'] = profile['profile']['sub'] # set flask session so user gets logged in
|
||||
|
||||
return redirect(url_for('dashboard'))
|
||||
return redirect(url_for('dashboard')) # redirect to dashboard
|
||||
|
||||
|
||||
@app.route('/dashboard', methods=['GET', 'POST'])
|
||||
def dashboard():
|
||||
if 'username' not in session:
|
||||
def dashboard(): # dns manager dashboard
|
||||
if 'username' not in session: # check if flask session is set(user logged in)
|
||||
return 'You are not logged in <a href="/">login</a>'
|
||||
|
||||
if request.method == 'POST':
|
||||
if request.method == 'POST': # if form posted
|
||||
na = request.form.get('name')
|
||||
ty = request.form.get('type')
|
||||
va = request.form.get('value')
|
||||
rq = request.form.get('request')
|
||||
|
||||
if rq != "Add" and rq != "Delete" and rq != "Update" and rq != "Query JWT":
|
||||
if rq != "Add" and rq != "Delete" and rq != "Update" and rq != "Query JWT" and rq != "Add SUB":
|
||||
# data is missing
|
||||
return 'Invalid request, did you use the dashboard?'
|
||||
|
||||
if rq == 'Add' or rq == 'Update':
|
||||
jwt = db.google_get_jwt(session['username'])
|
||||
if rq == 'Add' or rq == 'Update': # if user adds or updates a record
|
||||
jwt = db.google_get_jwt(session['username']) # get jwt from db
|
||||
|
||||
response = record_update(name=na, typ=ty, value=va, jwt=jwt)
|
||||
response = record_update(name=na, typ=ty, value=va, jwt=jwt) # update record
|
||||
|
||||
if response['error'] is True:
|
||||
if response['error'] is True: # if error with update record
|
||||
return f"Error processing request: {response['reason']}"
|
||||
|
||||
elif rq == 'Delete':
|
||||
jwt = db.google_get_jwt(session['username'])
|
||||
jwt = db.google_get_jwt(session['username']) # get jwt from db
|
||||
|
||||
response = record_delete(name=na, jwt=jwt)
|
||||
response = record_delete(name=na, jwt=jwt) # delete record
|
||||
|
||||
if response['error'] is True:
|
||||
if response['error'] is True: # if error with delete record
|
||||
return f"Error processing request: {response['reason']}"
|
||||
|
||||
elif rq == "Query JWT":
|
||||
jwt = db.google_get_jwt(session['username'])
|
||||
flash(jwt)
|
||||
elif rq == "Query JWT": # if user requests jwt from db
|
||||
jwt = db.google_get_jwt(session['username']) # get jwt from db
|
||||
flash(jwt) # flash jwt message
|
||||
|
||||
uuid = session['username']
|
||||
profile = db.google_get_profile(uuid)
|
||||
lastlogin = db.google_get_lastlogin(uuid)
|
||||
jwt = db.google_get_jwt(uuid)
|
||||
records = records_get(jwt)
|
||||
elif rq == "Add SUB": # user adds a sub
|
||||
db.google_add_new_sub(va) # add sub to db
|
||||
|
||||
# the functions below are always returned even if POST is used this will keep the page the same after a POST request
|
||||
|
||||
uuid = session['username'] # get uuid from session
|
||||
profile = db.google_get_profile(uuid) # get profile from db
|
||||
lastlogin = db.google_get_lastlogin(uuid) # get lastlogin from db
|
||||
jwt = db.google_get_jwt(uuid) # get jwt from db
|
||||
records = records_get(jwt) # request records from dns api
|
||||
|
||||
return render_template('dashboard.html', profile=profile, records=records, lastlogin=lastlogin)
|
||||
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('username', None)
|
||||
def logout(): # user logs out
|
||||
session.pop('username', None) # remove flask session
|
||||
return redirect(url_for('index'))
|
||||
|
||||
|
||||
@app.route('/login/gcp/addsub', methods=['POST'])
|
||||
def addsub():
|
||||
if 'username' not in session:
|
||||
return 'You are not logged in <a href="/">login</a>'
|
||||
|
||||
db.google_add_new_sub(request.form.get('sub'))
|
||||
return 'sub toegevoegd, gebruiker mag volgende keer inloggen'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
db = Mango('root', 'test', '192.168.66.113', 5027)
|
||||
GoogleOID = GoogleOID()
|
||||
app.run(debug=True, host='0.0.0.0')
|
||||
db = Mango(mongo_connection) # setup mango
|
||||
GoogleOID = GoogleOID() # initialize googleoid
|
||||
app.run(debug=debug, host='0.0.0.0') # run werkzeug
|
||||
|
67
eindopdracht/dns_gui/mango.py
Normal file
67
eindopdracht/dns_gui/mango.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from pymongo import MongoClient
|
||||
import datetime
|
||||
|
||||
|
||||
class Mango:
|
||||
def __init__(self, connect):
|
||||
try:
|
||||
self.client = MongoClient(connect)
|
||||
self.users = self.client['dns']['users']
|
||||
self.keys = self.client['dns']['keys']
|
||||
except ConnectionError:
|
||||
print('MongoDB connection error')
|
||||
|
||||
def google_check_sso_uuid(self, uuid): # checks if uuid exist in any document
|
||||
found = self.users.find_one({"sso.google.profile.sub": uuid})
|
||||
|
||||
if found is None: # None if nothing found
|
||||
return {"error": True, "reason": "User not found"}
|
||||
else:
|
||||
return {"error": False}
|
||||
|
||||
def google_update_lastlogin(self, uuid): # replaces lastlogin with current time for given user
|
||||
found = self.users.find_one({"sso.google.profile.sub": uuid})
|
||||
|
||||
if found is None:
|
||||
return {"error": True, "reason": "User not found"}
|
||||
|
||||
found['sso']['google']['lastlogin'] = datetime.datetime.utcnow()
|
||||
|
||||
self.users.replace_one({"sso.google.profile.sub": uuid}, found)
|
||||
|
||||
return {"error": False}
|
||||
|
||||
def google_update_profile(self, profile): # overwrites user profile with the one that google has given
|
||||
found = self.users.find_one({"sso.google.profile.sub": profile['sub']})
|
||||
|
||||
found['sso']['google']['profile'] = profile
|
||||
|
||||
self.users.replace_one({"sso.google.profile.sub": profile['sub']}, found)
|
||||
|
||||
return {"error": False}
|
||||
|
||||
def google_get_profile(self, uuid): # returns google profile as stored in db for a given user
|
||||
found = self.users.find_one(({"sso.google.profile.sub": uuid}))
|
||||
return found['sso']['google']['profile']
|
||||
|
||||
def google_get_lastlogin(self, uuid): # returns lastlogin in pretty format for given user
|
||||
found = self.users.find_one(({"sso.google.profile.sub": uuid}))
|
||||
return found['sso']['google']['lastlogin'].strftime('%A %d-%m-%Y, %H:%M:%S')
|
||||
|
||||
def google_add_new_sub(self, uuid): # adds new document in db with only the sub for a given uuid
|
||||
self.users.insert_one({'sso':{'google':{'profile':{"sub": str(uuid)}}}})
|
||||
|
||||
def google_overwrite_jwt(self, uuid, jwt): # overwrite jwt in db for given user
|
||||
found = self.users.find_one({"sso.google.profile.sub": uuid})
|
||||
|
||||
found['sso']['google']['jwt'] = jwt
|
||||
self.users.replace_one({"sso.google.profile.sub": uuid}, found)
|
||||
|
||||
def google_get_jwt(self, uuid): # return jwt as stored in db for given user
|
||||
found = self.users.find_one({"sso.google.profile.sub": uuid})
|
||||
return found['sso']['google']['jwt']
|
||||
|
||||
def check_api_key(self, key): # True or False if api key exists in api keys db
|
||||
if self.keys.find_one({"key": key}):
|
||||
return True
|
||||
return False
|
56
eindopdracht/dns_gui/openid.py
Normal file
56
eindopdracht/dns_gui/openid.py
Normal file
@@ -0,0 +1,56 @@
|
||||
class GoogleOID:
|
||||
def __init__(self):
|
||||
import requests
|
||||
import jwt
|
||||
import os
|
||||
client_secret = os.environ.get('OPENID_SECRET') # change this to your secret if running manually
|
||||
|
||||
if client_secret is None: # if environ not set
|
||||
print('No OPENID_SECRET environ')
|
||||
exit(1)
|
||||
|
||||
self.jwt = jwt
|
||||
self.requests = requests
|
||||
|
||||
self.settings = {'client_id': '954325872153-1v466clrtgg6h4ptt2ne5pgpb9mhilr5.apps.googleusercontent.com',
|
||||
'client_secret': client_secret,
|
||||
'callback_uri': 'https://dnsgui.mashallah.nl/login/gcp/callback',
|
||||
'key_server': 'https://www.googleapis.com/oauth2/v3/certs'} # global oid settings
|
||||
|
||||
def settings(self): # make it so that the settings variable is callable
|
||||
return self.settings
|
||||
|
||||
def get_token_from_code(self, code): # get usable api token from oauth code
|
||||
data = {'grant_type': 'authorization_code',
|
||||
'client_id': self.settings['client_id'],
|
||||
'client_secret': self.settings['client_secret'],
|
||||
'redirect_uri': self.settings['callback_uri'],
|
||||
'code': code}
|
||||
|
||||
r = self.requests.post('https://oauth2.googleapis.com/token', data=data) # exchange code for token
|
||||
|
||||
if r.status_code != 200: # if not successful
|
||||
return {'error': True, 'reason': f'Could not exchange code for access key {r.status_code}'}
|
||||
|
||||
return {'error': False, 'data': r.json()}
|
||||
|
||||
def get_profile_information(self, token): # get google profile with token
|
||||
headers = {'Authorization': f'Bearer {token}'}
|
||||
|
||||
r = self.requests.get('https://openidconnect.googleapis.com/v1/userinfo', headers=headers)
|
||||
|
||||
if r.status_code != 200: # if not successful
|
||||
return {'error': True, 'reason': f'Could not get profile info {r.status_code}'}
|
||||
|
||||
return {'error': False, 'profile': r.json()}
|
||||
|
||||
def check_jwt(self, bearer): # check if jwt is signed by google and valid
|
||||
jwks_client = self.jwt.PyJWKClient(self.settings['key_server']) # setup jwks client with keyserver
|
||||
signing_key = jwks_client.get_signing_key_from_jwt(bearer).key # extract signing key from jwt
|
||||
|
||||
try: # fails if jwt is not valid eg. not signed by google, expired, wrong application
|
||||
decoded = self.jwt.decode(bearer, signing_key, algorithms=["RS256"], audience=self.settings['client_id'])
|
||||
except self.jwt.exceptions.DecodeError: # catch generic error
|
||||
return {'error': True, 'reason': 'Error decoding JWT'}
|
||||
|
||||
return {'error': False, 'data': decoded}
|
6
eindopdracht/dns_gui/requirements.txt
Normal file
6
eindopdracht/dns_gui/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
flask
|
||||
pyjwt
|
||||
requests
|
||||
pymongo
|
||||
|
||||
werkzeug == 2.0.3 # er zit een fout in de laatste versie die plain http post requests altijd als json interperteerd
|
@@ -53,6 +53,16 @@ th {
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</tr>
|
||||
<form method="POST">
|
||||
<tr>
|
||||
<td>
|
||||
<input type="submit" value="Add SUB" name="request" >
|
||||
</td>
|
||||
<td>
|
||||
<input placeholder="Google UUID" name="value">
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</table>
|
||||
<hr>
|
||||
<h4>DNS Records school.test</h4>
|
||||
|
@@ -11,5 +11,7 @@
|
||||
<input type="text" placeholder="Username"><br>
|
||||
<input type="password" placeholder="password"><br>
|
||||
<a href="login/gcp/start"><button>Login with Google you scrub</button></a>
|
||||
<br>
|
||||
<p>Warning! By logging in you agree to submit your userdata such as full name and email address to the Republic of India</p>
|
||||
</body>
|
||||
</html>
|
40
eindopdracht/lambda/lambda_function.py
Normal file
40
eindopdracht/lambda/lambda_function.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import json
|
||||
import boto3
|
||||
import base64
|
||||
|
||||
|
||||
def lambda_handler(event, context):
|
||||
method = str(event.get('requestContext').get('http').get('method'))
|
||||
|
||||
dynamodb = boto3.resource('dynamodb')
|
||||
table = dynamodb.Table('lambdatable')
|
||||
|
||||
if method == "POST":
|
||||
key = str(event.get('pathParameters').get('id'))
|
||||
url = str(base64.b64decode(event.get('body')), 'utf-8')
|
||||
|
||||
table.put_item(Item={'key': key, 'url': url})
|
||||
|
||||
return {'message': url}
|
||||
elif method == "DELETE":
|
||||
key = str(event.get('pathParameters').get('id'))
|
||||
|
||||
table.delete_item(Key={'key': key})
|
||||
return {'message': key}
|
||||
|
||||
else:
|
||||
key = str(event.get('pathParameters').get('id'))
|
||||
|
||||
data = table.get_item(Key={"key": key})
|
||||
|
||||
if len(data) is 1:
|
||||
return {'message': 'No such key'}
|
||||
|
||||
response = {
|
||||
"statusCode": 302,
|
||||
"headers": {
|
||||
'Location': data['Item']['url']
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
14
eindopdracht/testomgeving/bind/dns.mashallah.nl.zone
Normal file
14
eindopdracht/testomgeving/bind/dns.mashallah.nl.zone
Normal file
@@ -0,0 +1,14 @@
|
||||
$ORIGIN nl.
|
||||
$TTL 1m
|
||||
|
||||
dns.mashallah IN SOA dnsns.mashallah.nl. admin.dns.mashallah.nl. (
|
||||
0 ; serial
|
||||
4h ; refresh (zone slave must request new zone file)
|
||||
15m ; retry (zone slave pulls after a failed attempt)
|
||||
8h ; expire (zone slave abstains further responses without zone refresh)
|
||||
5m) ; Negative caching TTL (TTL of SOA record)
|
||||
IN NS dnsns.mashallah.nl.
|
||||
|
||||
$ORIGIN dns.mashallah.nl.
|
||||
test IN A 192.168.0.1
|
||||
|
@@ -2,9 +2,9 @@
|
||||
// Do any local configuration here
|
||||
//
|
||||
|
||||
zone "school.test" in{
|
||||
zone "dns.mashallah.nl" in{
|
||||
type master;
|
||||
file "school.test.zone";
|
||||
file "/var/lib/bind/dns.mashallah.nl.zone";
|
||||
allow-update{any;};
|
||||
};
|
||||
|
@@ -1,15 +0,0 @@
|
||||
$ORIGIN test.
|
||||
$TTL 5m
|
||||
|
||||
school IN SOA ns1.school.test. admin.school.test. (
|
||||
0 ; serial
|
||||
4h ; refresh
|
||||
15m ; retry
|
||||
8h ; expire
|
||||
4m) ; Negative caching TTL
|
||||
IN NS ns1.school.test.
|
||||
IN NS ns2.school.test.
|
||||
|
||||
$ORIGIN school.test.
|
||||
ns1 IN A 10.0.1.1
|
||||
ns2 IN A 10.0.2.1
|
@@ -4,7 +4,7 @@ services:
|
||||
image: ubuntu/bind9
|
||||
volumes:
|
||||
- ./bind/named.conf.local:/etc/bind/named.conf.local:ro
|
||||
- ./bind/school.test.zone:/var/lib/bind/school.test.zone:ro
|
||||
- ./bind/dns.mashallah.nl.zone:/var/lib/bind/dns.mashallah.nl.zone:ro
|
||||
ports:
|
||||
- "5053:53/tcp"
|
||||
- "5053:53/udp"
|
||||
@@ -17,6 +17,7 @@ services:
|
||||
volumes:
|
||||
- ./mongo/import.sh:/docker-entrypoint-initdb.d/import.sh:ro
|
||||
- ./mongo/test-data.json:/docker-entrypoint-initdb.d/test-data.json:ro
|
||||
- ./mongo/test-keys.json:/docker-entrypoint-initdb.d/test-keys.json:ro
|
||||
ports:
|
||||
- "5027:27017"
|
||||
|
||||
@@ -30,4 +31,23 @@ services:
|
||||
ME_CONFIG_MONGODB_ADMINPASSWORD: test
|
||||
ME_CONFIG_MONGODB_URL: mongodb://root:test@mongo:27017/
|
||||
depends_on:
|
||||
- mongo
|
||||
- mongo
|
||||
|
||||
api:
|
||||
image: 4grxfq/api
|
||||
ports:
|
||||
- "5001:5001"
|
||||
environment:
|
||||
DNS_SERVER: bind
|
||||
DNS_PORT: 53
|
||||
OPENID_SECRET: CHANGEME
|
||||
MONGO_CONNECTIONSTRING: "mongodb://root:test@mongo:27017"
|
||||
|
||||
gui:
|
||||
image: 4grxfq/gui
|
||||
ports:
|
||||
- "5000:5000"
|
||||
environment:
|
||||
OPENID_SECRET: CHANGEME
|
||||
DNS_API: "http://api:5001"
|
||||
MONGO_CONNECTIONSTRING: "mongodb://root:test@mongo:27017"
|
@@ -1,2 +1,3 @@
|
||||
#!/bin/sh
|
||||
mongoimport /docker-entrypoint-initdb.d/test-data.json -d dns -c users --drop -u root -p test --authenticationDatabase admin
|
||||
mongoimport /docker-entrypoint-initdb.d/test-data.json -d dns -c users --drop -u root -p test --authenticationDatabase admin
|
||||
mongoimport /docker-entrypoint-initdb.d/test-keys.json -d dns -c keys --drop -u root -p test --authenticationDatabase admin
|
3
eindopdracht/testomgeving/mongo/test-keys.json
Normal file
3
eindopdracht/testomgeving/mongo/test-keys.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"key": "qQT0IuiJwTIz5Jlxw7CwFEeNdcPJUzQqM16PVebJUqaXcLsNFiSVgr8se74itZA="
|
||||
}
|
@@ -1,50 +1,55 @@
|
||||
1. regristreer gcp een applicatie en genereer client keys
|
||||
|
||||
2. zet op login met google button
|
||||
2. zet een webserver functie op die de client redirect naar google met de onderstaande GET parameters
|
||||
|
||||
3. de button opent functie dat de volgende request stuurt naar de url
|
||||
3. plaats een knop of hyperlink op de home pagina die naar de bovenste functie redirect
|
||||
|
||||
4. de flask applicatie redirect de client naar de onderstaande parameter, de onderstaande GET request wordt dus door de
|
||||
client uitgevoerd
|
||||
|
||||
```
|
||||
GET https://accounts.google.com/o/oauth2/v2/auth?
|
||||
client_id=CLIENTID &
|
||||
response_type=code &
|
||||
scope=openid profile email &
|
||||
redirect_uri=CALLBACK &
|
||||
nonce=RANDOM &
|
||||
client_id=CLIENTID & # de client id van je applicatie die je bij stap 1 hebt gegenereerd
|
||||
response_type=code & # je vraagt google om een code(deze kan je met je app secret van stap 1 een authorization token verkrijgen)
|
||||
scope=openid profile email & # de data die je opvraagt(openid=jwt profile=naam, foto enz... email=email)
|
||||
redirect_uri=CALLBACK & # waar google de client naar redirect met parameters
|
||||
nonce=RANDOM # om een replay attack te voorkomen
|
||||
|
||||
RESPONSE
|
||||
GET HTTP REDIRECT CALLBACK # een get request naar de callback met de volgende arguments
|
||||
code=AUTHORIZATIONCODE &
|
||||
scope=email profile
|
||||
authuser=0
|
||||
prompt=none
|
||||
RESPONSE # de onderstaande krijgt je applicatie terug op je callback
|
||||
GET http://localhost:5000/callback # een get request naar de callback met de volgende arguments
|
||||
code=AUTHORIZATIONCODE & # de code die je moet uitwisselen met je secret key
|
||||
scope=email profile & # de scopes die je mag opvragen
|
||||
authuser=0 & #
|
||||
prompt=none #
|
||||
```
|
||||
Hiervan moeten we de code parameter verkrijgen
|
||||
Hiervan moeten we de code parameter gebruiken
|
||||
|
||||
4. Nadat je de authorization code hebt verkregen moet je die omzetten in een (refresh)token, hierbij krijg je ook een
|
||||
5. Nadat je de authorization code hebt verkregen moet je die omzetten in een (refresh)token, hierbij krijg je ook een
|
||||
jwt met alle gebruiker profiel data.
|
||||
|
||||
6. De onderstaande request moet je applicatie in de achtergrond uitvoeren om de code om te wisselen naar bruikbare data
|
||||
|
||||
```
|
||||
POST https://oauth2.googleapis.com/token?
|
||||
code=AUTORIZATIONCODE &
|
||||
client_id=CLIENTID
|
||||
client_secret=CSECRET &
|
||||
redirect_uri=CALLBACK & # wordt niet gebruikt wel verplicht
|
||||
grant_type=authorization_code
|
||||
code=AUTORIZATIONCODE & # de code die je van de client hebt gekregen
|
||||
client_id=CLIENTID & # je applicatie id
|
||||
client_secret=CSECRET & # je applicatie secret
|
||||
redirect_uri=CALLBACK & # wordt niet gebruikt wel verplicht
|
||||
grant_type=authorization_code # de type code die je meegeeft
|
||||
|
||||
RESPONSE
|
||||
200 OK
|
||||
{
|
||||
"access_token": "ACCESS_TOKEN", # hoeft in principe niets mee gedaan te worden
|
||||
"expires_in": 3312,
|
||||
"scope": "https://www.googleapis.com/auth/userinfo.profile openid https://www.googleapis.com/auth/userinfo.email",
|
||||
"token_type": "Bearer",
|
||||
"id_token": "aaaa.bbbbbbbbbbbbbbbb.cccccccccc"
|
||||
# de JWT, als je deze checkt met de keys van google is de authorisatie voldoende, in het midden van de 2 punten is de profiel informatie te vinden encoded in base64
|
||||
"access_token": "ACCESS_TOKEN", # deze kan je gebruiken om extra profiel data op te vragen bij de google profile api
|
||||
"expires_in": 3312, # de tijd voor hoelang de access_token geldig is in seconden
|
||||
"scope": "https://www.googleapis.com/auth/userinfo.profile openid https://www.googleapis.com/auth/userinfo.email", # de scopes die je access_token mag benaderen
|
||||
"token_type": "Bearer", # de onderstaande id_token type
|
||||
"id_token": "aaaa.bbbbbbbbbbbbbbbb.cccccccccc" # de JWT, als je deze checkt met de keys van google is de authorisatie voldoende, in het midden van de 2 punten is de profiel informatie te vinden encoded in base64
|
||||
}
|
||||
```
|
||||
|
||||
5. Nadat je de response hebt gekeken moet je de id_token maniluperen zodat je de base64 encoded object tussen de twee
|
||||
punten verkrijgt.
|
||||
7. in principe voor puur openid authenticatie moet je de client de jwt toesturen, hiermee authentiseerd de client dan
|
||||
met je applicatie zolang de jwt geldig is.
|
||||
|
||||
6. In de base64 encoded JWT staat alle profiel data, bekijk de database of de sub key overeenkomt met wat er is opgeslagen
|
||||
8. aan de flask kant moet je de Authorization header van elke request van de client controleren of de meegegeven JWT
|
||||
Bearer token nog geldig is, door google is uitgegeven, geldend voor jouw applicatie, hier zijn libraries voor te vinden.
|
10
eindopdracht/update_containers.sh
Normal file
10
eindopdracht/update_containers.sh
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
git pull
|
||||
cd dns_api
|
||||
docker build . -t 4grxfq/api:latest
|
||||
cd ..
|
||||
cd dns_gui
|
||||
docker build . -t 4grxfq/gui:latest
|
||||
cd ..
|
||||
docker push 4grxfq/api:latest
|
||||
docker push 4grxfq/gui:latest
|
Reference in New Issue
Block a user