mirror of
https://github.com/laurivosandi/certidude
synced 2024-12-23 00:25:18 +00:00
Use filesystem extended attribute user.xdg.tags for tags, move leases to user.lease namespace
This commit is contained in:
parent
1813056fc7
commit
f806545bee
@ -59,15 +59,29 @@ class SessionResource(object):
|
|||||||
|
|
||||||
def serialize_certificates(g):
|
def serialize_certificates(g):
|
||||||
for common_name, path, buf, obj, server in g():
|
for common_name, path, buf, obj, server in g():
|
||||||
|
# Extract certificate tags from filesystem
|
||||||
try:
|
try:
|
||||||
last_seen = datetime.strptime(xattr.getxattr(path, "user.last_seen"), "%Y-%m-%dT%H:%M:%S.%fZ")
|
tags = []
|
||||||
|
for tag in xattr.getxattr(path, "user.xdg.tags").split(","):
|
||||||
|
if "=" in tag:
|
||||||
|
k, v = tag.split("=", 1)
|
||||||
|
else:
|
||||||
|
k, v = "other", tag
|
||||||
|
tags.append(dict(id=tag, key=k, value=v))
|
||||||
|
except IOError: # No such attribute(s)
|
||||||
|
tags = None
|
||||||
|
|
||||||
|
# Extract lease information from filesystem
|
||||||
|
try:
|
||||||
|
last_seen = datetime.strptime(xattr.getxattr(path, "user.lease.last_seen"), "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||||
lease = dict(
|
lease = dict(
|
||||||
address = xattr.getxattr(path, "user.address"),
|
address = xattr.getxattr(path, "user.lease.address"),
|
||||||
last_seen = last_seen,
|
last_seen = last_seen,
|
||||||
age = datetime.utcnow() - last_seen
|
age = datetime.utcnow() - last_seen
|
||||||
)
|
)
|
||||||
except IOError: # No such attribute(s)
|
except IOError: # No such attribute(s)
|
||||||
lease = None
|
lease = None
|
||||||
|
|
||||||
yield dict(
|
yield dict(
|
||||||
serial_number = "%x" % obj.serial_number,
|
serial_number = "%x" % obj.serial_number,
|
||||||
common_name = common_name,
|
common_name = common_name,
|
||||||
@ -77,10 +91,7 @@ class SessionResource(object):
|
|||||||
expires = obj.not_valid_after,
|
expires = obj.not_valid_after,
|
||||||
sha256sum = hashlib.sha256(buf).hexdigest(),
|
sha256sum = hashlib.sha256(buf).hexdigest(),
|
||||||
lease = lease,
|
lease = lease,
|
||||||
tags = dict([
|
tags = tags
|
||||||
(j[9:], xattr.getxattr(path, j).decode("utf-8"))
|
|
||||||
for j in xattr.listxattr(path)
|
|
||||||
if j.startswith("user.tag.")])
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if req.context.get("user").is_admin():
|
if req.context.get("user").is_admin():
|
||||||
@ -96,6 +107,7 @@ class SessionResource(object):
|
|||||||
),
|
),
|
||||||
request_submission_allowed = config.REQUEST_SUBMISSION_ALLOWED,
|
request_submission_allowed = config.REQUEST_SUBMISSION_ALLOWED,
|
||||||
authority = dict(
|
authority = dict(
|
||||||
|
tagging = [dict(name=t[0], type=t[1], title=t[2]) for t in config.TAG_TYPES],
|
||||||
lease = dict(
|
lease = dict(
|
||||||
offline = 600, # Seconds from last seen activity to consider lease offline, OpenVPN reneg-sec option
|
offline = 600, # Seconds from last seen activity to consider lease offline, OpenVPN reneg-sec option
|
||||||
dead = 604800 # Seconds from last activity to consider lease dead, X509 chain broken or machine discarded
|
dead = 604800 # Seconds from last activity to consider lease dead, X509 chain broken or machine discarded
|
||||||
@ -199,7 +211,7 @@ def certidude_app():
|
|||||||
app.add_route("/api/signed/{cn}/lease/", LeaseDetailResource())
|
app.add_route("/api/signed/{cn}/lease/", LeaseDetailResource())
|
||||||
|
|
||||||
# API call used to delete existing tags
|
# API call used to delete existing tags
|
||||||
app.add_route("/api/signed/{cn}/tag/{key}/", TagDetailResource())
|
app.add_route("/api/signed/{cn}/tag/{tag}/", TagDetailResource())
|
||||||
|
|
||||||
# Gateways can submit leases via this API call
|
# Gateways can submit leases via this API call
|
||||||
app.add_route("/api/lease/", LeaseResource())
|
app.add_route("/api/lease/", LeaseResource())
|
||||||
|
@ -16,8 +16,8 @@ class LeaseDetailResource(object):
|
|||||||
def on_get(self, req, resp, cn):
|
def on_get(self, req, resp, cn):
|
||||||
path, buf, cert = authority.get_signed(cn)
|
path, buf, cert = authority.get_signed(cn)
|
||||||
return dict(
|
return dict(
|
||||||
last_seen = xattr.getxattr(path, "user.last_seen"),
|
last_seen = xattr.getxattr(path, "user.lease.last_seen"),
|
||||||
address = xattr.getxattr(path, "user.address").decode("ascii")
|
address = xattr.getxattr(path, "user.lease.address").decode("ascii")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -29,8 +29,8 @@ class LeaseResource(object):
|
|||||||
if cert.serial != req.get_param_as_int("serial", required=True): # Badum we have OCSP!
|
if cert.serial != req.get_param_as_int("serial", required=True): # Badum we have OCSP!
|
||||||
raise # TODO proper exception
|
raise # TODO proper exception
|
||||||
if req.get_param("action") == "client-connect":
|
if req.get_param("action") == "client-connect":
|
||||||
xattr.setxattr(path, "user.address", req.get_param("address", required=True).encode("ascii"))
|
xattr.setxattr(path, "user.lease.address", req.get_param("address", required=True).encode("ascii"))
|
||||||
xattr.setxattr(path, "user.last_seen", datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z")
|
xattr.setxattr(path, "user.lease.last_seen", datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z")
|
||||||
push.publish("lease-update", common_name)
|
push.publish("lease-update", common_name)
|
||||||
|
|
||||||
# client-disconnect is pretty much unusable:
|
# client-disconnect is pretty much unusable:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import falcon
|
import falcon
|
||||||
import logging
|
import logging
|
||||||
import xattr
|
from xattr import getxattr, removexattr, setxattr
|
||||||
from certidude import authority
|
from certidude import authority, push
|
||||||
from certidude.auth import login_required, authorize_admin
|
from certidude.auth import login_required, authorize_admin
|
||||||
from certidude.decorators import serialize, csrf_protection
|
from certidude.decorators import serialize, csrf_protection
|
||||||
|
|
||||||
@ -13,19 +13,34 @@ class TagResource(object):
|
|||||||
@authorize_admin
|
@authorize_admin
|
||||||
def on_get(self, req, resp, cn):
|
def on_get(self, req, resp, cn):
|
||||||
path, buf, cert = authority.get_signed(cn)
|
path, buf, cert = authority.get_signed(cn)
|
||||||
return dict([
|
tags = []
|
||||||
(k[9:], xattr.getxattr(path, k))
|
try:
|
||||||
for k in xattr.listxattr(path)
|
for tag in getxattr(path, "user.xdg.tags").split(","):
|
||||||
if k.startswith("user.tag.")])
|
if "=" in tag:
|
||||||
|
k, v = tag.split("=", 1)
|
||||||
|
else:
|
||||||
|
k, v = "other", tag
|
||||||
|
tags.append(dict(id=tag, key=k, value=v))
|
||||||
|
except IOError: # No user.xdg.tags attribute
|
||||||
|
pass
|
||||||
|
return tags
|
||||||
|
|
||||||
|
|
||||||
@csrf_protection
|
@csrf_protection
|
||||||
@login_required
|
@login_required
|
||||||
@authorize_admin
|
@authorize_admin
|
||||||
def on_post(self, req, resp, cn):
|
def on_post(self, req, resp, cn):
|
||||||
from certidude import push
|
|
||||||
path, buf, cert = authority.get_signed(cn)
|
path, buf, cert = authority.get_signed(cn)
|
||||||
key, value = req.get_param("key", required=True), req.get_param("value", required=True)
|
key, value = req.get_param("key", required=True), req.get_param("value", required=True)
|
||||||
xattr.setxattr(path, "user.tag.%s" % key, value.encode("utf-8"))
|
try:
|
||||||
|
tags = set(getxattr(path, "user.xdg.tags").decode("utf-8").split(","))
|
||||||
|
except IOError:
|
||||||
|
tags = set()
|
||||||
|
if key == "other":
|
||||||
|
tags.add(value)
|
||||||
|
else:
|
||||||
|
tags.add("%s=%s" % (key,value))
|
||||||
|
setxattr(path, "user.xdg.tags", ",".join(tags).encode("utf-8"))
|
||||||
logger.debug(u"Tag %s=%s set for %s" % (key, value, cn))
|
logger.debug(u"Tag %s=%s set for %s" % (key, value, cn))
|
||||||
push.publish("tag-update", cn)
|
push.publish("tag-update", cn)
|
||||||
|
|
||||||
@ -34,9 +49,32 @@ class TagDetailResource(object):
|
|||||||
@csrf_protection
|
@csrf_protection
|
||||||
@login_required
|
@login_required
|
||||||
@authorize_admin
|
@authorize_admin
|
||||||
def on_delete(self, req, resp, cn, key):
|
def on_put(self, req, resp, cn, tag):
|
||||||
from certidude import push
|
|
||||||
path, buf, cert = authority.get_signed(cn)
|
path, buf, cert = authority.get_signed(cn)
|
||||||
xattr.removexattr(path, "user.tag.%s" % key)
|
value = req.get_param("value", required=True)
|
||||||
logger.debug(u"Tag %s removed for %s" % (key, cn))
|
try:
|
||||||
|
tags = set(getxattr(path, "user.xdg.tags").decode("utf-8").split(","))
|
||||||
|
except IOError:
|
||||||
|
tags = set()
|
||||||
|
tags.remove(tag)
|
||||||
|
if "=" in tag:
|
||||||
|
tags.add("%s=%s" % (tag.split("=")[0], value))
|
||||||
|
else:
|
||||||
|
tags.add(value)
|
||||||
|
setxattr(path, "user.xdg.tags", ",".join(tags).encode("utf-8"))
|
||||||
|
logger.debug(u"Tag %s set to %s for %s" % (tag, value, cn))
|
||||||
|
push.publish("tag-update", cn)
|
||||||
|
|
||||||
|
@csrf_protection
|
||||||
|
@login_required
|
||||||
|
@authorize_admin
|
||||||
|
def on_delete(self, req, resp, cn, tag):
|
||||||
|
path, buf, cert = authority.get_signed(cn)
|
||||||
|
tags = set(getxattr(path, "user.xdg.tags").split(","))
|
||||||
|
tags.remove(tag)
|
||||||
|
if not tags:
|
||||||
|
removexattr(path, "user.xdg.tags")
|
||||||
|
else:
|
||||||
|
setxattr(path, "user.xdg.tags", ",".join(tags))
|
||||||
|
logger.debug(u"Tag %s removed for %s" % (tag, cn))
|
||||||
push.publish("tag-update", cn)
|
push.publish("tag-update", cn)
|
||||||
|
@ -5,6 +5,7 @@ function normalizeCommonName(j) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setTag(cn, key, value, indicator) {
|
function setTag(cn, key, value, indicator) {
|
||||||
|
$(indicator).addClass("busy");
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/api/signed/" + cn + "/tag/",
|
url: "/api/signed/" + cn + "/tag/",
|
||||||
@ -24,18 +25,36 @@ function setTag(cn, key, value, indicator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onTagClicked(event) {
|
function onTagClicked(event) {
|
||||||
|
var tag = event.target;
|
||||||
var cn = $(event.target).attr("data-cn");
|
var cn = $(event.target).attr("data-cn");
|
||||||
var key = $(event.target).attr("data-key");
|
var id = $(event.target).attr("title");
|
||||||
var value = $(event.target).html();
|
var value = $(event.target).html();
|
||||||
var updated = prompt("Enter new tag or clear to remove the tag", value);
|
var updated = prompt("Enter new tag or clear to remove the tag", value);
|
||||||
$(event.target).addClass("busy");
|
|
||||||
if (updated == "") {
|
if (updated == "") {
|
||||||
|
$(event.target).addClass("busy");
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/api/signed/" + cn + "/tag/" + key + "/"
|
url: "/api/signed/" + cn + "/tag/" + id + "/"
|
||||||
});
|
});
|
||||||
} else if (updated && updated != value) {
|
} else if (updated && updated != value) {
|
||||||
setTag(cn, key, updated, menu);
|
$(tag).addClass("busy");
|
||||||
|
$.ajax({
|
||||||
|
method: "PUT",
|
||||||
|
url: "/api/signed/" + cn + "/tag/" + id + "/",
|
||||||
|
data: { value: updated },
|
||||||
|
dataType: "text",
|
||||||
|
complete: function(xhr, status) {
|
||||||
|
console.info("Tag added successfully", xhr.status, status);
|
||||||
|
},
|
||||||
|
success: function() {
|
||||||
|
$(tag).removeClass("busy");
|
||||||
|
},
|
||||||
|
error: function(xhr, status, e) {
|
||||||
|
console.info("Submitting request failed with:", status, e);
|
||||||
|
alert(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +66,6 @@ function onNewTagClicked(event) {
|
|||||||
var value = prompt("Enter new " + key + " tag for " + cn);
|
var value = prompt("Enter new " + key + " tag for " + cn);
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
if (value.length == 0) return;
|
if (value.length == 0) return;
|
||||||
$(menu).addClass("busy");
|
|
||||||
setTag(cn, key, value, event.target);
|
setTag(cn, key, value, event.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,9 @@
|
|||||||
</span>
|
</span>
|
||||||
<select class="icon tag" data-cn="{{ certificate.common_name }}" onChange="onNewTagClicked(event);">
|
<select class="icon tag" data-cn="{{ certificate.common_name }}" onChange="onNewTagClicked(event);">
|
||||||
<option value="">Add tag...</option>
|
<option value="">Add tag...</option>
|
||||||
{% include 'views/tagtypes.html' %}
|
{% for tag_type in session.authority.tagging %}
|
||||||
|
<option value="{{ tag_type.name }}">{{ tag_type.title }}</option>
|
||||||
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{% for key, value in certificate.tags %}
|
{% for tag in certificate.tags %}
|
||||||
<span onclick="onTagClicked(event);"
|
<span onclick="onTagClicked(event);"
|
||||||
title="{{ key }}={{ value }}" class="tag icon {{ key | replace('.', ' ') }}"
|
title="{{ tag.id }}" class="tag icon {{ tag.key | replace('.', ' ') }}"
|
||||||
data-cn="{{ certificate.common_name }}"
|
data-cn="{{ certificate.common_name }}">{{ tag.value }}</span>
|
||||||
data-key="{{ key }}">{{ value }}</span>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
<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.channel">Channel number</option>
|
|
||||||
<option value="usb.approved">Approved USB device</option>
|
|
@ -137,3 +137,9 @@ format = p12
|
|||||||
# Template for OpenVPN profile, copy certidude/templates/openvpn-client.conf
|
# Template for OpenVPN profile, copy certidude/templates/openvpn-client.conf
|
||||||
# to /etc/certidude/ and make modifications as necessary
|
# to /etc/certidude/ and make modifications as necessary
|
||||||
openvpn profile template = {{ openvpn_profile_template_path }}
|
openvpn profile template = {{ openvpn_profile_template_path }}
|
||||||
|
|
||||||
|
[tagging]
|
||||||
|
owner/string = Owner
|
||||||
|
location/string = Location
|
||||||
|
phone/string = Phone
|
||||||
|
other/ = Other
|
||||||
|
Loading…
Reference in New Issue
Block a user