예제 #1
0
    def __init__(self,
                 marathon_client,
                 group,
                 cert_store,
                 mlb_client,
                 txacme_client_creator,
                 reactor,
                 email=None):
        """
        Create the marathon-acme service.

        :param marathon_client: The Marathon API client.
        :param group: The name of the marathon-lb group.
        :param cert_store: The ``ICertificateStore`` instance to use.
        :param mlb_clinet: The marathon-lb API client.
        :param txacme_client_creator: Callable to create the txacme client.
        :param reactor: The reactor to use.
        :param email: The ACME registration email.
        """
        self.marathon_client = marathon_client
        self.group = group
        self.reactor = reactor

        responder = HTTP01Responder()
        self.server = MarathonAcmeServer(responder.resource)

        mlb_cert_store = MlbCertificateStore(cert_store, mlb_client)
        self.txacme_service = AcmeIssuingService(mlb_cert_store,
                                                 txacme_client_creator,
                                                 reactor, [responder], email)

        self._server_listening = None
예제 #2
0
 def _setUp(self):
     self.cert_store = MemoryStore(self._certs)
     self.clock = Clock()
     self.clock.rightNow = (self.now - datetime(1970, 1, 1)).total_seconds()
     if self.acme_client is None:
         acme_client = FakeClient(RSA_KEY_512,
                                  clock=self.clock,
                                  ca_key=RSA_KEY_512_RAW,
                                  controller=self.controller)
     else:
         acme_client = self.acme_client
     self.responder = RecordingResponder(set(), u'tls-sni-01')
     args = dict(cert_store=self.cert_store,
                 client_creator=lambda: succeed(acme_client),
                 clock=self.clock,
                 responders=[self.responder],
                 email=self._email,
                 panic_interval=self._panic_interval,
                 panic=self._panic,
                 generate_key=lambda: RSA_KEY_512_RAW)
     self.service = AcmeIssuingService(
         **{k: v
            for k, v in args.items() if v is not None})
     self.addCleanup(
         lambda: self.service.running and self.service.stopService())
예제 #3
0
 def action(secret):
     password = secret
     if driver_name == 'gandi':
         responders = [
             GandiV5Responder(api_key=password, zone_name=zone_name)
         ]
     elif driver_name == 'cloudflare':
         responders = [
             CloudflareV4Responder(email=user_name,
                                   api_key=password,
                                   zone_name=zone_name)
         ]
     else:
         responders = [
             LibcloudDNSResponder.create(reactor, driver_name, user_name,
                                         password, zone_name)
         ]
     acme_key = maybe_key(acme_path)
     cert_store = DirectoryStore(acme_path)
     if staging:
         le_url = LETSENCRYPT_STAGING_DIRECTORY
     else:
         le_url = LETSENCRYPT_DIRECTORY
     client_creator = partial(Client.from_url,
                              reactor=reactor,
                              url=le_url,
                              key=acme_key,
                              alg=RS256)
     clock = reactor
     service = AcmeIssuingService(cert_store, client_creator, clock,
                                  responders)
     service.startService()
     forever = Deferred()
     return forever
예제 #4
0
 def _got_port(port):
     self.service = AcmeIssuingService(
         cert_store=self.cert_store,
         client_creator=partial(self.client_creator, self.reactor,
                                self.directory),
         clock=self.reactor,
         responders=[responder],
         check_interval=self.check_interval,
         reissue_interval=self.reissue_interval,
         panic_interval=self.panic_interval,
         panic=self._panic,
         generate_key=self._generate_key)
     self.service.startService()
     return (self.service.when_certs_valid().addCallback(
         lambda _: _WrapperPort(port=port, service=self.service)))
예제 #5
0
def create_issuing_service(
    reactor: IReactorTCP,
    acme_url: str,
    account_key_file: str,
    well_known_resource: IResource,
) -> AcmeIssuingService:
    """Create an ACME issuing service, and attach it to a web Resource

    Args:
        reactor: twisted reactor
        acme_url: URL to use to request certificates
        account_key_file: where to store the account key
        well_known_resource: web resource for .well-known.
            we will attach a child resource for "acme-challenge".

    Returns:
        AcmeIssuingService
    """
    responder = HTTP01Responder()

    well_known_resource.putChild(b"acme-challenge", responder.resource)

    store = ErsatzStore()

    return AcmeIssuingService(
        cert_store=store,
        client_creator=(lambda: Client.from_url(
            reactor=reactor,
            url=URL.from_text(acme_url),
            key=load_or_create_client_key(account_key_file),
            alg=RS256,
        )),
        clock=reactor,
        responders=[responder],
    )
