diff --git a/README.md b/README.md index 694256eed5b2681db4cf8499cd5c9190016bc3cc..a9c0b0a452ec259db36977605dfd59a9fdea10ef 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Reads source metadata file(s) and outputs them signed to filesystem Starts a metadata signer server. Reads source metadata files(s) from mdsigner.yaml configuration, see example. Reloads metadata on inotify CLOSE_WRITE of metadata file. -Serves and caches signed by domain signer from memory, on request +Serves and caches signed by realm signer from memory, on request ## ```mdproxy.py``` Reads config from mdproxy.yaml configuration, see example. @@ -28,8 +28,8 @@ Caches signed and cached ```mdserver.py``` metadata requests ## Queries MDQ Queries can then be pointed at -- ```http://mdserver:5001/<domain>/entities/<entityid>``` -- ```http://mdproxy:5002/<domain>/entities/<entityid>``` +- ```http://mdserver:5001/<realm>/entities/<entityid>``` +- ```http://mdproxy:5002/<realm>/entities/<entityid>``` ## Bootstrap softHSM2 This is a very brief summary of the successive commands to initialize softHSM2 for testing. Tested on Ubuntu 21.10. @@ -47,4 +47,4 @@ This is a very brief summary of the successive commands to initialize softHSM2 f # pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -l --slot-index 0 --id a1b2 --label test -y cert -w hsm.der --pin secret # pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -l --pin secret -O -``` \ No newline at end of file +``` diff --git a/mdproxy.py b/mdproxy.py index 93f1f7b11235a35498d1b0430e9b42585d3679b4..51ebe350ad4dacba5b9da4603e43648258ca4b12 100755 --- a/mdproxy.py +++ b/mdproxy.py @@ -6,6 +6,7 @@ from urllib.parse import unquote from dateutil import parser, tz from datetime import datetime from isoduration import parse_duration +from email.utils import formatdate from utils import read_config, hasher, Entity @@ -37,11 +38,22 @@ def serve(domain, eid): if entityID in cached[domain]: if cached[domain][entityID].expires > datetime.now(tz.tzutc()): print(f"cache {entityID}") - return cached[domain][entityID].md + max_age = int((cached[domain][entityID].expires - + datetime.now(tz.tzutc())).total_seconds()) + last_modified = cached[domain][entityID].last_modified + response.headers['Cache-Control'] = f"max-age={max_age}" + response.headers['Last-Modified'] = last_modified + response.data = cached[domain][entityID].md + return response print(f"request {entityID}") - data = requests.get(f"{config[domain]['signer']}/{domain}" - f"/entities/{{sha1}}{entityID}").text + request = requests.get(f"{config[domain]['signer']}/{domain}" + f"/entities/{{sha1}}{entityID}") + data = request.text + last_modified = request.headers.get('Last-Modified', + formatdate(timeval=None, + localtime=False, + usegmt=True)) try: root = ET.fromstring(data) validUntil = root.get('validUntil') @@ -53,17 +65,25 @@ def serve(domain, eid): cached_entity.expires = min(datetime.now(tz.tzutc()) + cached_entity.cache_duration, cached_entity.valid_until) - if cached_entity.valid_until > datetime.now(tz.tzutc()): + cached_entity.last_modified = last_modified + if cached_entity.expires > datetime.now(tz.tzutc()): cached[domain][entityID] = cached_entity + max_age = int((cached_entity.expires - + datetime.now(tz.tzutc())).total_seconds()) else: raise KeyError except Exception: data = "No valid metadata\n" + max_age = 60 response.headers['Content-type'] = "text/html" + response.headers['Cache-Control'] = "max-age=60" response.status = 404 + response.headers['Cache-Control'] = f"max-age={max_age}" + response.headers['Last-Modified'] = last_modified response.data = data return response + if __name__ == "__main__": app.run(host='127.0.0.1', port=5002, debug=False) diff --git a/mdserver.py b/mdserver.py index a0c01690cddb36ad51afdf4ba1c2bf7c3ebb50cd..e587b0c2e8947c9efd4d689d3bb7c89ffba3420a 100755 --- a/mdserver.py +++ b/mdserver.py @@ -1,6 +1,10 @@ #!/usr/bin/env python from utils import read_config, Resource from flask import Flask, Response +from datetime import datetime +from dateutil import tz +from email.utils import formatdate +from time import mktime import logging log = logging.getLogger('werkzeug') @@ -19,12 +23,20 @@ def serve(domain, entity_id): response.headers['Content-Disposition'] = "filename = \"metadata.xml\"" try: - response.data = server[domain][entity_id] + data = server[domain][entity_id] + response.data = data.md + max_age = data.max_age + last_modified = data.last_modified except Exception: response.data = "No valid metadata\n" response.headers['Content-type'] = "text/html" response.status = 404 + max_age = 60 + last_modified = datetime.now(tz.tzutc()) + response.headers['Cache-Control'] = f"max-age={max_age}" + response.headers['Last-Modified'] = formatdate(timeval=mktime(last_modified.timetuple()), + localtime=False, usegmt=True) return response diff --git a/utils.py b/utils.py index fab98c00ea596bf2cd09c87b0cf0f2b5a47eae27..b5b7d463e725cfff2651b269b4abc0b98db2363a 100755 --- a/utils.py +++ b/utils.py @@ -2,7 +2,7 @@ import os from lxml import etree as ET from dateutil import parser, tz from isoduration import parse_duration -from datetime import datetime +from datetime import datetime, timedelta import hashlib from urllib.parse import unquote import yaml @@ -28,6 +28,14 @@ class Entity: self.md = None self.valid_until = 0 self.cache_duration = 0 + self.last_modified = 0 + + +class MData(object): + def __init__(self): + self.md = None + self.max_age = (datetime.now(tz.tzutc()) + + timedelta(seconds=60)) class EventProcessor(pyinotify.ProcessEvent): @@ -99,6 +107,7 @@ class Resource: cacheDuration = root.get('cacheDuration') valid_until = parser.isoparse(validUntil) cache_duration = parse_duration(cacheDuration) + last_modified = datetime.now(tz.tzutc()) if valid_until > datetime.now(tz.tzutc()): for entity_descriptor in root.findall('md:EntityDescriptor', ns): entityID = entity_descriptor.attrib.get('entityID', 'none') @@ -112,6 +121,7 @@ class Resource: entity.cache_duration = cache_duration entity.expires = min(datetime.now(tz.tzutc()) + cache_duration, valid_until) + entity.last_modified = last_modified self.idps[sha1] = entity self.__dict__.pop(sha1, None) if sha1 in old_idps: @@ -136,14 +146,15 @@ class Resource: else: sha1 = hasher(entityID) - data = None + data = MData() if sha1 in self.__dict__: signed_entity = self.__dict__[sha1] if signed_entity.expires > datetime.now(tz.tzutc()): print(f"cache {sha1}") - data = self.__dict__[sha1].md + data.md = self.__dict__[sha1].md + - if data is None and sha1 in self.idps: + if data.md is None and sha1 in self.idps: try: print(f"sign {sha1}") valid_until = self.idps[sha1].valid_until @@ -155,12 +166,16 @@ class Resource: signed_entity.md = signed_xml signed_entity.expires = (datetime.now(tz.tzutc()) + self.idps[sha1].cache_duration) + signed_entity.last_modified = self.idps[sha1].last_modified self.__dict__[sha1] = signed_entity - data = signed_xml + data.md = signed_xml else: raise KeyError except Exception as e: print(sha1) print(f" {e}") + data.max_age = int((signed_entity.expires - + datetime.now(tz.tzutc())).total_seconds()) + data.last_modified = signed_entity.last_modified return data