mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-30 17:09:19 +00:00 
			
		
		
		
	Several changes
* OCSP workaround for StrongSwan * Machine attributes framework * Scripting support * Default to nginx frontend
This commit is contained in:
		| @@ -8,6 +8,7 @@ import click | ||||
| import hashlib | ||||
| from datetime import datetime | ||||
| from time import sleep | ||||
| from xattr import listxattr, getxattr | ||||
| from certidude import authority, mailer | ||||
| from certidude.auth import login_required, authorize_admin | ||||
| from certidude.user import User | ||||
| @@ -32,7 +33,6 @@ class SessionResource(object): | ||||
|     @serialize | ||||
|     @login_required | ||||
|     def on_get(self, req, resp): | ||||
|         import xattr | ||||
|  | ||||
|         def serialize_requests(g): | ||||
|             for common_name, path, buf, obj, server in g(): | ||||
| @@ -50,7 +50,7 @@ class SessionResource(object): | ||||
|                 # Extract certificate tags from filesystem | ||||
|                 try: | ||||
|                     tags = [] | ||||
|                     for tag in xattr.getxattr(path, "user.xdg.tags").split(","): | ||||
|                     for tag in getxattr(path, "user.xdg.tags").split(","): | ||||
|                         if "=" in tag: | ||||
|                             k, v = tag.split("=", 1) | ||||
|                         else: | ||||
| @@ -59,12 +59,17 @@ class SessionResource(object): | ||||
|                 except IOError: # No such attribute(s) | ||||
|                     tags = None | ||||
|  | ||||
|                 attributes = {} | ||||
|                 for key in listxattr(path): | ||||
|                     if key.startswith("user.machine."): | ||||
|                         attributes[key[13:]] = getxattr(path, key) | ||||
|  | ||||
|                 # Extract lease information from filesystem | ||||
|                 try: | ||||
|                     last_seen = datetime.strptime(xattr.getxattr(path, "user.lease.last_seen"), "%Y-%m-%dT%H:%M:%S.%fZ") | ||||
|                     last_seen = datetime.strptime(getxattr(path, "user.lease.last_seen"), "%Y-%m-%dT%H:%M:%S.%fZ") | ||||
|                     lease = dict( | ||||
|                         inner_address = xattr.getxattr(path, "user.lease.inner_address"), | ||||
|                         outer_address = xattr.getxattr(path, "user.lease.outer_address"), | ||||
|                         inner_address = getxattr(path, "user.lease.inner_address"), | ||||
|                         outer_address = getxattr(path, "user.lease.outer_address"), | ||||
|                         last_seen = last_seen, | ||||
|                         age = datetime.utcnow() - last_seen | ||||
|                     ) | ||||
| @@ -80,7 +85,8 @@ class SessionResource(object): | ||||
|                     expires = obj.not_valid_after, | ||||
|                     sha256sum = hashlib.sha256(buf).hexdigest(), | ||||
|                     lease = lease, | ||||
|                     tags = tags | ||||
|                     tags = tags, | ||||
|                     attributes = attributes or None, | ||||
|                 ) | ||||
|  | ||||
|         if req.context.get("user").is_admin(): | ||||
| @@ -160,7 +166,7 @@ import ipaddress | ||||
| class NormalizeMiddleware(object): | ||||
|     def process_request(self, req, resp, *args): | ||||
|         assert not req.get_param("unicode") or req.get_param("unicode") == u"✓", "Unicode sanity check failed" | ||||
|         req.context["remote_addr"] = ipaddress.ip_address(req.env["REMOTE_ADDR"].decode("utf-8")) | ||||
|         req.context["remote_addr"] = ipaddress.ip_address(req.access_route[0].decode("utf-8")) | ||||
|  | ||||
|     def process_response(self, req, resp, resource=None): | ||||
|         # wtf falcon?! | ||||
| @@ -181,6 +187,7 @@ def certidude_app(log_handlers=[]): | ||||
|  | ||||
|     app = falcon.API(middleware=NormalizeMiddleware()) | ||||
|     app.req_options.auto_parse_form_urlencoded = True | ||||
|     #app.req_options.strip_url_path_trailing_slash = False | ||||
|  | ||||
|     # Certificate authority API calls | ||||
|     app.add_route("/api/certificate/", CertificateAuthorityResource()) | ||||
| @@ -194,7 +201,7 @@ def certidude_app(log_handlers=[]): | ||||
|         app.add_route("/api/token/", TokenResource()) | ||||
|  | ||||
|     # Extended attributes for scripting etc. | ||||
|     app.add_route("/api/signed/{cn}/attr/", AttributeResource()) | ||||
|     app.add_route("/api/signed/{cn}/attr/", AttributeResource(namespace="machine")) | ||||
|     app.add_route("/api/signed/{cn}/script/", ScriptResource()) | ||||
|  | ||||
|     # API calls used by pushed events on the JS end | ||||
| @@ -215,18 +222,13 @@ def certidude_app(log_handlers=[]): | ||||
|         from .scep import SCEPResource | ||||
|         app.add_route("/api/scep/", SCEPResource()) | ||||
|  | ||||
|     if config.OCSP_SUBNETS: | ||||
|         from .ocsp import OCSPResource | ||||
|         app.add_route("/api/ocsp/", OCSPResource()) | ||||
|  | ||||
|     # Add sink for serving static files | ||||
|     app.add_sink(StaticResource(os.path.join(__file__, "..", "..", "static"))) | ||||
|  | ||||
|     def log_exceptions(ex, req, resp, params): | ||||
|         logger.debug("Caught exception: %s" % ex) | ||||
|         raise ex | ||||
|  | ||||
|     app.add_error_handler(Exception, log_exceptions) | ||||
|     if config.OCSP_SUBNETS: | ||||
|         from .ocsp import OCSPResource | ||||
|         app.add_sink(OCSPResource(), prefix="/api/ocsp") | ||||
|  | ||||
|     # Set up log handlers | ||||
|     if config.LOGGING_BACKEND == "sql": | ||||
|   | ||||
| @@ -1,14 +1,24 @@ | ||||
| import click | ||||
| import falcon | ||||
| import logging | ||||
| from ipaddress import ip_address | ||||
| import re | ||||
| from xattr import setxattr, listxattr, removexattr | ||||
| from datetime import datetime | ||||
| from certidude import config, authority | ||||
| from certidude.decorators import serialize | ||||
| from certidude import config, authority, push | ||||
| from certidude.decorators import serialize, csrf_protection | ||||
| from certidude.firewall import whitelist_subject | ||||
| from certidude.auth import login_required, login_optional, authorize_admin | ||||
| from ipaddress import ip_address | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
| class AttributeResource(object): | ||||
|     def __init__(self, namespace): | ||||
|         self.namespace = namespace | ||||
|  | ||||
|     @serialize | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     def on_get(self, req, resp, cn): | ||||
|         """ | ||||
|         Return extended attributes stored on the server. | ||||
| @@ -17,22 +27,32 @@ class AttributeResource(object): | ||||
|         Results made available only to lease IP address. | ||||
|         """ | ||||
|         try: | ||||
|             path, buf, cert, attribs = authority.get_attributes(cn) | ||||
|             path, buf, cert, attribs = authority.get_attributes(cn, namespace=self.namespace) | ||||
|         except IOError: | ||||
|             raise falcon.HTTPNotFound() | ||||
|         else: | ||||
|             try: | ||||
|                 whitelist = ip_address(attribs.get("user").get("lease").get("inner_address").decode("ascii")) | ||||
|             except AttributeError: # TODO: probably race condition | ||||
|                 raise falcon.HTTPForbidden("Forbidden", | ||||
|                     "Attributes only accessible to the machine") | ||||
|  | ||||
|             if req.context.get("remote_addr") != whitelist: | ||||
|                 logger.info("Attribute access denied from %s, expected %s for %s", | ||||
|                     req.context.get("remote_addr"), | ||||
|                     whitelist, | ||||
|                     cn) | ||||
|                 raise falcon.HTTPForbidden("Forbidden", | ||||
|                     "Attributes only accessible to the machine") | ||||
|  | ||||
|             return attribs | ||||
|  | ||||
|     @csrf_protection | ||||
|     @whitelist_subject # TODO: sign instead | ||||
|     def on_post(self, req, resp, cn): | ||||
|         try: | ||||
|             path, buf, cert = authority.get_signed(cn) | ||||
|         except IOError: | ||||
|             raise falcon.HTTPNotFound() | ||||
|         else: | ||||
|             for key in req.params: | ||||
|                 if not re.match("[a-z0-9_\.]+$", key): | ||||
|                     raise falcon.HTTPBadRequest("Invalid key") | ||||
|             valid = set() | ||||
|             for key, value in req.params.items(): | ||||
|                 identifier = ("user.%s.%s" % (self.namespace, key)).encode("ascii") | ||||
|                 setxattr(path, identifier, value.encode("utf-8")) | ||||
|                 valid.add(identifier) | ||||
|             for key in listxattr(path): | ||||
|                 if not key.startswith("user.%s." % self.namespace): | ||||
|                     continue | ||||
|                 if key not in valid: | ||||
|                     removexattr(path, key) | ||||
|             push.publish("attribute-update", cn) | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| from __future__ import unicode_literals, division, absolute_import, print_function | ||||
| import click | ||||
| import hashlib | ||||
| import os | ||||
| @@ -14,19 +13,26 @@ from oscrypto import keys, asymmetric, symmetric | ||||
| from oscrypto.errors import SignatureError | ||||
|  | ||||
| class OCSPResource(object): | ||||
|     def on_post(self, req, resp): | ||||
|     def __call__(self, req, resp): | ||||
|         if req.method == "GET": | ||||
|             _, _, _, tail = req.path.split("/", 3) | ||||
|             body = b64decode(tail) | ||||
|         elif req.method == "POST": | ||||
|             body = req.stream.read(req.content_length or 0) | ||||
|         else: | ||||
|             raise falcon.HTTPMethodNotAllowed() | ||||
|  | ||||
|         fh = open(config.AUTHORITY_CERTIFICATE_PATH) | ||||
|         server_certificate = asymmetric.load_certificate(fh.read()) | ||||
|         fh.close() | ||||
|  | ||||
|         ocsp_req = ocsp.OCSPRequest.load(req.stream.read()) | ||||
|         print(ocsp_req["tbs_request"].native) | ||||
|  | ||||
|         ocsp_req = ocsp.OCSPRequest.load(body) | ||||
|         now = datetime.now(timezone.utc) | ||||
|         response_extensions = [] | ||||
|  | ||||
|         try: | ||||
|             for ext in ocsp_req["tbs_request"]["request_extensions"]: | ||||
|             if ext["extn_id"] == "nonce": | ||||
|                 if ext["extn_id"].native == "nonce": | ||||
|                     response_extensions.append( | ||||
|                         ocsp.ResponseDataExtension({ | ||||
|                             'extn_id': "nonce", | ||||
| @@ -34,6 +40,8 @@ class OCSPResource(object): | ||||
|                             'extn_value': ext["extn_value"] | ||||
|                         }) | ||||
|                     ) | ||||
|         except ValueError: # https://github.com/wbond/asn1crypto/issues/56 | ||||
|             pass | ||||
|  | ||||
|         responses = [] | ||||
|         for item in ocsp_req["tbs_request"]["request_list"]: | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| from __future__ import unicode_literals, division, absolute_import, print_function | ||||
| import click | ||||
| import hashlib | ||||
| import os | ||||
|   | ||||
| @@ -1,21 +1,37 @@ | ||||
| import falcon | ||||
| import logging | ||||
| from certidude import config, authority | ||||
| from certidude import const, config, authority | ||||
| from certidude.decorators import serialize | ||||
| from jinja2 import Environment, FileSystemLoader | ||||
| from certidude.firewall import whitelist_subject | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
| env = Environment(loader=FileSystemLoader(config.SCRIPT_DIR), trim_blocks=True) | ||||
|  | ||||
| class ScriptResource(): | ||||
|     @whitelist_subject | ||||
|     def on_get(self, req, resp, cn): | ||||
|  | ||||
|         try: | ||||
|             path, buf, cert, attribs = authority.get_attributes(cn) | ||||
|         except IOError: | ||||
|             raise falcon.HTTPNotFound() | ||||
|         else: | ||||
|             resp.set_header("Content-Type", "text/x-shellscript") | ||||
|             resp.body = env.get_template(config.SCRIPT_DEFAULT).render(attributes=attribs) | ||||
|             script = config.SCRIPT_DEFAULT | ||||
|             tags = [] | ||||
|             for tag in attribs.get("user").get("xdg").get("tags").split(","): | ||||
|                 if "=" in tag: | ||||
|                     k, v = tag.split("=", 1) | ||||
|                 else: | ||||
|                     k, v = "other", tag | ||||
|                 if k == "script": | ||||
|                     script = v | ||||
|                 tags.append(dict(id=tag, key=k, value=v)) | ||||
|  | ||||
|             resp.set_header("Content-Type", "text/x-shellscript") | ||||
|             resp.body = env.get_template(script).render( | ||||
|                 authority_name=const.FQDN, | ||||
|                 common_name=cn, | ||||
|                 tags=tags, | ||||
|                 attributes=attribs.get("user").get("machine")) | ||||
|             logger.info("Served script %s for %s at %s" % (script, cn, req.context["remote_addr"])) | ||||
|         # TODO: Assert time is within reasonable range | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from __future__ import unicode_literals, division, absolute_import, print_function | ||||
| from __future__ import division, absolute_import, print_function | ||||
| import click | ||||
| import os | ||||
| import random | ||||
| @@ -57,17 +57,19 @@ def get_revoked(serial): | ||||
|             datetime.utcfromtimestamp(os.stat(path).st_ctime) | ||||
|  | ||||
|  | ||||
| def get_attributes(cn): | ||||
| def get_attributes(cn, namespace=None): | ||||
|     path, buf, cert = get_signed(cn) | ||||
|     attribs = dict() | ||||
|     for key in listxattr(path): | ||||
|         if not key.startswith("user."): | ||||
|             continue | ||||
|         if namespace and not key.startswith("user.%s." % namespace): | ||||
|             continue | ||||
|         value = getxattr(path, key) | ||||
|         current = attribs | ||||
|         if "." in key: | ||||
|             namespace, key = key.rsplit(".", 1) | ||||
|             for component in namespace.split("."): | ||||
|             prefix, key = key.rsplit(".", 1) | ||||
|             for component in prefix.split("."): | ||||
|                 if component not in current: | ||||
|                     current[component] = dict() | ||||
|                 current = current[component] | ||||
| @@ -324,7 +326,6 @@ def sign(common_name, overwrite=False): | ||||
| def _sign(csr, buf, overwrite=False): | ||||
|     assert buf.startswith("-----BEGIN CERTIFICATE REQUEST-----\n") | ||||
|     assert isinstance(csr, x509.CertificateSigningRequest) | ||||
|     from xattr import getxattr, listxattr, setxattr | ||||
|  | ||||
|     common_name, = csr.subject.get_attributes_for_oid(NameOID.COMMON_NAME) | ||||
|     cert_path = os.path.join(config.SIGNED_DIR, "%s.pem" % common_name.value) | ||||
|   | ||||
| @@ -1004,10 +1004,6 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, | ||||
|     static_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), "static") | ||||
|     certidude_path = sys.argv[0] | ||||
|  | ||||
|     # Push server config generation | ||||
|     if os.path.exists("/etc/nginx"): | ||||
|         listen = "127.0.1.1" | ||||
|         port = "8080" | ||||
|     click.echo("Generating: %s" % nginx_config.name) | ||||
|     nginx_config.write(env.get_template("server/nginx.conf").render(vars())) | ||||
|     nginx_config.close() | ||||
| @@ -1017,11 +1013,6 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, | ||||
|     if os.path.exists("/etc/nginx/sites-enabled/default"): | ||||
|         os.unlink("/etc/nginx/sites-enabled/default") | ||||
|     os.system("service nginx restart") | ||||
|     else: | ||||
|         click.echo("Directory /etc/nginx does not exist, hence not creating nginx configuration") | ||||
|         click.echo("Remember to install/configure nchan capable nginx instead of regular nginx!") | ||||
|         listen = "0.0.0.0" | ||||
|         port = "80" | ||||
|  | ||||
|     if os.path.exists("/etc/systemd"): | ||||
|         if os.path.exists("/etc/systemd/system/certidude.service"): | ||||
| @@ -1284,16 +1275,19 @@ def certidude_cron(): | ||||
|  | ||||
| @click.command("serve", help="Run server") | ||||
| @click.option("-e", "--exit-handler", default=False, is_flag=True, help="Install /api/exit/ handler") | ||||
| @click.option("-p", "--port", default=80, help="Listen port") | ||||
| @click.option("-l", "--listen", default="0.0.0.0", help="Listen address") | ||||
| @click.option("-p", "--port", default=8080, help="Listen port") | ||||
| @click.option("-l", "--listen", default="127.0.0.1", help="Listen address") | ||||
| @click.option("-f", "--fork", default=False, is_flag=True, help="Fork to background") | ||||
| def certidude_serve(port, listen, fork, exit_handler): | ||||
|     import pwd | ||||
|     from setproctitle import setproctitle | ||||
|     from certidude.signer import SignServer | ||||
|     from certidude import authority, const | ||||
|     click.echo("Using configuration from: %s" % const.CONFIG_PATH) | ||||
|  | ||||
|     if port == 80: | ||||
|         click.echo("WARNING: Please run Certidude behind nginx, remote address is assumed to be forwarded by nginx!") | ||||
|  | ||||
|     click.echo("Using configuration from: %s" % const.CONFIG_PATH) | ||||
|  | ||||
|     log_handlers = [] | ||||
|  | ||||
|   | ||||
| @@ -98,4 +98,4 @@ TOKEN_SECRET = cp.get("token", "secret") | ||||
|  | ||||
| # The API call for looking up scripts uses following directory as root | ||||
| SCRIPT_DIR = os.path.join(os.path.dirname(__file__), "templates", "script") | ||||
| SCRIPT_DEFAULT = "openwrt.sh" | ||||
| SCRIPT_DEFAULT = "default.sh" | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
|  | ||||
| import falcon | ||||
| import logging | ||||
|  | ||||
| logger = logging.getLogger("api") | ||||
| @@ -20,7 +21,7 @@ def whitelist_subnets(subnets): | ||||
|                     req.env["PATH_INFO"], | ||||
|                     req.context.get("user", "unauthenticated user"), | ||||
|                     req.context.get("remote_addr")) | ||||
|                 raise falcon.HTTPForbidden("Forbidden", "Remote address %s not whitelisted" % remote_addr) | ||||
|                 raise falcon.HTTPForbidden("Forbidden", "Remote address %s not whitelisted" % req.context.get("remote_addr")) | ||||
|  | ||||
|             return func(self, req, resp, *args, **kwargs) | ||||
|         return wrapped | ||||
| @@ -39,3 +40,19 @@ def whitelist_content_types(*content_types): | ||||
|         return wrapped | ||||
|     return wrapper | ||||
|  | ||||
| def whitelist_subject(func): | ||||
|     def wrapped(self, req, resp, cn, *args, **kwargs): | ||||
|         from ipaddress import ip_address | ||||
|         from certidude import authority | ||||
|         from xattr import getxattr | ||||
|         try: | ||||
|             path, buf, cert = authority.get_signed(cn) | ||||
|         except IOError: | ||||
|             raise falcon.HTTPNotFound() | ||||
|         else: | ||||
|             inner_address = getxattr(path, "user.lease.inner_address").decode("ascii") | ||||
|             if req.context.get("remote_addr") != ip_address(inner_address): | ||||
|                 raise falcon.HTTPForbidden("Forbidden", "Remote address %s not whitelisted" % req.context.get("remote_addr")) | ||||
|             return func(self, req, resp, cn, *args, **kwargs) | ||||
|     return wrapped | ||||
|  | ||||
|   | ||||
| @@ -183,7 +183,8 @@ pre { | ||||
|     padding-bottom: 2px; | ||||
| } | ||||
|  | ||||
| .tags .tag { | ||||
| .tags .tag, | ||||
| .attributes .attribute { | ||||
|     display: inline; | ||||
|     background-size: 24px; | ||||
|     background-position: 0 4px; | ||||
| @@ -195,6 +196,15 @@ pre { | ||||
|     white-space: nowrap; | ||||
| } | ||||
|  | ||||
| .attribute { | ||||
|     opacity: 0.25; | ||||
| } | ||||
|  | ||||
| .attribute:hover { | ||||
|     opacity: 1; | ||||
| } | ||||
|  | ||||
|  | ||||
| select { | ||||
|    -webkit-appearance: none; | ||||
|    -moz-appearance: none; | ||||
| @@ -205,7 +215,8 @@ select { | ||||
|  | ||||
| } | ||||
|  | ||||
| .icon.tag { background-image: url("../img/iconmonstr-tag-3.svg"); } | ||||
| .icon.tag, | ||||
| .icon.attribute { background-image: url("../img/iconmonstr-tag-3.svg"); } | ||||
|  | ||||
| .icon.critical { background-image: url("../img/iconmonstr-error-4.svg"); } | ||||
| .icon.error { background-image: url("../img/iconmonstr-error-4.svg"); } | ||||
| @@ -227,5 +238,12 @@ select { | ||||
|  | ||||
| .icon.upload { background-image: url("../img/iconmonstr-upload-17.svg"); } | ||||
|  | ||||
| .icon.dist, | ||||
| .icon.kernel { background-image: url("../img/iconmonstr-linux-os-1.svg"); } | ||||
| .icon.if { background-image: url("../img/iconmonstr-sitemap-5.svg"); } | ||||
| .icon.cpu, | ||||
| .icon.mem, | ||||
| .icon.ram { background-image: url("../img/iconmonstr-cpu-1.svg"); } | ||||
|  | ||||
| /* Make sure this is the last one */ | ||||
| .icon.busy{background-image:url("https://software.opensuse.org/assets/ajax-loader-ea46060b6c9f42822a3d58d075c83ea2.gif");} | ||||
|   | ||||
							
								
								
									
										1
									
								
								certidude/static/img/iconmonstr-cpu-1.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								certidude/static/img/iconmonstr-cpu-1.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 17c0 1.104-.896 2-2 2h-11c-1.104 0-2-.896-2-2v-11c0-1.104.896-2 2-2h11c1.104 0 2 .896 2 2v11zm-11 3v3h-1v-3h1zm4 0v3h-1v-3h1zm2 0v3h-1v-3h1zm-4 0v3h-1v-3h1zm6 0v3h-1v-3h1zm-8-20v3h-1v-3h1zm4 0v3h-1v-3h1zm2 0v3h-1v-3h1zm-4 0v3h-1v-3h1zm6 0v3h-1v-3h1zm4 15h3v1h-3v-1zm0-4h3v1h-3v-1zm0-2h3v1h-3v-1zm0 4h3v1h-3v-1zm0-6h3v1h-3v-1zm-20 8h3v1h-3v-1zm0-4h3v1h-3v-1zm0-2h3v1h-3v-1zm0 4h3v1h-3v-1zm0-6h3v1h-3v-1z"/></svg> | ||||
| After Width: | Height: | Size: 507 B | 
							
								
								
									
										1
									
								
								certidude/static/img/iconmonstr-linux-os-1.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								certidude/static/img/iconmonstr-linux-os-1.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20.581 19.049c-.55-.446-.336-1.431-.907-1.917.553-3.365-.997-6.331-2.845-8.232-1.551-1.595-1.051-3.147-1.051-4.49 0-2.146-.881-4.41-3.55-4.41-2.853 0-3.635 2.38-3.663 3.738-.068 3.262.659 4.11-1.25 6.484-2.246 2.793-2.577 5.579-2.07 7.057-.237.276-.557.582-1.155.835-1.652.72-.441 1.925-.898 2.78-.13.243-.192.497-.192.74 0 .75.596 1.399 1.679 1.302 1.461-.13 2.809.905 3.681.905.77 0 1.402-.438 1.696-1.041 1.377-.339 3.077-.296 4.453.059.247.691.917 1.141 1.662 1.141 1.631 0 1.945-1.849 3.816-2.475.674-.225 1.013-.879 1.013-1.488 0-.39-.139-.761-.419-.988zm-9.147-10.465c-.319 0-.583-.258-1-.568-.528-.392-1.065-.618-1.059-1.03 0-.283.379-.37.869-.681.526-.333.731-.671 1.249-.671.53 0 .69.268 1.41.579.708.307 1.201.427 1.201.773 0 .355-.741.609-1.158.868-.613.378-.928.73-1.512.73zm1.665-5.215c.882.141.981 1.691.559 2.454l-.355-.145c.184-.543.181-1.437-.435-1.494-.391-.036-.643.48-.697.922-.153-.064-.32-.11-.523-.127.062-.923.658-1.737 1.451-1.61zm-3.403.331c.676-.168 1.075.618 1.078 1.435l-.31.19c-.042-.343-.195-.897-.579-.779-.411.128-.344 1.083-.115 1.279l-.306.17c-.42-.707-.419-2.133.232-2.295zm-2.115 19.243c-1.963-.893-2.63-.69-3.005-.69-.777 0-1.031-.579-.739-1.127.248-.465.171-.952.11-1.343-.094-.599-.111-.794.478-1.052.815-.346 1.177-.791 1.447-1.124.758-.937 1.523.537 2.15 1.85.407.851 1.208 1.282 1.455 2.225.227.871-.71 1.801-1.896 1.261zm6.987-1.874c-1.384.673-3.147.982-4.466.299-.195-.563-.507-.927-.843-1.293.539-.142.939-.814.46-1.489-.511-.721-1.555-1.224-2.61-2.04-.987-.763-1.299-2.644.045-4.746-.655 1.862-.272 3.578.057 4.069.068-.988.146-2.638 1.496-4.615.681-.998.691-2.316.706-3.14l.62.424c.456.337.838.708 1.386.708.81 0 1.258-.466 1.882-.853.244-.15.613-.302.923-.513.52 2.476 2.674 5.454 2.795 7.15.501-1.032-.142-3.514-.142-3.514.842 1.285.909 2.356.946 3.67.589.241 1.221.869 1.279 1.696l-.245-.028c-.126-.919-2.607-2.269-2.83-.539-1.19.181-.757 2.066-.997 3.288-.11.559-.314 1.001-.462 1.466zm4.846-.041c-.985.38-1.65 1.187-2.107 1.688-.88.966-2.044.503-2.168-.401-.131-.966.36-1.493.572-2.574.193-.987-.023-2.506.431-2.668.295 1.753 2.066 1.016 2.47.538.657 0 .712.222.859.837.092.385.219.709.578 1.09.418.447.29 1.133-.635 1.49zm-8-13.006c-.651 0-1.138-.433-1.534-.769-.203-.171.05-.487.253-.315.387.328.777.675 1.281.675.607 0 1.142-.519 1.867-.805.247-.097.388.285.143.382-.704.277-1.269.832-2.01.832z"/></svg> | ||||
| After Width: | Height: | Size: 2.4 KiB | 
							
								
								
									
										1
									
								
								certidude/static/img/iconmonstr-sitemap-5.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								certidude/static/img/iconmonstr-sitemap-5.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M16 6h-8v-6h8v6zm-10 12h-6v6h6v-6zm18 0h-6v6h6v-6zm-11-7v-3h-2v3h-9v5h2v-3h7v3h2v-3h7v3h2v-5h-9zm2 7h-6v6h6v-6z"/></svg> | ||||
| After Width: | Height: | Size: 212 B | 
| @@ -181,6 +181,24 @@ function onTagUpdated(e) { | ||||
|     }) | ||||
| } | ||||
|  | ||||
| function onAttributeUpdated(e) { | ||||
|     var cn = e.data; | ||||
|     console.log("Attributes updated", cn); | ||||
|     $.ajax({ | ||||
|         method: "GET", | ||||
|         url: "/api/signed/" + cn + "/attr/", | ||||
|         dataType: "json", | ||||
|         success:function(attributes, status, xhr) { | ||||
|             console.info("Updated", cn, "attributes", attributes); | ||||
|             $(".attributes[data-cn='" + cn + "']").html( | ||||
|                 nunjucks.render('views/attributes.html', { | ||||
|                     certificate: { | ||||
|                         common_name: cn, | ||||
|                         attributes:attributes }})); | ||||
|         } | ||||
|     }) | ||||
| } | ||||
|  | ||||
| $(document).ready(function() { | ||||
|     console.info("Loading CA, to debug: curl " + window.location.href + " --negotiate -u : -H 'Accept: application/json'"); | ||||
|     $.ajax({ | ||||
| @@ -228,6 +246,7 @@ $(document).ready(function() { | ||||
|                 source.addEventListener("request-signed", onRequestSigned); | ||||
|                 source.addEventListener("certificate-revoked", onCertificateRevoked); | ||||
|                 source.addEventListener("tag-update", onTagUpdated); | ||||
|                 source.addEventListener("attribute-update", onAttributeUpdated); | ||||
|  | ||||
|                 console.info("Swtiching to requests section"); | ||||
|                 $("section").hide(); | ||||
|   | ||||
							
								
								
									
										3
									
								
								certidude/static/views/attributes.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								certidude/static/views/attributes.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| {% for key, value in certificate.attributes %} | ||||
| <span class="attribute icon {{ key | replace('.', ' ') }}">{{ value }}</span> | ||||
| {% endfor %} | ||||
| @@ -61,4 +61,8 @@ | ||||
|     <div class="status"> | ||||
|     {% include 'views/status.html' %} | ||||
|     </div> | ||||
|  | ||||
|     <div class="attributes" data-cn="{{ certificate.common_name }}"> | ||||
|         {% include 'views/attributes.html' %} | ||||
|     </div> | ||||
| </li> | ||||
|   | ||||
							
								
								
									
										15
									
								
								certidude/templates/script/default.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								certidude/templates/script/default.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| # Tags: | ||||
| {% for tag in tags %} | ||||
| # {{ tag }} | ||||
| {% endfor %} | ||||
|  | ||||
| curl http://{{ authority_name }}/api/signed/{{ common_name }}/attr -X POST -d "\ | ||||
| dmi.product_name=$(cat /sys/class/dmi/id/product_name)&\ | ||||
| dmi.product_serial=$(cat /sys/class/dmi/id/product_serial)&\ | ||||
| kernel=$(uname -sr)&\ | ||||
| dist=$(lsb_release -si) $(lsb_release -sr)&\ | ||||
| cpu=$(cat /proc/cpuinfo  | grep '^model name' | head -n1 | cut -d ":" -f2 | xargs)&\ | ||||
| mem=$(dmidecode -t 17 | grep Size | cut -d ":" -f 2 | cut -d " " -f 2 | paste -sd+ | bc) MB&\ | ||||
| $(for j in /sys/class/net/[we]*; do echo -en if.$(basename $j).ether=$(cat $j/address)\&; done)" | ||||
| @@ -1,6 +1,6 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| # This script can executed on a preconfigured OpenWrt box | ||||
| # This script can be executed on a preconfigured OpenWrt box | ||||
| # https://lauri.vosandi.com/2017/01/reconfiguring-openwrt-as-dummy-ap.html | ||||
|  | ||||
| # Password protected wireless area | ||||
| @@ -10,13 +10,13 @@ for band in 2ghz 5ghz; do | ||||
|     uci set wireless.lan$band.mode=ap | ||||
|     uci set wireless.lan$band.device=radio$band | ||||
|     uci set wireless.lan$band.encryption=psk2 | ||||
|     {% if attributes.protected and attributes.protected.ssid %} | ||||
|     uci set wireless.lan$band.ssid={{ attrbutes.protected.ssid }} | ||||
|     {% if attributes.wireless.protected and attributes.wireless.protected.ssid %} | ||||
|     uci set wireless.lan$band.ssid={{ attrbutes.wireless.protected.ssid }} | ||||
|     {% else %} | ||||
|     uci set wireless.lan$band.ssid=$(uci get system.@system[0].hostname)-protected | ||||
|     {% endif %} | ||||
|     {% if attributes.protected and attributes.protected.psk %} | ||||
|     uci set wireless.lan$band.key={{ attributes.protected.psk }} | ||||
|     {% if attributes.wireless.protected and attributes.wireless.protected.psk %} | ||||
|     uci set wireless.lan$band.key={{ attributes.wireless.protected.psk }} | ||||
|     {% else %} | ||||
|     uci set wireless.lan$band.key=salakala | ||||
|     {% endif %} | ||||
| @@ -29,8 +29,8 @@ for band in 2ghz 5ghz; do | ||||
|     uci set wireless.guest$band.mode=ap | ||||
|     uci set wireless.guest$band.device=radio$band | ||||
|     uci set wireless.guest$band.encryption=none | ||||
|     {% if attributes.public and attributes.public.ssid %} | ||||
|     uci set wireless.guest$band.ssid={{ attrbutes.public.ssid }} | ||||
|     {% if attributes.wireless.public and attributes.wireless.public.ssid %} | ||||
|     uci set wireless.guest$band.ssid={{ attrbutes.wireless.public.ssid }} | ||||
|     {% else %} | ||||
|     uci set wireless.guest$band.ssid=$(uci get system.@system[0].hostname)-public | ||||
|     {% endif %} | ||||
|   | ||||
| @@ -23,7 +23,7 @@ server { | ||||
|     root {{static_path}}; | ||||
|  | ||||
|     location /api/ { | ||||
|         proxy_pass http://127.0.1.1{% if port != 80 %}:{{ port }}{% endif %}/api/; | ||||
|         proxy_pass http://127.0.1.1:8080/api/; | ||||
|         proxy_set_header Host $host; | ||||
|         proxy_set_header X-Real-IP $remote_addr; | ||||
|         proxy_connect_timeout 600; | ||||
|   | ||||
| @@ -7,8 +7,7 @@ Environment=PYTHON_EGG_CACHE=/tmp/.cache | ||||
| PIDFile=/run/certidude/server.pid | ||||
| ExecReload=/bin/kill -s HUP $MAINPID | ||||
| ExecStop=/bin/kill -s TERM $MAINPID | ||||
| ExecStart={{ certidude_path }} serve {% if listen %} -l {{listen}}{% endif %}{% if port %} -p {{port}}{% endif %} | ||||
|  | ||||
| ExecStart={{ certidude_path }} serve | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
|   | ||||
		Reference in New Issue
	
	Block a user