예제 #6
0
 def _setUp(self):
     self.cert_store = MemoryStore(self._certs)
     self.clock = Clock()
     self.clock.rightNow = (
         self.now - datetime(1970, 1, 1)).total_seconds()
     if self.acme_client is None:
         acme_client = FakeClient(
             RSA_KEY_512, clock=self.clock, ca_key=RSA_KEY_512_RAW,
             controller=self.controller)
     else:
         acme_client = self.acme_client
     self.responder = RecordingResponder(set(), u'tls-sni-01')
     args = dict(
         cert_store=self.cert_store,
         client_creator=lambda: succeed(acme_client),
         clock=self.clock,
         responders=[self.responder],
         email=self._email,
         panic_interval=self._panic_interval,
         panic=self._panic,
         generate_key=lambda: RSA_KEY_512_RAW)
     self.service = AcmeIssuingService(
         **{k: v for k, v in args.items() if v is not None})
     self.addCleanup(
         lambda: self.service.running and self.service.stopService())
예제 #7
0
 def action(secret):
     password = secret
     responders = [
         LibcloudDNSResponder.create(reactor, driver_name, user_name,
                                     password, zone_name)
     ]
     acme_key = maybe_key(acme_path)
     cert_store = DirectoryStore(acme_path)
     client_creator = partial(Client.from_url,
                              reactor=reactor,
                              url=LETSENCRYPT_DIRECTORY,
                              key=acme_key,
                              alg=RS256)
     clock = reactor
     service = AcmeIssuingService(cert_store, client_creator, clock,
                                  responders)
     service.startService()
     forever = Deferred()
     return forever
예제 #8
0
def makeService(config, reactor=reactor):
    parent = MultiService()
    basedir = FilePath(os.path.expanduser(config["basedir"]))
    basedir.makedirs(ignoreExistingDirectory=True)
    basedir.chmod(0o700)

    data = Data(basedir.child("config.json"))

    certFile = basedir.child("tub.data").path
    tub = Tub(certFile=certFile)
    tub.setOption("keepaliveTimeout", 60) # ping after 60s of idle
    tub.setOption("disconnectTimeout", 5*60) # disconnect/reconnect after 5m
    tub.listenOn("tcp:6319:interface=127.0.0.1")
    tub.setLocation("tcp:127.0.0.1:6319")
    tub.setServiceParent(parent)

    acme_path = basedir.asTextMode()
    acme_key = maybe_key(acme_path)
    cert_store = FlancerCertificateStore(data, basedir)
    staging = not config["really"]
    if staging:
        print("STAGING mode")
        le_url = LETSENCRYPT_STAGING_DIRECTORY
    else:
        print("REAL CERTIFICATE mode")
        le_url = LETSENCRYPT_DIRECTORY
    client_creator = partial(Client.from_url, reactor=reactor,
                             url=le_url,
                             key=acme_key, alg=RS256)
    r = FlancerResponder(tub, data)
    issuer = AcmeIssuingService(cert_store, client_creator, reactor, [r])
    issuer.setServiceParent(parent)

    if "dyndns_furl" in data:
        start_dyndns_canary(tub, data["dyndns_furl"].encode("ascii"))

    c = Controller(tub, data, issuer)
    tub.registerReference(c, furlFile=basedir.child("controller.furl").path)

    #TimerService(5*60.0, f.timerUpdateStats).setServiceParent(parent)
    return parent
예제 #9
0
 def _got_port(port):
     self.service = AcmeIssuingService(
         cert_store=self.cert_store,
         client_creator=partial(
             self.client_creator, self.reactor, self.directory),
         clock=self.reactor,
         responders=[responder],
         check_interval=self.check_interval,
         reissue_interval=self.reissue_interval,
         panic_interval=self.panic_interval,
         panic=self._panic,
         generate_key=self._generate_key)
     self.service.startService()
     return (
         self.service.when_certs_valid()
         .addCallback(
             lambda _: _WrapperPort(port=port, service=self.service)))
예제 #10
0
class AcmeFixture(Fixture):
    """
    A fixture for setting up an `~txacme.service.AcmeIssuingService`.
    """
    def __init__(self,
                 now=datetime(2000, 1, 1, 0, 0, 0),
                 certs=None,
                 panic_interval=None,
                 panic=None,
                 client=None,
                 email=None):
        super(AcmeFixture, self).__init__()
        self.now = now
        self._certs = certs
        self._panic_interval = panic_interval
        self._panic = panic
        self._email = email
        self.acme_client = client
        self.controller = FakeClientController()

    def _setUp(self):
        self.cert_store = MemoryStore(self._certs)
        self.clock = Clock()
        self.clock.rightNow = (self.now - datetime(1970, 1, 1)).total_seconds()
        if self.acme_client is None:
            acme_client = FakeClient(RSA_KEY_512,
                                     clock=self.clock,
                                     ca_key=RSA_KEY_512_RAW,
                                     controller=self.controller)
        else:
            acme_client = self.acme_client
        self.responder = RecordingResponder(set(), u'tls-sni-01')
        args = dict(cert_store=self.cert_store,
                    client_creator=lambda: succeed(acme_client),
                    clock=self.clock,
                    responders=[self.responder],
                    email=self._email,
                    panic_interval=self._panic_interval,
                    panic=self._panic,
                    generate_key=lambda: RSA_KEY_512_RAW)
        self.service = AcmeIssuingService(
            **{k: v
               for k, v in args.items() if v is not None})
        self.addCleanup(
            lambda: self.service.running and self.service.stopService())
