2016-03-21 21:42:39 +00:00
|
|
|
# encoding: utf-8
|
|
|
|
|
2015-12-12 22:34:08 +00:00
|
|
|
import falcon
|
|
|
|
import mimetypes
|
2016-03-21 21:42:39 +00:00
|
|
|
import logging
|
2015-12-12 22:34:08 +00:00
|
|
|
import os
|
|
|
|
import click
|
2016-03-21 21:42:39 +00:00
|
|
|
from datetime import datetime
|
2015-12-12 22:34:08 +00:00
|
|
|
from time import sleep
|
2016-03-21 21:42:39 +00:00
|
|
|
from certidude import authority, mailer
|
2015-12-12 22:34:08 +00:00
|
|
|
from certidude.auth import login_required, authorize_admin
|
2016-03-27 20:38:14 +00:00
|
|
|
from certidude.user import User
|
2016-03-21 21:42:39 +00:00
|
|
|
from certidude.decorators import serialize, event_source, csrf_protection
|
2015-12-12 22:34:08 +00:00
|
|
|
from certidude.wrappers import Request, Certificate
|
2016-09-17 21:00:14 +00:00
|
|
|
from certidude import const, config
|
2016-03-21 21:42:39 +00:00
|
|
|
|
|
|
|
logger = logging.getLogger("api")
|
2015-12-12 22:34:08 +00:00
|
|
|
|
|
|
|
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"))
|
2015-12-12 22:34:08 +00:00
|
|
|
resp.stream = open(config.AUTHORITY_CERTIFICATE_PATH, "rb")
|
2016-03-21 21:42:39 +00:00
|
|
|
resp.append_header("Content-Type", "application/x-x509-ca-cert")
|
2016-03-27 20:38:14 +00:00
|
|
|
resp.append_header("Content-Disposition", "attachment; filename=%s.crt" %
|
2016-09-17 21:00:14 +00:00
|
|
|
const.HOSTNAME.encode("ascii"))
|
2015-12-12 22:34:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SessionResource(object):
|
2016-03-27 20:38:14 +00:00
|
|
|
@csrf_protection
|
2015-12-12 22:34:08 +00:00
|
|
|
@serialize
|
|
|
|
@login_required
|
|
|
|
@event_source
|
|
|
|
def on_get(self, req, resp):
|
|
|
|
return dict(
|
2016-03-21 21:42:39 +00:00
|
|
|
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 = sum( # Dirty hack!
|
|
|
|
[req.context.get("remote_addr") in j
|
|
|
|
for j in config.REQUEST_SUBNETS]),
|
|
|
|
authority = dict(
|
2017-02-08 20:22:26 +00:00
|
|
|
outbox = dict(
|
|
|
|
server = config.OUTBOX,
|
|
|
|
name = config.OUTBOX_NAME,
|
|
|
|
mail = config.OUTBOX_MAIL
|
|
|
|
),
|
2016-03-31 22:55:51 +00:00
|
|
|
user_certificate_enrollment=config.USER_CERTIFICATE_ENROLLMENT,
|
|
|
|
user_mutliple_certificates=config.USER_MULTIPLE_CERTIFICATES,
|
2016-03-21 21:42:39 +00:00
|
|
|
certificate = authority.certificate,
|
2017-02-07 22:07:21 +00:00
|
|
|
events = config.EVENT_SOURCE_SUBSCRIBE % config.EVENT_SOURCE_TOKEN,
|
2016-03-21 21:42:39 +00:00
|
|
|
requests=authority.list_requests(),
|
|
|
|
signed=authority.list_signed(),
|
|
|
|
revoked=authority.list_revoked(),
|
2016-03-27 20:38:14 +00:00
|
|
|
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,
|
2016-03-29 12:43:34 +00:00
|
|
|
signature = dict(
|
|
|
|
certificate_lifetime=config.CERTIFICATE_LIFETIME,
|
|
|
|
revocation_list_lifetime=config.REVOCATION_LIST_LIFETIME
|
|
|
|
)
|
2016-03-27 20:38:14 +00:00
|
|
|
) if req.context.get("user").is_admin() else None,
|
2016-03-21 21:42:39 +00:00
|
|
|
features=dict(
|
|
|
|
tagging=config.TAGGING_BACKEND,
|
2017-01-26 21:59:12 +00:00
|
|
|
leases=config.LEASES_BACKEND,
|
2016-03-21 21:42:39 +00:00
|
|
|
logging=config.LOGGING_BACKEND))
|
2015-12-12 22:34:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
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")
|
2016-03-21 21:42:39 +00:00
|
|
|
click.echo("Serving: %s" % path)
|
2015-12-12 22:34:08 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2016-03-21 21:42:39 +00:00
|
|
|
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"))
|
|
|
|
|
2016-09-17 21:00:14 +00:00
|
|
|
def process_response(self, req, resp, resource=None):
|
2016-03-21 21:42:39 +00:00
|
|
|
# wtf falcon?!
|
|
|
|
if isinstance(resp.location, unicode):
|
|
|
|
resp.location = resp.location.encode("ascii")
|
|
|
|
|
2015-12-12 22:34:08 +00:00
|
|
|
def certidude_app():
|
2016-03-21 21:42:39 +00:00
|
|
|
from certidude import config
|
2016-03-31 22:55:51 +00:00
|
|
|
from .bundle import BundleResource
|
2015-12-12 22:34:08 +00:00
|
|
|
from .revoked import RevocationListResource
|
|
|
|
from .signed import SignedCertificateListResource, SignedCertificateDetailResource
|
|
|
|
from .request import RequestListResource, RequestDetailResource
|
2017-01-26 21:59:12 +00:00
|
|
|
from .lease import LeaseResource, StatusFileLeaseResource
|
2015-12-12 22:34:08 +00:00
|
|
|
from .whois import WhoisResource
|
2015-12-16 17:41:49 +00:00
|
|
|
from .tag import TagResource, TagDetailResource
|
2016-01-10 17:51:54 +00:00
|
|
|
from .cfg import ConfigResource, ScriptResource
|
2015-12-12 22:34:08 +00:00
|
|
|
|
2016-03-21 21:42:39 +00:00
|
|
|
app = falcon.API(middleware=NormalizeMiddleware())
|
2015-12-12 22:34:08 +00:00
|
|
|
|
|
|
|
# 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/signed/", SignedCertificateListResource())
|
|
|
|
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())
|
2015-12-12 22:34:08 +00:00
|
|
|
|
2016-03-31 22:55:51 +00:00
|
|
|
# Optional user enrollment API call
|
|
|
|
if config.USER_CERTIFICATE_ENROLLMENT:
|
|
|
|
app.add_route("/api/bundle/", BundleResource())
|
|
|
|
|
2016-03-21 21:42:39 +00:00
|
|
|
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)
|
|
|
|
|
2015-12-13 15:11:22 +00:00
|
|
|
|
2015-12-12 22:34:08 +00:00
|
|
|
return app
|