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