mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-30 00:49:19 +00:00 
			
		
		
		
	api: Added signed certificate tagging mechanism
This commit is contained in:
		| @@ -79,6 +79,7 @@ def certidude_app(): | ||||
|     from .lease import LeaseResource | ||||
|     from .whois import WhoisResource | ||||
|     from .log import LogResource | ||||
|     from .tag import TagResource, TagDetailResource | ||||
|  | ||||
|     app = falcon.API() | ||||
|  | ||||
| @@ -91,6 +92,8 @@ def certidude_app(): | ||||
|     app.add_route("/api/request/{cn}/", RequestDetailResource()) | ||||
|     app.add_route("/api/request/", RequestListResource()) | ||||
|     app.add_route("/api/log/", LogResource()) | ||||
|     app.add_route("/api/tag/", TagResource()) | ||||
|     app.add_route("/api/tag/{identifier}/", TagDetailResource()) | ||||
|     app.add_route("/api/", SessionResource()) | ||||
|  | ||||
|     # Gateway API calls, should this be moved to separate project? | ||||
| @@ -128,12 +131,13 @@ def certidude_app(): | ||||
|         logger.addHandler(push_handler) | ||||
|  | ||||
|  | ||||
|     logging.getLogger("cli").info("Started Certidude at %s", socket.getaddrinfo(socket.gethostname(), 0, flags=socket.AI_CANONNAME)[0][3]) | ||||
|     logging.getLogger("cli").debug("Started Certidude at %s", | ||||
|         socket.getaddrinfo(socket.gethostname(), 0, flags=socket.AI_CANONNAME)[0][3]) | ||||
|  | ||||
|     import atexit | ||||
|  | ||||
|     def exit_handler(): | ||||
|         logging.getLogger("cli").info("Shutting down Certidude") | ||||
|         logging.getLogger("cli").debug("Shutting down Certidude") | ||||
|  | ||||
|     atexit.register(exit_handler) | ||||
|  | ||||
|   | ||||
| @@ -73,7 +73,7 @@ class RequestListResource(object): | ||||
|             raise falcon.HTTPConflict( | ||||
|                 "CSR with such CN already exists", | ||||
|                 "Will not overwrite existing certificate signing request, explicitly delete CSR and try again") | ||||
|         push.publish("request_submitted", csr.common_name) | ||||
|         push.publish("request-submitted", csr.common_name) | ||||
|  | ||||
|         # Wait the certificate to be signed if waiting is requested | ||||
|         if req.get_param("wait"): | ||||
|   | ||||
| @@ -1,9 +1,12 @@ | ||||
|  | ||||
| import falcon | ||||
| import logging | ||||
| from certidude import authority | ||||
| from certidude.auth import login_required, authorize_admin | ||||
| from certidude.decorators import serialize | ||||
|  | ||||
| logger = logging.getLogger("api") | ||||
|  | ||||
| class SignedCertificateListResource(object): | ||||
|     @serialize | ||||
|     @authorize_admin | ||||
| @@ -26,13 +29,17 @@ class SignedCertificateDetailResource(object): | ||||
|     @serialize | ||||
|     def on_get(self, req, resp, cn): | ||||
|         try: | ||||
|             logger.info("Served certificate %s to %s", cn, req.env["REMOTE_ADDR"]) | ||||
|             resp.set_header("Content-Disposition", "attachment; filename=%s.crt" % cn) | ||||
|             return authority.get_signed(cn) | ||||
|         except FileNotFoundError: | ||||
|             logger.warning("Failed to serve non-existant certificate %s to %s", cn, req.env["REMOTE_ADDR"]) | ||||
|             resp.body = "No certificate CN=%s found" % cn | ||||
|             raise falcon.HTTPNotFound() | ||||
|  | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     def on_delete(self, req, resp, cn): | ||||
|         logger.info("Revoked certificate %s by %s from %s", cn, req.context["user"], req.env["REMOTE_ADDR"]) | ||||
|         authority.revoke_certificate(cn) | ||||
|  | ||||
|   | ||||
							
								
								
									
										90
									
								
								certidude/api/tag.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								certidude/api/tag.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
