mirror of
https://github.com/laurivosandi/certidude
synced 2024-11-14 00:46:44 +00:00
Move to pre-forking model for backend API-s
This commit is contained in:
parent
2f301d4fec
commit
6e50c85c85
@ -2,10 +2,13 @@
|
||||
|
||||
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):
|
||||
@ -15,89 +18,168 @@ class NormalizeMiddleware(object):
|
||||
else:
|
||||
req.context["user_agent"] = "Unknown user agent"
|
||||
|
||||
def certidude_app(log_handlers=[]):
|
||||
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 .builder import ImageBuilderResource
|
||||
from .session import SessionResource, CertificateAuthorityResource
|
||||
|
||||
app = falcon.API(middleware=NormalizeMiddleware())
|
||||
app.req_options.auto_parse_form_urlencoded = True
|
||||
class App(object):
|
||||
PORT = 8080
|
||||
FORKS = None
|
||||
DROP_PRIVILEGES = True
|
||||
|
||||
# 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))
|
||||
def __init__(self):
|
||||
app = falcon.API(middleware=NormalizeMiddleware())
|
||||
app.req_options.auto_parse_form_urlencoded = True
|
||||
self.attach(app)
|
||||
|
||||
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
|
||||
# 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:
|
||||
raise NotImplementedError("Token backend '%s' not supported" % config.TOKEN_BACKEND)
|
||||
return
|
||||
|
||||
app.add_route("/api/", SessionResource(authority, token_manager))
|
||||
def fork(self):
|
||||
for j in range(self.FORKS):
|
||||
if not os.fork():
|
||||
self.run()
|
||||
return True
|
||||
return False
|
||||
|
||||
# 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))
|
||||
class ReadWriteApp(App):
|
||||
NAME = "backend server"
|
||||
|
||||
# Gateways can submit leases via this API call
|
||||
app.add_route("/api/lease/", LeaseResource(authority))
|
||||
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
|
||||
|
||||
# Bootstrap resource
|
||||
app.add_route("/api/bootstrap/", BootstrapResource(authority))
|
||||
# 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))
|
||||
|
||||
# LEDE image builder resource
|
||||
app.add_route("/api/build/{profile}/{suggested_filename}", ImageBuilderResource())
|
||||
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)
|
||||
|
||||
# Add CRL handler if we have any whitelisted subnets
|
||||
if config.CRL_SUBNETS:
|
||||
from .revoked import RevocationListResource
|
||||
app.add_route("/api/revoked/", RevocationListResource(authority))
|
||||
app.add_route("/api/", SessionResource(authority, token_manager))
|
||||
|
||||
# Add SCEP handler if we have any whitelisted subnets
|
||||
if config.SCEP_SUBNETS:
|
||||
from .scep import SCEPResource
|
||||
app.add_route("/api/scep/", SCEPResource(authority))
|
||||
# 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))
|
||||
|
||||
if config.OCSP_SUBNETS:
|
||||
# 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
|
||||
|
||||
# Set up log handlers
|
||||
if config.LOGGING_BACKEND == "sql":
|
||||
from certidude.mysqllog import LogHandler
|
||||
|
||||
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")
|
||||
log_handlers.append(LogHandler(uri))
|
||||
app.add_route("/api/log/", LogResource(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)
|
||||
|
||||
return app
|
||||
return app
|
||||
|
@ -12,4 +12,4 @@ class LogResource(RelationalMixin):
|
||||
def on_get(self, req, resp):
|
||||
# TODO: Add last id parameter
|
||||
return self.iterfetch("select * from log order by created desc limit ?",
|
||||
req.get_param_as_int("limit"))
|
||||
req.get_param_as_int("limit", required=True))
|
||||
|
@ -30,3 +30,18 @@ class RevocationListResource(AuthorityHandler):
|
||||
logger.debug("Client %s asked revocation list in unsupported format" % req.context.get("remote_addr"))
|
||||
raise falcon.HTTPUnsupportedMediaType(
|
||||
"Client did not accept application/x-pkcs7-crl or application/x-pem-file")
|
||||
|
||||
|
||||
class RevokedCertificateDetailResource(AuthorityHandler):
|
||||
def on_get(self, req, resp, serial_number):
|
||||
try:
|
||||
path, buf, cert, signed, expires, revoked, reason = self.authority.get_revoked(serial_number)
|
||||
except EnvironmentError:
|
||||
logger.warning("Failed to serve non-existant revoked certificate with serial %s to %s",
|
||||
serial_number, req.context.get("remote_addr"))
|
||||
raise falcon.HTTPNotFound()
|
||||
resp.set_header("Content-Type", "application/x-pem-file")
|
||||
resp.set_header("Content-Disposition", ("attachment; filename=%x.pem" % cert.serial_number))
|
||||
resp.body = buf
|
||||
logger.debug("Served revoked certificate with serial %s to %s",
|
||||
serial_number, req.context.get("remote_addr"))
|
||||
|
@ -85,12 +85,10 @@ class SessionResource(AuthorityHandler):
|
||||
|
||||
# Extract lease information from filesystem
|
||||
try:
|
||||
last_seen = datetime.strptime(getxattr(path, "user.lease.last_seen").decode("ascii"), "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
lease = dict(
|
||||
inner_address = getxattr(path, "user.lease.inner_address").decode("ascii"),
|
||||
outer_address = getxattr(path, "user.lease.outer_address").decode("ascii"),
|
||||
last_seen = last_seen,
|
||||
age = datetime.utcnow() - last_seen
|
||||
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
|
||||
@ -166,10 +164,6 @@ class SessionResource(AuthorityHandler):
|
||||
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],
|
||||
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
|
||||
),
|
||||
certificate = dict(
|
||||
algorithm = self.authority.public_key.algorithm,
|
||||
common_name = self.authority.certificate.subject.native["common_name"],
|
||||
|
@ -13,7 +13,7 @@ from asn1crypto.csr import CertificationRequest
|
||||
from certbuilder import CertificateBuilder
|
||||
from certidude import config, push, mailer, const
|
||||
from certidude import errors
|
||||
from certidude.common import cn_to_dn, generate_serial, random
|
||||
from certidude.common import cn_to_dn, generate_serial
|
||||
from crlbuilder import CertificateListBuilder, pem_armor_crl
|
||||
from csrbuilder import CSRBuilder, pem_armor_csr
|
||||
from datetime import datetime, timedelta
|
||||
|
162
certidude/cli.py
162
certidude/cli.py
@ -23,6 +23,7 @@ from datetime import datetime, timedelta
|
||||
from glob import glob
|
||||
from ipaddress import ip_network
|
||||
from oscrypto import asymmetric
|
||||
from setproctitle import setproctitle
|
||||
|
||||
try:
|
||||
import coverage
|
||||
@ -43,6 +44,18 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
NOW = datetime.utcnow()
|
||||
|
||||
def make_runtime_dirs(func):
|
||||
def wrapped(**args):
|
||||
# systemd doesn't support RuntimeDirectoryPreserve=yes on Xenial
|
||||
# otherwise this should be part of service files
|
||||
# with RuntimeDirectory=certidude
|
||||
if not os.path.exists(const.RUN_DIR):
|
||||
click.echo("Creating: %s" % const.RUN_DIR)
|
||||
os.makedirs(const.RUN_DIR)
|
||||
os.chmod(const.RUN_DIR, 0o755)
|
||||
return func(**args)
|
||||
return wrapped
|
||||
|
||||
def fqdn_required(func):
|
||||
def wrapped(**args):
|
||||
common_name = args.get("common_name")
|
||||
@ -1024,6 +1037,12 @@ def certidude_provision_authority(username, kerberos_keytab, nginx_config, tls_c
|
||||
assert subprocess.check_output(["/usr/bin/lsb_release", "-cs"]) in (b"trusty\n", b"xenial\n", b"bionic\n"), "Only Ubuntu 16.04 supported at the moment"
|
||||
assert os.getuid() == 0 and os.getgid() == 0, "Authority can be set up only by root"
|
||||
|
||||
session = dict(
|
||||
authority = dict(
|
||||
hostname = common_name
|
||||
)
|
||||
)
|
||||
|
||||
def verbose_symlink(name, target):
|
||||
if not os.path.islink(name):
|
||||
click.echo("Symlinking %s to %s" % (name, target))
|
||||
@ -1173,14 +1192,9 @@ def certidude_provision_authority(username, kerberos_keytab, nginx_config, tls_c
|
||||
|
||||
def verbose_render_systemd_service(template, target, context):
|
||||
target_path = "/etc/systemd/system/%s" % target
|
||||
if os.path.exists(target_path):
|
||||
click.echo("File %s already exists, remove to regenerate" % target_path)
|
||||
else:
|
||||
buf = env.get_template(template).render(context)
|
||||
with open(target_path, "w") as fh:
|
||||
fh.write(buf)
|
||||
click.echo("File %s created" % target_path)
|
||||
os.system("systemctl daemon-reload")
|
||||
buf = env.get_template(template).render(context)
|
||||
with open(target_path, "w") as fh:
|
||||
fh.write(buf)
|
||||
|
||||
verbose_symlink("/etc/nginx/sites-enabled/certidude.conf", "../sites-available/certidude.conf")
|
||||
|
||||
@ -1192,6 +1206,9 @@ def certidude_provision_authority(username, kerberos_keytab, nginx_config, tls_c
|
||||
verbose_render_systemd_service("server/ldap-kinit.timer", "certidude-ldap-kinit.timer", vars())
|
||||
verbose_render_systemd_service("snippets/nginx-ocsp-cache.service", "certidude-ocsp-cache.service", vars())
|
||||
verbose_render_systemd_service("snippets/nginx-ocsp-cache.timer", "certidude-ocsp-cache.timer", vars())
|
||||
verbose_render_systemd_service("server/housekeeping-daily.service", "certidude-housekeeping-daily.service", vars())
|
||||
verbose_render_systemd_service("server/housekeeping-daily.timer", "certidude-housekeeping-daily.timer", vars())
|
||||
os.system("systemctl daemon-reload")
|
||||
else:
|
||||
raise NotImplementedError("Not systemd based OS, don't know how to set up initscripts")
|
||||
|
||||
@ -1409,14 +1426,6 @@ def certidude_provision_authority(username, kerberos_keytab, nginx_config, tls_c
|
||||
assert os.stat("/etc/nginx/sites-available/certidude.conf").st_mode == 0o100600
|
||||
assert os.stat("/etc/certidude/server.conf").st_mode == 0o100600
|
||||
|
||||
# Disable legacy garbage
|
||||
if os.path.exists("/etc/cron.hourly/certidude"):
|
||||
os.unlink("/etc/cron.hourly/certidude")
|
||||
if os.path.exists("/etc/cron.daily/certidude"):
|
||||
os.unlink("/etc/cron.daily/certidude")
|
||||
if os.path.exists("/etc/systemd/system/certidude.service"):
|
||||
os.unlink("/etc/systemd/system/certidude.service")
|
||||
|
||||
click.echo("To enable e-mail notifications install Postfix as sattelite system and set mailer address in %s" % const.SERVER_CONFIG_PATH)
|
||||
click.echo()
|
||||
click.echo("Use following commands to inspect the newly created files:")
|
||||
@ -1434,6 +1443,8 @@ def certidude_provision_authority(username, kerberos_keytab, nginx_config, tls_c
|
||||
os.system("systemctl enable certidude-backend.service")
|
||||
os.system("systemctl enable nginx")
|
||||
os.system("systemctl enable certidude-ocsp-cache.timer")
|
||||
os.system("systemctl enable certidude-housekeeping-daily.timer")
|
||||
os.system("systemctl start certidude-housekeeping-daily.timer")
|
||||
os.system("systemctl start certidude-ocsp-cache.timer")
|
||||
if realm:
|
||||
os.system("systemctl enable certidude-ldap-kinit.timer")
|
||||
@ -1561,21 +1572,24 @@ def certidude_revoke(common_name, reason):
|
||||
|
||||
|
||||
@click.command("kinit", help="Initialize Kerberos credential cache for LDAP")
|
||||
@make_runtime_dirs
|
||||
def certidude_housekeeping_kinit():
|
||||
from certidude import config
|
||||
|
||||
# Update LDAP service ticket if Certidude is joined to domain
|
||||
if os.path.exists("/etc/krb5.keytab"):
|
||||
if not os.path.exists("/run/certidude"):
|
||||
os.makedirs("/run/certidude")
|
||||
_, kdc = config.LDAP_ACCOUNTS_URI.rsplit("/", 1)
|
||||
cmd = "KRB5CCNAME=/run/certidude/krb5cc.part kinit -k %s$ -S ldap/%s@%s -t /etc/krb5.keytab" % (
|
||||
const.HOSTNAME.upper(), kdc, config.KERBEROS_REALM
|
||||
)
|
||||
click.echo("Executing: %s" % cmd)
|
||||
os.system(cmd)
|
||||
os.system("chown certidude:certidude /run/certidude/krb5cc.part")
|
||||
os.rename("/run/certidude/krb5cc.part", "/run/certidude/krb5cc")
|
||||
if not os.path.exists("/etc/krb5.keytab"):
|
||||
raise click.ClickException("No Kerberos keytab configured")
|
||||
|
||||
_, kdc = config.LDAP_ACCOUNTS_URI.rsplit("/", 1)
|
||||
cmd = "KRB5CCNAME=%s.part kinit -k %s$ -S ldap/%s@%s -t /etc/krb5.keytab" % (
|
||||
config.LDAP_GSSAPI_CRED_CACHE,
|
||||
const.HOSTNAME.upper(), kdc, config.KERBEROS_REALM
|
||||
)
|
||||
click.echo("Executing: %s" % cmd)
|
||||
if os.system(cmd):
|
||||
raise click.ClickException("Failed to initialize Kerberos credential cache!")
|
||||
os.system("chown certidude:certidude %s.part" % config.LDAP_GSSAPI_CRED_CACHE)
|
||||
os.rename("%s.part" % config.LDAP_GSSAPI_CRED_CACHE, config.LDAP_GSSAPI_CRED_CACHE)
|
||||
|
||||
|
||||
@click.command("daily", help="Send notifications about expired certificates")
|
||||
@ -1614,44 +1628,18 @@ def certidude_housekeeping_expiration():
|
||||
# TODO: Send separate e-mails to subjects
|
||||
|
||||
|
||||
@click.command("serve", help="Run server")
|
||||
@click.option("-p", "--port", default=8080, help="Listen port")
|
||||
@click.option("-l", "--listen", default="127.0.1.1", help="Listen address")
|
||||
@click.command("serve", help="Run API backend server")
|
||||
@click.option("-f", "--fork", default=False, is_flag=True, help="Fork to background")
|
||||
def certidude_serve(port, listen, fork):
|
||||
from certidude import authority, const, push
|
||||
|
||||
if port == 80:
|
||||
click.echo("WARNING: Please run Certidude behind nginx, remote address is assumed to be forwarded by nginx!")
|
||||
|
||||
@make_runtime_dirs
|
||||
def certidude_serve(fork):
|
||||
from certidude import const, push, config
|
||||
click.echo("Using configuration from: %s" % const.SERVER_CONFIG_PATH)
|
||||
|
||||
log_handlers = []
|
||||
|
||||
from certidude import config
|
||||
|
||||
click.echo("OCSP responder subnets: %s" % config.OCSP_SUBNETS)
|
||||
click.echo("CRL subnets: %s" % config.CRL_SUBNETS)
|
||||
click.echo("SCEP subnets: %s" % config.SCEP_SUBNETS)
|
||||
|
||||
click.echo("Loading signature profiles:")
|
||||
for profile in config.PROFILES.values():
|
||||
click.echo("- %s" % profile)
|
||||
click.echo()
|
||||
|
||||
# Rebuild reverse mapping
|
||||
for cn, path, buf, cert, signed, expires in authority.list_signed():
|
||||
by_serial = os.path.join(config.SIGNED_BY_SERIAL_DIR, "%040x.pem" % cert.serial_number)
|
||||
if not os.path.exists(by_serial):
|
||||
click.echo("Linking %s to ../%s.pem" % (by_serial, cn))
|
||||
os.symlink("../%s.pem" % cn, by_serial)
|
||||
|
||||
# Process directories
|
||||
if not os.path.exists(const.RUN_DIR):
|
||||
click.echo("Creating: %s" % const.RUN_DIR)
|
||||
os.makedirs(const.RUN_DIR)
|
||||
os.chmod(const.RUN_DIR, 0o755)
|
||||
|
||||
click.echo("Users subnets: %s" %
|
||||
", ".join([str(j) for j in config.USER_SUBNETS]))
|
||||
click.echo("Administrative subnets: %s" %
|
||||
@ -1661,47 +1649,43 @@ def certidude_serve(port, listen, fork):
|
||||
click.echo("Request submissions allowed from following subnets: %s" %
|
||||
", ".join([str(j) for j in config.REQUEST_SUBNETS]))
|
||||
|
||||
click.echo("Serving API at %s:%d" % (listen, port))
|
||||
from wsgiref.simple_server import make_server, WSGIServer
|
||||
from certidude.api import certidude_app
|
||||
|
||||
|
||||
click.echo("Listening on %s:%d" % (listen, port))
|
||||
|
||||
app = certidude_app(log_handlers)
|
||||
httpd = make_server(listen, port, app, WSGIServer)
|
||||
|
||||
|
||||
"""
|
||||
Drop privileges
|
||||
"""
|
||||
|
||||
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)
|
||||
from certidude.api import ReadWriteApp, BuilderApp, ResponderApp, RevocationListApp, LogApp
|
||||
|
||||
if not fork or not os.fork():
|
||||
pid = os.getpid()
|
||||
with open(const.SERVER_PID_PATH, "w") as pidfile:
|
||||
pidfile.write("%d\n" % pid)
|
||||
|
||||
# Rebuild reverse mapping
|
||||
from certidude import authority
|
||||
for cn, path, buf, cert, signed, expires in authority.list_signed():
|
||||
by_serial = os.path.join(config.SIGNED_BY_SERIAL_DIR, "%040x.pem" % cert.serial_number)
|
||||
if not os.path.exists(by_serial):
|
||||
click.echo("Linking %s to ../%s.pem" % (by_serial, cn))
|
||||
os.symlink("../%s.pem" % cn, by_serial)
|
||||
|
||||
push.publish("server-started")
|
||||
logger.debug("Started Certidude at %s", const.FQDN)
|
||||
|
||||
drop_privileges()
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
click.echo("Caught Ctrl-C, exiting...")
|
||||
push.publish("server-stopped")
|
||||
logger.debug("Shutting down Certidude")
|
||||
return
|
||||
if fork and config.OCSP_SUBNETS:
|
||||
click.echo("OCSP responder subnets: %s" % config.OCSP_SUBNETS)
|
||||
if ResponderApp().fork():
|
||||
return
|
||||
if fork and config.CRL_SUBNETS:
|
||||
click.echo("CRL subnets: %s" % config.CRL_SUBNETS)
|
||||
if RevocationListApp().fork():
|
||||
return
|
||||
if fork:
|
||||
if BuilderApp().fork():
|
||||
return
|
||||
if fork and config.LOGGING_BACKEND == "sql":
|
||||
if LogApp().fork():
|
||||
return
|
||||
|
||||
ReadWriteApp().run()
|
||||
push.publish("server-stopped")
|
||||
logger.debug("Shutting down Certidude API backend")
|
||||
return
|
||||
|
||||
|
||||
@click.command("yubikey", help="Set up Yubikey as client authentication token")
|
||||
|
@ -2,6 +2,7 @@
|
||||
import os
|
||||
import click
|
||||
import subprocess
|
||||
from setproctitle import getproctitle
|
||||
from random import SystemRandom
|
||||
|
||||
random = SystemRandom()
|
||||
@ -98,8 +99,8 @@ def drop_privileges():
|
||||
os.setgroups(restricted_groups)
|
||||
os.setgid(gid)
|
||||
os.setuid(uid)
|
||||
click.echo("Switched to user %s (uid=%d, gid=%d); member of groups %s" %
|
||||
("certidude", os.getuid(), os.getgid(), ", ".join([str(j) for j in os.getgroups()])))
|
||||
click.echo("Switched %s (pid=%d) to user %s (uid=%d, gid=%d); member of groups %s" %
|
||||
(getproctitle(), os.getpid(), "certidude", os.getuid(), os.getgid(), ", ".join([str(j) for j in os.getgroups()])))
|
||||
os.umask(0o007)
|
||||
|
||||
def apt(packages):
|
||||
|
@ -22,7 +22,6 @@ PROFILE_CONFIG_PATH = os.path.join(CONFIG_DIR, "profile.conf")
|
||||
CLIENT_CONFIG_PATH = os.path.join(CONFIG_DIR, "client.conf")
|
||||
SERVICES_CONFIG_PATH = os.path.join(CONFIG_DIR, "services.conf")
|
||||
SERVER_PID_PATH = os.path.join(RUN_DIR, "server.pid")
|
||||
SERVER_LOG_PATH = "/var/log/certidude-server.log"
|
||||
STORAGE_PATH = "/var/lib/certidude/"
|
||||
|
||||
try:
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"title": "502 Bad Gateway",
|
||||
"description": "It seems the server had bit of a hiccup, perhaps this helps: systemctl restart certidude && journalctl -f"
|
||||
"description": "It seems the server had bit of a hiccup, perhaps this helps: systemctl restart certidude-backend && journalctl -f"
|
||||
}
|
||||
|
7
certidude/templates/server/housekeeping-daily.service
Normal file
7
certidude/templates/server/housekeeping-daily.service
Normal file
@ -0,0 +1,7 @@
|
||||
[Unit]
|
||||
Description=Run daily housekeeping jobs, eg certificate expiration notifications
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart={{ certidude_path }} housekeeping daily
|
||||
|
6
certidude/templates/server/housekeeping-daily.timer
Normal file
6
certidude/templates/server/housekeeping-daily.timer
Normal file
@ -0,0 +1,6 @@
|
||||
[Timer]
|
||||
Persistent=true
|
||||
OnCalendar=daily
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -31,6 +31,16 @@ server {
|
||||
server_name {{ common_name }};
|
||||
listen 80 default_server;
|
||||
|
||||
# Proxy pass CRL server
|
||||
location /api/revoked/ {
|
||||
proxy_pass http://127.0.1.1:8082/api/revoked/;
|
||||
}
|
||||
|
||||
# Proxy pass OCSP responder
|
||||
location /api/ocsp/ {
|
||||
proxy_pass http://127.0.1.1:8081/api/ocsp/;
|
||||
}
|
||||
|
||||
# Proxy pass to backend
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.1.1:8080/api/;
|
||||
@ -90,6 +100,16 @@ server {
|
||||
# once it has been configured
|
||||
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
|
||||
|
||||
# Proxy pass image builder
|
||||
location /api/log/ {
|
||||
proxy_pass http://127.0.1.1:8084/api/log/;
|
||||
}
|
||||
|
||||
# Proxy pass image builder
|
||||
location /api/builder/ {
|
||||
proxy_pass http://127.0.1.1:8083/api/builder/;
|
||||
}
|
||||
|
||||
# Proxy pass to backend
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.1.1:8080/api/;
|
||||
|
0
certidude/templates/server/responder.service
Normal file
0
certidude/templates/server/responder.service
Normal file
@ -1,17 +0,0 @@
|
||||
[Unit]
|
||||
Description=Certidude server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=/etc/environment
|
||||
Environment=LANG=C.UTF-8
|
||||
Environment=PYTHON_EGG_CACHE=/tmp/.cache
|
||||
PIDFile=/run/certidude/server.pid
|
||||
KillSignal=SIGINT
|
||||
ExecStart={{ certidude_path }} serve
|
||||
TimeoutSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
@ -1,7 +1,7 @@
|
||||
[Unit]
|
||||
Description=Cache OCSP responses for nginx OCSP stapling
|
||||
Requires=nginx.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
Requires=nginx.service
|
||||
ExecStart=-/usr/bin/curl --cert-status https://{{ common_name }}:8443/ --cacert /etc/certidude/authority/{{ session.authority.hostname }}/ca_cert.pem
|
||||
ExecStart=-/usr/bin/curl --cert-status https://{{ session.authority.hostname }}:8443/ --cacert /etc/certidude/authority/{{ session.authority.hostname }}/ca_cert.pem
|
||||
|
@ -7,3 +7,4 @@ oscrypto
|
||||
requests
|
||||
jinja2
|
||||
ipsecparse
|
||||
setproctitle
|
||||
|
@ -7,6 +7,7 @@ import re
|
||||
import shutil
|
||||
import sys
|
||||
from asn1crypto import pem, x509
|
||||
from glob import glob
|
||||
from oscrypto import asymmetric
|
||||
from csrbuilder import CSRBuilder, pem_armor_csr
|
||||
from asn1crypto.util import OrderedDict
|
||||
@ -159,13 +160,11 @@ def clean_server():
|
||||
files = [
|
||||
"/etc/krb5.keytab",
|
||||
"/etc/samba/smb.conf",
|
||||
"/etc/certidude/server.conf",
|
||||
"/etc/certidude/builder.conf",
|
||||
"/etc/certidude/profile.conf",
|
||||
"/etc/certidude/*.conf",
|
||||
"/var/log/certidude.log",
|
||||
"/etc/cron.daily/certidude",
|
||||
"/etc/cron.hourly/certidude",
|
||||
"/etc/systemd/system/certidude.service",
|
||||
"/etc/systemd/system/certidude*",
|
||||
"/etc/nginx/sites-available/ca.conf",
|
||||
"/etc/nginx/sites-enabled/ca.conf",
|
||||
"/etc/nginx/sites-available/certidude.conf",
|
||||
@ -179,11 +178,12 @@ def clean_server():
|
||||
"/usr/bin/node",
|
||||
]
|
||||
|
||||
for filename in files:
|
||||
try:
|
||||
os.unlink(filename)
|
||||
except:
|
||||
pass
|
||||
for pattern in files:
|
||||
for filename in glob(pattern):
|
||||
try:
|
||||
os.unlink(filename)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Remove OpenVPN stuff
|
||||
if os.path.exists("/etc/openvpn"):
|
||||
|
Loading…
Reference in New Issue
Block a user