예제 #11
0
class AcmeFixture(Fixture):
    """
    A fixture for setting up an `~txacme.service.AcmeIssuingService`.
    """
    def __init__(self, now=datetime(2000, 1, 1, 0, 0, 0), certs=None,
                 panic_interval=None, panic=None, client=None, email=None):
        super(AcmeFixture, self).__init__()
        self.now = now
        self._certs = certs
        self._panic_interval = panic_interval
        self._panic = panic
        self._email = email
        self.acme_client = client
        self.controller = FakeClientController()

    def _setUp(self):
        self.cert_store = MemoryStore(self._certs)
        self.clock = Clock()
        self.clock.rightNow = (
            self.now - datetime(1970, 1, 1)).total_seconds()
        if self.acme_client is None:
            acme_client = FakeClient(
                RSA_KEY_512, clock=self.clock, ca_key=RSA_KEY_512_RAW,
                controller=self.controller)
        else:
            acme_client = self.acme_client
        self.responder = RecordingResponder(set(), u'tls-sni-01')
        args = dict(
            cert_store=self.cert_store,
            client_creator=lambda: succeed(acme_client),
            clock=self.clock,
            responders=[self.responder],
            email=self._email,
            panic_interval=self._panic_interval,
            panic=self._panic,
            generate_key=lambda: RSA_KEY_512_RAW)
        self.service = AcmeIssuingService(
            **{k: v for k, v in args.items() if v is not None})
        self.addCleanup(
            lambda: self.service.running and self.service.stopService())
예제 #12
0
def get_things_done():
    """
    Here is where the service part is setup and action is done.
    """
    responders = yield start_responders()

    store = MemoryStore()

    # We first validate the directory.
    account_key = _get_account_key()
    try:
        client = yield Client.from_url(
            reactor,
            URL.fromText(acme_url.decode('utf-8')),
            key=JWKRSA(key=account_key),
            alg=RS256,
        )
    except Exception as error:
        print('\n\nFailed to connect to ACME directory. %s' % (error, ))
        yield reactor.stop()
        defer.returnValue(None)

    service = AcmeIssuingService(
        email='[email protected],[email protected]',
        cert_store=store,
        client=client,
        clock=reactor,
        responders=responders,
        panic=on_panic,
    )

    # Start the service and wait for it to start.
    yield service.start()

    # Wait for the existing certificate from the storage to be available.
    yield service.when_certs_valid()

    # Request a SAN ... if passed via command line.
    yield service.issue_cert(','.join(requested_domains))

    yield service.stopService()

    print('That was all the example.')
예제 #13
0
class AutoTLSEndpoint(object):
    """
    A server endpoint that does TLS SNI, with certificates automatically
    (re)issued from an ACME certificate authority.

    :param reactor: The Twisted reactor.
    :param directory: ``twisted.python.url.URL`` for the ACME directory to use
        for issuing certs.

    :type client_creator: Callable[[reactor, ``twisted.python.url.URL``],
        Deferred[`txacme.client.Client`]]
    :param client_creator: A callable called with the reactor and directory URL
        for creating the ACME client.  For example, ``partial(Client.from_url,
        key=acme_key, alg=RS256)``.
    :type cert_store: `txacme.interfaces.ICertificateStore`
    :param cert_store: The certificate
        store containing the certificates to manage.  For example,
        `txacme.store.DirectoryStore`.
    :param dict cert_mapping: The certificate mapping to use for SNI; for
        example, ``txsni.snimap.HostDirectoryMap``.  Usually this should
        correspond to the same underlying storage as ``cert_store``.
    :param ~datetime.timedelta check_interval: How often to check for expiring
        certificates.
    :param ~datetime.timedelta reissue_interval: If a certificate is expiring
        in less time than this interval, it will be reissued.
    :param ~datetime.timedelta panic_interval: If a certificate is expiring in
        less time than this interval, and reissuing fails, the panic callback
        will be invoked.

    :type panic: Callable[[Failure, `str`], Deferred]
    :param panic: A callable invoked with the failure and server name when
        reissuing fails for a certificate expiring in the ``panic_interval``.
        For example, you could generate a monitoring alert.  The default
        callback logs a message at *CRITICAL* level.
    :param generate_key: A 0-arg callable used to generate a private key for a
        new cert.  Normally you would not pass this unless you have specialized
        key generation requirements.
    """
    reactor = attr.ib()
    directory = attr.ib(
        validator=lambda inst, a, value: check_directory_url_type(value))
    client_creator = attr.ib()
    cert_store = attr.ib()
    cert_mapping = attr.ib()
    sub_endpoint = attr.ib()
    check_interval = attr.ib(default=timedelta(days=1))
    reissue_interval = attr.ib(default=timedelta(days=30))
    panic_interval = attr.ib(default=timedelta(days=15))
    _panic = attr.ib(default=_default_panic)
    _generate_key = attr.ib(default=partial(generate_private_key, u'rsa'))

    def listen(self, protocolFactory):  # noqa
        """
        Start an issuing service, and wait until initial issuing is complete.
        """
        def _got_port(port):
            self.service = AcmeIssuingService(
                cert_store=self.cert_store,
                client_creator=partial(
                    self.client_creator, self.reactor, self.directory),
                clock=self.reactor,
                responders=[responder],
                check_interval=self.check_interval,
                reissue_interval=self.reissue_interval,
                panic_interval=self.panic_interval,
                panic=self._panic,
                generate_key=self._generate_key)
            self.service.startService()
            return (
                self.service.when_certs_valid()
                .addCallback(
                    lambda _: _WrapperPort(port=port, service=self.service)))

        responder = TLSSNI01Responder()
        sni_map = SNIMap(responder.wrap_host_map(self.cert_mapping))
        return (
            maybeDeferred(
                self.sub_endpoint.listen,
                TLSMemoryBIOFactory(
                    contextFactory=sni_map,
                    isClient=False,
                    wrappedFactory=protocolFactory))
            .addCallback(_got_port))
