certidude/certidude/api/__init__.py

196 lines
8.0 KiB
Python
Raw Normal View History

# encoding: utf-8
import falcon
import mimetypes
import logging
import os
import click
import hashlib
from datetime import datetime
from time import sleep
from certidude import authority, mailer
from certidude.auth import login_required, authorize_admin
from certidude.user import User
from certidude.decorators import serialize, event_source, csrf_protection
from cryptography.x509.oid import NameOID
from certidude import const, config
logger = logging.getLogger("api")
class CertificateStatusResource(object):
"""
openssl ocsp -issuer CAcert_class1.pem -serial 0x<serial no in hex> -url http://localhost -CAfile cacert_both.pem
"""
def on_post(self, req, resp):
ocsp_request = req.stream.read(req.content_length)
for component in decoder.decode(ocsp_request):
click.echo(component)
resp.append_header("Content-Type", "application/ocsp-response")
resp.status = falcon.HTTP_200
raise NotImplementedError()
class CertificateAuthorityResource(object):
def on_get(self, req, resp):
2016-03-29 05:54:55 +00:00
logger.info(u"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.encode("ascii"))
class SessionResource(object):
@csrf_protection
@serialize
@login_required
@event_source
def on_get(self, req, resp):
def serialize_requests(g):
for common_name, path, buf, obj, server in g():
yield dict(
common_name = common_name,
server = server,
md5sum = hashlib.md5(buf).hexdigest(),
sha1sum = hashlib.sha1(buf).hexdigest(),
sha256sum = hashlib.sha256(buf).hexdigest(),
sha512sum = hashlib.sha512(buf).hexdigest()
)
def serialize_certificates(g):
for common_name, path, buf, obj, server in g():
yield dict(
serial_number = "%x" % obj.serial_number,
common_name = common_name,
server = server,
# TODO: key type, key length, key exponent, key modulo
signed = obj.not_valid_before,
expires = obj.not_valid_after,
sha256sum = hashlib.sha256(buf).hexdigest()
)
if req.context.get("user").is_admin():
logger.info("Logged in authority administrator %s" % req.context.get("user"))
else:
logger.info("Logged in authority user %s" % req.context.get("user"))
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,
authority = dict(
common_name = authority.ca_cert.subject.get_attributes_for_oid(
NameOID.COMMON_NAME)[0].value,
outbox = dict(
server = config.OUTBOX,
name = config.OUTBOX_NAME,
mail = config.OUTBOX_MAIL
),
machine_enrollment_allowed=config.MACHINE_ENROLLMENT_ALLOWED,
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(authority.list_requests),
signed=serialize_certificates(authority.list_signed),
revoked=serialize_certificates(authority.list_revoked),
users=User.objects.all(),
admin_users = User.objects.filter_admins(),
user_subnets = config.USER_SUBNETS,
autosign_subnets = config.AUTOSIGN_SUBNETS,
request_subnets = config.REQUEST_SUBNETS,
admin_subnets=config.ADMIN_SUBNETS,
signature = dict(
server_certificate_lifetime=config.SERVER_CERTIFICATE_LIFETIME,
client_certificate_lifetime=config.CLIENT_CERTIFICATE_LIFETIME,
revocation_list_lifetime=config.REVOCATION_LIST_LIFETIME
)
) if req.context.get("user").is_admin() else None,
features=dict(
tagging=config.TAGGING_BACKEND,
2017-01-26 21:59:12 +00:00
leases=config.LEASES_BACKEND,
logging=config.LOGGING_BACKEND))
class StaticResource(object):
def __init__(self, root):
self.root = os.path.realpath(root)
def __call__(self, req, resp):
path = os.path.realpath(os.path.join(self.root, req.path[1:]))
if not path.startswith(self.root):
raise falcon.HTTPForbidden
if os.path.isdir(path):
path = os.path.join(path, "index.html")
click.echo("Serving: %s" % path)
if os.path.exists(path):
content_type, content_encoding = mimetypes.guess_type(path)
if content_type:
resp.append_header("Content-Type", content_type)
if content_encoding:
resp.append_header("Content-Encoding", content_encoding)
resp.stream = open(path, "rb")
else:
resp.status = falcon.HTTP_404
resp.body = "File '%s' not found" % req.path
import ipaddress
class NormalizeMiddleware(object):
def process_request(self, req, resp, *args):
assert not req.get_param("unicode") or req.get_param("unicode") == u"", "Unicode sanity check failed"
req.context["remote_addr"] = ipaddress.ip_address(req.env["REMOTE_ADDR"].decode("utf-8"))
def process_response(self, req, resp, resource=None):
# wtf falcon?!
if isinstance(resp.location, unicode):
resp.location = resp.location.encode("ascii")
def certidude_app():
from certidude import config
from .bundle import BundleResource
from .revoked import RevocationListResource
from .signed import SignedCertificateDetailResource
from .request import RequestListResource, RequestDetailResource
2017-01-26 21:59:12 +00:00
from .lease import LeaseResource, StatusFileLeaseResource
from .whois import WhoisResource
from .tag import TagResource, TagDetailResource
2016-01-10 17:51:54 +00:00
from .cfg import ConfigResource, ScriptResource
app = falcon.API(middleware=NormalizeMiddleware())
# Certificate authority API calls
app.add_route("/api/ocsp/", CertificateStatusResource())
app.add_route("/api/certificate/", CertificateAuthorityResource())
app.add_route("/api/revoked/", RevocationListResource())
app.add_route("/api/signed/{cn}/", SignedCertificateDetailResource())
app.add_route("/api/request/{cn}/", RequestDetailResource())
app.add_route("/api/request/", RequestListResource())
app.add_route("/api/", SessionResource())
# Gateway API calls, should this be moved to separate project?
2017-01-26 21:59:12 +00:00
if config.LEASES_BACKEND == "openvpn-status":
app.add_route("/api/lease/", StatusFileLeaseResource(config.OPENVPN_STATUS_URI))
elif config.LEASES_BACKEND == "sql":
app.add_route("/api/lease/", LeaseResource())
app.add_route("/api/whois/", WhoisResource())
# Optional user enrollment API call
if config.USER_ENROLLMENT_ALLOWED:
app.add_route("/api/bundle/", BundleResource())
if config.TAGGING_BACKEND == "sql":
uri = config.cp.get("tagging", "database")
app.add_route("/api/tag/", TagResource(uri))
app.add_route("/api/tag/{identifier}/", TagDetailResource(uri))
app.add_route("/api/config/", ConfigResource(uri))
app.add_route("/api/script/", ScriptResource(uri))
elif config.TAGGING_BACKEND:
raise ValueError("Invalid tagging.backend = %s" % config.TAGGING_BACKEND)
return app