Release version 0.1.20

This commit is contained in:
Lauri Võsandi 2016-01-10 19:51:54 +02:00
parent 6a45592cd0
commit de08ba759d
22 changed files with 371 additions and 87 deletions

View File

@ -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

View File

@ -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
View 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

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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"]

View File

@ -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()

View File

@ -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":

View File

@ -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

View File

@ -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>

View 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 %}

View File

@ -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");}

View 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

View 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

View File

@ -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>

View File

@ -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
}
}));
}
}
}
});
} }
}); });
}); });

View File

@ -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 %}

View File

@ -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>

View File

@ -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

View 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>

View File

@ -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.",