예제 #14
0
파일: __init__.py 프로젝트: hawkowl/rproxy
def makeService(config):

    ini = ConfigParser.RawConfigParser()
    ini.read(config['config'])

    configPath = FilePath(config['config']).parent()

    rproxyConf = dict(ini.items("rproxy"))
    hostsConf = dict(ini.items("hosts"))

    hosts = {}

    for k, v in hostsConf.items():

        k = k.lower()
        hostname, part = k.rsplit("_", 1)

        if hostname not in hosts:
            hosts[hostname] = {}

        hosts[hostname][part] = v

    if not hosts:
        raise ValueError("No hosts configured.")

    for i in hosts:

        if "port" not in hosts[i]:
            raise ValueError("All hosts need a port.")

        if "host" not in hosts[i]:
            print("%s does not have a host, making localhost" % (i,))
            hosts[i]["host"] = "localhost"

        if "wwwtoo" not in hosts[i]:
            print("%s does not have an wwwtoo setting, making True" % (i,))
            hosts[i]["wwwtoo"] = "True"

        if "proxysecure" not in hosts[i]:
            print("%s does not have an proxysecure setting, making False" % (i,))
            hosts[i]["proxysecure"] = False

        hosts[i]["wwwtoo"] = True if hosts[i]["wwwtoo"]=="True" else False
        hosts[i]["proxysecure"] = True if hosts[i]["proxysecure"]=="True" else False
        hosts[i]["sendhsts"] = True if hosts[i].get("sendhsts")=="True" else False


    from twisted.internet import reactor
    pool = HTTPConnectionPool(reactor)

    resource = EncodingResourceWrapper(
        RProxyResource(hosts, rproxyConf.get("clacks"), pool, reactor, {}, False),
        [server.GzipEncoderFactory()])

    responder = HTTP01Responder()
    site = server.Site(EnsureHTTPS(resource, responder.resource),)
    multiService = service.MultiService()
    certificates = rproxyConf.get("certificates", None)

    if certificates:
        try:
            configPath.child(certificates).makedirs()
        except:
            pass

        certificates = configPath.child(certificates).path
        for i in rproxyConf.get("https_ports").split(","):
            print("Starting HTTPS on port " + i)
            multiService.addService(strports.service('txsni:' + certificates + ':tcp:' + i, site))

        for host in hosts.keys():
            with open(FilePath(certificates).child(host + ".pem").path, 'r+'):
                # Open it so that txacme can find it
                pass
            if hosts[host]["wwwtoo"]:
                with open(FilePath(certificates).child("www." + host + ".pem").path, 'r+'):
                    # Open it so that txacme can find it
                    pass

    for i in rproxyConf.get("http_ports", "").split(","):
        print("Starting HTTP on port " + i)
        multiService.addService(strports.service('tcp:' + i, site))

    issuingService = AcmeIssuingService(
        cert_store=DirectoryStore(FilePath(certificates)),
        client_creator=(lambda: Client.from_url(
            reactor=reactor,
            url=LETSENCRYPT_DIRECTORY,
            key=load_or_create_client_key(FilePath(certificates)),
            alg=RS256,
        )),
        clock=reactor,
        responders=[responder],
    )

    issuingService.setServiceParent(multiService)

    return multiService
