mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-30 17:09:19 +00:00 
			
		
		
		
	Release version 0.1.20
This commit is contained in:
		| @@ -80,6 +80,7 @@ def certidude_app(): | ||||
|     from .whois import WhoisResource | ||||
|     from .log import LogResource | ||||
|     from .tag import TagResource, TagDetailResource | ||||
|     from .cfg import ConfigResource, ScriptResource | ||||
|  | ||||
|     app = falcon.API() | ||||
|  | ||||
| @@ -94,6 +95,8 @@ def certidude_app(): | ||||
|     app.add_route("/api/log/", LogResource()) | ||||
|     app.add_route("/api/tag/", TagResource()) | ||||
|     app.add_route("/api/tag/{identifier}/", TagDetailResource()) | ||||
|     app.add_route("/api/config/", ConfigResource()) | ||||
|     app.add_route("/api/script/", ScriptResource()) | ||||
|     app.add_route("/api/", SessionResource()) | ||||
|  | ||||
|     # Gateway API calls, should this be moved to separate project? | ||||
| @@ -115,7 +118,6 @@ def certidude_app(): | ||||
|     class PushLogHandler(logging.Handler): | ||||
|         def emit(self, record): | ||||
|             from certidude.push import publish | ||||
|             print("EVENT HAPPENED:", record.created) | ||||
|             publish("log-entry", dict( | ||||
|                 created = datetime.fromtimestamp(record.created), | ||||
|                 message = record.msg % record.args, | ||||
|   | ||||
							
								
								
									
										113
									
								
								certidude/api/cfg.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								certidude/api/cfg.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| import falcon | ||||
| import logging | ||||
| import ipaddress | ||||
| import string | ||||
| from random import choice | ||||
| from certidude import config | ||||
| from certidude.auth import login_required, authorize_admin | ||||
| from certidude.decorators import serialize | ||||
| from jinja2 import Environment, FileSystemLoader | ||||
|  | ||||
| logger = logging.getLogger("api") | ||||
|  | ||||
| env = Environment(loader=FileSystemLoader("/etc/certidude/scripts"), trim_blocks=True) | ||||
|  | ||||
| SQL_SELECT_INHERITED = """ | ||||
| select `key`, `value` from tag_inheritance where tag_id in (select | ||||
| 	tag.id | ||||
| from | ||||
| 	device_tag | ||||
| join | ||||
| 	tag on device_tag.tag_id = tag.id | ||||
| join | ||||
| 	device on device_tag.device_id = device.id | ||||
| where | ||||
| 	device.cn = %s) | ||||
| """ | ||||
|  | ||||
| SQL_SELECT_TAGS = """ | ||||
| select | ||||
| 	tag.`key` as `key`, | ||||
| 	tag.`value` as `value` | ||||
| from | ||||
| 	device_tag | ||||
| join | ||||
| 	tag on device_tag.tag_id = tag.id | ||||
| join | ||||
| 	device on device_tag.device_id = device.id | ||||
| where | ||||
|     device.cn = %s | ||||
| """ | ||||
|  | ||||
| SQL_SELECT_INHERITANCE = """ | ||||
| select | ||||
|     tag_inheritance.`id` as `id`, | ||||
|     tag.id as `tag_id`, | ||||
|     tag.`key` as `match_key`, | ||||
|     tag.`value` as `match_value`, | ||||
|     tag_inheritance.`key` as `key`, | ||||
|     tag_inheritance.`value` as `value` | ||||
| from tag_inheritance | ||||
| join tag on tag.id = tag_inheritance.tag_id | ||||
| """ | ||||
|  | ||||
| class ConfigResource(object): | ||||
|     @serialize | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     def on_get(self, req, resp): | ||||
|         conn = config.DATABASE_POOL.get_connection() | ||||
|         cursor = conn.cursor(dictionary=True) | ||||
|         cursor.execute(SQL_SELECT_INHERITANCE) | ||||
|         def g(): | ||||
|             for row in cursor: | ||||
|                 yield row | ||||
|             cursor.close() | ||||
|             conn.close() | ||||
|         return g() | ||||
|  | ||||
| class ScriptResource(object): | ||||
|     def on_get(self, req, resp): | ||||
|         from certidude.api.whois import address_to_identity | ||||
|  | ||||
|         node = address_to_identity( | ||||
|             config.DATABASE_POOL.get_connection(), | ||||
|             ipaddress.ip_address(req.env["REMOTE_ADDR"]) | ||||
|         ) | ||||
|         if not node: | ||||
|             resp.body = "Could not map IP address: %s" % req.env["REMOTE_ADDR"] | ||||
|             resp.status = falcon.HTTP_404 | ||||
|             return | ||||
|  | ||||
|         address, acquired, identity = node | ||||
|  | ||||
|         key, common_name = identity.split("=") | ||||
|         assert "=" not in common_name | ||||
|  | ||||
|         conn = config.DATABASE_POOL.get_connection() | ||||
|         cursor = conn.cursor() | ||||
|  | ||||
|         resp.set_header("Content-Type", "text/x-shellscript") | ||||
|  | ||||
|         args = common_name, | ||||
|         ctx = dict() | ||||
|  | ||||
|         for query in SQL_SELECT_INHERITED, SQL_SELECT_TAGS: | ||||
|             cursor.execute(query, args) | ||||
|  | ||||
|             for key, value in cursor: | ||||
|                 current = ctx | ||||
|                 if "." in key: | ||||
|                     path, key = key.rsplit(".", 1) | ||||
|  | ||||
|                     for component in path.split("."): | ||||
|                         if component not in current: | ||||
|                             current[component] = dict() | ||||
|                         current = current[component] | ||||
|                 current[key] = value | ||||
|         cursor.close() | ||||
|         conn.close() | ||||
|  | ||||
|         resp.body = env.get_template("uci.sh").render(ctx) | ||||
|  | ||||
|         # TODO: Assert time is within reasonable range | ||||
| @@ -54,8 +54,8 @@ class LeaseResource(object): | ||||
|                 addresses.id | ||||
|             desc | ||||
|         """ | ||||
|         cnx = config.DATABASE_POOL.get_connection() | ||||
|         cursor = cnx.cursor() | ||||
|         conn = config.DATABASE_POOL.get_connection() | ||||
|         cursor = conn.cursor() | ||||
|         cursor.execute(SQL_LEASES) | ||||
|  | ||||
|         for acquired, released, address, identity in cursor: | ||||
| @@ -66,3 +66,5 @@ class LeaseResource(object): | ||||
|                 "identity": parse_dn(bytes(identity)) | ||||
|             } | ||||
|  | ||||
|         cursor.close() | ||||
|         conn.close() | ||||
|   | ||||
| @@ -28,6 +28,9 @@ class SignedCertificateListResource(object): | ||||
| class SignedCertificateDetailResource(object): | ||||
|     @serialize | ||||
|     def on_get(self, req, resp, cn): | ||||
|         # Compensate for NTP lag | ||||
|         from time import sleep | ||||
|         sleep(5) | ||||
|         try: | ||||
|             logger.info("Served certificate %s to %s", cn, req.env["REMOTE_ADDR"]) | ||||
|             resp.set_header("Content-Disposition", "attachment; filename=%s.crt" % cn) | ||||
|   | ||||
| @@ -7,6 +7,26 @@ from certidude.decorators import serialize | ||||
|  | ||||
| logger = logging.getLogger("api") | ||||
|  | ||||
| SQL_TAG_LIST = """ | ||||
| select | ||||
|     device_tag.id as `id`, | ||||
| 	tag.key as `key`, | ||||
| 	tag.value as `value`, | ||||
| 	device.cn as `cn` | ||||
| from | ||||
| 	device_tag | ||||
| join | ||||
| 	tag | ||||
| on | ||||
| 	device_tag.tag_id = tag.id | ||||
| join | ||||
| 	device | ||||
| on | ||||
| 	device_tag.device_id = device.id | ||||
| """ | ||||
|  | ||||
| SQL_TAG_DETAIL = SQL_TAG_LIST + " where device_tag.id = %s" | ||||
|  | ||||
| class TagResource(object): | ||||
|     @serialize | ||||
|     @login_required | ||||
| @@ -14,7 +34,7 @@ class TagResource(object): | ||||
|     def on_get(self, req, resp): | ||||
|         conn = config.DATABASE_POOL.get_connection() | ||||
|         cursor = conn.cursor(dictionary=True) | ||||
|         cursor.execute("select * from tag") | ||||
|         cursor.execute(SQL_TAG_LIST) | ||||
|  | ||||
|         def g(): | ||||
|             for row in cursor: | ||||
| @@ -30,10 +50,24 @@ class TagResource(object): | ||||
|         from certidude import push | ||||
|         conn = config.DATABASE_POOL.get_connection() | ||||
|         cursor = conn.cursor() | ||||
|         args = req.get_param("cn"), req.get_param("key"), req.get_param("value") | ||||
|  | ||||
|         args = req.get_param("cn"), | ||||
|         cursor.execute( | ||||
|             "insert into tag (`cn`, `key`, `value`) values (%s, %s, %s)", args) | ||||
|             "insert ignore device (`cn`) values (%s) on duplicate key update used = NOW();", args) | ||||
|         device_id = cursor.lastrowid | ||||
|  | ||||
|         args = req.get_param("key"), req.get_param("value") | ||||
|         cursor.execute( | ||||
|             "insert into tag (`key`, `value`) values (%s, %s) on duplicate key update used = NOW();", args) | ||||
|         tag_id = cursor.lastrowid | ||||
|  | ||||
|         args = device_id, tag_id | ||||
|         cursor.execute( | ||||
|             "insert into device_tag (`device_id`, `tag_id`) values (%s, %s);", args) | ||||
|  | ||||
|         push.publish("tag-added", str(cursor.lastrowid)) | ||||
|  | ||||
|         args = req.get_param("cn"), req.get_param("key"), req.get_param("value") | ||||
|         logger.debug("Tag cn=%s, key=%s, value=%s added" % args) | ||||
|         conn.commit() | ||||
|         cursor.close() | ||||
| @@ -47,7 +81,7 @@ class TagDetailResource(object): | ||||
|     def on_get(self, req, resp, identifier): | ||||
|         conn = config.DATABASE_POOL.get_connection() | ||||
|         cursor = conn.cursor(dictionary=True) | ||||
|         cursor.execute("select * from tag where `id` = %s", (identifier,)) | ||||
|         cursor.execute(SQL_TAG_DETAIL, (identifier,)) | ||||
|         for row in cursor: | ||||
|             cursor.close() | ||||
|             conn.close() | ||||
| @@ -63,11 +97,21 @@ class TagDetailResource(object): | ||||
|         from certidude import push | ||||
|         conn = config.DATABASE_POOL.get_connection() | ||||
|         cursor = conn.cursor() | ||||
|         cursor.execute("update tag set `value` = %s where `id` = %s limit 1", | ||||
|             (req.get_param("value"), identifier)) | ||||
|  | ||||
|         # Create tag if necessary | ||||
|         args = req.get_param("key"), req.get_param("value") | ||||
|         cursor.execute( | ||||
|             "insert into tag (`key`, `value`) values (%s, %s) on duplicate key update used = NOW();", args) | ||||
|         tag_id = cursor.lastrowid | ||||
|  | ||||
|         # Attach tag to device | ||||
|         cursor.execute("update device_tag set tag_id = %s where `id` = %s limit 1", | ||||
|             (tag_id, identifier)) | ||||
|         conn.commit() | ||||
|  | ||||
|         cursor.close() | ||||
|         conn.close() | ||||
|  | ||||
|         logger.debug("Tag %s updated, value set to %s", | ||||
|             identifier, req.get_param("value")) | ||||
|         push.publish("tag-updated", identifier) | ||||
| @@ -80,7 +124,7 @@ class TagDetailResource(object): | ||||
|         from certidude import push | ||||
|         conn = config.DATABASE_POOL.get_connection() | ||||
|         cursor = conn.cursor() | ||||
|         cursor.execute("delete from tag where tag.id = %s", (identifier,)) | ||||
|         cursor.execute("delete from device_tag where id = %s", (identifier,)) | ||||
|         conn.commit() | ||||
|         cursor.close() | ||||
|         conn.close() | ||||
|   | ||||
| @@ -6,7 +6,7 @@ from certidude import config | ||||
| from certidude.decorators import serialize | ||||
| from certidude.api.lease import parse_dn | ||||
|  | ||||
| def address_to_identity(cnx, addr): | ||||
| def address_to_identity(conn, addr): | ||||
|     """ | ||||
|     Translate currently online client's IP-address to distinguished name | ||||
|     """ | ||||
| @@ -27,29 +27,32 @@ def address_to_identity(cnx, addr): | ||||
|             released is not null | ||||
|     """ | ||||
|  | ||||
|     cursor = cnx.cursor() | ||||
|     cursor = conn.cursor() | ||||
|     import struct | ||||
|     cursor.execute(SQL_LEASES, (struct.pack("!L", int(addr)),)) | ||||
|  | ||||
|     for acquired, released, identity in cursor: | ||||
|         return { | ||||
|             "address": addr, | ||||
|             "acquired": datetime.utcfromtimestamp(acquired), | ||||
|             "identity": parse_dn(bytes(identity)) | ||||
|         } | ||||
|         cursor.close() | ||||
|         return addr, datetime.utcfromtimestamp(acquired), parse_dn(bytes(identity)) | ||||
|  | ||||
|     cursor.close() | ||||
|     return None | ||||
|  | ||||
|  | ||||
| class WhoisResource(object): | ||||
|     @serialize | ||||
|     def on_get(self, req, resp): | ||||
|         conn = config.DATABASE_POOL.get_connection() | ||||
|  | ||||
|         identity = address_to_identity( | ||||
|             config.DATABASE_POOL.get_connection(), | ||||
|             conn, | ||||
|             ipaddress.ip_address(req.get_param("address") or req.env["REMOTE_ADDR"]) | ||||
|         ) | ||||
|  | ||||
|         conn.close() | ||||
|  | ||||
|         if identity: | ||||
|             return identity | ||||
|             return dict(address=identity[0], acquired=identity[1], identity=identity[2]) | ||||
|         else: | ||||
|             resp.status = falcon.HTTP_403 | ||||
|             resp.body = "Failed to look up node %s" % req.env["REMOTE_ADDR"] | ||||
|   | ||||
| @@ -69,6 +69,9 @@ def certidude_spawn(kill, no_interaction): | ||||
|     """ | ||||
|     from certidude import config | ||||
|  | ||||
|     _, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude") | ||||
|     os.setgid(gid) | ||||
|  | ||||
|     # Check whether we have privileges | ||||
|     os.umask(0o027) | ||||
|     uid = os.getuid() | ||||
| @@ -472,7 +475,7 @@ def certidude_setup_strongswan_networkmanager(url, email_address, common_name, o | ||||
| @click.option("--username", default="certidude", help="Service user account, created if necessary, 'certidude' by default") | ||||
| @click.option("--hostname", default=HOSTNAME, help="nginx hostname, '%s' by default" % HOSTNAME) | ||||
| @click.option("--static-path", default=os.path.join(os.path.dirname(__file__), "static"), help="Static files") | ||||
| @click.option("--kerberos-keytab", default="/etc/certidude.keytab", help="Specify Kerberos keytab") | ||||
| @click.option("--kerberos-keytab", default="/etc/certidude/server.keytab", help="Specify Kerberos keytab") | ||||
| @click.option("--nginx-config", "-n", | ||||
|     default="/etc/nginx/nginx.conf", | ||||
|     type=click.File(mode="w", atomic=True, lazy=True), | ||||
| @@ -540,6 +543,10 @@ def certidude_setup_authority(parent, country, state, locality, organization, or | ||||
|     if os.path.lexists(directory): | ||||
|         raise click.ClickException("Output directory {} already exists.".format(directory)) | ||||
|  | ||||
|     certidude_conf = os.path.join("/etc/certidude/server.conf") | ||||
|     if os.path.exists(certidude_conf): | ||||
|         raise click.ClickException("Configuration file %s already exists" % certidude_conf) | ||||
|  | ||||
|     click.echo("CA configuration files are saved to: {}".format(directory)) | ||||
|  | ||||
|     click.echo("Generating 4096-bit RSA key...") | ||||
| @@ -588,6 +595,10 @@ def certidude_setup_authority(parent, country, state, locality, organization, or | ||||
|             b"keyUsage", | ||||
|             True, | ||||
|             b"keyCertSign, cRLSign"), | ||||
|         crypto.X509Extension( | ||||
|             b"extendedKeyUsage", | ||||
|             True, | ||||
|             b"serverAuth,1.3.6.1.5.5.8.2.2"), | ||||
|         crypto.X509Extension( | ||||
|             b"subjectKeyIdentifier", | ||||
|             False, | ||||
| @@ -606,6 +617,12 @@ def certidude_setup_authority(parent, country, state, locality, organization, or | ||||
|             False, | ||||
|             subject_alt_name.encode("ascii")) | ||||
|     ]) | ||||
|     ca.add_extensions([ | ||||
|         crypto.X509Extension( | ||||
|             b"subjectAltName", | ||||
|             True, | ||||
|             ("DNS:%s" % common_name).encode("ascii")) | ||||
|     ]) | ||||
|  | ||||
|     if ocsp_responder_url: | ||||
|         raise NotImplementedError() | ||||
| @@ -628,33 +645,40 @@ def certidude_setup_authority(parent, country, state, locality, organization, or | ||||
|  | ||||
|     ca.sign(key, "sha256") | ||||
|  | ||||
|     _, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude") | ||||
|     os.setgid(gid) | ||||
|  | ||||
|     # Create authority directory with 750 permissions | ||||
|     os.umask(0o027) | ||||
|     if not os.path.exists(directory): | ||||
|         os.makedirs(directory) | ||||
|  | ||||
|     # Create subdirectories with 770 permissions | ||||
|     os.umask(0o007) | ||||
|  | ||||
|     for subdir in ("signed", "requests", "revoked"): | ||||
|         if not os.path.exists(os.path.join(directory, subdir)): | ||||
|             os.mkdir(os.path.join(directory, subdir)) | ||||
|  | ||||
|     # Create CRL and serial file with 644 permissions | ||||
|     os.umask(0o133) | ||||
|     with open(ca_crl, "wb") as fh: | ||||
|         crl = crypto.CRL() | ||||
|         fh.write(crl.export(ca, key, days=revocation_list_lifetime)) | ||||
|     with open(os.path.join(directory, "serial"), "w") as fh: | ||||
|         fh.write("1") | ||||
|  | ||||
|     os.umask(0o027) | ||||
|     # Set permission bits to 640 | ||||
|     os.umask(0o137) | ||||
|     with open(certidude_conf, "w") as fh: | ||||
|         fh.write(env.get_template("certidude.conf").render(locals())) | ||||
|     with open(ca_crt, "wb") as fh: | ||||
|         fh.write(crypto.dump_certificate(crypto.FILETYPE_PEM, ca)) | ||||
|  | ||||
|     os.umask(0o077) | ||||
|     # Set permission bits to 600 | ||||
|     os.umask(0o177) | ||||
|     with open(ca_key, "wb") as fh: | ||||
|         fh.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) | ||||
|  | ||||
|     certidude_conf = os.path.join("/etc/certidude.conf") | ||||
|     with open(certidude_conf, "w") as fh: | ||||
|         fh.write(env.get_template("certidude.conf").render(locals())) | ||||
|  | ||||
|     click.echo() | ||||
|     click.echo("Use following commands to inspect the newly created files:") | ||||
|     click.echo() | ||||
| @@ -665,7 +689,7 @@ def certidude_setup_authority(parent, country, state, locality, organization, or | ||||
|     click.echo() | ||||
|     click.echo("Use following to launch privilege isolated signer processes:") | ||||
|     click.echo() | ||||
|     click.echo("  certidude spawn") | ||||
|     click.echo("  certidude spawn -k") | ||||
|     click.echo() | ||||
|     click.echo("Use following command to serve CA read-only:") | ||||
|     click.echo() | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import string | ||||
| from random import choice | ||||
|  | ||||
| cp = configparser.ConfigParser() | ||||
| cp.read("/etc/certidude.conf") | ||||
| cp.read("/etc/certidude/server.conf") | ||||
|  | ||||
| ADMIN_USERS = set([j for j in  cp.get("authorization", "admin_users").split(" ") if j]) | ||||
| ADMIN_SUBNETS = set([ipaddress.ip_network(j) for j in cp.get("authorization", "admin_subnets").split(" ") if j]) | ||||
| @@ -48,7 +48,6 @@ except configparser.NoOptionError: | ||||
|     PUSH_LONG_POLL = PUSH_SERVER + "/lp/%s" | ||||
|     PUSH_PUBLISH = PUSH_SERVER + "/pub?id=%s" | ||||
|  | ||||
|  | ||||
| from urllib.parse import urlparse | ||||
| o = urlparse(cp.get("authority", "database")) | ||||
| if o.scheme == "mysql": | ||||
|   | ||||
| @@ -97,7 +97,7 @@ def raw_sign(private_key, ca_cert, request, basic_constraints, lifetime, key_usa | ||||
|                     extended_key_usage.encode("ascii"))]) | ||||
|  | ||||
|         # Set certificate lifetime | ||||
|         cert.gmtime_adj_notBefore(0) | ||||
|         cert.gmtime_adj_notBefore(-3600) | ||||
|         cert.gmtime_adj_notAfter(lifetime * 24 * 60 * 60) | ||||
|  | ||||
|         # Generate serial from 0x10000000000000000000 to 0xffffffffffffffffffff | ||||
|   | ||||
| @@ -52,9 +52,7 @@ | ||||
| <section id="revoked"> | ||||
|     <h1>Revoked certificates</h1> | ||||
|     <p>To fetch certificate revocation list:</p> | ||||
|     <pre> | ||||
|     curl {{window.location.href}}api/revoked/ | openssl crl -text -noout | ||||
|     </pre> | ||||
|     <pre>curl {{window.location.href}}api/revoked/ | openssl crl -text -noout</pre> | ||||
|     <!-- | ||||
|     <p>To perform online certificate status request</p> | ||||
|  | ||||
| @@ -74,3 +72,6 @@ | ||||
| 	    {% endfor %} | ||||
|     </ul> | ||||
| </section> | ||||
|  | ||||
| <section id="config"> | ||||
| </section> | ||||
|   | ||||
							
								
								
									
										31
									
								
								certidude/static/configuration.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								certidude/static/configuration.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
|  | ||||
| <h1>Create a rule</h1> | ||||
| <p> | ||||
|  | ||||
|     <datalist id="tag_autocomplete"> | ||||
|  | ||||
|     </datalist> | ||||
|  | ||||
|      <span>Filter</span> | ||||
|       <select id="tags_autocomplete"></select> | ||||
|       attaches attribute | ||||
|       <select> | ||||
|         {% include 'tagtypes.html' %} | ||||
|       </select> | ||||
|       <span contenteditable>something</span> | ||||
|       <button>Add rule</button> | ||||
| </p> | ||||
|  | ||||
| {% for grouper, items in configuration | groupby('tag_id') %} | ||||
|  | ||||
| <h1>Filter {{ items[0].match_key }} is {{ items[0].match_value }}</h1> | ||||
| <ul> | ||||
|  | ||||
| {% for item in items %} | ||||
|     <li>Attach {{ item.key }} attribute {{ item.value }}</li> | ||||
| {% endfor %} | ||||
| </ul> | ||||
|  | ||||
| {% endfor %} | ||||
|  | ||||
|  | ||||
| @@ -216,5 +216,8 @@ select { | ||||
| .icon.room { background-image: url("../img/iconmonstr-home-4-icon.svg"); } | ||||
| .icon.serial { background-image: url("../img/iconmonstr-barcode-4-icon.svg"); } | ||||
|  | ||||
| .icon.wireless { background-image: url("../img/iconmonstr-wireless-6-icon.svg"); } | ||||
| .icon.password { background-image: url("../img/iconmonstr-lock-3-icon.svg"); } | ||||
|  | ||||
| /* Make sure this is the last one */ | ||||
| .icon.busy{background-image:url("https://software.opensuse.org/assets/ajax-loader-ea46060b6c9f42822a3d58d075c83ea2.gif");} | ||||
|   | ||||
							
								
								
									
										13
									
								
								certidude/static/img/iconmonstr-lock-3-icon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								certidude/static/img/iconmonstr-lock-3-icon.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
|  | ||||
| <!-- The icon can be used freely in both personal and commercial projects with no attribution required, but always appreciated.  | ||||
| You may NOT sub-license, resell, rent, redistribute or otherwise transfer the icon without express written permission from iconmonstr.com --> | ||||
|  | ||||
| <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | ||||
| <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||
| 	 width="32px" height="32px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"> | ||||
| <path id="lock-3-icon" d="M195.334,223.333h-50v-62.666C145.334,99.645,194.979,50,256,50c61.022,0,110.667,49.645,110.667,110.667 | ||||
| 	v62.666h-50v-62.666C316.667,127.215,289.452,100,256,100c-33.451,0-60.666,27.215-60.666,60.667V223.333z M404,253.333V462H108 | ||||
| 	V253.333H404z M283,341c0-14.912-12.088-27-27-27s-27,12.088-27,27c0,7.811,3.317,14.844,8.619,19.773 | ||||
| 	c4.385,4.075,6.881,9.8,6.881,15.785V399.5h23v-22.941c0-5.989,2.494-11.708,6.881-15.785C279.683,355.844,283,348.811,283,341z"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										16
									
								
								certidude/static/img/iconmonstr-wireless-6-icon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								certidude/static/img/iconmonstr-wireless-6-icon.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
|  | ||||
| <!-- The icon can be used freely in both personal and commercial projects with no attribution required, but always appreciated.  | ||||
| You may NOT sub-license, resell, rent, redistribute or otherwise transfer the icon without express written permission from iconmonstr.com --> | ||||
|  | ||||
| <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | ||||
| <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||
| 	 width="32px" height="32px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"> | ||||
| <path id="wireless-6-icon" d="M50,178.599c52.72-52.72,125.552-85.328,206-85.328c80.448,0,153.28,32.608,206,85.328l-35,35 | ||||
| 	c-43.763-43.763-104.221-70.83-171-70.83c-66.78,0-127.237,27.067-171,70.83L50,178.599z M148.196,276.796 | ||||
| 	c27.589-27.59,65.704-44.654,107.804-44.654s80.215,17.064,107.804,44.654l35.935-35.936 | ||||
| 	c-36.785-36.787-87.604-59.539-143.738-59.539s-106.953,22.752-143.738,59.539L148.196,276.796z M211,339.599 | ||||
| 	c11.517-11.517,27.427-18.64,45-18.64s33.483,7.123,45,18.64l35.313-35.312c-20.554-20.554-48.949-33.269-80.313-33.269 | ||||
| 	s-59.76,12.715-80.313,33.269L211,339.599z M256,356.138c-17.284,0-31.299,14.01-31.299,31.297 | ||||
| 	c0,17.285,14.015,31.295,31.299,31.295c17.283,0,31.296-14.01,31.296-31.295C287.296,370.147,273.283,356.138,256,356.138z"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.4 KiB | 
| @@ -16,6 +16,7 @@ | ||||
|           <li data-section="requests">Requests</li> | ||||
|           <li data-section="signed">Signed</li> | ||||
|           <li data-section="revoked">Revoked</li> | ||||
|           <li data-section="config">Configuration</li> | ||||
|           <li data-section="log">Log</li> | ||||
|         </ul> | ||||
|     </nav> | ||||
|   | ||||
| @@ -15,6 +15,7 @@ function onTagClicked() { | ||||
|             url: "/api/tag/" + $(this).attr("data-id"), | ||||
|             dataType: "json", | ||||
|             data: { | ||||
|                 key: $(this).attr("data-key"), | ||||
|                 value: updated | ||||
|             } | ||||
|         }); | ||||
| @@ -40,6 +41,11 @@ function onNewTagClicked() { | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function onTagFilterChanged() { | ||||
|     var key = $(event.target).val(); | ||||
|     console.info("New key is:", key); | ||||
| } | ||||
|  | ||||
| function onLogEntry (e) { | ||||
|     var entry = JSON.parse(e.data); | ||||
|     if ($("#log_level_" + entry.severity).prop("checked")) { | ||||
| @@ -128,7 +134,7 @@ function onTagAdded(e) { | ||||
|         dataType: "json", | ||||
|         success: function(tag, status, xhr) { | ||||
|             // TODO: Deduplicate | ||||
|             $tag = $("<span id=\"tag_" + tag.id + "\" class=\"" + tag.key + " icon tag\" data-id=\""+tag.id+"\">" + tag.value + "</span>"); | ||||
|             $tag = $("<span id=\"tag_" + tag.id + "\" title=\"" + tag.key + "=" + tag.value + "\" class=\"" + tag.key.replace(/\./g, " ") + " icon tag\" data-id=\""+tag.id+"\" data-key=\"" + tag.key + "\">" + tag.value + "</span>"); | ||||
|             $tags = $("#signed_certificates [data-cn='" + tag.cn + "'] .tags").prepend(" "); | ||||
|             $tags = $("#signed_certificates [data-cn='" + tag.cn + "'] .tags").prepend($tag); | ||||
|             $tag.click(onTagClicked); | ||||
| @@ -201,29 +207,6 @@ $(document).ready(function() { | ||||
|                 $("section#" + $(e.target).attr("data-section")).show(); | ||||
|             }); | ||||
|  | ||||
|             /** | ||||
|              * Fetch log entries | ||||
|              */ | ||||
|             $.ajax({ | ||||
|                 method: "GET", | ||||
|                 url: "/api/log/", | ||||
|                 dataType: "json", | ||||
|                 success:function(entries, status, xhr) { | ||||
|                     console.info("Got", entries.length, "log entries"); | ||||
|                     for (var j = 0; j < entries.length; j++) { | ||||
|                         if ($("#log_level_" + entries[j].severity).prop("checked")) { | ||||
|                             $("#log_entries").append(nunjucks.render("logentry.html", { | ||||
|                                 entry: { | ||||
|                                     created: new Date(entries[j].created).toLocaleString("et-EE"), | ||||
|                                     message: entries[j].message, | ||||
|                                     severity: entries[j].severity | ||||
|                                 } | ||||
|                             })); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             /** | ||||
|              * Set up search bar | ||||
|               */ | ||||
| @@ -248,22 +231,35 @@ $(document).ready(function() { | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             /** | ||||
|              * Fetch tags for certificates | ||||
|              */ | ||||
|  | ||||
|  | ||||
|             $.ajax({ | ||||
|                 method: "GET", | ||||
|                 url: "/api/tag/", | ||||
|                 url: "/api/config/", | ||||
|                 dataType: "json", | ||||
|                 success:function(tags, status, xhr) { | ||||
|                     console.info("Got", tags.length, "tags"); | ||||
|                     for (var j = 0; j < tags.length; j++) { | ||||
|                         // TODO: Deduplicate | ||||
|                         $tag = $("<span id=\"tag_" + tags[j].id + "\" class=\"" + tags[j].key + " icon tag\" data-id=\""+tags[j].id+"\">" + tags[j].value + "</span>"); | ||||
|                         $tags = $("#signed_certificates [data-cn='" + tags[j].cn + "'] .tags").prepend(" "); | ||||
|                         $tags = $("#signed_certificates [data-cn='" + tags[j].cn + "'] .tags").prepend($tag); | ||||
|                         $tag.click(onTagClicked); | ||||
|                     } | ||||
|                 success: function(configuration, status, xhr) { | ||||
|                     console.info("Appending " + configuration.length + " configuration items"); | ||||
|                     $("#config").html(nunjucks.render('configuration.html', { configuration:configuration})); | ||||
|                     /** | ||||
|                      * Fetch tags for certificates | ||||
|                      */ | ||||
|                     $.ajax({ | ||||
|                         method: "GET", | ||||
|                         url: "/api/tag/", | ||||
|                         dataType: "json", | ||||
|                         success:function(tags, status, xhr) { | ||||
|                             console.info("Got", tags.length, "tags"); | ||||
|                             for (var j = 0; j < tags.length; j++) { | ||||
|                                 // TODO: Deduplicate | ||||
|                                 $tag = $("<span id=\"tag_" + tags[j].id + "\"  title=\"" + tags[j].key + "=" + tags[j].value + "\" class=\"" + tags[j].key.replace(/\./g, " ") + " icon tag\" data-id=\""+tags[j].id+"\" data-key=\"" + tags[j].key + "\">" + tags[j].value + "</span>"); | ||||
|                                 console.info("Inserting tag", tags[j], $tag); | ||||
|                                 $tags = $("#signed_certificates [data-cn='" + tags[j].cn + "'] .tags").prepend(" "); | ||||
|                                 $tags = $("#signed_certificates [data-cn='" + tags[j].cn + "'] .tags").prepend($tag); | ||||
|                                 $tag.click(onTagClicked); | ||||
|                                 $("#tags_autocomplete").prepend("<option value=\"" + tags[j].id + "\">" + tags[j].key + "='" + tags[j].value + "'</option>"); | ||||
|                             } | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
| @@ -293,6 +289,29 @@ $(document).ready(function() { | ||||
|  | ||||
|                 } | ||||
|             }); | ||||
|             return; | ||||
|             /** | ||||
|              * Fetch log entries | ||||
|              */ | ||||
|             $.ajax({ | ||||
|                 method: "GET", | ||||
|                 url: "/api/log/", | ||||
|                 dataType: "json", | ||||
|                 success:function(entries, status, xhr) { | ||||
|                     console.info("Got", entries.length, "log entries"); | ||||
|                     for (var j = 0; j < entries.length; j++) { | ||||
|                         if ($("#log_level_" + entries[j].severity).prop("checked")) { | ||||
|                             $("#log_entries").append(nunjucks.render("logentry.html", { | ||||
|                                 entry: { | ||||
|                                     created: new Date(entries[j].created).toLocaleString("et-EE"), | ||||
|                                     message: entries[j].message, | ||||
|                                     severity: entries[j].severity | ||||
|                                 } | ||||
|                             })); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| <li id="request_{{ request.sha256sum }}" class="filterable"> | ||||
| <li id="request_{{ request.common_name }}" class="filterable"> | ||||
|  | ||||
| <a class="button icon download" href="/api/request/{{request.common_name}}/">Fetch</a> | ||||
| {% if request.signable %} | ||||
|   | ||||
| @@ -11,6 +11,8 @@ | ||||
|     <div class="email">{% include 'img/iconmonstr-email-2-icon.svg' %} {{ certificate.email_address }}</div> | ||||
|     {% endif %} | ||||
|  | ||||
|     {# | ||||
|  | ||||
|     <div class="monospace"> | ||||
|     {% include 'img/iconmonstr-key-2-icon.svg' %} | ||||
|     <span title="SHA-256 of public key"> | ||||
| @@ -25,13 +27,12 @@ | ||||
|     {{certificate.key_usage}} | ||||
|     </div> | ||||
|  | ||||
|     #} | ||||
|  | ||||
|     <div class="tags"> | ||||
|       <select class="icon tag" data-cn="{{ certificate.common_name }}" onChange="onNewTagClicked();"> | ||||
|         <option value="">Add tag...</option> | ||||
|         <option value="location">Location</option> | ||||
|         <option value="phone">Phone</option> | ||||
|         <option value="room">Room</option> | ||||
|         <option value="serial">Product serial</option> | ||||
|         {% include 'tagtypes.html' %} | ||||
|       </select> | ||||
|     </div> | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| {% if lease.released %} | ||||
| Last seen {{ lease.released }} at {{ lease.address }} | ||||
| {% else %} | ||||
| Online since {{ lease.acquired }} at {{ lease.address }} | ||||
| Online since {{ lease.acquired }} at <a target="{{ lease.address }}" href="http://{{ lease.address }}">{{ lease.address }}</a> | ||||
| {% endif %} | ||||
| {% else %} | ||||
| Not seen | ||||
|   | ||||
| Before Width: | Height: | Size: 428 B After Width: | Height: | Size: 498 B | 
							
								
								
									
										11
									
								
								certidude/static/tagtypes.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								certidude/static/tagtypes.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <option value="location">Location</option> | ||||
| <option value="phone">Phone</option> | ||||
| <option value="room">Room</option> | ||||
| <option value="serial">Product serial</option> | ||||
|  | ||||
| <option value="wireless.protected.password">Protected wireless network password</option> | ||||
| <option value="wireless.protected.name">Protected wireless network name</option> | ||||
| <option value="wireless.public.name">Public wireless network name</option> | ||||
| <option value="wireless.channela">5GHz channel number</option> | ||||
| <option value="wireless.channelb">2.4GHz channel number</option> | ||||
| <option value="usb.approved">Approved USB device</option> | ||||
		Reference in New Issue
	
	Block a user