|  | ||||
| import falcon | ||||
| import logging | ||||
| from certidude import config | ||||
| from certidude.auth import login_required, authorize_admin | ||||
| from certidude.decorators import serialize | ||||
|  | ||||
| logger = logging.getLogger("api") | ||||
|  | ||||
| class TagResource(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("select * from tag") | ||||
|  | ||||
|         def g(): | ||||
|             for row in cursor: | ||||
|                 yield row | ||||
|             cursor.close() | ||||
|             conn.close() | ||||
|         return tuple(g()) | ||||
|  | ||||
|     @serialize | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     def on_post(self, req, resp): | ||||
|         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") | ||||
|         cursor.execute( | ||||
|             "insert into tag (`cn`, `key`, `value`) values (%s, %s, %s)", args) | ||||
|         push.publish("tag-added", str(cursor.lastrowid)) | ||||
|         logger.debug("Tag cn=%s, key=%s, value=%s added" % args) | ||||
|         conn.commit() | ||||
|         cursor.close() | ||||
|         conn.close() | ||||
|  | ||||
|  | ||||
| class TagDetailResource(object): | ||||
|     @serialize | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     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,)) | ||||
|         for row in cursor: | ||||
|             cursor.close() | ||||
|             conn.close() | ||||
|             return row | ||||
|         cursor.close() | ||||
|         conn.close() | ||||
|         raise falcon.HTTPNotFound() | ||||
|  | ||||
|     @serialize | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     def on_put(self, req, resp, identifier): | ||||
|         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)) | ||||
|         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) | ||||
|  | ||||
|  | ||||
|     @serialize | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     def on_delete(self, req, resp, identifier): | ||||
|         from certidude import push | ||||
|         conn = config.DATABASE_POOL.get_connection() | ||||
|         cursor = conn.cursor() | ||||
|         cursor.execute("delete from tag where tag.id = %s", (identifier,)) | ||||
|         conn.commit() | ||||
|         cursor.close() | ||||
|         conn.close() | ||||
|         push.publish("tag-removed", identifier) | ||||
|         logger.debug("Tag %s removed" % identifier) | ||||
|  | ||||
|  | ||||
| @@ -1,8 +1,10 @@ | ||||
|  | ||||
| import falcon | ||||
| import ipaddress | ||||
| from datetime import datetime | ||||
| from certidude import config | ||||
| from certidude.decorators import serialize | ||||
| from certidude.api.lease import parse_dn | ||||
|  | ||||
| def address_to_identity(cnx, addr): | ||||
|     """ | ||||
| @@ -10,19 +12,19 @@ def address_to_identity(cnx, addr): | ||||
|     """ | ||||
|  | ||||
|     SQL_LEASES = """ | ||||
|         SELECT | ||||
|         select | ||||
|             acquired, | ||||
|             released, | ||||
|             identities.data as identity | ||||
|         FROM | ||||
|         from | ||||
|             addresses | ||||
|         RIGHT JOIN | ||||
|         right join | ||||
|             identities | ||||
|         ON | ||||
|         on | ||||
|             identities.id = addresses.identity | ||||
|         WHERE | ||||
|             address = %s AND | ||||
|             released IS NOT NULL | ||||
|         where | ||||
|             address = %s and | ||||
|             released is not null | ||||
|     """ | ||||
|  | ||||
|     cursor = cnx.cursor() | ||||
| @@ -31,6 +33,7 @@ def address_to_identity(cnx, addr): | ||||
|  | ||||
|     for acquired, released, identity in cursor: | ||||
|         return { | ||||
|             "address": addr, | ||||
|             "acquired": datetime.utcfromtimestamp(acquired), | ||||
|             "identity": parse_dn(bytes(identity)) | ||||
|         } | ||||
|   | ||||
| @@ -29,7 +29,7 @@ def publish_certificate(func): | ||||
|             click.echo("Publishing certificate at %s, waiting for response..." % url) | ||||
|             response = urllib.request.urlopen(notification) | ||||
|             response.read() | ||||
|             push.publish("request_signed", csr.common_name) | ||||
|             push.publish("request-signed", csr.common_name) | ||||
|         return cert | ||||
|     return wrapped | ||||
|  | ||||
| @@ -93,7 +93,7 @@ def revoke_certificate(common_name): | ||||
|     cert = get_signed(common_name) | ||||
|     revoked_filename = os.path.join(config.REVOKED_DIR, "%s.pem" % cert.serial_number) | ||||
|     os.rename(cert.path, revoked_filename) | ||||
|     push.publish("certificate_revoked", cert.fingerprint()) | ||||
|     push.publish("certificate-revoked", cert.fingerprint()) | ||||
|  | ||||
|  | ||||
| def list_requests(directory=config.REQUESTS_DIR): | ||||
| @@ -141,7 +141,7 @@ def delete_request(common_name): | ||||
|     os.unlink(path) | ||||
|  | ||||
|     # Publish event at CA channel | ||||
|     push.publish("request_deleted", request_sha1sum) | ||||
|     push.publish("request-deleted", request_sha1sum) | ||||
|  | ||||
|     # Write empty certificate to long-polling URL | ||||
|     url = config.PUSH_PUBLISH % request_sha1sum | ||||
|   | ||||
| @@ -13,7 +13,6 @@ import signal | ||||
| import socket | ||||
| import subprocess | ||||
| import sys | ||||
| from certidude import authority | ||||
| from certidude.signer import SignServer | ||||
| from certidude.common import expand_paths | ||||
| from datetime import datetime | ||||
| @@ -165,7 +164,7 @@ def certidude_setup_client(quiet, **kwargs): | ||||
|  | ||||
| @click.command("server", help="Set up OpenVPN server") | ||||
| @click.argument("url") | ||||
| @click.option("--common-name", "-cn", default=HOSTNAME, help="Common name, %s by default" % HOSTNAME) | ||||
| @click.option("--common-name", "-cn", default=FQDN, help="Common name, %s by default" % FQDN) | ||||
| @click.option("--org-unit", "-ou", help="Organizational unit") | ||||
| @click.option("--email-address", "-m", default=EMAIL, help="E-mail associated with the request, '%s' by default" % EMAIL) | ||||
| @click.option("--subnet", "-s", default="192.168.33.0/24", type=ip_network, help="OpenVPN subnet, 192.168.33.0/24 by default") | ||||
| @@ -252,7 +251,7 @@ def certidude_setup_openvpn_server(url, config, subnet, route, email_address, co | ||||
| @click.option("--authority-path", "-a", default="ca.crt", help="Certificate authority certificate path, ca.crt relative to --dir by default") | ||||
| @expand_paths() | ||||
| def certidude_setup_openvpn_client(url, config, email_address, common_name, org_unit, directory, key_path, request_path, certificate_path, authority_path, proto, remote): | ||||
|  | ||||
|     from certidude.helpers import certidude_request_certificate | ||||
|     retval = certidude_request_certificate( | ||||
|         url, | ||||
|         key_path, | ||||
| @@ -280,7 +279,7 @@ def certidude_setup_openvpn_client(url, config, email_address, common_name, org_ | ||||
|  | ||||
| @click.command("server", help="Set up strongSwan server") | ||||
| @click.argument("url") | ||||
| @click.option("--common-name", "-cn", default=HOSTNAME, help="Common name, %s by default" % HOSTNAME) | ||||
| @click.option("--common-name", "-cn", default=FQDN, help="Common name, %s by default" % FQDN) | ||||
| @click.option("--org-unit", "-ou", help="Organizational unit") | ||||
| @click.option("--fqdn", "-f", default=FQDN, help="Fully qualified hostname associated with the certificate") | ||||
| @click.option("--email-address", "-m", default=EMAIL, help="E-mail associated with the request, %s by default" % EMAIL) | ||||
| @@ -302,13 +301,17 @@ def certidude_setup_openvpn_client(url, config, email_address, common_name, org_ | ||||
| @click.option("--authority-path", "-ca", default="cacerts/ca.pem", help="Certificate authority certificate path, cacerts/ca.pem by default") | ||||
| @expand_paths() | ||||
| def certidude_setup_strongswan_server(url, config, secrets, subnet, route, email_address, common_name, org_unit, directory, key_path, request_path, certificate_path, authority_path, local, fqdn): | ||||
|     if "." not in common_name: | ||||
|         raise ValueError("Hostname has to be fully qualified!") | ||||
|     if not local: | ||||
|         raise ValueError("Please specify local IP address") | ||||
|  | ||||
|     if not os.path.exists(certificate_path): | ||||
|         click.echo("As strongSwan server certificate needs specific key usage extensions please") | ||||
|         click.echo("use following command to sign on Certidude server instead of web interface:") | ||||
|         click.echo() | ||||
|         click.echo("  certidude sign %s" % common_name) | ||||
|  | ||||
|     from certidude.helpers import certidude_request_certificate | ||||
|     retval = certidude_request_certificate( | ||||
|         url, | ||||
|         key_path, | ||||
| @@ -368,7 +371,7 @@ def certidude_setup_strongswan_server(url, config, secrets, subnet, route, email | ||||
| @click.option("--authority-path", "-ca", default="cacerts/ca.pem", help="Certificate authority certificate path, cacerts/ca.pem by default") | ||||
| @expand_paths() | ||||
| def certidude_setup_strongswan_client(url, config, secrets, email_address, common_name, org_unit, directory, key_path, request_path, certificate_path, authority_path, remote, auto, dpdaction): | ||||
|  | ||||
|     from certidude.helpers import certidude_request_certificate | ||||
|     retval = certidude_request_certificate( | ||||
|         url, | ||||
|         key_path, | ||||
| @@ -409,7 +412,7 @@ def certidude_setup_strongswan_client(url, config, secrets, email_address, commo | ||||
| @click.option("--authority-path", "-ca", default="cacerts/ca.pem", help="Certificate authority certificate path, cacerts/ca.pem by default") | ||||
| @expand_paths() | ||||
| def certidude_setup_strongswan_networkmanager(url, email_address, common_name, org_unit, directory, key_path, request_path, certificate_path, authority_path, remote): | ||||
|  | ||||
|     from certidude.helpers import certidude_request_certificate | ||||
|     retval = certidude_request_certificate( | ||||
|         url, | ||||
|         key_path, | ||||
| @@ -685,6 +688,8 @@ def certidude_list(verbose, show_key_type, show_extensions, show_path, show_sign | ||||
|     #   y - not valid yet | ||||
|     #   r - revoked | ||||
|  | ||||
|     from certidude import authority | ||||
|  | ||||
|     from pycountry import countries | ||||
|     def dump_common(j): | ||||
|  | ||||
| @@ -796,6 +801,7 @@ def certidude_list(verbose, show_key_type, show_extensions, show_path, show_sign | ||||
| @click.option("--overwrite", "-o", default=False, is_flag=True, help="Revoke valid certificate with same CN") | ||||
| @click.option("--lifetime", "-l", help="Lifetime") | ||||
| def certidude_sign(common_name, overwrite, lifetime): | ||||
|     from certidude import authority | ||||
|     request = authority.get_request(common_name) | ||||
|     if request.signable: | ||||
|         # Sign via signer process | ||||
|   | ||||
| @@ -1,4 +1,7 @@ | ||||
|  | ||||
| import os | ||||
| import click | ||||
|  | ||||
| def expand_paths(): | ||||
|     """ | ||||
|     Prefix '..._path' keyword arguments of target function with 'directory' keyword argument | ||||
|   | ||||
| @@ -2,11 +2,9 @@ | ||||
| import click | ||||
| import os | ||||
| import urllib.request | ||||
| from certidude import config | ||||
| from certidude.wrappers import Certificate, Request | ||||
| from OpenSSL import crypto | ||||
|  | ||||
|  | ||||
|  | ||||
| def certidude_request_certificate(url, key_path, request_path, certificate_path, authority_path, common_name, org_unit, email_address=None, given_name=None, surname=None, autosign=False, wait=False, key_usage=None, extended_key_usage=None, ip_address=None, dns=None): | ||||
|     """ | ||||
|     Exchange CSR for certificate using Certidude HTTP API server | ||||
|   | ||||
| @@ -1,19 +1,19 @@ | ||||
|  | ||||
| <section id="about"> | ||||
| <p>Hi {{session.username}},</p> | ||||
|  | ||||
| <p>Request submission is allowed from: {% if session.request_subnets %}{% for i in session.request_subnets %}{{ i }} {% endfor %}{% else %}anywhere{% endif %}</p> | ||||
| <p>Autosign is allowed from: {% if session.autosign_subnets %}{% for i in session.autosign_subnets %}{{ i }} {% endfor %}{% else %}nowhere{% endif %}</p> | ||||
| <p>Authority administration is allowed from: {% if session.admin_subnets %}{% for i in session.admin_subnets %}{{ i }} {% endfor %}{% else %}anywhere{% endif %} | ||||
| <p>Authority administration allowed for: {% for i in session.admin_users %}{{ i }} {% endfor %}</p> | ||||
|  | ||||
| </section> | ||||
| {% set s = session.certificate.identity %} | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| <div id="requests"> | ||||
| <section id="requests"> | ||||
|     <h1>Pending requests</h1> | ||||
|  | ||||
|  | ||||
|     <ul id="pending_requests"> | ||||
|         {% for request in session.requests %} | ||||
|              {% include "request.html" %} | ||||
| @@ -23,19 +23,20 @@ | ||||
|             <pre>certidude setup client {{session.common_name}}</pre> | ||||
|         </li> | ||||
|     </ul> | ||||
| </div> | ||||
| </section> | ||||
|  | ||||
|  | ||||
| <div id="signed"> | ||||
| <section id="signed"> | ||||
|     <h1>Signed certificates</h1> | ||||
|     <input id="search" type="search" class="icon search"> | ||||
|     <ul id="signed_certificates"> | ||||
|         {% for certificate in session.signed | sort | reverse %} | ||||
|             {% include "signed.html" %} | ||||
| 	    {% endfor %} | ||||
|     </ul> | ||||
| </div> | ||||
| </section> | ||||
|  | ||||
| <div id="log"> | ||||
| <section id="log"> | ||||
|     <h1>Log</h1> | ||||
|     <p> | ||||
|         <input id="log_level_critical" type="checkbox" checked/> <label for="log_level_critical">Critical</label> | ||||
| @@ -46,9 +47,9 @@ | ||||
|     </p> | ||||
|     <ul id="log_entries"> | ||||
|     </ul> | ||||
| </div> | ||||
| </section> | ||||
|  | ||||
| <div id="revoked"> | ||||
| <section id="revoked"> | ||||
|     <h1>Revoked certificates</h1> | ||||
|     <p>To fetch certificate revocation list:</p> | ||||
|     <pre> | ||||
| @@ -72,4 +73,4 @@ | ||||
|             <li>Great job! No certificate signing requests to sign.</li> | ||||
| 	    {% endfor %} | ||||
|     </ul> | ||||
| </div> | ||||
| </section> | ||||
|   | ||||
| @@ -119,7 +119,7 @@ h2 svg { | ||||
|     top: 16px; | ||||
| } | ||||
|  | ||||
| p, td, footer, li, button, input { | ||||
| p, td, footer, li, button, input, select { | ||||
|     font-family: 'PT Sans Narrow'; | ||||
|     font-size: 14pt; | ||||
| } | ||||
| @@ -159,6 +159,7 @@ pre { | ||||
|     display: inline; | ||||
|     margin: 1mm 5mm 1mm 0; | ||||
|     line-height: 200%; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| .icon{ | ||||
| @@ -170,24 +171,49 @@ pre { | ||||
|     text-decoration: none; | ||||
| } | ||||
|  | ||||
| li span.icon { | ||||
| #log_entries li span.icon { | ||||
|     background-size: 32px; | ||||
|     padding-left: 42px; | ||||
|     padding-top: 2px; | ||||
|     padding-bottom: 2px; | ||||
| } | ||||
|  | ||||
| .tags .tag { | ||||
|     display: inline; | ||||
|     background-size: 32px; | ||||
|     padding-top: 4px; | ||||
|     padding-bottom: 4px; | ||||
|     padding-right: 1em; | ||||
|     line-height: 200%; | ||||
|     cursor: pointer; | ||||
|     white-space: nowrap; | ||||
| } | ||||
|  | ||||
| select { | ||||
|    -webkit-appearance: none; | ||||
|    -moz-appearance: none; | ||||
|    appearance: none; | ||||
|    background: none; | ||||
|    border: none; | ||||
|    box-sizing: border-box; | ||||
|  | ||||
| } | ||||
|  | ||||
| .icon.tag { background-image: url("../img/iconmonstr-tag-2-icon.svg"); } | ||||
|  | ||||
| .icon.critical { background-image: url("../img/iconmonstr-error-4-icon.svg"); } | ||||
| .icon.error { background-image: url("../img/iconmonstr-error-4-icon.svg"); } | ||||
| .icon.warning { background-image: url("../img/iconmonstr-warning-6-icon.svg"); } | ||||
| .icon.info { background-image: url("../img/iconmonstr-info-6-icon.svg"); } | ||||
|  | ||||
|  | ||||
| .icon.revoke { background-image: url("../img/iconmonstr-x-mark-5-icon.svg"); } | ||||
| .icon.download { background-image: url("../img/iconmonstr-download-12-icon.svg"); } | ||||
| .icon.sign { background-image: url("../img/iconmonstr-pen-10-icon.svg"); } | ||||
| .icon.search { background-image: url("../img/iconmonstr-magnifier-4-icon.svg"); } | ||||
|  | ||||
| .icon.phone { background-image: url("../img/iconmonstr-mobile-phone-6-icon.svg"); } | ||||
| .icon.location { background-image: url("../img/iconmonstr-compass-7-icon.svg"); } | ||||
| .icon.room { background-image: url("../img/iconmonstr-home-4-icon.svg"); } | ||||
|  | ||||
| /* Make sure this is the last one */ | ||||
| .icon.busy{background-image:url("https://software.opensuse.org/assets/ajax-loader-ea46060b6c9f42822a3d58d075c83ea2.gif");} | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 38 KiB | 
							
								
								
									
										19
									
								
								certidude/static/img/iconmonstr-compass-7-icon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								certidude/static/img/iconmonstr-compass-7-icon.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| <?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="compass-7-icon" d="M256,90c91.74,0,166,74.243,166,166c0,91.741-74.245,166-166,166c-91.741,0-166-74.245-166-166 | ||||
| 	C90,164.259,164.244,90,256,90 M256,50C142.229,50,50,142.229,50,256s92.229,206,206,206s206-92.229,206-206S369.771,50,256,50z | ||||
| 	 M197.686,216.466l-28.355-47.135l47.225,28.408C209.145,202.733,202.736,209.099,197.686,216.466z M296.709,198.612 | ||||
| 	c6.459,4.562,12.119,10.179,16.729,16.602l29.232-45.883L296.709,198.612z M198.312,297.179l-28.982,45.492l45.416-28.936 | ||||
| 	C208.398,309.163,202.838,303.563,198.312,297.179z M296.018,314.604l46.652,28.066l-28.117-46.74 | ||||
| 	C309.596,303.253,303.299,309.593,296.018,314.604z M400.199,256.001l-99.238,21.998c-4.369,8.913-11.312,16.328-19.859,21.295 | ||||
| 	L256,400.2l-25.104-100.908c-8.545-4.965-15.488-12.381-19.857-21.293l-99.238-21.998l99.238-21.999 | ||||
| 	c4.369-8.913,11.312-16.328,19.857-21.294L256,111.8l25.104,100.908c8.545,4.966,15.488,12.381,19.857,21.294L400.199,256.001z | ||||
| 	 M278.406,256c0-12.374-10.031-22.407-22.406-22.407S233.592,243.626,233.592,256c0,12.376,10.033,22.408,22.408,22.408 | ||||
| 	S278.406,268.376,278.406,256z"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										19
									
								
								certidude/static/img/iconmonstr-home-4-icon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								certidude/static/img/iconmonstr-home-4-icon.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| <?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="home-4-icon" d="M419.492,275.815v166.213H300.725v-90.33h-89.451v90.33H92.507V275.815H50L256,69.972l206,205.844H419.492 | ||||
|  | ||||
| 	z M394.072,88.472h-47.917v38.311l47.917,48.023V88.472z"/> | ||||
|  | ||||
| </svg> | ||||
|  | ||||
| After Width: | Height: | Size: 836 B | 
							
								
								
									
										17
									
								
								certidude/static/img/iconmonstr-mobile-phone-6-icon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								certidude/static/img/iconmonstr-mobile-phone-6-icon.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| <?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="mobile-phone-6-icon" d="M139.59,131.775c-13.807,0-25,11.197-25,25.01V436.99c0,13.812,11.193,25.01,25,25.01h150.49 | ||||
| 	c13.807,0,25-11.198,25-25.01V156.766c0-13.802-11.186-24.99-24.98-24.99H139.59z M179.832,416.514h-30.996v-24.51h30.996V416.514z | ||||
| 	 M179.832,372.203h-30.996v-24.51h30.996V372.203z M230.334,416.514h-30.996v-24.51h30.996V416.514z M230.334,372.203h-30.996 | ||||
| 	v-24.51h30.996V372.203z M280.836,416.514H249.84v-24.51h30.996V416.514z M280.836,372.203H249.84v-24.51h30.996V372.203z | ||||
| 	 M280.836,312.887h-132V183.226h132V312.887z M283.451,111.408c13.445-0.01,26.9,5.113,37.164,15.369s15.4,23.699,15.41,37.147 | ||||
| 	h22.121c-0.012-19.113-7.312-38.231-21.898-52.805c-14.588-14.573-33.691-21.854-52.797-21.842V111.408z M283.451,72.682 | ||||
| 	c23.354-0.015,46.691,8.882,64.52,26.696c17.828,17.812,26.75,41.187,26.766,64.547h22.674c-0.02-29.166-11.16-58.358-33.418-80.597 | ||||
| 	C341.734,61.089,312.605,49.982,283.451,50V72.682z"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										25
									
								
								certidude/static/img/iconmonstr-tag-2-icon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								certidude/static/img/iconmonstr-tag-2-icon.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| <?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="tag-2-icon" d="M234.508,50L50.068,50.262l-0.004,184.311L277.365,462l184.57-184.57L234.508,50z M114.877,167.365 | ||||
|  | ||||
| 	c-15.027-15.027-15.027-39.395,0-54.424c15.029-15.029,39.396-15.029,54.426,0s15.029,39.396,0,54.424 | ||||
|  | ||||
| 	C154.273,182.395,129.906,182.395,114.877,167.365z M242.316,327.94l-76.225-76.226l17.678-17.678l76.225,76.226L242.316,327.94z | ||||
|  | ||||
| 	 M317.609,335.887L199.764,218.041l17.678-17.678l117.846,117.846L317.609,335.887z M351.818,301.678L233.973,183.832l17.678-17.678 | ||||
|  | ||||
| 	L369.496,284L351.818,301.678z"/> | ||||
|  | ||||
| </svg> | ||||
|  | ||||
| After Width: | Height: | Size: 1.1 KiB | 
| @@ -11,14 +11,14 @@ | ||||
|     <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"> | ||||
| </head> | ||||
| <body> | ||||
|     <div id="menu"> | ||||
|     <nav id="menu"> | ||||
|         <ul class="container"> | ||||
|           <li>Requests</li> | ||||
|           <li>Signed</li> | ||||
|           <li>Revoked</li> | ||||
|           <li>Log</li> | ||||
|           <li data-section="requests">Requests</li> | ||||
|           <li data-section="signed">Signed</li> | ||||
|           <li data-section="revoked">Revoked</li> | ||||
|           <li data-section="log">Log</li> | ||||
|         </ul> | ||||
|     </div> | ||||
|     </nav> | ||||
|     <div id="container" class="container"> | ||||
|         Loading certificate authority... | ||||
|     </div> | ||||
|   | ||||
| @@ -1,3 +1,159 @@ | ||||
|  | ||||
| function onTagClicked() { | ||||
|     var value = $(this).html(); | ||||
|     var updated = prompt("Enter new tag or clear to remove the tag", value); | ||||
|     if (updated == "") { | ||||
|         $(this).addClass("busy"); | ||||
|         $.ajax({ | ||||
|             method: "DELETE", | ||||
|             url: "/api/tag/" + $(this).attr("data-id") | ||||
|         }); | ||||
|  | ||||
|     } else if (updated && updated != value) { | ||||
|         $.ajax({ | ||||
|             method: "PUT", | ||||
|             url: "/api/tag/" + $(this).attr("data-id"), | ||||
|             dataType: "json", | ||||
|             data: { | ||||
|                 value: updated | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function onNewTagClicked() { | ||||
|     var cn = $(event.target).attr("data-cn"); | ||||
|     var key = $(event.target).val(); | ||||
|     $(event.target).val(""); | ||||
|     var value = prompt("Enter new " + key + " tag for " + cn); | ||||
|     if (!value) return; | ||||
|     if (value.length == 0) return; | ||||
|     $.ajax({ | ||||
|         method: "POST", | ||||
|         url: "/api/tag/", | ||||
|         dataType: "json", | ||||
|         data: { | ||||
|             cn: cn, | ||||
|             value: value, | ||||
|             key: key | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function onLogEntry (e) { | ||||
|     var entry = JSON.parse(e.data); | ||||
|     if ($("#log_level_" + entry.severity).prop("checked")) { | ||||
|         console.info("Received log entry:", entry); | ||||
|         $("#log_entries").prepend(nunjucks.render("logentry.html", { | ||||
|             entry: { | ||||
|                 created: new Date(entry.created).toLocaleString(), | ||||
|                 message: entry.message, | ||||
|                 severity: entry.severity | ||||
|             } | ||||
|         })); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| function onRequestSubmitted(e) { | ||||
|     console.log("Request submitted:", e.data); | ||||
|     $.ajax({ | ||||
|         method: "GET", | ||||
|         url: "/api/request/" + e.data + "/", | ||||
|         dataType: "json", | ||||
|         success: function(request, status, xhr) { | ||||
|             console.info(request); | ||||
|             $("#pending_requests").prepend( | ||||
|                 nunjucks.render('request.html', { request: request })); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function onRequestDeleted(e) { | ||||
|     console.log("Removing deleted request #" + e.data); | ||||
|     $("#request_" + e.data).remove(); | ||||
| } | ||||
|  | ||||
| function onClientUp(e) { | ||||
|     console.log("Adding security association:" + e.data); | ||||
|     var lease = JSON.parse(e.data); | ||||
|     var $status = $("#signed_certificates [data-dn='" + lease.identity + "'] .status"); | ||||
|     $status.html(nunjucks.render('status.html', { | ||||
|         lease: { | ||||
|             address: lease.address, | ||||
|             identity: lease.identity, | ||||
|             acquired: new Date(), | ||||
|             released: null | ||||
|         }})); | ||||
| } | ||||
|  | ||||
| function onClientDown(e) { | ||||
|     console.log("Removing security association:" + e.data); | ||||
|     var lease = JSON.parse(e.data); | ||||
|     var $status = $("#signed_certificates [data-dn='" + lease.identity + "'] .status"); | ||||
|     $status.html(nunjucks.render('status.html', { | ||||
|         lease: { | ||||
|             address: lease.address, | ||||
|             identity: lease.identity, | ||||
|             acquired: null, | ||||
|             released: new Date() | ||||
|         }})); | ||||
| } | ||||
|  | ||||
| function onRequestSigned(e) { | ||||
|     console.log("Request signed:", e.data); | ||||
|     $("#request_" + e.data).slideUp("normal", function() { $(this).remove(); }); | ||||
|  | ||||
|     $.ajax({ | ||||
|         method: "GET", | ||||
|         url: "/api/signed/" + e.data + "/", | ||||
|         dataType: "json", | ||||
|         success: function(certificate, status, xhr) { | ||||
|             console.info(certificate); | ||||
|             $("#signed_certificates").prepend( | ||||
|                 nunjucks.render('signed.html', { certificate: certificate })); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function onCertificateRevoked(e) { | ||||
|     console.log("Removing revoked certificate #" + e.data); | ||||
|     $("#certificate_" + e.data).slideUp("normal", function() { $(this).remove(); }); | ||||
| } | ||||
|  | ||||
| function onTagAdded(e) { | ||||
|     console.log("Tag added #" + e.data); | ||||
|     $.ajax({ | ||||
|         method: "GET", | ||||
|         url: "/api/tag/" + e.data + "/", | ||||
|         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>"); | ||||
|             $tags = $("#signed_certificates [data-cn='" + tag.cn + "'] .tags").prepend(" "); | ||||
|             $tags = $("#signed_certificates [data-cn='" + tag.cn + "'] .tags").prepend($tag); | ||||
|             $tag.click(onTagClicked); | ||||
|         } | ||||
|     }) | ||||
| } | ||||
|  | ||||
| function onTagRemoved(e) { | ||||
|     console.log("Tag removed #" + e.data); | ||||
|     $("#tag_" + e.data).remove(); | ||||
| } | ||||
|  | ||||
| function onTagUpdated(e) { | ||||
|     console.log("Tag updated #" + e.data); | ||||
|     $.ajax({ | ||||
|         method: "GET", | ||||
|         url: "/api/tag/" + e.data + "/", | ||||
|         dataType: "json", | ||||
|         success:function(tag, status, xhr) { | ||||
|             console.info("Updated tag", tag); | ||||
|             $("#tag_" + tag.id).html(tag.value); | ||||
|         } | ||||
|     }) | ||||
| } | ||||
|  | ||||
| $(document).ready(function() { | ||||
|     console.info("Loading CA, to debug: curl " + window.location.href + " --negotiate -u : -H 'Accept: application/json'"); | ||||
|     $.ajax({ | ||||
| @@ -13,8 +169,6 @@ $(document).ready(function() { | ||||
|             $("#container").html(nunjucks.render('error.html', { message: msg })); | ||||
|         }, | ||||
|         success: function(session, status, xhr) { | ||||
|             console.info("Got:", session); | ||||
|  | ||||
|             console.info("Opening EventSource from:", session.event_channel); | ||||
|  | ||||
|             var source = new EventSource(session.event_channel); | ||||
| @@ -23,90 +177,33 @@ $(document).ready(function() { | ||||
|                 console.log("Received server-sent event:", event); | ||||
|             } | ||||
|  | ||||
|             source.addEventListener("log-entry", function(e) { | ||||
|                 var entry = JSON.parse(e.data); | ||||
|                 console.info("Received log entry:", entry, "gonna prepend:", $("#log_level_" + entry.severity).prop("checked")); | ||||
|                 if ($("#log_level_" + entry.severity).prop("checked")) { | ||||
|                     $("#log_entries").prepend(nunjucks.render("logentry.html", { | ||||
|                         entry: { | ||||
|                             created: new Date(entry.created).toLocaleString(), | ||||
|                             message: entry.message, | ||||
|                             severity: entry.severity | ||||
|                         } | ||||
|                     })); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             source.addEventListener("up-client", function(e) { | ||||
|                 console.log("Adding security association:" + e.data); | ||||
|                 var lease = JSON.parse(e.data); | ||||
|                 var $status = $("#signed_certificates [data-dn='" + lease.identity + "'] .status"); | ||||
|                 $status.html(nunjucks.render('status.html', { | ||||
|                     lease: { | ||||
|                         address: lease.address, | ||||
|                         identity: lease.identity, | ||||
|                         acquired: new Date(), | ||||
|                         released: null | ||||
|                     }})); | ||||
|             }); | ||||
|  | ||||
|             source.addEventListener("down-client", function(e) { | ||||
|                 console.log("Removing security association:" + e.data); | ||||
|                 var lease = JSON.parse(e.data); | ||||
|                 var $status = $("#signed_certificates [data-dn='" + lease.identity + "'] .status"); | ||||
|                 $status.html(nunjucks.render('status.html', { | ||||
|                     lease: { | ||||
|                         address: lease.address, | ||||
|                         identity: lease.identity, | ||||
|                         acquired: null, | ||||
|                         released: new Date() | ||||
|                     }})); | ||||
|             }); | ||||
|  | ||||
|             source.addEventListener("request_deleted", function(e) { | ||||
|                 console.log("Removing deleted request #" + e.data); | ||||
|                 $("#request_" + e.data).remove(); | ||||
|             }); | ||||
|  | ||||
|             source.addEventListener("request_submitted", function(e) { | ||||
|                 console.log("Request submitted:", e.data); | ||||
|                 $.ajax({ | ||||
|                     method: "GET", | ||||
|                     url: "/api/request/" + e.data + "/", | ||||
|                     dataType: "json", | ||||
|                     success: function(request, status, xhr) { | ||||
|                         console.info(request); | ||||
|                         $("#pending_requests").prepend( | ||||
|                             nunjucks.render('request.html', { request: request })); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             source.addEventListener("request_signed", function(e) { | ||||
|                 console.log("Request signed:", e.data); | ||||
|                 $("#request_" + e.data).slideUp("normal", function() { $(this).remove(); }); | ||||
|  | ||||
|                 $.ajax({ | ||||
|                     method: "GET", | ||||
|                     url: "/api/signed/" + e.data + "/", | ||||
|                     dataType: "json", | ||||
|                     success: function(certificate, status, xhr) { | ||||
|                         console.info(certificate); | ||||
|                         $("#signed_certificates").prepend( | ||||
|                             nunjucks.render('signed.html', { certificate: certificate })); | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             source.addEventListener("certificate_revoked", function(e) { | ||||
|                 console.log("Removing revoked certificate #" + e.data); | ||||
|                 $("#certificate_" + e.data).slideUp("normal", function() { $(this).remove(); }); | ||||
|             }); | ||||
|             source.addEventListener("log-entry", onLogEntry); | ||||
|             source.addEventListener("up-client", onClientUp); | ||||
|             source.addEventListener("down-client", onClientDown); | ||||
|             source.addEventListener("request-deleted", onRequestDeleted); | ||||
|             source.addEventListener("request-submitted", onRequestSubmitted); | ||||
|             source.addEventListener("request-signed", onRequestSigned); | ||||
|             source.addEventListener("certificate-revoked", onCertificateRevoked); | ||||
|             source.addEventListener("tag-added", onTagAdded); | ||||
|             source.addEventListener("tag-removed", onTagRemoved); | ||||
|             source.addEventListener("tag-updated", onTagUpdated); | ||||
|  | ||||
|             /** | ||||
|              * Render authority views | ||||
|              **/ | ||||
|             $("#container").html(nunjucks.render('authority.html', { session: session, window: window })); | ||||
|             console.info("Swtiching to requests section"); | ||||
|             $("section").hide(); | ||||
|             $("section#requests").show(); | ||||
|  | ||||
|             $("nav#menu li").click(function(e) { | ||||
|                 $("section").hide(); | ||||
|                 $("section#" + $(e.target).attr("data-section")).show(); | ||||
|             }); | ||||
|  | ||||
|             /** | ||||
|              * Fetch log entries | ||||
|              */ | ||||
|             $.ajax({ | ||||
|                 method: "GET", | ||||
|                 url: "/api/log/", | ||||
| @@ -127,6 +224,52 @@ $(document).ready(function() { | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             /** | ||||
|              * Set up search bar | ||||
|               */ | ||||
|             $(window).on("search", function() { | ||||
|                 var q = $("#search").val(); | ||||
|                 $(".filterable").each(function(i, e) { | ||||
|                     if ($(e).attr("data-dn").toLowerCase().indexOf(q) >= 0) { | ||||
|                         $(e).show(); | ||||
|                     } else { | ||||
|                         $(e).hide(); | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             /** | ||||
|              * Bind key up event of search bar | ||||
|              */ | ||||
|             $("#search").on("keyup", function() { | ||||
|                 if (window.searchTimeout) { clearTimeout(window.searchTimeout); } | ||||
|                 window.searchTimeout = setTimeout(function() { $(window).trigger("search"); }, 500); | ||||
|                 console.info("Setting timeout", window.searchTimeout); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             /** | ||||
|              * 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 + "\" 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); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             /** | ||||
|              * Fetch leases associated with certificates | ||||
|              */ | ||||
|             $.ajax({ | ||||
|                 method: "GET", | ||||
|                 url: "/api/lease/", | ||||
| @@ -148,17 +291,6 @@ $(document).ready(function() { | ||||
|                             }})); | ||||
|                     } | ||||
|  | ||||
|                     /* Set up search box */ | ||||
|                     $("#search").on("keyup", function() { | ||||
|                         var q = $("#search").val().toLowerCase(); | ||||
|                         $(".filterable").each(function(i, e) { | ||||
|                             if ($(e).attr("data-dn").toLowerCase().indexOf(q) >= 0) { | ||||
|                                 $(e).show(); | ||||
|                             } else { | ||||
|                                 $(e).hide(); | ||||
|                             } | ||||
|                         }); | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| <li id="certificate_{{ certificate.sha256sum }}" data-dn="{{ certificate.identity }}" class="filterable"> | ||||
| <li id="certificate_{{ certificate.sha256sum }}" data-dn="{{ certificate.identity }}" data-cn="{{ certificate.common_name }}" class="filterable"> | ||||
|     <a class="button icon download" href="/api/signed/{{certificate.common_name}}/">Fetch</a> | ||||
|     <button class="icon revoke" onClick="javascript:$(this).addClass('busy');$.ajax({url:'/api/signed/{{certificate.common_name}}/',type:'delete'});">Revoke</button> | ||||
|  | ||||
| @@ -25,6 +25,15 @@ | ||||
|     {{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> | ||||
|       </select> | ||||
|     </div> | ||||
|  | ||||
|     <div class="status"> | ||||
|     {% include 'status.html' %} | ||||
|     </div> | ||||
|   | ||||
| @@ -18,11 +18,11 @@ conn site-to-clients | ||||
| 	rightsourceip={{subnet}} # Serve virtual IP-s from this pool | ||||
| 	left={{local}} # Gateway IP address | ||||
| 	leftcert={{certificate_path}} # Gateway certificate | ||||
| 	{% if route %} | ||||
| 	{% if route | length == 1 %} | ||||
| {% if route %} | ||||
| {% if route | length == 1 %} | ||||
| 	leftsubnet={{route[0]}} # Advertise routes via this connection | ||||
| 	{% else %} | ||||
| {% else %} | ||||
| 	leftsubnet={ {{ route | join(', ') }} } | ||||
| 	{% endif %} | ||||
| 	{% endif %} | ||||
| {% endif %} | ||||
| {% endif %} | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import hashlib | ||||
| import re | ||||
| import click | ||||
| import io | ||||
| from certidude import push | ||||
| from Crypto.Util import asn1 | ||||
| from OpenSSL import crypto | ||||
| from datetime import datetime | ||||
|   | ||||
		Reference in New Issue
	
	Block a user