예제 #15
0
파일: acme.py 프로젝트: zymptomLabs/synapse
class AcmeHandler(object):
    def __init__(self, hs):
        self.hs = hs
        self.reactor = hs.get_reactor()
        self._acme_domain = hs.config.acme_domain

    @defer.inlineCallbacks
    def start_listening(self):

        # Configure logging for txacme, if you need to debug
        # from eliot import add_destinations
        # from eliot.twisted import TwistedDestination
        #
        # add_destinations(TwistedDestination())

        from txacme.challenges import HTTP01Responder
        from txacme.service import AcmeIssuingService
        from txacme.endpoint import load_or_create_client_key
        from txacme.client import Client
        from josepy.jwa import RS256

        self._store = ErsatzStore()
        responder = HTTP01Responder()

        self._issuer = AcmeIssuingService(
            cert_store=self._store,
            client_creator=(lambda: Client.from_url(
                reactor=self.reactor,
                url=URL.from_text(self.hs.config.acme_url),
                key=load_or_create_client_key(
                    FilePath(self.hs.config.config_dir_path)),
                alg=RS256,
            )),
            clock=self.reactor,
            responders=[responder],
        )

        well_known = Resource()
        well_known.putChild(b'acme-challenge', responder.resource)
        responder_resource = Resource()
        responder_resource.putChild(b'.well-known', well_known)
        responder_resource.putChild(b'check',
                                    static.Data(b'OK', b'text/plain'))

        srv = server.Site(responder_resource)

        bind_addresses = self.hs.config.acme_bind_addresses
        for host in bind_addresses:
            logger.info(
                "Listening for ACME requests on %s:%i",
                host,
                self.hs.config.acme_port,
            )
            try:
                self.reactor.listenTCP(
                    self.hs.config.acme_port,
                    srv,
                    interface=host,
                )
            except twisted.internet.error.CannotListenError as e:
                check_bind_error(e, host, bind_addresses)

        # Make sure we are registered to the ACME server. There's no public API
        # for this, it is usually triggered by startService, but since we don't
        # want it to control where we save the certificates, we have to reach in
        # and trigger the registration machinery ourselves.
        self._issuer._registered = False
        yield self._issuer._ensure_registered()

    @defer.inlineCallbacks
    def provision_certificate(self):

        logger.warning("Reprovisioning %s", self._acme_domain)

        try:
            yield self._issuer.issue_cert(self._acme_domain)
        except Exception:
            logger.exception("Fail!")
            raise
        logger.warning("Reprovisioned %s, saving.", self._acme_domain)
        cert_chain = self._store.certs[self._acme_domain]

        try:
            with open(self.hs.config.tls_private_key_file,
                      "wb") as private_key_file:
                for x in cert_chain:
                    if x.startswith(b"-----BEGIN RSA PRIVATE KEY-----"):
                        private_key_file.write(x)

            with open(self.hs.config.tls_certificate_file,
                      "wb") as certificate_file:
                for x in cert_chain:
                    if x.startswith(b"-----BEGIN CERTIFICATE-----"):
                        certificate_file.write(x)
        except Exception:
            logger.exception("Failed saving!")
            raise

        defer.returnValue(True)
