mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-31 01:19:11 +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 .lease import LeaseResource | ||||||
|     from .whois import WhoisResource |     from .whois import WhoisResource | ||||||
|     from .log import LogResource |     from .log import LogResource | ||||||
|  |     from .tag import TagResource, TagDetailResource | ||||||
|  |  | ||||||
|     app = falcon.API() |     app = falcon.API() | ||||||
|  |  | ||||||
| @@ -91,6 +92,8 @@ def certidude_app(): | |||||||
|     app.add_route("/api/request/{cn}/", RequestDetailResource()) |     app.add_route("/api/request/{cn}/", RequestDetailResource()) | ||||||
|     app.add_route("/api/request/", RequestListResource()) |     app.add_route("/api/request/", RequestListResource()) | ||||||
|     app.add_route("/api/log/", LogResource()) |     app.add_route("/api/log/", LogResource()) | ||||||
|  |     app.add_route("/api/tag/", TagResource()) | ||||||
|  |     app.add_route("/api/tag/{identifier}/", TagDetailResource()) | ||||||
|     app.add_route("/api/", SessionResource()) |     app.add_route("/api/", SessionResource()) | ||||||
|  |  | ||||||
|     # Gateway API calls, should this be moved to separate project? |     # Gateway API calls, should this be moved to separate project? | ||||||
| @@ -128,12 +131,13 @@ def certidude_app(): | |||||||
|         logger.addHandler(push_handler) |         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 |     import atexit | ||||||
|  |  | ||||||
|     def exit_handler(): |     def exit_handler(): | ||||||
|         logging.getLogger("cli").info("Shutting down Certidude") |         logging.getLogger("cli").debug("Shutting down Certidude") | ||||||
|  |  | ||||||
|     atexit.register(exit_handler) |     atexit.register(exit_handler) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -73,7 +73,7 @@ class RequestListResource(object): | |||||||
|             raise falcon.HTTPConflict( |             raise falcon.HTTPConflict( | ||||||
|                 "CSR with such CN already exists", |                 "CSR with such CN already exists", | ||||||
|                 "Will not overwrite existing certificate signing request, explicitly delete CSR and try again") |                 "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 |         # Wait the certificate to be signed if waiting is requested | ||||||
|         if req.get_param("wait"): |         if req.get_param("wait"): | ||||||
|   | |||||||
| @@ -1,9 +1,12 @@ | |||||||
|  |  | ||||||
| import falcon | import falcon | ||||||
|  | import logging | ||||||
| from certidude import authority | from certidude import authority | ||||||
| from certidude.auth import login_required, authorize_admin | from certidude.auth import login_required, authorize_admin | ||||||
| from certidude.decorators import serialize | from certidude.decorators import serialize | ||||||
|  |  | ||||||
|  | logger = logging.getLogger("api") | ||||||
|  |  | ||||||
| class SignedCertificateListResource(object): | class SignedCertificateListResource(object): | ||||||
|     @serialize |     @serialize | ||||||
|     @authorize_admin |     @authorize_admin | ||||||
| @@ -26,13 +29,17 @@ class SignedCertificateDetailResource(object): | |||||||
|     @serialize |     @serialize | ||||||
|     def on_get(self, req, resp, cn): |     def on_get(self, req, resp, cn): | ||||||
|         try: |         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) |             return authority.get_signed(cn) | ||||||
|         except FileNotFoundError: |         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 |             resp.body = "No certificate CN=%s found" % cn | ||||||
|             raise falcon.HTTPNotFound() |             raise falcon.HTTPNotFound() | ||||||
|  |  | ||||||
|     @login_required |     @login_required | ||||||
|     @authorize_admin |     @authorize_admin | ||||||
|     def on_delete(self, req, resp, cn): |     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) |         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 falcon | ||||||
| import ipaddress | import ipaddress | ||||||
|  | from datetime import datetime | ||||||
| from certidude import config | from certidude import config | ||||||
| from certidude.decorators import serialize | from certidude.decorators import serialize | ||||||
|  | from certidude.api.lease import parse_dn | ||||||
|  |  | ||||||
| def address_to_identity(cnx, addr): | def address_to_identity(cnx, addr): | ||||||
|     """ |     """ | ||||||
| @@ -10,19 +12,19 @@ def address_to_identity(cnx, addr): | |||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     SQL_LEASES = """ |     SQL_LEASES = """ | ||||||
|         SELECT |         select | ||||||
|             acquired, |             acquired, | ||||||
|             released, |             released, | ||||||
|             identities.data as identity |             identities.data as identity | ||||||
|         FROM |         from | ||||||
|             addresses |             addresses | ||||||
|         RIGHT JOIN |         right join | ||||||
|             identities |             identities | ||||||
|         ON |         on | ||||||
|             identities.id = addresses.identity |             identities.id = addresses.identity | ||||||
|         WHERE |         where | ||||||
|             address = %s AND |             address = %s and | ||||||
|             released IS NOT NULL |             released is not null | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     cursor = cnx.cursor() |     cursor = cnx.cursor() | ||||||
| @@ -31,6 +33,7 @@ def address_to_identity(cnx, addr): | |||||||
|  |  | ||||||
|     for acquired, released, identity in cursor: |     for acquired, released, identity in cursor: | ||||||
|         return { |         return { | ||||||
|  |             "address": addr, | ||||||
|             "acquired": datetime.utcfromtimestamp(acquired), |             "acquired": datetime.utcfromtimestamp(acquired), | ||||||
|             "identity": parse_dn(bytes(identity)) |             "identity": parse_dn(bytes(identity)) | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ def publish_certificate(func): | |||||||
|             click.echo("Publishing certificate at %s, waiting for response..." % url) |             click.echo("Publishing certificate at %s, waiting for response..." % url) | ||||||
|             response = urllib.request.urlopen(notification) |             response = urllib.request.urlopen(notification) | ||||||
|             response.read() |             response.read() | ||||||
|             push.publish("request_signed", csr.common_name) |             push.publish("request-signed", csr.common_name) | ||||||
|         return cert |         return cert | ||||||
|     return wrapped |     return wrapped | ||||||
|  |  | ||||||
| @@ -93,7 +93,7 @@ def revoke_certificate(common_name): | |||||||
|     cert = get_signed(common_name) |     cert = get_signed(common_name) | ||||||
|     revoked_filename = os.path.join(config.REVOKED_DIR, "%s.pem" % cert.serial_number) |     revoked_filename = os.path.join(config.REVOKED_DIR, "%s.pem" % cert.serial_number) | ||||||
|     os.rename(cert.path, revoked_filename) |     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): | def list_requests(directory=config.REQUESTS_DIR): | ||||||
| @@ -141,7 +141,7 @@ def delete_request(common_name): | |||||||
|     os.unlink(path) |     os.unlink(path) | ||||||
|  |  | ||||||
|     # Publish event at CA channel |     # Publish event at CA channel | ||||||
|     push.publish("request_deleted", request_sha1sum) |     push.publish("request-deleted", request_sha1sum) | ||||||
|  |  | ||||||
|     # Write empty certificate to long-polling URL |     # Write empty certificate to long-polling URL | ||||||
|     url = config.PUSH_PUBLISH % request_sha1sum |     url = config.PUSH_PUBLISH % request_sha1sum | ||||||
|   | |||||||
| @@ -13,7 +13,6 @@ import signal | |||||||
| import socket | import socket | ||||||
| import subprocess | import subprocess | ||||||
| import sys | import sys | ||||||
| from certidude import authority |  | ||||||
| from certidude.signer import SignServer | from certidude.signer import SignServer | ||||||
| from certidude.common import expand_paths | from certidude.common import expand_paths | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| @@ -165,7 +164,7 @@ def certidude_setup_client(quiet, **kwargs): | |||||||
|  |  | ||||||
| @click.command("server", help="Set up OpenVPN server") | @click.command("server", help="Set up OpenVPN server") | ||||||
| @click.argument("url") | @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("--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("--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") | @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") | @click.option("--authority-path", "-a", default="ca.crt", help="Certificate authority certificate path, ca.crt relative to --dir by default") | ||||||
| @expand_paths() | @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): | 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( |     retval = certidude_request_certificate( | ||||||
|         url, |         url, | ||||||
|         key_path, |         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.command("server", help="Set up strongSwan server") | ||||||
| @click.argument("url") | @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("--org-unit", "-ou", help="Organizational unit") | ||||||
| @click.option("--fqdn", "-f", default=FQDN, help="Fully qualified hostname associated with the certificate") | @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) | @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") | @click.option("--authority-path", "-ca", default="cacerts/ca.pem", help="Certificate authority certificate path, cacerts/ca.pem by default") | ||||||
| @expand_paths() | @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): | 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): |     if not os.path.exists(certificate_path): | ||||||
|         click.echo("As strongSwan server certificate needs specific key usage extensions please") |         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("use following command to sign on Certidude server instead of web interface:") | ||||||
|         click.echo() |         click.echo() | ||||||
|         click.echo("  certidude sign %s" % common_name) |         click.echo("  certidude sign %s" % common_name) | ||||||
|  |     from certidude.helpers import certidude_request_certificate | ||||||
|     retval = certidude_request_certificate( |     retval = certidude_request_certificate( | ||||||
|         url, |         url, | ||||||
|         key_path, |         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") | @click.option("--authority-path", "-ca", default="cacerts/ca.pem", help="Certificate authority certificate path, cacerts/ca.pem by default") | ||||||
| @expand_paths() | @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): | 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( |     retval = certidude_request_certificate( | ||||||
|         url, |         url, | ||||||
|         key_path, |         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") | @click.option("--authority-path", "-ca", default="cacerts/ca.pem", help="Certificate authority certificate path, cacerts/ca.pem by default") | ||||||
| @expand_paths() | @expand_paths() | ||||||
| def certidude_setup_strongswan_networkmanager(url, email_address, common_name, org_unit, directory, key_path, request_path, certificate_path, authority_path, remote): | 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( |     retval = certidude_request_certificate( | ||||||
|         url, |         url, | ||||||
|         key_path, |         key_path, | ||||||
| @@ -685,6 +688,8 @@ def certidude_list(verbose, show_key_type, show_extensions, show_path, show_sign | |||||||
|     #   y - not valid yet |     #   y - not valid yet | ||||||
|     #   r - revoked |     #   r - revoked | ||||||
|  |  | ||||||
|  |     from certidude import authority | ||||||
|  |  | ||||||
|     from pycountry import countries |     from pycountry import countries | ||||||
|     def dump_common(j): |     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("--overwrite", "-o", default=False, is_flag=True, help="Revoke valid certificate with same CN") | ||||||
| @click.option("--lifetime", "-l", help="Lifetime") | @click.option("--lifetime", "-l", help="Lifetime") | ||||||
| def certidude_sign(common_name, overwrite, lifetime): | def certidude_sign(common_name, overwrite, lifetime): | ||||||
|  |     from certidude import authority | ||||||
|     request = authority.get_request(common_name) |     request = authority.get_request(common_name) | ||||||
|     if request.signable: |     if request.signable: | ||||||
|         # Sign via signer process |         # Sign via signer process | ||||||
|   | |||||||
| @@ -1,4 +1,7 @@ | |||||||
|  |  | ||||||
|  | import os | ||||||
|  | import click | ||||||
|  |  | ||||||
| def expand_paths(): | def expand_paths(): | ||||||
|     """ |     """ | ||||||
|     Prefix '..._path' keyword arguments of target function with 'directory' keyword argument |     Prefix '..._path' keyword arguments of target function with 'directory' keyword argument | ||||||
|   | |||||||
| @@ -2,11 +2,9 @@ | |||||||
| import click | import click | ||||||
| import os | import os | ||||||
| import urllib.request | import urllib.request | ||||||
| from certidude import config | from certidude.wrappers import Certificate, Request | ||||||
| from OpenSSL import crypto | 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): | 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 |     Exchange CSR for certificate using Certidude HTTP API server | ||||||
|   | |||||||
| @@ -1,19 +1,19 @@ | |||||||
|  |  | ||||||
|  | <section id="about"> | ||||||
| <p>Hi {{session.username}},</p> | <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>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>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 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> | <p>Authority administration allowed for: {% for i in session.admin_users %}{{ i }} {% endfor %}</p> | ||||||
|  | </section> | ||||||
| {% set s = session.certificate.identity %} | {% set s = session.certificate.identity %} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | <section id="requests"> | ||||||
|  |  | ||||||
| <div id="requests"> |  | ||||||
|     <h1>Pending requests</h1> |     <h1>Pending requests</h1> | ||||||
|  |  | ||||||
|  |  | ||||||
|     <ul id="pending_requests"> |     <ul id="pending_requests"> | ||||||
|         {% for request in session.requests %} |         {% for request in session.requests %} | ||||||
|              {% include "request.html" %} |              {% include "request.html" %} | ||||||
| @@ -23,19 +23,20 @@ | |||||||
|             <pre>certidude setup client {{session.common_name}}</pre> |             <pre>certidude setup client {{session.common_name}}</pre> | ||||||
|         </li> |         </li> | ||||||
|     </ul> |     </ul> | ||||||
| </div> | </section> | ||||||
|  |  | ||||||
|  |  | ||||||
| <div id="signed"> | <section id="signed"> | ||||||
|     <h1>Signed certificates</h1> |     <h1>Signed certificates</h1> | ||||||
|  |     <input id="search" type="search" class="icon search"> | ||||||
|     <ul id="signed_certificates"> |     <ul id="signed_certificates"> | ||||||
|         {% for certificate in session.signed | sort | reverse %} |         {% for certificate in session.signed | sort | reverse %} | ||||||
|             {% include "signed.html" %} |             {% include "signed.html" %} | ||||||
| 	    {% endfor %} | 	    {% endfor %} | ||||||
|     </ul> |     </ul> | ||||||
| </div> | </section> | ||||||
|  |  | ||||||
| <div id="log"> | <section id="log"> | ||||||
|     <h1>Log</h1> |     <h1>Log</h1> | ||||||
|     <p> |     <p> | ||||||
|         <input id="log_level_critical" type="checkbox" checked/> <label for="log_level_critical">Critical</label> |         <input id="log_level_critical" type="checkbox" checked/> <label for="log_level_critical">Critical</label> | ||||||
| @@ -46,9 +47,9 @@ | |||||||
|     </p> |     </p> | ||||||
|     <ul id="log_entries"> |     <ul id="log_entries"> | ||||||
|     </ul> |     </ul> | ||||||
| </div> | </section> | ||||||
|  |  | ||||||
| <div id="revoked"> | <section id="revoked"> | ||||||
|     <h1>Revoked certificates</h1> |     <h1>Revoked certificates</h1> | ||||||
|     <p>To fetch certificate revocation list:</p> |     <p>To fetch certificate revocation list:</p> | ||||||
|     <pre> |     <pre> | ||||||
| @@ -72,4 +73,4 @@ | |||||||
|             <li>Great job! No certificate signing requests to sign.</li> |             <li>Great job! No certificate signing requests to sign.</li> | ||||||
| 	    {% endfor %} | 	    {% endfor %} | ||||||
|     </ul> |     </ul> | ||||||
| </div> | </section> | ||||||
|   | |||||||
| @@ -119,7 +119,7 @@ h2 svg { | |||||||
|     top: 16px; |     top: 16px; | ||||||
| } | } | ||||||
|  |  | ||||||
| p, td, footer, li, button, input { | p, td, footer, li, button, input, select { | ||||||
|     font-family: 'PT Sans Narrow'; |     font-family: 'PT Sans Narrow'; | ||||||
|     font-size: 14pt; |     font-size: 14pt; | ||||||
| } | } | ||||||
| @@ -159,6 +159,7 @@ pre { | |||||||
|     display: inline; |     display: inline; | ||||||
|     margin: 1mm 5mm 1mm 0; |     margin: 1mm 5mm 1mm 0; | ||||||
|     line-height: 200%; |     line-height: 200%; | ||||||
|  |     cursor: pointer; | ||||||
| } | } | ||||||
|  |  | ||||||
| .icon{ | .icon{ | ||||||
| @@ -170,24 +171,49 @@ pre { | |||||||
|     text-decoration: none; |     text-decoration: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| li span.icon { | #log_entries li span.icon { | ||||||
|     background-size: 32px; |     background-size: 32px; | ||||||
|     padding-left: 42px; |     padding-left: 42px; | ||||||
|     padding-top: 2px; |     padding-top: 2px; | ||||||
|     padding-bottom: 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.critical { background-image: url("../img/iconmonstr-error-4-icon.svg"); } | ||||||
| .icon.error { 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.warning { background-image: url("../img/iconmonstr-warning-6-icon.svg"); } | ||||||
| .icon.info { background-image: url("../img/iconmonstr-info-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.revoke { background-image: url("../img/iconmonstr-x-mark-5-icon.svg"); } | ||||||
| .icon.download { background-image: url("../img/iconmonstr-download-12-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.sign { background-image: url("../img/iconmonstr-pen-10-icon.svg"); } | ||||||
| .icon.search { background-image: url("../img/iconmonstr-magnifier-4-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 */ | /* Make sure this is the last one */ | ||||||
| .icon.busy{background-image:url("https://software.opensuse.org/assets/ajax-loader-ea46060b6c9f42822a3d58d075c83ea2.gif");} | .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"> |     <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"> | ||||||
| </head> | </head> | ||||||
| <body> | <body> | ||||||
|     <div id="menu"> |     <nav id="menu"> | ||||||
|         <ul class="container"> |         <ul class="container"> | ||||||
|           <li>Requests</li> |           <li data-section="requests">Requests</li> | ||||||
|           <li>Signed</li> |           <li data-section="signed">Signed</li> | ||||||
|           <li>Revoked</li> |           <li data-section="revoked">Revoked</li> | ||||||
|           <li>Log</li> |           <li data-section="log">Log</li> | ||||||
|         </ul> |         </ul> | ||||||
|     </div> |     </nav> | ||||||
|     <div id="container" class="container"> |     <div id="container" class="container"> | ||||||
|         Loading certificate authority... |         Loading certificate authority... | ||||||
|     </div> |     </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() { | $(document).ready(function() { | ||||||
|     console.info("Loading CA, to debug: curl " + window.location.href + " --negotiate -u : -H 'Accept: application/json'"); |     console.info("Loading CA, to debug: curl " + window.location.href + " --negotiate -u : -H 'Accept: application/json'"); | ||||||
|     $.ajax({ |     $.ajax({ | ||||||
| @@ -13,8 +169,6 @@ $(document).ready(function() { | |||||||
|             $("#container").html(nunjucks.render('error.html', { message: msg })); |             $("#container").html(nunjucks.render('error.html', { message: msg })); | ||||||
|         }, |         }, | ||||||
|         success: function(session, status, xhr) { |         success: function(session, status, xhr) { | ||||||
|             console.info("Got:", session); |  | ||||||
|  |  | ||||||
|             console.info("Opening EventSource from:", session.event_channel); |             console.info("Opening EventSource from:", session.event_channel); | ||||||
|  |  | ||||||
|             var source = new EventSource(session.event_channel); |             var source = new EventSource(session.event_channel); | ||||||
| @@ -23,90 +177,33 @@ $(document).ready(function() { | |||||||
|                 console.log("Received server-sent event:", event); |                 console.log("Received server-sent event:", event); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             source.addEventListener("log-entry", function(e) { |             source.addEventListener("log-entry", onLogEntry); | ||||||
|                 var entry = JSON.parse(e.data); |             source.addEventListener("up-client", onClientUp); | ||||||
|                 console.info("Received log entry:", entry, "gonna prepend:", $("#log_level_" + entry.severity).prop("checked")); |             source.addEventListener("down-client", onClientDown); | ||||||
|                 if ($("#log_level_" + entry.severity).prop("checked")) { |             source.addEventListener("request-deleted", onRequestDeleted); | ||||||
|                     $("#log_entries").prepend(nunjucks.render("logentry.html", { |             source.addEventListener("request-submitted", onRequestSubmitted); | ||||||
|                         entry: { |             source.addEventListener("request-signed", onRequestSigned); | ||||||
|                             created: new Date(entry.created).toLocaleString(), |             source.addEventListener("certificate-revoked", onCertificateRevoked); | ||||||
|                             message: entry.message, |             source.addEventListener("tag-added", onTagAdded); | ||||||
|                             severity: entry.severity |             source.addEventListener("tag-removed", onTagRemoved); | ||||||
|                         } |             source.addEventListener("tag-updated", onTagUpdated); | ||||||
|                     })); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|             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(); }); |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * Render authority views | ||||||
|  |              **/ | ||||||
|             $("#container").html(nunjucks.render('authority.html', { session: session, window: window })); |             $("#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({ |             $.ajax({ | ||||||
|                 method: "GET", |                 method: "GET", | ||||||
|                 url: "/api/log/", |                 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({ |             $.ajax({ | ||||||
|                 method: "GET", |                 method: "GET", | ||||||
|                 url: "/api/lease/", |                 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> |     <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> |     <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}} |     {{certificate.key_usage}} | ||||||
|     </div> |     </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"> |     <div class="status"> | ||||||
|     {% include 'status.html' %} |     {% include 'status.html' %} | ||||||
|     </div> |     </div> | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ import hashlib | |||||||
| import re | import re | ||||||
| import click | import click | ||||||
| import io | import io | ||||||
| from certidude import push |  | ||||||
| from Crypto.Util import asn1 | from Crypto.Util import asn1 | ||||||
| from OpenSSL import crypto | from OpenSSL import crypto | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user