1
0
mirror of https://github.com/laurivosandi/certidude synced 2024-12-23 00:25:18 +00:00

Added preliminary interfacing with updown scripts

This commit is contained in:
Lauri Võsandi 2015-11-13 23:16:38 +01:00
parent 887743cc0b
commit e6f050c257
17 changed files with 156 additions and 44 deletions

View File

@ -76,20 +76,20 @@ def validate_common_name(func):
class MyEncoder(json.JSONEncoder): class MyEncoder(json.JSONEncoder):
REQUEST_ATTRIBUTES = "signable", "subject", "changed", "common_name", \ REQUEST_ATTRIBUTES = "signable", "identity", "changed", "common_name", \
"organizational_unit", "given_name", "surname", "fqdn", "email_address", \ "organizational_unit", "given_name", "surname", "fqdn", "email_address", \
"key_type", "key_length", "md5sum", "sha1sum", "sha256sum", "key_usage" "key_type", "key_length", "md5sum", "sha1sum", "sha256sum", "key_usage"
CERTIFICATE_ATTRIBUTES = "revokable", "subject", "changed", "common_name", \ CERTIFICATE_ATTRIBUTES = "revokable", "identity", "changed", "common_name", \
"organizational_unit", "given_name", "surname", "fqdn", "email_address", \ "organizational_unit", "given_name", "surname", "fqdn", "email_address", \
"key_type", "key_length", "sha256sum", "serial_number", "key_usage" "key_type", "key_length", "sha256sum", "serial_number", "key_usage"
def default(self, obj): def default(self, obj):
if isinstance(obj, crypto.X509Name): if isinstance(obj, crypto.X509Name):
try: try:
return "".join(["/%s=%s" % (k.decode("ascii"),v.decode("utf-8")) for k, v in obj.get_components()]) return ", ".join(["%s=%s" % (k.decode("ascii"),v.decode("utf-8")) for k, v in obj.get_components()])
except UnicodeDecodeError: # Work around old buggy pyopenssl except UnicodeDecodeError: # Work around old buggy pyopenssl
return "".join(["/%s=%s" % (k.decode("ascii"),v.decode("iso8859")) for k, v in obj.get_components()]) return ", ".join(["%s=%s" % (k.decode("ascii"),v.decode("iso8859")) for k, v in obj.get_components()])
if isinstance(obj, ipaddress._IPAddressBase): if isinstance(obj, ipaddress._IPAddressBase):
return str(obj) return str(obj)
if isinstance(obj, set): if isinstance(obj, set):
@ -110,6 +110,7 @@ class MyEncoder(json.JSONEncoder):
if hasattr(obj, key) and getattr(obj, key)]) if hasattr(obj, key) and getattr(obj, key)])
if isinstance(obj, CertificateAuthority): if isinstance(obj, CertificateAuthority):
return dict( return dict(
event_channel = obj.push_server + "/ev/" + obj.uuid,
slug = obj.slug, slug = obj.slug,
certificate = obj.certificate, certificate = obj.certificate,
admin_users = obj.admin_users, admin_users = obj.admin_users,
@ -225,11 +226,12 @@ class LeaseResource(CertificateAuthorityBase):
if remainder: if remainder:
raise ValueError() raise ValueError()
# TODO: Check for duplicate entries? # TODO: Check for duplicate entries?
for chunk in chunks: def generate():
for chunkette in chunk: for chunk in chunks:
key, value = chunkette for chunkette in chunk:
dn += "/" + OIDS[key] + "=" + value key, value = chunkette
return str(dn) yield str(OIDS[key] + "=" + value)
return ", ".join(generate())
# BUGBUG # BUGBUG
SQL_LEASES = """ SQL_LEASES = """
@ -237,7 +239,7 @@ class LeaseResource(CertificateAuthorityBase):
acquired, acquired,
released, released,
address, address,
identities.data as dn identities.data as identity
FROM FROM
addresses addresses
RIGHT JOIN RIGHT JOIN
@ -256,7 +258,7 @@ class LeaseResource(CertificateAuthorityBase):
row["acquired"] = datetime.utcfromtimestamp(row["acquired"]) row["acquired"] = datetime.utcfromtimestamp(row["acquired"])
row["released"] = datetime.utcfromtimestamp(row["released"]) if row["released"] else None row["released"] = datetime.utcfromtimestamp(row["released"]) if row["released"] else None
row["address"] = ip_address(bytes(row["address"])) row["address"] = ip_address(bytes(row["address"]))
row["dn"] = parse_dn(bytes(row["dn"])) row["identity"] = parse_dn(bytes(row["identity"]))
yield row yield row
@ -270,7 +272,7 @@ class SignedCertificateListResource(CertificateAuthorityBase):
yield omit( yield omit(
key_type=j.key_type, key_type=j.key_type,
key_length=j.key_length, key_length=j.key_length,
subject=j.distinguished_name, identity=j.identity,
cn=j.common_name, cn=j.common_name,
c=j.country_code, c=j.country_code,
st=j.state_or_county, st=j.state_or_county,
@ -324,7 +326,7 @@ class RequestListResource(CertificateAuthorityBase):
yield omit( yield omit(
key_type=j.key_type, key_type=j.key_type,
key_length=j.key_length, key_length=j.key_length,
subject=j.distinguished_name, identity=j.identity,
cn=j.common_name, cn=j.common_name,
c=j.country_code, c=j.country_code,
st=j.state_or_county, st=j.state_or_county,

View File

@ -7,7 +7,7 @@
<p>Authority administration is allowed from: {% if authority.admin_subnets %}{% for i in authority.admin_subnets %}{{ i }} {% endfor %}{% else %}anywhere{% endif %} <p>Authority administration is allowed from: {% if authority.admin_subnets %}{% for i in authority.admin_subnets %}{{ i }} {% endfor %}{% else %}anywhere{% endif %}
<p>Authority administration allowed for: {% for i in authority.admin_users %}{{ i }} {% endfor %}</p> <p>Authority administration allowed for: {% for i in authority.admin_users %}{{ i }} {% endfor %}</p>
{% set s = authority.certificate.subject %} {% set s = authority.certificate.identity %}
<h1>Pending requests</h1> <h1>Pending requests</h1>
@ -23,21 +23,21 @@
<ul id="signed_certificates"> <ul id="signed_certificates">
{% for j in authority.signed | sort | reverse %} {% for j in authority.signed | sort | reverse %}
<li id="certificate_{{ j.sha256sum }}" data-dn="{{ j.subject }}"> <li id="certificate_{{ j.sha256sum }}" data-dn="{{ j.identity }}">
<a class="button" href="/api/{{authority.slug}}/signed/{{j.subject}}/">Fetch</a> <a class="button icon download" href="/api/ca/{{authority.slug}}/signed/{{j.common_name}}/">Fetch</a>
<button onClick="javascript:$.ajax({url:'/api/{{authority.slug}}/signed/{{j.subject.CN}}/',type:'delete'});">Revoke</button> <button class="icon revoke" onClick="javascript:$(this).addClass('busy');$.ajax({url:'/api/ca/{{authority.slug}}/signed/{{j.common_name}}/',type:'delete'});">Revoke</button>
<div class="monospace"> <div class="monospace">
{% include 'iconmonstr-certificate-15-icon.svg' %} {% include 'img/iconmonstr-certificate-15-icon.svg' %}
{{j.subject}} {{j.identity}}
</div> </div>
{% if j.email_address %} {% if j.email_address %}
<div class="email">{% include 'iconmonstr-email-2-icon.svg' %} {{ j.email_address }}</div> <div class="email">{% include 'img/iconmonstr-email-2-icon.svg' %} {{ j.email_address }}</div>
{% endif %} {% endif %}
<div class="monospace"> <div class="monospace">
{% include '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">
{{ j.sha256sum }} {{ j.sha256sum }}
</span> </span>
@ -46,13 +46,12 @@
</div> </div>
<div> <div>
{% include 'iconmonstr-flag-3-icon.svg' %} {% include 'img/iconmonstr-flag-3-icon.svg' %}
{{j.key_usage}} {{j.key_usage}}
</div> </div>
<div class="status"> <div class="status">
{% include 'status.html' %} {% include 'status.html' %}
</div> </div>
</li> </li>
{% endfor %} {% endfor %}
@ -76,7 +75,7 @@ openssl ocsp -issuer authority.pem -CAfile authority.pem -url {{request.url}}/oc
{% for j in authority.revoked %} {% for j in authority.revoked %}
<li id="certificate_{{ j.sha256sum }}"> <li id="certificate_{{ j.sha256sum }}">
{{j.changed}} {{j.changed}}
{{j.serial_number}} <span class="monospace">{{j.distinguished_name}}</span> {{j.serial_number}} <span class="monospace">{{j.identity}}</span>
</li> </li>
{% else %} {% else %}
<li>Great job! No certificate signing requests to sign.</li> <li>Great job! No certificate signing requests to sign.</li>

View File

@ -42,7 +42,8 @@ button, .button {
background-color: #eee; background-color: #eee;
border-radius: 6px; border-radius: 6px;
margin: 2px; margin: 2px;
padding: 4px 8px; padding: 6px 12px;
background-position: 6px;
box-sizing: border-box; box-sizing: border-box;
} }
@ -140,3 +141,20 @@ li {
clear: both; clear: both;
border-top: 1px dashed #ccc; border-top: 1px dashed #ccc;
} }
.icon{
background-size: 24px;
padding-left: 36px;
background-repeat: no-repeat;
display: block;
vertical-align: text-bottom;
text-decoration: none;
}
.icon.revoke { background-image: url("../img/iconmonstr-x-mark-5-icon.svg"); }
.icon.download { background-image: url("../img/iconmonstr-download-12-icon.svg"); }
.icon.sign { background-image: url("../img/iconmonstr-pen-10-icon.svg"); }
/* Make sure this is the last one */
.icon.busy{background-image:url("https://software.opensuse.org/assets/ajax-loader-ea46060b6c9f42822a3d58d075c83ea2.gif");}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,29 @@
<?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="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<path id="download-12-icon" d="M462,246.575c0,44.318-35.928,80.246-80.246,80.246H331.58v-38.119
c-0.998-43.379,40.92-44.379,59.67-46.379c-27.168-33.334-70.918-48.244-104.611-48.244c-66.546,0-108.17,39.104-108.17,104.808
v27.935h-48.223C85.928,326.821,50,290.894,50,246.575c0-40.982,30.729-74.766,70.396-79.623
c2.891-42.287,49.035-66.355,85.217-45.898c19.236-30.605,53.297-50.953,92.115-50.953c57.107,0,103.932,44.033,108.375,100
C438.516,180.413,462,210.747,462,246.575z M301.58,288.702c0-30.761,6.053-48.484,31.926-56.837
c-20.066-8.452-125.037-28.815-125.037,67.021c0,32.187,0,58.909,0,58.909h-37.408l83.963,84.104l83.965-84.104H301.58
C301.58,357.796,301.58,315.59,301.58,288.702z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 982 B

View File

Before

Width:  |  Height:  |  Size: 786 B

After

Width:  |  Height:  |  Size: 786 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- License Agreement at http://iconmonstr.com/license/ -->
<!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="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<path id="pen-10-icon" d="M244.558,199.493l67.827,67.826l-73.17,134.531c0,0-90.805,23.4-147.694,60.027l-14.185-14.182
l68.113-68.105c5.975-5.982,13.726-9.773,22.11-10.807c4.642-0.582,9.128-2.621,12.696-6.205c8.538-8.547,8.546-22.4-0.002-30.951
c-8.549-8.543-22.407-8.543-30.959-0.002c-3.573,3.572-5.623,8.061-6.199,12.693c-1.028,8.371-4.834,16.15-10.8,22.117
l-68.104,68.105L50,420.354c37.028-57.496,60.021-147.693,60.021-147.693L244.558,199.493z M315.896,50.122
c-22.784,44.143-53.014,100-53.014,100l98.872,98.869c0,0,55.909-30.086,100.246-52.766L315.896,50.122z"/>
</svg>

After

Width:  |  Height:  |  Size: 1016 B

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,21 @@
<?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="x-mark-5-icon" d="M432.546,133.462L367.133,76.39L254.078,210.715L140.967,73.702l-61.513,65.068
c33.791,43.885,78.146,89.797,123.688,132.465L82.993,413.987l19.865,22.629c29.251-20.31,87.839-65.578,150.312-120.092
c63.662,55.812,122.861,101.336,151.301,121.773l21.438-19.443L303.804,270.95C352.439,225.709,399.308,177.442,432.546,133.462z"/>
</svg>

After

Width:  |  Height:  |  Size: 1001 B

View File

@ -20,14 +20,40 @@ $(document).ready(function() {
success: function(authority, status, xhr) { success: function(authority, status, xhr) {
console.info("Got CA:", authority); console.info("Got CA:", authority);
console.info("Opening EventSource from:", "/api/ca/" + authority.slug); console.info("Opening EventSource from:", authority.event_channel);
var source = new EventSource("/api/" + authority.slug); var source = new EventSource(authority.event_channel);
source.onmessage = function(event) { source.onmessage = function(event) {
console.log("Received server-sent event:", event); console.log("Received server-sent event:", event);
} }
source.addEventListener("up-client", function(e) {
console.log("Adding security association:" + e.data);
var lease = JSON.parse(e.data);
var $status = $("#signed_certificates [data-dn='" + lease.identity + "'] .status");
$status.html(nunjucks.render('status.html', {
lease: {
address: lease.address,
identity: lease.identity,
acquired: new Date(),
released: null
}}));
});
source.addEventListener("down-client", function(e) {
console.log("Removing security association:" + e.data);
var lease = JSON.parse(e.data);
var $status = $("#signed_certificates [data-dn='" + lease.identity + "'] .status");
$status.html(nunjucks.render('status.html', {
lease: {
address: lease.address,
identity: lease.identity,
acquired: null,
released: new Date()
}}));
});
source.addEventListener("request_deleted", function(e) { source.addEventListener("request_deleted", function(e) {
console.log("Removing deleted request #" + e.data); console.log("Removing deleted request #" + e.data);
$("#request_" + e.data).remove(); $("#request_" + e.data).remove();
@ -39,13 +65,13 @@ $(document).ready(function() {
source.addEventListener("request_signed", function(e) { source.addEventListener("request_signed", function(e) {
console.log("Request signed:", e.data); console.log("Request signed:", e.data);
$("#request_" + e.data).remove(); $("#request_" + e.data).slideUp("normal", function() { $(this).remove(); });
// TODO: Insert <li> to signed certs list // TODO: Insert <li> to signed certs list
}); });
source.addEventListener("certificate_revoked", function(e) { source.addEventListener("certificate_revoked", function(e) {
console.log("Removing revoked certificate #" + e.data); console.log("Removing revoked certificate #" + e.data);
$("#certificate_" + e.data).remove(); $("#certificate_" + e.data).slideUp("normal", function() { $(this).remove(); });
}); });
$("#container").html(nunjucks.render('authority.html', { authority: authority, session: session })); $("#container").html(nunjucks.render('authority.html', { authority: authority, session: session }));
@ -57,7 +83,7 @@ $(document).ready(function() {
success: function(leases, status, xhr) { success: function(leases, status, xhr) {
console.info("Got leases:", leases); console.info("Got leases:", leases);
for (var j = 0; j < leases.length; j++) { for (var j = 0; j < leases.length; j++) {
var $status = $("#signed_certificates [data-dn='" + leases[j].dn + "'] .status"); var $status = $("#signed_certificates [data-dn='" + leases[j].identity + "'] .status");
if (!$status.length) { if (!$status.length) {
console.info("Detected rogue client:", leases[j]); console.info("Detected rogue client:", leases[j]);
continue; continue;
@ -65,7 +91,7 @@ $(document).ready(function() {
$status.html(nunjucks.render('status.html', { $status.html(nunjucks.render('status.html', {
lease: { lease: {
address: leases[j].address, address: leases[j].address,
dn: leases[j].dn, identity: leases[j].identity,
acquired: new Date(leases[j].acquired).toLocaleString(), acquired: new Date(leases[j].acquired).toLocaleString(),
released: leases[j].released ? new Date(leases[j].released).toLocaleString() : null released: leases[j].released ? new Date(leases[j].released).toLocaleString() : null
}})); }}));

View File

@ -1,25 +1,25 @@
<li id="request_{{ j.md5sum }}"> <li id="request_{{ j.md5sum }}">
<a class="button" href="/api/{{authority.slug}}/request/{{j.common_name}}/">Fetch</a> <a class="button icon download" href="/api/ca/{{authority.slug}}/request/{{j.common_name}}/">Fetch</a>
{% if j.signable %} {% if j.signable %}
<button onClick="javascript:$.ajax({url:'/api/{{authority.slug}}/request/{{j.common_name}}/',type:'patch'});">Sign</button> <button class="icon sign" onClick="javascript:$(this).addClass('busy');$.ajax({url:'/api/ca/{{authority.slug}}/request/{{j.common_name}}/',type:'patch'});">Sign</button>
{% else %} {% else %}
<button title="Please use certidude command-line utility to sign unusual requests" disabled>Sign</button> <button title="Please use certidude command-line utility to sign unusual requests" disabled>Sign</button>
{% endif %} {% endif %}
<button onClick="javascript:$.ajax({url:'/api/{{authority.slug}}/request/{{j.common_name}}/',type:'delete'});">Delete</button> <button class="icon revoke" onClick="javascript:$(this).addClass('busy');$.ajax({url:'/api/ca/{{authority.slug}}/request/{{j.common_name}}/',type:'delete'});">Delete</button>
<div class="monospace"> <div class="monospace">
{% include 'iconmonstr-certificate-15-icon.svg' %} {% include 'img/iconmonstr-certificate-15-icon.svg' %}
{{j.subject}} {{j.identity}}
</div> </div>
{% if j.email_address %} {% if j.email_address %}
<div class="email">{% include 'iconmonstr-email-2-icon.svg' %} {{ j.email_address }}</div> <div class="email">{% include 'img/iconmonstr-email-2-icon.svg' %} {{ j.email_address }}</div>
{% endif %} {% endif %}
<div class="monospace"> <div class="monospace">
{% include 'iconmonstr-key-2-icon.svg' %} {% include 'img/iconmonstr-key-2-icon.svg' %}
<span title="SHA-1 of public key"> <span title="SHA-1 of public key">
{{ j.sha256sum }} {{ j.sha256sum }}
</span> </span>
@ -30,7 +30,7 @@
{% set key_usage = j.key_usage %} {% set key_usage = j.key_usage %}
{% if key_usage %} {% if key_usage %}
<div> <div>
{% include 'iconmonstr-flag-3-icon.svg' %} {% include 'img/iconmonstr-flag-3-icon.svg' %}
{{j.key_usage}} {{j.key_usage}}
</div> </div>
{% endif %} {% endif %}

View File

@ -1,5 +1,5 @@
<svg height="32" width="32"> <svg height="32" width="32">
<circle cx="16" cy="16" r="13" stroke="black" stroke-width="3" fill="{% if lease %}{% if lease.released %}green{%else %}red{% endif %}{% else %}yellow{% endif %}" /> <circle cx="16" cy="16" r="13" stroke="black" stroke-width="3" fill="{% if lease %}{% if lease.released %}red{%else %}green{% endif %}{% else %}yellow{% endif %}" />
</svg> </svg>
<span> <span>

Before

Width:  |  Height:  |  Size: 428 B

After

Width:  |  Height:  |  Size: 428 B

View File

@ -59,8 +59,8 @@ def subject2dn(subject):
bits = [] bits = []
for j in "CN", "GN", "SN", "C", "S", "L", "O", "OU": for j in "CN", "GN", "SN", "C", "S", "L", "O", "OU":
if getattr(subject, j, None): if getattr(subject, j, None):
bits.append("/%s=%s" % (j, getattr(subject, j))) bits.append("%s=%s" % (j, getattr(subject, j)))
return "".join(bits) return ", ".join(bits)
class CertificateAuthorityConfig(object): class CertificateAuthorityConfig(object):
""" """
@ -225,7 +225,7 @@ class CertificateBase:
return subject2dn(self.issuer) return subject2dn(self.issuer)
@property @property
def distinguished_name(self): def identity(self):
return subject2dn(self.subject) return subject2dn(self.subject)
@property @property

4
doc/strongswan-updown.sh Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
cat << EOF | curl -X POST -d @- -H "Event-Type: $PLUTO_VERB" http://ca.example.com/pub/?id=CA-channel-identifier-goes-here
{"address": "$PLUTO_PEER_SOURCEIP","peer": "$PLUTO_PEER","identity": "$PLUTO_PEER_ID","routed_subnet": "$PLUTO_MY_CLIENT"}
EOF