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