예제 #16
0
class MarathonAcme(object):
    log = Logger()

    def __init__(self,
                 marathon_client,
                 group,
                 cert_store,
                 mlb_client,
                 txacme_client_creator,
                 reactor,
                 email=None):
        """
        Create the marathon-acme service.

        :param marathon_client: The Marathon API client.
        :param group: The name of the marathon-lb group.
        :param cert_store: The ``ICertificateStore`` instance to use.
        :param mlb_clinet: The marathon-lb API client.
        :param txacme_client_creator: Callable to create the txacme client.
        :param reactor: The reactor to use.
        :param email: The ACME registration email.
        """
        self.marathon_client = marathon_client
        self.group = group
        self.reactor = reactor

        responder = HTTP01Responder()
        self.server = MarathonAcmeServer(responder.resource)

        mlb_cert_store = MlbCertificateStore(cert_store, mlb_client)
        self.txacme_service = AcmeIssuingService(mlb_cert_store,
                                                 txacme_client_creator,
                                                 reactor, [responder], email)

        self._server_listening = None

    def run(self, endpoint_description):
        self.log.info('Starting marathon-acme...')

        # Start the server
        d = self.server.listen(self.reactor, endpoint_description)

        def on_server_listening(listening_port):
            self._server_listening = listening_port

            # Start the txacme service and wait for the initial check
            self.txacme_service.startService()
            return self.txacme_service.when_certs_valid()

        d.addCallback(on_server_listening)

        # Then listen for events...
        d.addCallback(lambda _: self.listen_events())

        # If anything goes wrong or listening for events returns, stop
        d.addBoth(self._stop)

        return d

    def _stop(self, result):
        if isinstance(result, Failure):
            self.log.failure('Unhandle error during operation', result)
        self.log.warn('Stopping marathon-acme...')

        # If the server failed to start we have nothing to cancel yet
        if self._server_listening is not None:
            return gatherResults([
                self._server_listening.stopListening(),
                self.txacme_service.stopService()
            ],
                                 consumeErrors=True)

    def listen_events(self, reconnects=0):
        """
        Start listening for events from Marathon, running a sync when we first
        successfully subscribe and triggering a sync on API request events.
        """
        self.log.info('Listening for events from Marathon...')
        self._attached = False

        def on_finished(result, reconnects):
            # If the callback fires then the HTTP request to the event stream
            # went fine, but the persistent connection for the SSE stream was
            # dropped. Just reconnect for now- if we can't actually connect
            # then the errback will fire rather.
            self.log.warn(
                'Connection lost listening for events, '
                'reconnecting... ({reconnects} so far)',
                reconnects=reconnects)
            reconnects += 1
            return self.listen_events(reconnects)

        def log_failure(failure):
            self.log.failure('Failed to listen for events', failure)
            return failure

        return self.marathon_client.get_events({
            'event_stream_attached':
            self._sync_on_event_stream_attached,
            'api_post_event':
            self._sync_on_api_post_event
        }).addCallbacks(on_finished, log_failure, callbackArgs=[reconnects])

    def _sync_on_event_stream_attached(self, event):
        if self._attached:
            self.log.debug(
                'event_stream_attached event received (timestamp: '
                '"{timestamp}", remoteAddress: "{remoteAddress}"), but '
                'already attached',
                timestamp=event['timestamp'],
                remoteAddress=event['remoteAddress'])
            return

        self._attached = True
        self.log.info(
            'event_stream_attached event received (timestamp: "{timestamp}", '
            'remoteAddress: "{remoteAddress}"), running initial sync...',
            timestamp=event['timestamp'],
            remoteAddress=event['remoteAddress'])
        return self.sync()

    def _sync_on_api_post_event(self, event):
        self.log.info(
            'api_post_event event received (timestamp: "{timestamp}", uri: '
            '"{uri}"), triggering a sync...',
            timestamp=event['timestamp'],
            uri=event['uri'])
        return self.sync()

    def sync(self):
        """
        Fetch the list of apps from Marathon, find the domains that require
        certificates, and issue certificates for any domains that don't already
        have a certificate.
        """
        self.log.info('Starting a sync...')

        def log_success(result):
            self.log.info('Sync completed successfully')
            return result

        def log_failure(failure):
            self.log.failure('Sync failed', failure, LogLevel.error)
            return failure

        return (self.marathon_client.get_apps().addCallback(
            self._apps_acme_domains).addCallback(
                self._filter_new_domains).addCallback(
                    self._issue_certs).addCallbacks(log_success, log_failure))

    def _apps_acme_domains(self, apps):
        domains = []
        for app in apps:
            domains.extend(self._app_acme_domains(app))

        self.log.debug('Found {len_domains} domains for apps: {domains}',
                       len_domains=len(domains),
                       domains=domains)

        return domains

    def _app_acme_domains(self, app):
        app_domains = []
        labels = app['labels']
        app_group = labels.get('HAPROXY_GROUP')

        # Prefer the 'portDefinitions' field added in Marathon 1.0.0 but fall
        # back to the deprecated 'ports' array if that's not present.
        if 'portDefinitions' in app:
            ports = app['portDefinitions']
        else:
            ports = app['ports']

        # Iterate through the ports, checking for corresponding labels
        for port_index, _ in enumerate(ports):
            # Get the port group label, defaulting to the app group label
            port_group = labels.get('HAPROXY_%d_GROUP' % (port_index, ),
                                    app_group)

            if port_group == self.group:
                domain_label = labels.get(
                    'MARATHON_ACME_%d_DOMAIN' % (port_index, ), '')
                port_domains = parse_domain_label(domain_label)

                if port_domains:
                    # TODO: Support SANs- for now just use the first domain
                    if len(port_domains) > 1:
                        self.log.warn(
                            'Multiple domains found for port {port} of app '
                            '{app}, only the first will be used',
                            port=port_index,
                            app=app['id'])

                    app_domains.append(port_domains[0])

        self.log.debug('Found {len_domains} domains for app {app}: {domains}',
                       len_domains=len(app_domains),
                       app=app['id'],
                       domains=app_domains)

        return app_domains

    def _filter_new_domains(self, marathon_domains):
        def filter_domains(stored_domains):
            return set(marathon_domains) - set(stored_domains.keys())

        d = self.txacme_service.cert_store.as_dict()
        d.addCallback(filter_domains)
        return d

    def _issue_certs(self, domains):
        if domains:
            self.log.info(
                'Issuing certificates for {len_domains} domains: {domains}',
                len_domains=len(domains),
                domains=domains)
        else:
            self.log.debug('No new domains to issue certificates for')
        return gatherResults([self._issue_cert(domain) for domain in domains])

    def _issue_cert(self, domain):
        """
        Issue a certificate for the given domain.
        """
        def errback(failure):
            # Don't fail on some of the errors we could get from the ACME
            # server, rather just log an error so that we can continue with
            # other domains.
            failure.trap(txacme_ServerError)
            acme_error = failure.value.message

            # FIXME: The acme error code stuff is a mess pre- the unreleased
            # 0.10 version. Update this to use the 'code' attribute when the
            # new acme library is released.
            acme_error_code = str(acme_error.typ).split(':')[-1]
            if acme_error_code in [
                    'rateLimited', 'serverInternal', 'connection',
                    'unknownHost'
            ]:
                # TODO: Fire off an error to Sentry or something?
                self.log.error(
                    'Error ({code}) issuing certificate for "{domain}": '
                    '{detail}',
                    code=acme_error_code,
                    domain=domain,
                    detail=acme_error.detail)
            else:
                # There are more error codes but if they happen then something
                # serious has gone wrong-- carry on error-ing.
                return failure

        d = self.txacme_service.issue_cert(domain)
        return d.addErrback(errback)
