Archived
1

Compare commits

..

28 Commits

Author SHA1 Message Date
ventilaar
741ac73e35 redirect to secure site, since it's supported 2022-04-06 12:48:13 +02:00
ventilaar
bb5390928e working kubernetes services, just add the secret variables 2022-04-06 12:33:02 +02:00
ventilaar
e9be4808ed change callback domain and add mango to dockerfile 2022-04-06 12:28:38 +02:00
ventilaar
3b6312b596 change callback domain and add mango to dockerfile 2022-04-06 12:28:24 +02:00
ventilaar
f31221caf0 function serverless works! 2022-04-05 15:31:38 +02:00
ventilaar
f1b0ace7ae terraform works! 2022-04-05 14:02:10 +02:00
ventilaar
c77485c4a1 Merge remote-tracking branch 'origin/master' 2022-04-04 22:23:35 +02:00
ventilaar
755e3be41e works but need to set network security groups straight 2022-04-04 22:23:26 +02:00
Ventilaar
d7dd7b5947 beter gedetailleerder uitgelegd 2022-04-04 22:15:05 +02:00
Ventilaar
4b56c4bd55 created kube deployment of api and gui, just need to test it 2022-04-04 21:59:14 +02:00
Ventilaar
aadfe81674 add required mongo connection string 2022-04-03 13:12:36 +02:00
Ventilaar
265476d24e cloud-init script to download and setup bind vm 2022-04-03 13:09:11 +02:00
Ventilaar
af6fc43067 update test data 2022-04-03 13:08:50 +02:00
Ventilaar
67ed0e0f34 add option to use basic authorization with api key 2022-04-03 13:06:35 +02:00
Ventilaar
ed2e93ed8e change verbosity back 2022-04-02 23:03:35 +02:00
Ventilaar
e9cbcbed42 be more verbose 2022-04-02 23:00:57 +02:00
Ventilaar
85ea999996 be more verbose 2022-04-02 22:56:07 +02:00
Ventilaar
9a50318740 forgot to change class 2022-04-02 22:41:02 +02:00
Ventilaar
0af718949c reflect new mongo connection type 2022-04-02 22:36:10 +02:00
Ventilaar
a0ee4b3d34 no default mongo connection string 2022-04-02 22:34:41 +02:00
Ventilaar
e52b494759 change mongo connection type 2022-04-02 22:33:46 +02:00
Ventilaar
57ae8543f2 add warning, thanks azure 2022-04-02 22:30:21 +02:00
Ventilaar
8941b805d5 update 2022-04-02 22:03:29 +02:00
Ventilaar
05325c2905 change callback domain 2022-04-02 21:50:59 +02:00
Ventilaar
52c9b053be forgor one 2022-04-02 21:49:30 +02:00
Ventilaar
d4e105d980 forgor one 2022-04-02 21:45:55 +02:00
Ventilaar
84b5c1760f change school.test to dns.mashallah.nl 2022-04-02 21:44:36 +02:00
Ventilaar
22f3855e68 formatting 2022-04-02 18:27:55 +02:00
22 changed files with 456 additions and 75 deletions

View 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

View 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

View 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

File diff suppressed because one or more lines are too long

View File

@@ -6,5 +6,6 @@ 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"]

View File

@@ -6,6 +6,7 @@ from ipaddress import ip_address, IPv4Address
from openid import *
import os
from dns import resolver
from mango import Mango
app = Flask(__name__)
api = Api(app)
@@ -18,13 +19,14 @@ parser.add_argument('type')
# 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:
print('You did not set a DNS_SERVER environ')
if dnsserver is None or mongo_connect is None:
print('You did not set DNS_SERVER or MONGO_CONNECTIONSTRING environ')
exit(1)
@@ -47,15 +49,18 @@ def make_fqdn_check(subname, parentdomain): # make fqdn from given subdomain na
def validate_authorization(req): # validate authorization header
jwt = req.get('Authorization')
if jwt is None:
jwt = req.get('Authorization') # get Authorization header
if jwt is None: # if not set
return False
jwt = jwt.split(' ')[1] # get jwt from header
if GoogleOID.check_jwt(jwt)['error'] is False: # check if jwt is valid
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
@@ -69,7 +74,7 @@ class domains(Resource):
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):
@@ -190,4 +195,5 @@ if __name__ == '__main__':
# 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

