def test_duration2timedelta(self): for expr, secs in TestDuration.DURATIONS: td = duration2timedelta(expr) print "timedelta: %s" % td print "duration: %s" % expr print "expected seconds: %s" % secs assert(int(td.total_seconds()) == secs) assert(int(total_seconds(td)) == secs)
def test_duration2timedelta(self): for expr, secs in TestDuration.DURATIONS: td = duration2timedelta(expr) print("timedelta: %s" % td) print("duration: %s" % expr) print("expected seconds: %s" % secs) assert(int(td.total_seconds()) == secs) assert(int(total_seconds(td)) == secs)
def certreport(req, *opts): """ Generate a report of the certificates (optionally limited by expiration time or key size) found in the selection. :param req: The request :param opts: Options (not used) :return: always returns the unmodified working document **Examples** .. code-block:: yaml - certreport: error_seconds: 0 warning_seconds: 864000 error_bits: 1024 warning_bits: 2048 For key size checking this will report keys with a size *less* than the size specified, defaulting to errors for keys smaller than 1024 bits and warnings for keys smaller than 2048 bits. It should be understood as the minimum key size for each report level, as such everything below will create report entries. Remember that you need a 'publish' or 'emit' call after certreport in your plumbing to get useful output. PyFF ships with a couple of xslt transforms that are useful for turning metadata with certreport annotation into HTML. """ if req.t is None: raise PipeException("Your pipeline is missing a select statement.") if not req.args: req.args = {} if type(req.args) is not dict: raise PipeException("usage: certreport {warning: 864000, error: 0}") error_seconds = int(req.args.get('error_seconds', "0")) warning_seconds = int(req.args.get('warning_seconds', "864000")) error_bits = int(req.args.get('error_bits', "1024")) warning_bits = int(req.args.get('warning_bits', "2048")) seen = {} for eid in req.t.xpath("//md:EntityDescriptor/@entityID", namespaces=NS, smart_strings=False): for cd in req.t.xpath("md:EntityDescriptor[@entityID='%s']//ds:X509Certificate" % eid, namespaces=NS, smart_strings=False): try: cert_pem = cd.text cert_der = base64.b64decode(cert_pem) m = hashlib.sha1() m.update(cert_der) fp = m.hexdigest() if not seen.get(fp, False): seen[fp] = True cdict = xmlsec.utils.b642cert(cert_pem) keysize = cdict['modulus'].bit_length() cert = cdict['cert'] if keysize < error_bits: e = cd.getparent().getparent().getparent().getparent().getparent() req.md.annotate(e, "certificate-error", "keysize too small", "%s has keysize of %s bits (less than %s)" % (cert.getSubject(), keysize, error_bits)) log.error("%s has keysize of %s" % (eid, keysize)) elif keysize < warning_bits: e = cd.getparent().getparent().getparent().getparent().getparent() req.md.annotate(e, "certificate-warning", "keysize small", "%s has keysize of %s bits (less than %s)" % (cert.getSubject(), keysize, warning_bits)) log.warn("%s has keysize of %s" % (eid, keysize)) et = datetime.strptime("%s" % cert.getNotAfter(), "%y%m%d%H%M%SZ") now = datetime.now() dt = et - now if total_seconds(dt) < error_seconds: e = cd.getparent().getparent().getparent().getparent().getparent() req.md.annotate(e, "certificate-error", "certificate has expired", "%s expired %s ago" % (cert.getSubject(), -dt)) log.error("%s expired %s ago" % (eid, -dt)) elif total_seconds(dt) < warning_seconds: e = cd.getparent().getparent().getparent().getparent().getparent() req.md.annotate(e, "certificate-warning", "certificate about to expire", "%s expires in %s" % (cert.getSubject(), dt)) log.warn("%s expires in %s" % (eid, dt)) except Exception, ex: log.error(ex)
e.set('validUntil', dt.strftime("%Y-%m-%dT%H:%M:%SZ")) except ValueError, ex: log.error("Unable to parse validUntil: %s (%s)" % (valid_until, ex)) # set a reasonable default: 50% of the validity # we replace this below if we have cacheDuration set req.state['cache'] = int(total_seconds(offset) / 50) cache_duration = req.args.get('cacheDuration', e.get('cacheDuration', None)) if cache_duration is not None and len(cache_duration) > 0: offset = duration2timedelta(cache_duration) if offset is None: raise PipeException("Unable to parse %s as xs:duration" % cache_duration) e.set('cacheDuration', cache_duration) req.state['cache'] = int(total_seconds(offset)) return req.t @pipe(name='reginfo') def _reginfo(req, *opts): """ Sets registration info extension on EntityDescription element :param req: The request :param opts: Options (not used) :return: A modified working document Transforms the working document by setting the specified attribute on all of the EntityDescriptor elements of the active document.
def finalize(req, *opts): """ Prepares the working document for publication/rendering. :param req: The request :param opts: Options (not used) :return: returns the working document with @Name, @cacheDuration and @validUntil set Set Name, ID, cacheDuration and validUntil on the toplevel EntitiesDescriptor element of the working document. Unless explicit provided the @Name is set from the request URI if the pipeline is executed in the pyFF server. The @ID is set to a string representing the current date/time and will be prefixed with the string provided, which defaults to '_'. The @cacheDuration element must be a valid xsd duration (eg PT5H for 5 hrs) and @validUntil can be either an absolute ISO 8601 time string or (more comonly) a relative time on the form .. code-block:: none \+?([0-9]+d)?\s*([0-9]+h)?\s*([0-9]+m)?\s*([0-9]+s)? For instance +45d 2m results in a time delta of 45 days and 2 minutes. The '+' sign is optional. If operating on a single EntityDescriptor then @Name is ignored (cf :py:mod:`pyff.pipes.builtins.first`). **Examples** .. code-block:: yaml - finalize: cacheDuration: PT8H validUntil: +10d ID: pyff """ if req.t is None: raise PipeException("Your plumbing is missing a select statement.") e = root(req.t) if e.tag == "{%s}EntitiesDescriptor" % NS['md']: name = req.args.get('name', None) if name is None or not len(name): name = req.args.get('Name', None) if name is None or not len(name): name = req.state.get('url', None) if name is None or not len(name): name = e.get('Name', None) if name is not None and len(name): e.set('Name', name) now = datetime.utcnow() mdid = req.args.get('ID', 'prefix _') if re.match('(\s)*prefix(\s)*', mdid): prefix = re.sub('^(\s)*prefix(\s)*', '', mdid) ID = now.strftime(prefix + "%Y%m%dT%H%M%SZ") else: ID = mdid if not e.get('ID'): e.set('ID', ID) valid_until = str(req.args.get('validUntil', e.get('validUntil', None))) if valid_until is not None and len(valid_until) > 0: offset = duration2timedelta(valid_until) if offset is not None: dt = now + offset e.set('validUntil', dt.strftime("%Y-%m-%dT%H:%M:%SZ")) elif valid_until is not None: try: dt = iso8601.parse_date(valid_until) dt = dt.replace(tzinfo=None) # make dt "naive" (tz-unaware) offset = dt - now e.set('validUntil', dt.strftime("%Y-%m-%dT%H:%M:%SZ")) except ValueError, ex: log.error("Unable to parse validUntil: %s (%s)" % (valid_until, ex)) # set a reasonable default: 50% of the validity # we replace this below if we have cacheDuration set req.state['cache'] = int(total_seconds(offset) / 50)
def finalize(req, *opts): """ Prepares the working document for publication/rendering. :param req: The request :param opts: Options (not used) :return: returns the working document with @Name, @cacheDuration and @validUntil set Set Name, cacheDuration and validUntil on the toplevel EntitiesDescriptor element of the working document. Unless explicit provided the @Name is set from the request URI if the pipeline is executed in the pyFF server. The @cacheDuration element must be a valid xsd duration (eg PT5H for 5 hrs) and @validUntil can be either an absolute ISO 8601 time string or (more comonly) a relative time on the form .. code-block:: none \+?([0-9]+d)?\s*([0-9]+h)?\s*([0-9]+m)?\s*([0-9]+s)? For instance +45d 2m results in a time delta of 45 days and 2 minutes. The '+' sign is optional. If operating on a single EntityDescriptor then @Name is ignored (cf :py:mod:`pyff.pipes.builtins.first`). **Examples** .. code-block:: yaml - finalize: cacheDuration: PT8H validUntil: +10d """ if req.t is None: raise PipeException("Your plumbing is missing a select statement.") e = root(req.t) if e.tag == "{%s}EntitiesDescriptor" % NS['md']: name = req.args.get('name', None) if name is None or not len(name): name = req.args.get('Name', None) if name is None or not len(name): name = req.state.get('url', None) if name is None or not len(name): name = e.get('Name', None) if name is not None and len(name): e.set('Name', name) if not e.get('ID'): e.set('ID', datetime.now().strftime("pyff%Y%m%dT%H%M%S%Z")) validUntil = req.args.get('validUntil', e.get('validUntil', None)) if validUntil is not None and len(validUntil) > 0: offset = duration2timedelta(validUntil) if offset is not None: dt = datetime.now() + offset e.set('validUntil', dt.isoformat()) elif validUntil is not None: dt = iso8601.parse_date(validUntil) offset = dt - datetime.now() # set a reasonable default: 50% of the validity # we replace this below if we have cacheDuration set req.state['cache'] = int(total_seconds(offset) / 50) cacheDuration = req.args.get('cacheDuration', e.get('cacheDuration', None)) if cacheDuration is not None and len(cacheDuration) > 0: offset = duration2timedelta(cacheDuration) if offset is None: raise PipeException("Unable to parse %s as xs:duration" % cacheDuration) e.set('cacheDuration', cacheDuration) req.state['cache'] = int(total_seconds(offset)) return req.t
def certreport(req, *opts): """ Generate a report of the certificates (optionally limited by expiration time) found in the selection. :param req: The request :param opts: Options (not used) :return: always returns the unmodified working document **Examples** .. code-block:: yaml - certreport: error_seconds: 0 warning_seconds: 864000 Remember that you need a 'publish' or 'emit' call after certreport in your plumbing to get useful output. PyFF ships with a couple of xslt transforms that are useful for turning metadata with certreport annotation into HTML. """ if req.t is None: raise PipeException("Your plumbing is missing a select statement.") if not req.args: req.args = {} if type(req.args) is not dict: raise PipeException("usage: certreport {warning: 864000, error: 0}") error_seconds = int(req.args.get('error', "0")) warning_seconds = int(req.args.get('warning', "864000")) seen = {} for eid in req.t.xpath("//md:EntityDescriptor/@entityID", namespaces=NS): for cd in req.t.xpath("md:EntityDescriptor[@entityID='%s']//ds:X509Certificate" % eid, namespaces=NS): try: cert_pem = cd.text cert_der = base64.b64decode(cert_pem) m = hashlib.sha1() m.update(cert_der) fp = m.hexdigest() if not seen.get(fp, False): seen[fp] = True cdict = xmlsec.b642cert(cert_pem) cert = cdict['cert'] et = datetime.strptime("%s" % cert.getNotAfter(), "%Y%m%d%H%M%SZ") now = datetime.now() dt = et - now if total_seconds(dt) < error_seconds: e = cd.getparent().getparent().getparent().getparent().getparent() req.md.annotate(e, "certificate-error", "certificate has expired", "%s expired %s ago" % (cert.getSubject(), -dt)) log.error("%s expired %s ago" % (eid, -dt)) elif total_seconds(dt) < warning_seconds: e = cd.getparent().getparent().getparent().getparent().getparent() req.md.annotate(e, "certificate-warning", "certificate about to expire", "%s expires in %s" % (cert.getSubject(), dt)) log.warn("%s expires in %s" % (eid, dt)) except Exception, ex: log.error(ex)
def certreport(req, *opts): """ Generate a report of the certificates (optionally limited by expiration time or key size) found in the selection. :param req: The request :param opts: Options (not used) :return: always returns the unmodified working document **Examples** .. code-block:: yaml - certreport: error_seconds: 0 warning_seconds: 864000 error_bits: 1024 warning_bits: 2048 For key size checking this will report keys with a size *less* than the size specified, defaulting to errors for keys smaller than 1024 bits and warnings for keys smaller than 2048 bits. It should be understood as the minimum key size for each report level, as such everything below will create report entries. Remember that you need a 'publish' or 'emit' call after certreport in your plumbing to get useful output. PyFF ships with a couple of xslt transforms that are useful for turning metadata with certreport annotation into HTML. """ if req.t is None: raise PipeException("Your pipeline is missing a select statement.") if not req.args: req.args = {} if type(req.args) is not dict: raise PipeException("usage: certreport {warning: 864000, error: 0}") error_seconds = int(req.args.get('error_seconds', "0")) warning_seconds = int(req.args.get('warning_seconds', "864000")) error_bits = int(req.args.get('error_bits', "1024")) warning_bits = int(req.args.get('warning_bits', "2048")) seen = {} for eid in req.t.xpath("//md:EntityDescriptor/@entityID", namespaces=NS, smart_strings=False): for cd in req.t.xpath( "md:EntityDescriptor[@entityID='%s']//ds:X509Certificate" % eid, namespaces=NS, smart_strings=False): try: cert_pem = cd.text cert_der = base64.b64decode(cert_pem) m = hashlib.sha1() m.update(cert_der) fp = m.hexdigest() if not seen.get(fp, False): entity_elt = cd.getparent().getparent().getparent( ).getparent().getparent() seen[fp] = True cdict = xmlsec.utils.b642cert(cert_pem) keysize = cdict['modulus'].bit_length() cert = cdict['cert'] if keysize < error_bits: req.md.annotate( entity_elt, "certificate-error", "keysize too small", "%s has keysize of %s bits (less than %s)" % (cert.getSubject(), keysize, error_bits)) log.error("%s has keysize of %s" % (eid, keysize)) elif keysize < warning_bits: req.md.annotate( entity_elt, "certificate-warning", "keysize small", "%s has keysize of %s bits (less than %s)" % (cert.getSubject(), keysize, warning_bits)) log.warn("%s has keysize of %s" % (eid, keysize)) notafter = cert.getNotAfter() if notafter is None: req.md.annotate( entity_elt, "certificate-error", "certificate has no expiration time", "%s has no expiration time" % cert.getSubject()) else: try: et = datetime.strptime("%s" % notafter, "%y%m%d%H%M%SZ") now = datetime.now() dt = et - now if total_seconds(dt) < error_seconds: req.md.annotate( entity_elt, "certificate-error", "certificate has expired", "%s expired %s ago" % (cert.getSubject(), -dt)) log.error("%s expired %s ago" % (eid, -dt)) elif total_seconds(dt) < warning_seconds: req.md.annotate( entity_elt, "certificate-warning", "certificate about to expire", "%s expires in %s" % (cert.getSubject(), dt)) log.warn("%s expires in %s" % (eid, dt)) except ValueError as ex: req.md.annotate( entity_elt, "certificate-error", "certificate has unknown expiration time", "%s unknown expiration time %s" % (cert.getSubject(), notafter)) except Exception as ex: log.error(ex)
(valid_until, ex)) # set a reasonable default: 50% of the validity # we replace this below if we have cacheDuration set req.state['cache'] = int(total_seconds(offset) / 50) cache_duration = req.args.get('cacheDuration', e.get('cacheDuration', None)) if cache_duration is not None and len(cache_duration) > 0: offset = duration2timedelta(cache_duration) if offset is None: raise PipeException("Unable to parse %s as xs:duration" % cache_duration) e.set('cacheDuration', cache_duration) req.state['cache'] = int(total_seconds(offset)) return req.t @pipe(name='reginfo') def _reginfo(req, *opts): """ Sets registration info extension on EntityDescription element :param req: The request :param opts: Options (not used) :return: A modified working document Transforms the working document by setting the specified attribute on all of the EntityDescriptor elements of the active document.