예제 #17
0
파일: acme.py 프로젝트: zymptomLabs/synapse
    def start_listening(self):

        # Configure logging for txacme, if you need to debug
        # from eliot import add_destinations
        # from eliot.twisted import TwistedDestination
        #
        # add_destinations(TwistedDestination())

        from txacme.challenges import HTTP01Responder
        from txacme.service import AcmeIssuingService
        from txacme.endpoint import load_or_create_client_key
        from txacme.client import Client
        from josepy.jwa import RS256

        self._store = ErsatzStore()
        responder = HTTP01Responder()

        self._issuer = AcmeIssuingService(
            cert_store=self._store,
            client_creator=(lambda: Client.from_url(
                reactor=self.reactor,
                url=URL.from_text(self.hs.config.acme_url),
                key=load_or_create_client_key(
                    FilePath(self.hs.config.config_dir_path)),
                alg=RS256,
            )),
            clock=self.reactor,
            responders=[responder],
        )

        well_known = Resource()
        well_known.putChild(b'acme-challenge', responder.resource)
        responder_resource = Resource()
        responder_resource.putChild(b'.well-known', well_known)
        responder_resource.putChild(b'check',
                                    static.Data(b'OK', b'text/plain'))

        srv = server.Site(responder_resource)

        bind_addresses = self.hs.config.acme_bind_addresses
        for host in bind_addresses:
            logger.info(
                "Listening for ACME requests on %s:%i",
                host,
                self.hs.config.acme_port,
            )
            try:
                self.reactor.listenTCP(
                    self.hs.config.acme_port,
                    srv,
                    interface=host,
                )
            except twisted.internet.error.CannotListenError as e:
                check_bind_error(e, host, bind_addresses)

        # Make sure we are registered to the ACME server. There's no public API
        # for this, it is usually triggered by startService, but since we don't
        # want it to control where we save the certificates, we have to reach in
        # and trigger the registration machinery ourselves.
        self._issuer._registered = False
        yield self._issuer._ensure_registered()
예제 #18
0
class AutoTLSEndpoint(object):
    """
    A server endpoint that does TLS SNI, with certificates automatically
    (re)issued from an ACME certificate authority.

    :param reactor: The Twisted reactor.
    :param directory: ``twisted.python.url.URL`` for the ACME directory to use
        for issuing certs.

    :type client_creator: Callable[[reactor, ``twisted.python.url.URL``],
        Deferred[`txacme.client.Client`]]
    :param client_creator: A callable called with the reactor and directory URL
        for creating the ACME client.  For example, ``partial(Client.from_url,
        key=acme_key, alg=RS256)``.
    :param .ICertificateStore cert_store: The certificate store containing the
        certificates to manage.  For example, `txacme.store.DirectoryStore`.
    :param dict cert_mapping: The certificate mapping to use for SNI; for
        example, ``txsni.snimap.HostDirectoryMap``.  Usually this should
        correspond to the same underlying storage as ``cert_store``.
    :param ~datetime.timedelta check_interval: How often to check for expiring
        certificates.
    :param ~datetime.timedelta reissue_interval: If a certificate is expiring
        in less time than this interval, it will be reissued.
    :param ~datetime.timedelta panic_interval: If a certificate is expiring in
        less time than this interval, and reissuing fails, the panic callback
        will be invoked.

    :type panic: Callable[[Failure, `str`], Deferred]
    :param panic: A callable invoked with the failure and server name when
        reissuing fails for a certificate expiring in the ``panic_interval``.
        For example, you could generate a monitoring alert.  The default
        callback logs a message at *CRITICAL* level.
    :param generate_key: A 0-arg callable used to generate a private key for a
        new cert.  Normally you would not pass this unless you have specialized
        key generation requirements.
    """
    reactor = attr.ib()
    directory = attr.ib(
        validator=lambda inst, a, value: check_directory_url_type(value))
    client_creator = attr.ib()
    cert_store = attr.ib()
    cert_mapping = attr.ib()
    sub_endpoint = attr.ib()
    check_interval = attr.ib(default=timedelta(days=1))
    reissue_interval = attr.ib(default=timedelta(days=30))
    panic_interval = attr.ib(default=timedelta(days=15))
    _panic = attr.ib(default=_default_panic)
    _generate_key = attr.ib(default=partial(generate_private_key, u'rsa'))

    def listen(self, protocolFactory):  # noqa
        """
        Start an issuing service, and wait until initial issuing is complete.
        """
        def _got_port(port):
            self.service = AcmeIssuingService(
                cert_store=self.cert_store,
                client_creator=partial(
                    self.client_creator, self.reactor, self.directory),
                clock=self.reactor,
                responders=[responder],
                check_interval=self.check_interval,
                reissue_interval=self.reissue_interval,
                panic_interval=self.panic_interval,
                panic=self._panic,
                generate_key=self._generate_key)
            self.service.startService()
            return (
                self.service.when_certs_valid()
                .addCallback(
                    lambda _: _WrapperPort(port=port, service=self.service)))

        responder = TLSSNI01Responder()
        sni_map = SNIMap(responder.wrap_host_map(self.cert_mapping))
        return (
            maybeDeferred(
                self.sub_endpoint.listen,
                TLSMemoryBIOFactory(
                    contextFactory=sni_map,
                    isClient=False,
                    wrappedFactory=protocolFactory))
            .addCallback(_got_port))