View 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

View File

@@ -14,7 +14,7 @@ class GoogleOID:
self.settings = {'client_id': '954325872153-1v466clrtgg6h4ptt2ne5pgpb9mhilr5.apps.googleusercontent.com',
'client_secret': client_secret,
'callback_uri': 'http://dns.mashallah.nl:5000/login/gcp/callback',
'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

View File

@@ -3,5 +3,6 @@ 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

View File

@@ -17,6 +17,7 @@ https://hub.docker.com/r/4grxfq/gui/ vergeet ook poort 5000 niet te openen naar
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.
![login page](.assets/login.png)
### Dashboard page

View File

@@ -7,19 +7,16 @@ 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 = 'school.test'
zone = 'dns.mashallah.nl'
dns_api = os.environ.get('DNS_API')
nosql_url = os.environ.get('NOSQL_URL')
nosql_user = os.environ.get('NOSQL_USER')
nosql_pass = os.environ.get('NOSQL_PASS')
nosql_port = int(os.environ.get('NOSQL_PORT', default=27017)) # get NOSQL_PORT as int
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 nosql_pass is None or nosql_port is None or nosql_user is None or nosql_url is None:
if dns_api is None or mongo_connection is None:
# check if vars are not set
print('Missing DNS or NOSQL environs')
print('Missing DNS or MONGO environs')
exit(1)
app = Flask(__name__)
@@ -161,6 +158,6 @@ def logout(): # user logs out
if __name__ == '__main__':
db = Mango(nosql_user, nosql_pass, nosql_url, nosql_port) # setup mango
db = Mango(mongo_connection) # setup mango
GoogleOID = GoogleOID() # initialize googleoid
app.run(debug=debug, host='0.0.0.0') # run werkzeug

View File

@@ -3,10 +3,11 @@ 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')
@@ -59,3 +60,8 @@ class Mango:
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

View File

@@ -14,7 +14,7 @@ class GoogleOID:
self.settings = {'client_id': '954325872153-1v466clrtgg6h4ptt2ne5pgpb9mhilr5.apps.googleusercontent.com',
'client_secret': client_secret,
'callback_uri': 'http://dns.mashallah.nl:5000/login/gcp/callback',
'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
@@ -30,7 +30,7 @@ class GoogleOID:
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'}
return {'error': True, 'reason': f'Could not exchange code for access key {r.status_code}'}
return {'error': False, 'data': r.json()}
@@ -40,7 +40,7 @@ class GoogleOID:
r = self.requests.get('https://openidconnect.googleapis.com/v1/userinfo', headers=headers)
if r.status_code != 200: # if not successful
return {'error': True, 'reason': 'Could not get profile info'}
return {'error': True, 'reason': f'Could not get profile info {r.status_code}'}
return {'error': False, 'profile': r.json()}

View File

@@ -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>

View 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

View 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

View File

@@ -2,9 +2,9 @@
// Do any local configuration here
//
zone "school.test" in{
zone "dns.mashallah.nl" in{
type master;
file "/var/lib/bind/school.test.zone";
file "/var/lib/bind/dns.mashallah.nl.zone";
allow-update{any;};
};

View File

@@ -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

View File

@@ -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"
@@ -40,6 +41,7 @@ services:
DNS_SERVER: bind
DNS_PORT: 53
OPENID_SECRET: CHANGEME
MONGO_CONNECTIONSTRING: "mongodb://root:test@mongo:27017"
gui:
image: 4grxfq/gui
@@ -48,7 +50,4 @@ services:
environment:
OPENID_SECRET: CHANGEME
DNS_API: "http://api:5001"
NOSQL_URL: mongo
NOSQL_USER: root
NOSQL_PASS: test
NOSQL_PORT: 27017
MONGO_CONNECTIONSTRING: "mongodb://root:test@mongo:27017"

View File

@@ -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

View File

@@ -0,0 +1,3 @@
{
"key": "qQT0IuiJwTIz5Jlxw7CwFEeNdcPJUzQqM16PVebJUqaXcLsNFiSVgr8se74itZA="
}

View File

@@ -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.