certidude/certidude/api/session.py

211 lines
10 KiB
Python
Raw Normal View History

from datetime import datetime
from xattr import listxattr, getxattr
import falcon
import hashlib
import logging
from certidude import const, config
from certidude.common import cert_to_dn
from certidude.decorators import serialize, csrf_protection
from certidude.user import User
from .utils import AuthorityHandler
from .utils.firewall import login_required, authorize_admin
logger = logging.getLogger(__name__)
class CertificateAuthorityResource(object):
def on_get(self, req, resp):
logger.info("Served CA certificate to %s", req.context.get("remote_addr"))
resp.stream = open(config.AUTHORITY_CERTIFICATE_PATH, "rb")
resp.append_header("Content-Type", "application/x-x509-ca-cert")
resp.append_header("Content-Disposition", "attachment; filename=%s.crt" %
const.HOSTNAME)
class SessionResource(AuthorityHandler):
def __init__(self, authority, token_manager):
AuthorityHandler.__init__(self, authority)
self.token_manager = token_manager
@csrf_protection
@serialize
@login_required
@authorize_admin
def on_get(self, req, resp):
def serialize_requests(g):
for common_name, path, buf, req, submitted, server in g():
try:
submission_address = getxattr(path, "user.request.address").decode("ascii") # TODO: move to authority.py
except IOError:
submission_address = None
try:
submission_hostname = getxattr(path, "user.request.hostname").decode("ascii") # TODO: move to authority.py
except IOError:
submission_hostname = None
yield dict(
submitted = submitted,
common_name = common_name,
address = submission_address,
hostname = submission_hostname if submission_hostname != submission_address else None,
md5sum = hashlib.md5(buf).hexdigest(),
sha1sum = hashlib.sha1(buf).hexdigest(),
sha256sum = hashlib.sha256(buf).hexdigest(),
sha512sum = hashlib.sha512(buf).hexdigest()
)
def serialize_revoked(g):
for common_name, path, buf, cert, signed, expired, revoked, reason in g(limit=5):
yield dict(
serial = "%x" % cert.serial_number,
common_name = common_name,
# TODO: key type, key length, key exponent, key modulo
signed = signed,
expired = expired,
revoked = revoked,
reason = reason,
sha256sum = hashlib.sha256(buf).hexdigest())
def serialize_certificates(g):
for common_name, path, buf, cert, signed, expires in g():
# Extract certificate tags from filesystem
try:
tags = []
for tag in getxattr(path, "user.xdg.tags").decode("utf-8").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
attributes = {}
for key in listxattr(path):
if key.startswith(b"user.machine."):
attributes[key[13:].decode("ascii")] = getxattr(path, key).decode("ascii")
# Extract lease information from filesystem
try:
lease = dict(
inner_address = getxattr(path, "user.lease.inner_address").decode("ascii"),
outer_address = getxattr(path, "user.lease.outer_address").decode("ascii"),
last_seen = datetime.strptime(getxattr(path, "user.lease.last_seen").decode("ascii"), "%Y-%m-%dT%H:%M:%S.%fZ")
)
except IOError: # No such attribute(s)
lease = None
try:
signer_username = getxattr(path, "user.signature.username").decode("ascii")
except IOError:
signer_username = None
# TODO: dedup
serialized = dict(
serial = "%x" % cert.serial_number,
organizational_unit = cert.subject.native.get("organizational_unit_name"),
common_name = common_name,
# TODO: key type, key length, key exponent, key modulo
signed = signed,
expires = expires,
sha256sum = hashlib.sha256(buf).hexdigest(),
signer = signer_username,
lease = lease,
tags = tags,
attributes = attributes or None,
responder_url = None
)
for e in cert["tbs_certificate"]["extensions"].native:
if e["extn_id"] == "key_usage":
serialized["key_usage"] = e["extn_value"]
elif e["extn_id"] == "extended_key_usage":
serialized["extended_key_usage"] = e["extn_value"]
elif e["extn_id"] == "basic_constraints":
serialized["basic_constraints"] = e["extn_value"]
elif e["extn_id"] == "crl_distribution_points":
for c in e["extn_value"]:
serialized["revoked_url"] = c["distribution_point"]
break
serialized["extended_key_usage"] = e["extn_value"]
elif e["extn_id"] == "authority_information_access":
for a in e["extn_value"]:
if a["access_method"] == "ocsp":
serialized["responder_url"] = a["access_location"]
else:
raise NotImplementedError("Don't know how to handle AIA access method %s" % a["access_method"])
elif e["extn_id"] == "authority_key_identifier":
pass
elif e["extn_id"] == "key_identifier":
pass
elif e["extn_id"] == "subject_alt_name":
serialized["subject_alt_name"], = e["extn_value"]
else:
raise NotImplementedError("Don't know how to handle extension %s" % e["extn_id"])
yield serialized
logger.info("Logged in authority administrator %s from %s with %s" % (
req.context.get("user"), req.context.get("remote_addr"), req.context.get("user_agent")))
return dict(
user = dict(
name=req.context.get("user").name,
gn=req.context.get("user").given_name,
sn=req.context.get("user").surname,
mail=req.context.get("user").mail
),
request_submission_allowed = config.REQUEST_SUBMISSION_ALLOWED,
service = dict(
protocols = config.SERVICE_PROTOCOLS,
routers = [j[0] for j in self.authority.list_signed(
common_name=config.SERVICE_ROUTERS)]
),
builder = dict(
profiles = config.IMAGE_BUILDER_PROFILES or None
),
authority = dict(
hostname = const.FQDN,
tokens = self.token_manager.list() if self.token_manager else None,
tagging = [dict(name=t[0], type=t[1], title=t[2]) for t in config.TAG_TYPES],
certificate = dict(
algorithm = self.authority.public_key.algorithm,
common_name = self.authority.certificate.subject.native["common_name"],
distinguished_name = cert_to_dn(self.authority.certificate),
md5sum = hashlib.md5(self.authority.certificate_buf).hexdigest(),
blob = self.authority.certificate_buf.decode("ascii"),
organization = self.authority.certificate["tbs_certificate"]["subject"].native.get("organization_name"),
signed = self.authority.certificate["tbs_certificate"]["validity"]["not_before"].native.replace(tzinfo=None),
expires = self.authority.certificate["tbs_certificate"]["validity"]["not_after"].native.replace(tzinfo=None)
),
mailer = dict(
name = config.MAILER_NAME,
address = config.MAILER_ADDRESS
) if config.MAILER_ADDRESS else None,
user_enrollment_allowed=config.USER_ENROLLMENT_ALLOWED,
user_multiple_certificates=config.USER_MULTIPLE_CERTIFICATES,
events = config.EVENT_SOURCE_SUBSCRIBE % config.EVENT_SOURCE_TOKEN,
requests=serialize_requests(self.authority.list_requests),
signed=serialize_certificates(self.authority.list_signed),
revoked=serialize_revoked(self.authority.list_revoked),
signature = dict(
revocation_list_lifetime=config.REVOCATION_LIST_LIFETIME,
profiles = sorted([p.serialize() for p in config.PROFILES.values()], key=lambda p:p.get("slug")),
)
),
authorization = dict(
admin_users = User.objects.filter_admins(),
user_subnets = config.USER_SUBNETS or None,
autosign_subnets = config.AUTOSIGN_SUBNETS or None,
request_subnets = config.REQUEST_SUBNETS or None,
machine_enrollment_subnets=config.MACHINE_ENROLLMENT_SUBNETS or None,
admin_subnets=config.ADMIN_SUBNETS or None,
ocsp_subnets = config.OCSP_SUBNETS or None,
crl_subnets = config.CRL_SUBNETS or None,
scep_subnets = config.SCEP_SUBNETS or None,
),
features=dict(
token=bool(config.TOKEN_URL),
tagging=True,
leases=True,
logging=config.LOGGING_BACKEND)
)