186 lines
6.4 KiB
Python
186 lines
6.4 KiB
Python
# encoding: utf-8
|
|
|
|
import falcon
|
|
import ipaddress
|
|
import logging
|
|
import os
|
|
from certidude import config
|
|
from certidude.common import drop_privileges
|
|
from user_agents import parse
|
|
from wsgiref.simple_server import make_server, WSGIServer
|
|
from setproctitle import setproctitle
|
|
|
|
class NormalizeMiddleware(object):
|
|
def process_request(self, req, resp, *args):
|
|
req.context["remote_addr"] = ipaddress.ip_address(req.access_route[0])
|
|
if req.user_agent:
|
|
req.context["user_agent"] = parse(req.user_agent)
|
|
else:
|
|
req.context["user_agent"] = "Unknown user agent"
|
|
|
|
|
|
class App(object):
|
|
PORT = 8080
|
|
FORKS = None
|
|
DROP_PRIVILEGES = True
|
|
|
|
def __init__(self):
|
|
app = falcon.API(middleware=NormalizeMiddleware())
|
|
app.req_options.auto_parse_form_urlencoded = True
|
|
self.attach(app)
|
|
|
|
# Set up log handlers
|
|
log_handlers = []
|
|
if config.LOGGING_BACKEND == "sql":
|
|
from certidude.mysqllog import LogHandler
|
|
from certidude.api.log import LogResource
|
|
uri = config.cp.get("logging", "database")
|
|
log_handlers.append(LogHandler(uri))
|
|
elif config.LOGGING_BACKEND == "syslog":
|
|
from logging.handlers import SysLogHandler
|
|
log_handlers.append(SysLogHandler())
|
|
# Browsing syslog via HTTP is obviously not possible out of the box
|
|
elif config.LOGGING_BACKEND:
|
|
raise ValueError("Invalid logging.backend = %s" % config.LOGGING_BACKEND)
|
|
from certidude.push import EventSourceLogHandler
|
|
log_handlers.append(EventSourceLogHandler())
|
|
|
|
for j in logging.Logger.manager.loggerDict.values():
|
|
if isinstance(j, logging.Logger): # PlaceHolder is what?
|
|
if j.name.startswith("certidude."):
|
|
j.setLevel(logging.DEBUG)
|
|
for handler in log_handlers:
|
|
j.addHandler(handler)
|
|
|
|
self.server = make_server("127.0.1.1", self.PORT, app, WSGIServer)
|
|
setproctitle("certidude: %s" % self.NAME)
|
|
|
|
def run(self):
|
|
if self.DROP_PRIVILEGES:
|
|
drop_privileges()
|
|
try:
|
|
self.server.serve_forever()
|
|
except KeyboardInterrupt:
|
|
return
|
|
else:
|
|
return
|
|
|
|
def fork(self):
|
|
for j in range(self.FORKS):
|
|
if not os.fork():
|
|
self.run()
|
|
return True
|
|
return False
|
|
|
|
|
|
|
|
class ReadWriteApp(App):
|
|
NAME = "backend server"
|
|
|
|
def attach(self, app):
|
|
from certidude import authority, config
|
|
from certidude.tokens import TokenManager
|
|
from .signed import SignedCertificateDetailResource
|
|
from .request import RequestListResource, RequestDetailResource
|
|
from .lease import LeaseResource, LeaseDetailResource
|
|
from .script import ScriptResource
|
|
from .tag import TagResource, TagDetailResource
|
|
from .attrib import AttributeResource
|
|
from .bootstrap import BootstrapResource
|
|
from .token import TokenResource
|
|
from .session import SessionResource, CertificateAuthorityResource
|
|
from .revoked import RevokedCertificateDetailResource
|
|
|
|
# Certificate authority API calls
|
|
app.add_route("/api/certificate/", CertificateAuthorityResource())
|
|
app.add_route("/api/signed/{cn}/", SignedCertificateDetailResource(authority))
|
|
app.add_route("/api/request/{cn}/", RequestDetailResource(authority))
|
|
app.add_route("/api/request/", RequestListResource(authority))
|
|
app.add_route("/api/revoked/{serial_number}/", RevokedCertificateDetailResource(authority))
|
|
|
|
token_resource = None
|
|
token_manager = None
|
|
if config.USER_ENROLLMENT_ALLOWED: # TODO: add token enable/disable flag for config
|
|
if config.TOKEN_BACKEND == "sql":
|
|
token_manager = TokenManager(config.TOKEN_DATABASE)
|
|
token_resource = TokenResource(authority, token_manager)
|
|
app.add_route("/api/token/", token_resource)
|
|
elif not config.TOKEN_BACKEND:
|
|
pass
|
|
else:
|
|
raise NotImplementedError("Token backend '%s' not supported" % config.TOKEN_BACKEND)
|
|
|
|
app.add_route("/api/", SessionResource(authority, token_manager))
|
|
|
|
# Extended attributes for scripting etc.
|
|
app.add_route("/api/signed/{cn}/attr/", AttributeResource(authority, namespace="machine"))
|
|
app.add_route("/api/signed/{cn}/script/", ScriptResource(authority))
|
|
|
|
# API calls used by pushed events on the JS end
|
|
app.add_route("/api/signed/{cn}/tag/", TagResource(authority))
|
|
app.add_route("/api/signed/{cn}/lease/", LeaseDetailResource(authority))
|
|
|
|
# API call used to delete existing tags
|
|
app.add_route("/api/signed/{cn}/tag/{tag}/", TagDetailResource(authority))
|
|
|
|
# Gateways can submit leases via this API call
|
|
app.add_route("/api/lease/", LeaseResource(authority))
|
|
|
|
# Bootstrap resource
|
|
app.add_route("/api/bootstrap/", BootstrapResource(authority))
|
|
|
|
# Add SCEP handler if we have any whitelisted subnets
|
|
if config.SCEP_SUBNETS:
|
|
from .scep import SCEPResource
|
|
app.add_route("/api/scep/", SCEPResource(authority))
|
|
return app
|
|
|
|
|
|
class ResponderApp(App):
|
|
PORT = 8081
|
|
FORKS = 4
|
|
NAME = "ocsp responder"
|
|
|
|
def attach(self, app):
|
|
from certidude import authority
|
|
from .ocsp import OCSPResource
|
|
app.add_sink(OCSPResource(authority), prefix="/api/ocsp")
|
|
return app
|
|
|
|
|
|
class RevocationListApp(App):
|
|
PORT = 8082
|
|
FORKS = 2
|
|
NAME = "crl server"
|
|
|
|
def attach(self, app):
|
|
from certidude import authority
|
|
from .revoked import RevocationListResource
|
|
app.add_route("/api/revoked/", RevocationListResource(authority))
|
|
return app
|
|
|
|
|
|
class BuilderApp(App):
|
|
PORT = 8083
|
|
FORKS = 1
|
|
NAME = "image builder"
|
|
|
|
def attach(self, app):
|
|
# LEDE image builder resource
|
|
from certidude import authority
|
|
from .builder import ImageBuilderResource
|
|
app.add_route("/api/build/{profile}/{suggested_filename}", ImageBuilderResource())
|
|
return app
|
|
|
|
|
|
class LogApp(App):
|
|
PORT = 8084
|
|
FORKS = 2
|
|
NAME = "log server"
|
|
|
|
def attach(self, app):
|
|
from certidude.api.log import LogResource
|
|
uri = config.cp.get("logging", "database")
|
|
app.add_route("/api/log/", LogResource(uri))
|
|
return app
|