예제 #19
0
def makeService(config):

    ini = ConfigParser.RawConfigParser()
    ini.read(config['config'])

    configPath = FilePath(config['config']).parent()

    rproxyConf = dict(ini.items("rproxy"))
    hostsConf = dict(ini.items("hosts"))

    hosts = {}

    for k, v in hostsConf.items():

        k = k.lower()
        hostname, part = k.rsplit("_", 1)

        if hostname not in hosts:
            hosts[hostname] = {}

        hosts[hostname][part] = v

    if not hosts:
        raise ValueError("No hosts configured.")

    for i in hosts:

        if "port" not in hosts[i]:
            raise ValueError("All hosts need a port.")

        if "host" not in hosts[i]:
            print("%s does not have a host, making localhost" % (i, ))
            hosts[i]["host"] = "localhost"

        if "wwwtoo" not in hosts[i]:
            print("%s does not have an wwwtoo setting, making True" % (i, ))
            hosts[i]["wwwtoo"] = "True"

        if "proxysecure" not in hosts[i]:
            print("%s does not have an proxysecure setting, making False" %
                  (i, ))
            hosts[i]["proxysecure"] = False

        hosts[i]["wwwtoo"] = True if hosts[i]["wwwtoo"] == "True" else False
        hosts[i]["proxysecure"] = True if hosts[i][
            "proxysecure"] == "True" else False
        hosts[i]["sendhsts"] = True if hosts[i].get(
            "sendhsts") == "True" else False

    from twisted.internet import reactor
    pool = HTTPConnectionPool(reactor)

    resource = EncodingResourceWrapper(
        RProxyResource(hosts, rproxyConf.get("clacks"), pool, reactor, {},
                       False), [server.GzipEncoderFactory()])

    responder = HTTP01Responder()
    site = server.Site(EnsureHTTPS(resource, responder.resource), )
    multiService = service.MultiService()
    certificates = rproxyConf.get("certificates", None)

    if certificates:
        try:
            configPath.child(certificates).makedirs()
        except:
            pass

        certificates = configPath.child(certificates).path
        for i in rproxyConf.get("https_ports").split(","):
            print("Starting HTTPS on port " + i)
            multiService.addService(
                strports.service('txsni:' + certificates + ':tcp:' + i, site))

        for host in hosts.keys():
            with open(FilePath(certificates).child(host + ".pem").path, 'w'):
                # Open it so that txacme can find it
                pass
            if hosts[host]["wwwtoo"]:
                with open(
                        FilePath(certificates).child("www." + host +
                                                     ".pem").path, 'w'):
                    # Open it so that txacme can find it
                    pass

    for i in rproxyConf.get("http_ports", "").split(","):
        print("Starting HTTP on port " + i)
        multiService.addService(strports.service('tcp:' + i, site))

    issuingService = AcmeIssuingService(
        cert_store=DirectoryStore(FilePath(certificates)),
        client_creator=(lambda: Client.from_url(
            reactor=reactor,
            url=LETSENCRYPT_DIRECTORY,
            key=load_or_create_client_key(FilePath(certificates)),
            alg=RS256,
        )),
        clock=reactor,
        responders=[responder],
    )

    issuingService.setServiceParent(multiService)

    return multiService