mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-31 17:39:12 +00:00 
			
		
		
		
	Use filesystem extended attribute user.xdg.tags for tags, move leases to user.lease namespace
This commit is contained in:
		| @@ -59,15 +59,29 @@ class SessionResource(object): | ||||
|  | ||||
|         def serialize_certificates(g): | ||||
|             for common_name, path, buf, obj, server in g(): | ||||
|                 # Extract certificate tags from filesystem | ||||
|                 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( | ||||
|                         address = xattr.getxattr(path, "user.address"), | ||||
|                         address = xattr.getxattr(path, "user.lease.address"), | ||||
|                         last_seen = last_seen, | ||||
|                         age = datetime.utcnow() - last_seen | ||||
|                     ) | ||||
|                 except IOError: # No such attribute(s) | ||||
|                     lease = None | ||||
|  | ||||
|                 yield dict( | ||||
|                     serial_number = "%x" % obj.serial_number, | ||||
|                     common_name = common_name, | ||||
| @@ -77,10 +91,7 @@ class SessionResource(object): | ||||
|                     expires = obj.not_valid_after, | ||||
|                     sha256sum = hashlib.sha256(buf).hexdigest(), | ||||
|                     lease = lease, | ||||
|                     tags = dict([ | ||||
|                         (j[9:], xattr.getxattr(path, j).decode("utf-8")) | ||||
|                         for j in xattr.listxattr(path) | ||||
|                         if j.startswith("user.tag.")]) | ||||
|                     tags = tags | ||||
|                 ) | ||||
|  | ||||
|         if req.context.get("user").is_admin(): | ||||
| @@ -96,6 +107,7 @@ class SessionResource(object): | ||||
|             ), | ||||
|             request_submission_allowed = config.REQUEST_SUBMISSION_ALLOWED, | ||||
|             authority = dict( | ||||
|                 tagging = [dict(name=t[0], type=t[1], title=t[2]) for t in config.TAG_TYPES], | ||||
|                 lease = dict( | ||||
|                     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 | ||||
| @@ -199,7 +211,7 @@ def certidude_app(): | ||||
|     app.add_route("/api/signed/{cn}/lease/", LeaseDetailResource()) | ||||
|  | ||||
|     # 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 | ||||
|     app.add_route("/api/lease/", LeaseResource()) | ||||
|   | ||||
| @@ -16,8 +16,8 @@ class LeaseDetailResource(object): | ||||
|     def on_get(self, req, resp, cn): | ||||
|         path, buf, cert = authority.get_signed(cn) | ||||
|         return dict( | ||||
|             last_seen = xattr.getxattr(path, "user.last_seen"), | ||||
|             address = xattr.getxattr(path, "user.address").decode("ascii") | ||||
|             last_seen = xattr.getxattr(path, "user.lease.last_seen"), | ||||
|             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! | ||||
|             raise # TODO proper exception | ||||
|         if req.get_param("action") == "client-connect": | ||||
|             xattr.setxattr(path, "user.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.address", req.get_param("address", required=True).encode("ascii")) | ||||
|             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) | ||||
|  | ||||
|         # client-disconnect is pretty much unusable: | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import falcon | ||||
| import logging | ||||
| import xattr | ||||
| from certidude import authority | ||||
| from xattr import getxattr, removexattr, setxattr | ||||
| from certidude import authority, push | ||||
| from certidude.auth import login_required, authorize_admin | ||||
| from certidude.decorators import serialize, csrf_protection | ||||
|  | ||||
| @@ -13,19 +13,34 @@ class TagResource(object): | ||||
|     @authorize_admin | ||||
|     def on_get(self, req, resp, cn): | ||||
|         path, buf, cert = authority.get_signed(cn) | ||||
|         return dict([ | ||||
|             (k[9:], xattr.getxattr(path, k)) | ||||
|             for k in xattr.listxattr(path) | ||||
|             if k.startswith("user.tag.")]) | ||||
|         tags = [] | ||||
|         try: | ||||
|             for tag in 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 user.xdg.tags attribute | ||||
|             pass | ||||
|         return tags | ||||
|  | ||||
|  | ||||
|     @csrf_protection | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     def on_post(self, req, resp, cn): | ||||
|         from certidude import push | ||||
|         path, buf, cert = authority.get_signed(cn) | ||||
|         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)) | ||||
|         push.publish("tag-update", cn) | ||||
|  | ||||
| @@ -34,9 +49,32 @@ class TagDetailResource(object): | ||||
|     @csrf_protection | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     def on_delete(self, req, resp, cn, key): | ||||
|         from certidude import push | ||||
|     def on_put(self, req, resp, cn, tag): | ||||
|         path, buf, cert = authority.get_signed(cn) | ||||
|         xattr.removexattr(path, "user.tag.%s" % key) | ||||
|         logger.debug(u"Tag %s removed for %s" % (key, cn)) | ||||
|         value = req.get_param("value", required=True) | ||||
|         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) | ||||
|   | ||||
| @@ -5,6 +5,7 @@ function normalizeCommonName(j) { | ||||
| } | ||||
|  | ||||
| function setTag(cn, key, value, indicator) { | ||||
|     $(indicator).addClass("busy"); | ||||
|     $.ajax({ | ||||
|         method: "POST", | ||||
|         url: "/api/signed/" + cn + "/tag/", | ||||
| @@ -24,18 +25,36 @@ function setTag(cn, key, value, indicator) { | ||||
| } | ||||
|  | ||||
| function onTagClicked(event) { | ||||
|     var tag = event.target; | ||||
|     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 updated = prompt("Enter new tag or clear to remove the tag", value); | ||||
|     $(event.target).addClass("busy"); | ||||
|     if (updated == "") { | ||||
|         $(event.target).addClass("busy"); | ||||
|         $.ajax({ | ||||
|             method: "DELETE", | ||||
|             url: "/api/signed/" + cn + "/tag/" + key + "/" | ||||
|             url: "/api/signed/" + cn + "/tag/" + id + "/" | ||||
|         }); | ||||
|     } 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); | ||||
|     if (!value) return; | ||||
|     if (value.length == 0) return; | ||||
|     $(menu).addClass("busy"); | ||||
|     setTag(cn, key, value, event.target); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -51,7 +51,9 @@ | ||||
|         </span> | ||||
|         <select class="icon tag" data-cn="{{ certificate.common_name }}" onChange="onNewTagClicked(event);"> | ||||
|             <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> | ||||
|     </div> | ||||
|     {% endif %} | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| {% for key, value in certificate.tags %} | ||||
| {% for tag in certificate.tags %} | ||||
| <span onclick="onTagClicked(event);" | ||||
| title="{{ key }}={{ value }}" class="tag icon {{ key | replace('.', ' ') }}" | ||||
| data-cn="{{ certificate.common_name }}" | ||||
| data-key="{{ key }}">{{ value }}</span> | ||||
| title="{{ tag.id }}" class="tag icon {{ tag.key | replace('.', ' ') }}" | ||||
| data-cn="{{ certificate.common_name }}">{{ tag.value }}</span> | ||||
| {% 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 | ||||
| # to /etc/certidude/ and make modifications as necessary | ||||
| openvpn profile template = {{ openvpn_profile_template_path }} | ||||
|  | ||||
| [tagging] | ||||
| owner/string = Owner | ||||
| location/string = Location | ||||
| phone/string = Phone | ||||
| other/ = Other | ||||
|   | ||||
		Reference in New Issue
	
	Block a user