예제 #1
0
    def test_cancellation(self, server_name):
        """
        Cancelling the deferred returned by ``issue_cert`` cancels the actual
        issuing process.
        """
        with AcmeFixture() as fixture:
            fixture.service.startService()
            self.assertThat(
                fixture.cert_store.as_dict(),
                succeeded(
                    Not(Contains(server_name))))

            fixture.controller.pause()
            d1 = fixture.service.issue_cert(server_name)
            self.assertThat(d1, has_no_result())
            d2 = fixture.service.issue_cert(server_name)
            self.assertThat(d2, has_no_result())
            self.assertThat(fixture.controller.count(), Equals(1))
            d2.cancel()

            fixture.controller.resume()
            self.assertThat(d1, failed_with(IsInstance(CancelledError)))
            self.assertThat(d2, failed_with(IsInstance(CancelledError)))
            self.assertThat(
                fixture.cert_store.as_dict(),
                succeeded(
                    Not(Contains(server_name))))
예제 #2
0
    def test_cancel_job(self):
        """
        Tests that scheduler is stopped whenever a port is failing.
        """
        port_out = object()
        port_in = Mock(spec=_port_callback)
        self.flowmap[port_out] = port_in

        run_deferred = self.scheduler.run(self.clock)
        self.scheduler.send('some item', port_out)

        self.assertEquals(len(list(self.scheduler.pending)), 1)

        self.scheduler.stop('bye!')

        self.assertEquals(len(list(self.scheduler.pending)), 1)

        join_deferred = self.scheduler.join()
        self.clock.advance(self.epsilon)
        assert_that(join_deferred, twistedsupport.succeeded(matchers.Always()))
        assert_that(run_deferred, twistedsupport.succeeded(matchers.Equals('bye!')))

        self.assertEquals(len(list(self.scheduler.pending)), 0)

        self.assertEquals(port_in.call_count, 0)
예제 #3
0
    def test_get_apps(self):
        """
        When the list of apps is requested, a list of apps added via add_app()
        should be returned.
        """
        app = {
            'id':
            '/my-app_1',
            'cmd':
            'sleep 50',
            'tasks': [{
                "host": "host1.local",
                "id": "my-app_1-1396592790353",
                "ports": []
            }, {
                "host": "host2.local",
                "id": "my-app_1-1396592784349",
                "ports": []
            }]
        }
        self.marathon.add_app(app)

        response = self.client.get('http://localhost/v2/apps')
        assert_that(
            response,
            succeeded(
                MatchesAll(
                    IsJsonResponseWithCode(200),
                    After(json_content, succeeded(Equals({'apps': [app]}))))))
예제 #4
0
    def test_start_responding(self, token, subdomain, zone_name):
        """
        Calling ``start_responding`` causes an appropriate TXT record to be
        created.
        """
        challenge = self._challenge_factory(token=token)
        response = challenge.response(RSA_KEY_512)
        responder = self._responder_factory(zone_name=zone_name)
        server_name = u'{}.{}'.format(subdomain, zone_name)
        zone = responder._driver.list_zones()[0]

        self.assertThat(zone.list_records(), HasLength(0))
        d = responder.start_responding(server_name, challenge, response)
        self._perform()
        self.assertThat(d, succeeded(Always()))
        self.assertThat(
            zone.list_records(),
            MatchesListwise([
                MatchesStructure(
                    name=EndsWith(u'.' + subdomain),
                    type=Equals('TXT'),
                )
            ]))

        # Starting twice before stopping doesn't break things
        d = responder.start_responding(server_name, challenge, response)
        self._perform()
        self.assertThat(d, succeeded(Always()))
        self.assertThat(zone.list_records(), HasLength(1))

        d = responder.stop_responding(server_name, challenge, response)
        self._perform()
        self.assertThat(d, succeeded(Always()))
        self.assertThat(zone.list_records(), HasLength(0))
예제 #5
0
    def test_start_responding(self, token, subdomain, zone_name):
        """
        Calling ``start_responding`` causes an appropriate TXT record to be
        created.
        """
        challenge = self._challenge_factory(token=token)
        response = challenge.response(RSA_KEY_512)
        responder = self._responder_factory(zone_name=zone_name)
        server_name = u'{}.{}'.format(subdomain, zone_name)
        zone = responder._driver.list_zones()[0]

        self.assertThat(zone.list_records(), HasLength(0))
        d = responder.start_responding(server_name, challenge, response)
        self._perform()
        self.assertThat(d, succeeded(Always()))
        self.assertThat(
            zone.list_records(),
            MatchesListwise([
                MatchesStructure(
                    name=EndsWith(u'.' + subdomain),
                    type=Equals('TXT'),
                    )]))

        # Starting twice before stopping doesn't break things
        d = responder.start_responding(server_name, challenge, response)
        self._perform()
        self.assertThat(d, succeeded(Always()))
        self.assertThat(zone.list_records(), HasLength(1))

        d = responder.stop_responding(server_name, challenge, response)
        self._perform()
        self.assertThat(d, succeeded(Always()))
        self.assertThat(zone.list_records(), HasLength(0))
예제 #6
0
    def test_sync_acme_server_failure_acceptable(self):
        """
        When a sync is run and we try to issue a certificate for a domain but
        the ACME server returns an error, if that error is of an acceptable
        type then it should be ignored.
        """
        self.fake_marathon.add_app({
            'id':
            '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com'
            },
            'portDefinitions': [{
                'port': 9000,
                'protocol': 'tcp',
                'labels': {}
            }]
        })
        acme_error = acme_Error(typ='urn:acme:error:rateLimited', detail='bar')
        # Server error takes an ACME error and a treq response...but we don't
        # have a response
        self.txacme_client.issuance_error = txacme_ServerError(
            acme_error, None)

        d = self.marathon_acme.sync()

        assert_that(d, succeeded(Equals([None])))
        # Nothing stored, nothing notified
        assert_that(self.cert_store.as_dict(), succeeded(Equals({})))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(),
                    Equals(False))
예제 #7
0
    def test_create_snapshot_twice(self, filename, content1, content2):
        """
        If a snapshot already exists for a file, adding a new snapshot to it
        should refer to the existing snapshot as a parent.
        """
        foo = self.magic.child(filename)
        foo.asBytesMode("utf-8").setContent(content1)

        # make sure the store_local_snapshot() succeeds
        self.assertThat(
            self.snapshot_creator.store_local_snapshot(foo),
            succeeded(Always()),
        )

        foo_magicname = path2magic(filename)
        stored_snapshot1 = self.db.get_local_snapshot(foo_magicname)

        # now modify the file with some new content.
        foo.asBytesMode("utf-8").setContent(content2)

        # make sure the second call succeeds as well
        self.assertThat(
            self.snapshot_creator.store_local_snapshot(foo),
            succeeded(Always()),
        )
        stored_snapshot2 = self.db.get_local_snapshot(foo_magicname)

        self.assertThat(
            stored_snapshot2.parents_local[0],
            MatchesStructure(
                content_path=Equals(stored_snapshot1.content_path)))
    def test_sync_acme_server_failure_acceptable(self):
        """
        When a sync is run and we try to issue a certificate for a domain but
        the ACME server returns an error, if that error is of an acceptable
        type then it should be ignored.
        """
        self.fake_marathon.add_app({
            'id': '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com'
            },
            'portDefinitions': [
                {'port': 9000, 'protocol': 'tcp', 'labels': {}}
            ]
        })
        acme_error = acme_Error(typ='urn:acme:error:rateLimited', detail='bar')
        # Server error takes an ACME error and a treq response...but we don't
        # have a response
        self.txacme_client.issuance_error = txacme_ServerError(
            acme_error, None)

        marathon_acme = self.mk_marathon_acme()
        d = marathon_acme.sync()

        assert_that(d, succeeded(Equals([None])))
        # Nothing stored, nothing notified
        assert_that(self.cert_store.as_dict(), succeeded(Equals({})))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(),
                    Equals(False))
    def test_sync_app_port_group_mismatch(self):
        """
        When a sync is run and Marathon has an app and that app has a matching
        group but mismatching port group, then no certificates should be
        fetched and marathon-lb should not be notified.
        """
        self.fake_marathon.add_app({
            'id': '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'HAPROXY_0_GROUP': 'internal',
                'HAPROXY_0_VHOST': 'example.com',
                'MARATHON_ACME_0_DOMAIN': 'example.com',
            },
            'portDefinitions': [
                {'port': 9000, 'protocol': 'tcp', 'labels': {}}
            ]
        })

        marathon_acme = self.mk_marathon_acme()
        d = marathon_acme.sync()
        assert_that(d, succeeded(Equals([])))

        # Nothing stored, nothing notified, but Marathon checked
        assert_that(
            self.fake_marathon_api.check_called_get_apps(), Equals(True))
        assert_that(self.cert_store.as_dict(), succeeded(Equals({})))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(),
                    Equals(False))
    def test_sync_app_existing_cert(self):
        """
        When a sync is run and Marathon has an app with a domain label but we
        already have a certificate for that app then a new certificate should
        not be fetched.
        """
        self.fake_marathon.add_app({
            'id': '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com'
            },
            'portDefinitions': [
                {'port': 9000, 'protocol': 'tcp', 'labels': {}}
            ]
        })
        self.cert_store.store('example.com', 'certcontent')

        marathon_acme = self.mk_marathon_acme()
        d = marathon_acme.sync()
        assert_that(d, succeeded(Equals([])))

        # Existing cert unchanged, marathon-lb not notified, but Marathon
        # checked
        assert_that(
            self.fake_marathon_api.check_called_get_apps(), Equals(True))
        assert_that(self.cert_store.as_dict(), succeeded(
            Equals({'example.com': 'certcontent'})))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(),
                    Equals(False))
    def test_sync_app_no_domains(self):
        """
        When a sync is run and Marathon has an app but that app has no
        marathon-acme domains, then no certificates should be fetched and
        marathon-lb should not be notified.
        """
        self.fake_marathon.add_app({
            'id': '/my-app_1',
            'labels': {
                'HAPROXY_0_VHOST': 'example.com'
            },
            'portDefinitions': [
                {'port': 9000, 'protocol': 'tcp', 'labels': {}}
            ]
        })

        marathon_acme = self.mk_marathon_acme()
        d = marathon_acme.sync()
        assert_that(d, succeeded(Equals([])))

        # Nothing stored, nothing notified, but Marathon checked
        assert_that(
            self.fake_marathon_api.check_called_get_apps(), Equals(True))
        assert_that(self.cert_store.as_dict(), succeeded(Equals({})))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(),
                    Equals(False))
    def test_sync_app_label_but_no_domains(self):
        """
        When a sync is run and Marathon has an app and that app has a domain
        label but that label has no domains, then no certificates should be
        fetched and marathon-lb should not be notified.
        """
        # Store an app in Marathon with a marathon-acme domain
        self.fake_marathon.add_app({
            'id': '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': '',
            },
            'portDefinitions': [
                {'port': 9000, 'protocol': 'tcp', 'labels': {}}
            ]
        })

        marathon_acme = self.mk_marathon_acme()
        d = marathon_acme.sync()
        assert_that(d, succeeded(Equals([])))

        # Nothing stored, nothing notified, but Marathon checked
        assert_that(
            self.fake_marathon_api.check_called_get_apps(), Equals(True))
        assert_that(self.cert_store.as_dict(), succeeded(Equals({})))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(),
                    Equals(False))
    def test_sync_app_multiple_domains_multiple_certs_allowed(self):
        """
        When a sync is run and there is an app with a domain label containing
        multiple domains, and ``allow_multiple_certs`` is True, all domains
        are considered.
        """
        self.fake_marathon.add_app({
            'id': '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com,example2.com'
            },
            'portDefinitions': [
                {'port': 9000, 'protocol': 'tcp', 'labels': {}}
            ]
        })

        marathon_acme = self.mk_marathon_acme(allow_multiple_certs=True)
        d = marathon_acme.sync()
        assert_that(d, succeeded(MatchesListwise([  # Per domain
            is_marathon_lb_sigusr_response,
            is_marathon_lb_sigusr_response,
        ])))

        assert_that(self.cert_store.as_dict(), succeeded(MatchesDict({
            'example.com': Not(Is(None)),
            'example2.com': Not(Is(None)),
        })))

        assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(True))
예제 #14
0
    def test_create_local_snapshots(self, content1, content2, filename):
        """
        Create a local snapshot and then change the content of the file
        to make another snapshot.
        """
        data1 = io.BytesIO(content1)
        parents = []

        d = create_snapshot(
            name=filename,
            author=self.alice,
            data_producer=data1,
            snapshot_stash_dir=self.stash_dir,
        )
        d.addCallback(parents.append)
        self.assertThat(
            d,
            succeeded(Always()),
        )

        data2 = io.BytesIO(content2)
        d = create_snapshot(
            name=filename,
            author=self.alice,
            data_producer=data2,
            snapshot_stash_dir=self.stash_dir,
            parents=parents,
        )
        d.addCallback(parents.append)
        self.assertThat(
            d,
            succeeded(Always()),
        )
    def test_sync_app_multiple_ports(self):
        """
        When a sync is run and there is an app with domain labels for multiple
        ports, then certificates should be fetched for each port.
        """
        # Store an app in Marathon with a marathon-acme domain
        self.fake_marathon.add_app({
            'id': '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com',
                'MARATHON_ACME_1_DOMAIN': 'example2.com'
            },
            'portDefinitions': [
                {'port': 9000, 'protocol': 'tcp', 'labels': {}},
                {'port': 9001, 'protocol': 'tcp', 'labels': {}}
            ]
        })

        marathon_acme = self.mk_marathon_acme()
        d = marathon_acme.sync()
        assert_that(d, succeeded(MatchesListwise([  # Per domain
            is_marathon_lb_sigusr_response,
            is_marathon_lb_sigusr_response
        ])))

        assert_that(self.cert_store.as_dict(), succeeded(MatchesDict({
            'example.com': Not(Is(None)),
            'example2.com': Not(Is(None))
        })))

        assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(True))
    def test_sync_app(self):
        """
        When a sync is run and there is an app with a domain label and no
        existing certificate, then a new certificate should be issued for the
        domain. The certificate should be stored in the certificate store and
        marathon-lb should be notified.
        """
        # Store an app in Marathon with a marathon-acme domain
        self.fake_marathon.add_app({
            'id': '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com'
            },
            'portDefinitions': [
                {'port': 9000, 'protocol': 'tcp', 'labels': {}}
            ]
        })

        marathon_acme = self.mk_marathon_acme()
        d = marathon_acme.sync()
        assert_that(d, succeeded(MatchesListwise([  # Per domain
            is_marathon_lb_sigusr_response
        ])))

        assert_that(self.cert_store.as_dict(), succeeded(MatchesDict({
            'example.com': Not(Is(None))
        })))

        assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(True))
예제 #17
0
    def test_add_directory_entry(self):
        """
        Adding a capability to a mutable directory
        """
        content = b"content " * 200
        http_client = create_tahoe_treq_client()
        tahoe_client = create_tahoe_client(
            DecodedURL.from_text(u"http://example.com/"),
            http_client,
        )

        # first create a mutable directory
        mut_cap = yield tahoe_client.create_mutable_directory()

        # create an immutable and link it into the directory
        file_cap = yield tahoe_client.create_immutable(content)
        yield tahoe_client.add_entry_to_mutable_directory(
            mut_cap, u"foo", file_cap)

        # prove we can access the expected file via a GET
        uri = DecodedURL.from_text(u"http://example.com/uri/")
        uri = uri.child(mut_cap.danger_real_capability_string(), u"foo")
        resp = http_client.get(uri.to_uri().to_text())

        self.assertThat(resp, succeeded(MatchesStructure(code=Equals(200), )))
        self.assertThat(
            resp.result.content(),
            succeeded(Equals(content)),
        )
예제 #18
0
    def test_add_multiple_files(self, filenames, data):
        """
        Add a bunch of files one by one and check whether the operation is
        successful.
        """
        files = []
        for filename in filenames:
            to_add = self.magic_path.child(filename)
            content = data.draw(binary())
            to_add.asBytesMode("utf-8").setContent(content)
            files.append(to_add)

        self.snapshot_service.startService()

        list_d = []
        for file in files:
            result_d = self.snapshot_service.add_file(file)
            list_d.append(result_d)

        d = defer.gatherResults(list_d)

        self.assertThat(
            d,
            succeeded(Always()),
        )

        self.assertThat(self.snapshot_service.stopService(),
                        succeeded(Always()))

        self.assertThat(sorted(self.snapshot_creator.processed),
                        Equals(sorted(files)))
예제 #19
0
    def test_sync_app_existing_cert(self):
        """
        When a sync is run and Marathon has an app with a domain label but we
        already have a certificate for that app then a new certificate should
        not be fetched.
        """
        self.fake_marathon.add_app({
            'id':
            '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com'
            },
            'portDefinitions': [{
                'port': 9000,
                'protocol': 'tcp',
                'labels': {}
            }]
        })
        self.cert_store.store('example.com', 'certcontent')

        d = self.marathon_acme.sync()
        assert_that(d, succeeded(Equals([])))

        # Existing cert unchanged, marathon-lb not notified, but Marathon
        # checked
        assert_that(self.fake_marathon_api.check_called_get_apps(),
                    Equals(True))
        assert_that(self.cert_store.as_dict(),
                    succeeded(Equals({'example.com': 'certcontent'})))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(),
                    Equals(False))
예제 #20
0
def succeeded_with_unblinded_tokens_with_matcher(
    all_token_count,
    match_unblinded_tokens,
    match_lease_maint_spending,
):
    """
    :return: A matcher which matches a Deferred which fires with a response
        like the one returned by the **unblinded-tokens** endpoint.

    :param int all_token_count: The expected value in the ``total`` field of
        the response.

    :param match_unblinded_tokens: A matcher for the ``unblinded-tokens``
        field of the response.

    :param match_lease_maint_spending: A matcher for the
        ``lease-maintenance-spending`` field of the response.
    """
    return succeeded(
        MatchesAll(
            ok_response(headers=application_json()),
            AfterPreprocessing(
                json_content,
                succeeded(
                    ContainsDict({
                        u"total":
                        Equals(all_token_count),
                        u"unblinded-tokens":
                        match_unblinded_tokens,
                        u"lease-maintenance-spending":
                        match_lease_maint_spending,
                    }), ),
            ),
        ), )
예제 #21
0
    def test_issue_concurrently(self, server_name):
        """
        Invoking ``issue_cert`` multiple times concurrently for the same name
        will not start multiple issuing processes, only wait for the first
        process to complete.
        """
        with AcmeFixture() as fixture:
            fixture.service.startService()
            self.assertThat(
                fixture.cert_store.as_dict(),
                succeeded(
                    Not(Contains(server_name))))

            fixture.controller.pause()
            d1 = fixture.service.issue_cert(server_name)
            self.assertThat(d1, has_no_result())
            d2 = fixture.service.issue_cert(server_name)
            self.assertThat(d2, has_no_result())
            self.assertThat(fixture.controller.count(), Equals(1))

            fixture.controller.resume()
            self.assertThat(d1, succeeded(Always()))
            self.assertThat(d2, succeeded(Always()))

            self.assertThat(
                fixture.cert_store.as_dict(),
                succeeded(
                    MatchesDict({server_name: Not(Equals([]))})))
예제 #22
0
    def test_sync_app_multiple_domains(self):
        """
        When a sync is run and there is an app with a domain label containing
        multiple domains, then only the first domain is considered.
        """
        self.fake_marathon.add_app({
            'id':
            '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com,example2.com'
            },
            'portDefinitions': [{
                'port': 9000,
                'protocol': 'tcp',
                'labels': {}
            }]
        })

        d = self.marathon_acme.sync()
        assert_that(
            d,
            succeeded(
                MatchesListwise([  # Per domain
                    is_marathon_lb_sigusr_response
                ])))

        assert_that(self.cert_store.as_dict(),
                    succeeded(MatchesDict({'example.com': Not(Is(None))})))

        assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(True))
예제 #23
0
    def test_issue_concurrently(self, server_name):
        """
        Invoking ``issue_cert`` multiple times concurrently for the same name
        will not start multiple issuing processes, only wait for the first
        process to complete.
        """
        with AcmeFixture() as fixture:
            fixture.service.startService()
            self.assertThat(fixture.cert_store.as_dict(),
                            succeeded(Not(Contains(server_name))))

            fixture.controller.pause()
            d1 = fixture.service.issue_cert(server_name)
            self.assertThat(d1, has_no_result())
            d2 = fixture.service.issue_cert(server_name)
            self.assertThat(d2, has_no_result())
            self.assertThat(fixture.controller.count(), Equals(1))

            fixture.controller.resume()
            self.assertThat(d1, succeeded(Always()))
            self.assertThat(d2, succeeded(Always()))

            self.assertThat(
                fixture.cert_store.as_dict(),
                succeeded(MatchesDict({server_name: Not(Equals([]))})))
    def test_delete_snapshot(self, filename, content):
        """
        Create a snapshot and then a deletion snapshot of it.
        """
        foo = self.magic.child(filename)
        foo.setContent(content)

        # make sure the store_local_snapshot() succeeds
        self.assertThat(
            self.snapshot_creator.store_local_snapshot(foo),
            succeeded(Always()),
        )

        # delete the file
        foo.remove()

        # store a new snapshot
        self.assertThat(
            self.snapshot_creator.store_local_snapshot(foo),
            succeeded(Always()),
        )
        stored_snapshot2 = self.db.get_local_snapshot(filename)

        self.assertThat(
            stored_snapshot2.is_delete(),
            Equals(True),
        )
예제 #25
0
    def test_sync_app_no_domains(self):
        """
        When a sync is run and Marathon has an app but that app has no
        marathon-acme domains, then no certificates should be fetched and
        marathon-lb should not be notified.
        """
        self.fake_marathon.add_app({
            'id':
            '/my-app_1',
            'labels': {
                'HAPROXY_0_VHOST': 'example.com'
            },
            'portDefinitions': [{
                'port': 9000,
                'protocol': 'tcp',
                'labels': {}
            }]
        })

        d = self.marathon_acme.sync()
        assert_that(d, succeeded(Equals([])))

        # Nothing stored, nothing notified, but Marathon checked
        assert_that(self.fake_marathon_api.check_called_get_apps(),
                    Equals(True))
        assert_that(self.cert_store.as_dict(), succeeded(Equals({})))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(),
                    Equals(False))
예제 #26
0
    def test_sync_app_no_port_definitions(self):
        """
        When a sync is run and this Marathon doesn't return the
        'portDefinitions' field with the app definitions, the 'ports' field
        should be used instead and a regular sync is completed.
        """
        # Store an app in Marathon with a marathon-acme domain
        self.fake_marathon.add_app({
            'id': '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com'
            },
            'ports': [10007]  # Some random service port
        })

        d = self.marathon_acme.sync()
        assert_that(
            d,
            succeeded(
                MatchesListwise([  # Per domain
                    is_marathon_lb_sigusr_response
                ])))

        assert_that(self.cert_store.as_dict(),
                    succeeded(MatchesDict({'example.com': Not(Is(None))})))

        assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(True))
예제 #27
0
    def test_cancellation(self, server_name):
        """
        Cancelling the deferred returned by ``issue_cert`` cancels the actual
        issuing process.
        """
        with AcmeFixture() as fixture:
            fixture.service.startService()
            self.assertThat(
                fixture.cert_store.as_dict(),
                succeeded(
                    Not(Contains(server_name))))

            fixture.controller.pause()
            d1 = fixture.service.issue_cert(server_name)
            self.assertThat(d1, has_no_result())
            d2 = fixture.service.issue_cert(server_name)
            self.assertThat(d2, has_no_result())
            self.assertThat(fixture.controller.count(), Equals(1))
            d2.cancel()

            fixture.controller.resume()
            self.assertThat(d1, failed_with(IsInstance(CancelledError)))
            self.assertThat(d2, failed_with(IsInstance(CancelledError)))
            self.assertThat(
                fixture.cert_store.as_dict(),
                succeeded(
                    Not(Contains(server_name))))
예제 #28
0
    def test_sync_app(self):
        """
        When a sync is run and there is an app with a domain label and no
        existing certificate, then a new certificate should be issued for the
        domain. The certificate should be stored in the certificate store and
        marathon-lb should be notified.
        """
        # Store an app in Marathon with a marathon-acme domain
        self.fake_marathon.add_app({
            'id':
            '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com'
            },
            'portDefinitions': [{
                'port': 9000,
                'protocol': 'tcp',
                'labels': {}
            }]
        })

        d = self.marathon_acme.sync()
        assert_that(
            d,
            succeeded(
                MatchesListwise([  # Per domain
                    is_marathon_lb_sigusr_response
                ])))

        assert_that(self.cert_store.as_dict(),
                    succeeded(MatchesDict({'example.com': Not(Is(None))})))

        assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(True))
예제 #29
0
    def test_sync_app_port_group_mismatch(self):
        """
        When a sync is run and Marathon has an app and that app has a matching
        group but mismatching port group, then no certificates should be
        fetched and marathon-lb should not be notified.
        """
        self.fake_marathon.add_app({
            'id':
            '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'HAPROXY_0_GROUP': 'internal',
                'HAPROXY_0_VHOST': 'example.com',
                'MARATHON_ACME_0_DOMAIN': 'example.com',
            },
            'portDefinitions': [{
                'port': 9000,
                'protocol': 'tcp',
                'labels': {}
            }]
        })

        d = self.marathon_acme.sync()
        assert_that(d, succeeded(Equals([])))

        # Nothing stored, nothing notified, but Marathon checked
        assert_that(self.fake_marathon_api.check_called_get_apps(),
                    Equals(True))
        assert_that(self.cert_store.as_dict(), succeeded(Equals({})))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(),
                    Equals(False))
예제 #30
0
    def test_write_snapshot_to_tahoe_fails(self, name, contents):
        """
        If any part of a snapshot upload fails then the metadata for that snapshot
        is retained in the local database and the snapshot content is retained
        in the stash.
        """
        broken_root = ErrorPage(500, "It's broken.", "It's broken.")

        f = self.useFixture(
            RemoteSnapshotCreatorFixture(
                temp=FilePath(self.mktemp()),
                author=self.author,
                root=broken_root,
                upload_dircap="URI:DIR2:foo:bar",
            ))
        config = f.config
        remote_snapshot_creator = f.remote_snapshot_creator

        snapshots = []
        parents = []
        for content in contents:
            data = io.BytesIO(content)
            d = create_snapshot(
                name=name,
                author=self.author,
                data_producer=data,
                snapshot_stash_dir=config.stash_path,
                parents=parents,
            )
            d.addCallback(snapshots.append)
            self.assertThat(
                d,
                succeeded(Always()),
            )
            parents = [snapshots[-1]]

        local_snapshot = snapshots[-1]
        config.store_local_snapshot(snapshots[-1])

        d = remote_snapshot_creator.upload_local_snapshots()
        self.assertThat(
            d,
            succeeded(Always()),
        )

        self.eliot_logger.flushTracebacks(TahoeAPIError)

        self.assertEqual(
            local_snapshot,
            config.get_local_snapshot(name),
        )
        self.assertThat(
            local_snapshot.content_path.getContent(),
            Equals(content),
        )
예제 #31
0
 def test_unicode_keys(self, server_name, pem_objects):
     """
     The keys of the dict returned by ``as_dict`` are ``unicode``.
     """
     self.assertThat(self.cert_store.store(server_name, pem_objects),
                     succeeded(Is(None)))
     self.assertThat(
         self.cert_store.as_dict(),
         succeeded(
             AfterPreprocessing(methodcaller('keys'),
                                AllMatch(IsInstance(unicode)))))
 def test_acme_challenge_ping(self):
     """
     When a GET request is made to the ACME challenge path ping endpoint,
     a pong message should be returned.
     """
     response = self.client.get(
         'http://localhost/.well-known/acme-challenge/ping')
     assert_that(response, succeeded(MatchesAll(
         IsJsonResponseWithCode(200),
         After(json_content, succeeded(Equals({'message': 'pong'})))
     )))
예제 #33
0
 def test_get_apps_empty(self):
     """
     When the list of apps is requested and there are no apps, an empty list
     of apps should be returned.
     """
     response = self.client.get('http://localhost/v2/apps')
     assert_that(
         response,
         succeeded(
             MatchesAll(
                 IsJsonResponseWithCode(200),
                 After(json_content, succeeded(Equals({'apps': []}))))))
예제 #34
0
    def _test_get_known_voucher(self, get_config, api_auth_token, now, voucher,
                                voucher_matcher):
        """
        Assert that a voucher that is ``PUT`` and then ``GET`` is represented in
        the JSON response.

        :param voucher_matcher: A matcher which matches the voucher expected
            to be returned by the ``GET``.
        """
        config = get_config_with_api_token(
            self.useFixture(TempDir()),
            get_config,
            api_auth_token,
        )
        root = root_from_config(config, lambda: now)
        agent = RequestTraversalAgent(root)
        putting = authorized_request(
            api_auth_token,
            agent,
            b"PUT",
            b"http://127.0.0.1/voucher",
            data=BytesIO(dumps({u"voucher": voucher})),
        )
        self.assertThat(
            putting,
            succeeded(ok_response(), ),
        )

        getting = authorized_request(
            api_auth_token,
            agent,
            b"GET",
            u"http://127.0.0.1/voucher/{}".format(
                quote(
                    voucher.encode("utf-8"),
                    safe=b"",
                ).decode("utf-8"), ).encode("ascii"),
        )
        self.assertThat(
            getting,
            succeeded(
                MatchesAll(
                    ok_response(headers=application_json()),
                    AfterPreprocessing(
                        readBody,
                        succeeded(
                            AfterPreprocessing(
                                Voucher.from_json,
                                voucher_matcher,
                            ), ),
                    ),
                ), ),
        )
예제 #35
0
 def test_insert(self, server_name, pem_objects):
     """
     Inserting an entry causes the same entry to be returned by ``get`` and
     ``as_dict``.
     """
     self.assertThat(self.cert_store.store(server_name, pem_objects),
                     succeeded(Is(None)))
     self.assertThat(self.cert_store.get(server_name),
                     succeeded(Equals(pem_objects)))
     self.assertThat(
         self.cert_store.as_dict(),
         succeeded(ContainsDict({server_name: Equals(pem_objects)})))
예제 #36
0
 def test_blank_cert(self, server_name):
     """
     An empty certificate file will be treated like an expired certificate.
     """
     with AcmeFixture(certs={server_name: []}) as fixture:
         fixture.service.startService()
         self.assertThat(fixture.service.when_certs_valid(),
                         succeeded(Always()))
         self.assertThat(
             fixture.cert_store.as_dict(),
             succeeded(MatchesDict({server_name: Not(Equals([]))})))
         self.assertThat(fixture.responder.challenges, HasLength(0))
예제 #37
0
 def test_unicode_keys(self, server_name, pem_objects):
     """
     The keys of the dict returned by ``as_dict`` are ``unicode``.
     """
     self.assertThat(
         self.cert_store.store(server_name, pem_objects),
         succeeded(Is(None)))
     self.assertThat(
         self.cert_store.as_dict(),
         succeeded(AfterPreprocessing(
             methodcaller('keys'),
             AllMatch(IsInstance(unicode)))))
예제 #38
0
    def test_listen_events_api_request_triggers_sync(self):
        """
        When we listen for events from Marathon, and something happens that
        triggers an API request event, a sync should be performed and
        certificates issued for any new domains.
        """
        self.marathon_acme.listen_events()

        self.fake_marathon.add_app({
            'id':
            '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com'
            },
            'portDefinitions': [{
                'port': 9000,
                'protocol': 'tcp',
                'labels': {}
            }]
        })

        # Observe that the certificate was stored and marathon-lb notified
        assert_that(self.cert_store.as_dict(),
                    succeeded(MatchesDict({'example.com': Not(Is(None))})))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(True))

        # Try one more app
        self.fake_marathon.add_app({
            'id':
            '/my-app_2',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example2.com',
            },
            'portDefinitions': [
                {
                    'port': 8000,
                    'protocol': 'tcp',
                    'labels': {}
                },
            ]
        })

        assert_that(
            self.cert_store.as_dict(),
            succeeded(
                MatchesDict({
                    'example.com': Not(Is(None)),
                    'example2.com': Not(Is(None)),
                })))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(True))
예제 #39
0
    def test_snapshot_bad_metadata(self, raw_metadata):
        """
        Test error-handling cases when de-serializing a snapshot. If the
        snapshot version is missing or wrong we should error.
        """

        # arbitrary (but valid) content-cap
        contents = []
        content_cap_d = self.tahoe_client.create_immutable(b"0" * 256)
        content_cap_d.addCallback(contents.append)
        self.assertThat(content_cap_d, succeeded(Always()))
        content_cap = contents[0]

        # invalid metadata cap (we use Hypothesis to give us two
        # definitely-invalid versions)
        metadata_caps = []

        d = self.tahoe_client.create_immutable(
            json.dumps(raw_metadata).encode("utf8"))
        d.addCallback(metadata_caps.append)
        self.assertThat(d, succeeded(Always()))

        # create a Snapshot using the wrong metadata
        raw_snapshot_data = {
            u"content":
            format_filenode(content_cap),
            u"metadata":
            format_filenode(
                metadata_caps[0],
                {
                    u"magic_folder": {
                        u"author_signature": u"not valid",
                    },
                },
            ),
        }

        snapshot_cap = []
        d = self.tahoe_client.create_immutable_directory(raw_snapshot_data)
        d.addCallback(snapshot_cap.append)
        self.assertThat(d, succeeded(Always()))

        # now when we read back the snapshot with incorrect metadata,
        # it should fail
        snapshot_d = create_snapshot_from_capability(snapshot_cap[0],
                                                     self.tahoe_client)

        self.assertThat(
            snapshot_d,
            failed(
                MatchesStructure(value=AfterPreprocessing(
                    str, Contains("snapshot_version")), )))
예제 #40
0
    def test_snapshot_roundtrip(self, content, filename):
        """
        Create a local snapshot, write into tahoe to create a remote snapshot,
        then read back the data from the snapshot cap to recreate the remote
        snapshot and check if it is the same as the previous one.
        """
        data = io.BytesIO(content)

        snapshots = []
        # create LocalSnapshot
        d = create_snapshot(
            name=filename,
            author=self.alice,
            data_producer=data,
            snapshot_stash_dir=self.stash_dir,
            parents=[],
        )
        d.addCallback(snapshots.append)
        self.assertThat(
            d,
            succeeded(Always()),
        )

        # create remote snapshot
        d = write_snapshot_to_tahoe(snapshots[0], self.alice,
                                    self.tahoe_client)
        d.addCallback(snapshots.append)

        self.assertThat(
            d,
            succeeded(Always()),
        )

        # snapshots[1] is a RemoteSnapshot
        note("remote snapshot: {}".format(snapshots[1]))

        # now, recreate remote snapshot from the cap string and compare with the original.
        # Check whether information is preserved across these changes.

        snapshot_d = create_snapshot_from_capability(snapshots[1].capability,
                                                     self.tahoe_client)
        snapshot_d.addCallback(snapshots.append)
        self.assertThat(snapshot_d, succeeded(Always()))
        snapshot = snapshots[-1]

        self.assertThat(snapshot, MatchesStructure(name=Equals(filename)))
        content_io = io.BytesIO()
        self.assertThat(
            snapshot.fetch_content(self.tahoe_client, content_io),
            succeeded(Always()),
        )
        self.assertEqual(content_io.getvalue(), content)
예제 #41
0
    def test_sync_multiple_apps(self):
        """
        When a sync is run and there are multiple apps, certificates are
        fetched for the domains of all the apps.
        """
        self.fake_marathon.add_app({
            'id':
            '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com',
            },
            'portDefinitions': [{
                'port': 9000,
                'protocol': 'tcp',
                'labels': {}
            }]
        })
        self.fake_marathon.add_app({
            'id':
            '/my-app_2',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example2.com',
            },
            'portDefinitions': [
                {
                    'port': 8000,
                    'protocol': 'tcp',
                    'labels': {}
                },
            ]
        })

        d = self.marathon_acme.sync()
        assert_that(
            d,
            succeeded(
                MatchesListwise([  # Per domain
                    is_marathon_lb_sigusr_response,
                    is_marathon_lb_sigusr_response
                ])))

        assert_that(
            self.cert_store.as_dict(),
            succeeded(
                MatchesDict({
                    'example.com': Not(Is(None)),
                    'example2.com': Not(Is(None))
                })))

        assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(True))
 def test_health_handler_unset(self):
     """
     When a GET request is made to the health endpoint, and the health
     handler hasn't been set, a 501 status code should be returned together
     with a JSON message that explains that the handler is not set.
     """
     response = self.client.get('http://localhost/health')
     assert_that(response, succeeded(MatchesAll(
         IsJsonResponseWithCode(501),
         After(json_content, succeeded(Equals({
             'error': 'Cannot determine service health: no handler set'
         })))
     )))
예제 #43
0
 def test_insert_twice(self, server_name, pem_objects, pem_objects2):
     """
     Inserting an entry a second time overwrites the first entry.
     """
     self.assertThat(self.cert_store.store(server_name, pem_objects),
                     succeeded(Is(None)))
     self.assertThat(self.cert_store.store(server_name, pem_objects2),
                     succeeded(Is(None)))
     self.assertThat(self.cert_store.get(server_name),
                     succeeded(Equals(pem_objects2)))
     self.assertThat(
         self.cert_store.as_dict(),
         succeeded(ContainsDict({server_name: Equals(pem_objects2)})))
예제 #44
0
 def test_issue_one_cert(self, server_name):
     """
     ``issue_cert`` will (re)issue a single certificate unconditionally.
     """
     with AcmeFixture() as fixture:
         fixture.service.startService()
         self.assertThat(fixture.cert_store.as_dict(),
                         succeeded(Not(Contains(server_name))))
         self.assertThat(fixture.service.issue_cert(server_name),
                         succeeded(Always()))
         self.assertThat(
             fixture.cert_store.as_dict(),
             succeeded(MatchesDict({server_name: Not(Equals([]))})))
    def test_delegates_to_agent_for_location(self, agent):
        """
        When a request is made using the agent, the added agents are delegated
        to based on the URI location/authority.
        """
        agent.add_agent(b'foo:8080', DummyAgent())
        agent.add_agent(b'bar:8080', FailingAgent(RuntimeError('bar')))
        agent.add_agent(b'foo:9090', FailingAgent(RuntimeError('9090')))

        d = agent.request(b'GET', b'http://foo:8080')
        assert_that(d, succeeded(MatchesListwise([
            MatchesListwise([Equals(b'GET'), Equals(b'http://foo:8080')]),
            MatchesDict({'headers': Is(None), 'bodyProducer': Is(None)})
        ])))

        # Scheme doesn't matter
        d = agent.request(b'GET', b'https://foo:8080')
        assert_that(d, succeeded(MatchesListwise([
            MatchesListwise([Equals(b'GET'), Equals(b'https://foo:8080')]),
            MatchesDict({'headers': Is(None), 'bodyProducer': Is(None)})
        ])))

        # Path doesn't matter
        d = agent.request(b'GET', b'http://foo:8080/bar/baz')
        assert_that(d, succeeded(MatchesListwise([
            MatchesListwise([
                Equals(b'GET'), Equals(b'http://foo:8080/bar/baz')]),
            MatchesDict({'headers': Is(None), 'bodyProducer': Is(None)})
        ])))

        # Hostname *does* matter
        d = agent.request(b'GET', b'http://bar:8080')
        assert_that(d, failed(MatchesStructure(value=MatchesAll(
            IsInstance(RuntimeError),
            MatchesPredicate(str, Equals('bar'))
        ))))

        # Port *does* matter
        d = agent.request(b'GET', b'http://foo:9090')
        assert_that(d, failed(MatchesStructure(value=MatchesAll(
            IsInstance(RuntimeError),
            MatchesPredicate(str, Equals('9090'))
        ))))

        # Other args passed through
        d = agent.request(b'GET', b'http://foo:8080', 'bar', 'baz')
        assert_that(d, succeeded(MatchesListwise([
            MatchesListwise([Equals(b'GET'), Equals(b'http://foo:8080')]),
            MatchesDict(
                {'headers': Equals('bar'), 'bodyProducer': Equals('baz')})
        ])))
예제 #46
0
    def _test_get_known_voucher(self, get_config, now, voucher,
                                voucher_matcher):
        """
        Assert that a voucher that is ``PUT`` and then ``GET`` is represented in
        the JSON response.

        :param voucher_matcher: A matcher which matches the voucher expected
            to be returned by the ``GET``.
        """
        tempdir = self.useFixture(TempDir())
        config = get_config(tempdir.join(b"tahoe"), b"tub.port")
        root = root_from_config(config, lambda: now)
        agent = RequestTraversalAgent(root)

        producer = FileBodyProducer(
            BytesIO(dumps({u"voucher": voucher})),
            cooperator=uncooperator(),
        )
        putting = agent.request(
            b"PUT",
            b"http://127.0.0.1/voucher",
            bodyProducer=producer,
        )
        self.assertThat(
            putting,
            succeeded(ok_response(), ),
        )

        getting = agent.request(
            b"GET",
            u"http://127.0.0.1/voucher/{}".format(
                quote(
                    voucher.encode("utf-8"),
                    safe=b"",
                ).decode("utf-8"), ).encode("ascii"),
        )
        self.assertThat(
            getting,
            succeeded(
                MatchesAll(
                    ok_response(headers=application_json()),
                    AfterPreprocessing(
                        readBody,
                        succeeded(
                            AfterPreprocessing(
                                Voucher.from_json,
                                voucher_matcher,
                            ), ),
                    ),
                ), ),
        )
    def test_health_unhealthy(self):
        """
        When a GET request is made to the health endpoint, and the health
        handler reports that the service is unhealthy, a 503 status code should
        be returned together with the JSON message from the handler.
        """
        self.server.set_health_handler(
            lambda: Health(False, {'error': "I'm sad :("}))

        response = self.client.get('http://localhost/health')
        assert_that(response, succeeded(MatchesAll(
            IsJsonResponseWithCode(503),
            After(json_content, succeeded(Equals({'error': "I'm sad :("})))
        )))
예제 #48
0
 def test_blank_cert(self, server_name):
     """
     An empty certificate file will be treated like an expired certificate.
     """
     with AcmeFixture(certs={server_name: []}) as fixture:
         fixture.service.startService()
         self.assertThat(
             fixture.service.when_certs_valid(),
             succeeded(Always()))
         self.assertThat(
             fixture.cert_store.as_dict(),
             succeeded(
                 MatchesDict({server_name: Not(Equals([]))})))
         self.assertThat(fixture.responder.challenges, HasLength(0))
    def test_sync_no_apps(self):
        """
        When a sync is run and Marathon has no apps for us then no certificates
        should be fetched and marathon-lb should not be notified.
        """
        marathon_acme = self.mk_marathon_acme()
        d = marathon_acme.sync()
        assert_that(d, succeeded(Equals([])))

        # Nothing stored, nothing notified, but Marathon checked
        assert_that(
            self.fake_marathon_api.check_called_get_apps(), Equals(True))
        assert_that(self.cert_store.as_dict(), succeeded(Equals({})))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(),
                    Equals(False))
예제 #50
0
 def test_insert(self, server_name, pem_objects):
     """
     Inserting an entry causes the same entry to be returned by ``get`` and
     ``as_dict``.
     """
     self.assertThat(
         self.cert_store.store(server_name, pem_objects),
         succeeded(Is(None)))
     self.assertThat(
         self.cert_store.get(server_name),
         succeeded(Equals(pem_objects)))
     self.assertThat(
         self.cert_store.as_dict(),
         succeeded(ContainsDict(
             {server_name: Equals(pem_objects)})))
예제 #51
0
 def test_get_session(self):
     """
     Create a session.
     """
     def _response_for(method, url, params, headers, data):
         self.assertThat(method, Equals(b'GET'))
         self.assertThat(
             url,
             MatchesAll(
                 IsInstance(bytes),
                 Equals(b'http://example.com/sessions/1234')))
         return (
             200,
             {},
             json.dumps(
                 {u'links':
                  {u'self': u'http://example.com/sessions/1234'}}))
     resource = StringStubbingResource(_response_for)
     treq = StubTreq(resource)
     self.assertThat(
         get_session(u'http://example.com/sessions/1234', treq.request),
         succeeded(
             MatchesStructure(
                 _session_info=Equals(
                     {u'self': u'http://example.com/sessions/1234'}))))
예제 #52
0
    def test_perform_action(self):
        """
        Perform an action within a session.
        """
        payload = {u'links':
                   {u'result': u'https://example.com/result'}}
        action = {u'action': u'some_action',
                  u'parameters': {u'foo': 42}}

        def _response_for(method, url, params, headers, data):
            self.assertThat(method, Equals(b'POST'))
            self.assertThat(
                url,
                MatchesAll(
                    IsInstance(bytes),
                    Equals(b'http://example.com/perform')))
            self.assertThat(
                json.loads(data),
                Equals(action))
            return (200,
                    {b'Content-Type': b'application/json'},
                    json.dumps(payload))
        resource = StringStubbingResource(_response_for)
        treq = StubTreq(resource)
        session = Session({u'perform': u'http://example.com/perform'},
                          treq.request)
        self.assertThat(
            session.perform_action((action, lambda x: x)),
            succeeded(Equals(payload)))
예제 #53
0
 def test_get_content(self):
     """
     Stream content.
     """
     def _response_for(method, url, params, headers, data):
         self.assertThat(method, Equals(b'GET'))
         self.assertThat(
             url,
             MatchesAll(
                 IsInstance(bytes),
                 Equals(b'http://example.com/some_content')))
         return (200,
                 {b'Content-Type': b'text/plain'},
                 b'hello world')
     resource = StringStubbingResource(_response_for)
     treq = StubTreq(resource)
     session = Session({}, treq.request)
     buf = StringIO()
     self.assertThat(
         session.get_content(b'http://example.com/some_content', buf.write),
         succeeded(
             Equals(b'text/plain')))
     self.assertThat(
         buf.getvalue(),
         Equals(b'hello world'))
예제 #54
0
 def test_success(self):
     """
     Status codes indicating success pass the response through without any
     exceptions.
     """
     def _response_for(method, url, params, headers, data):
         return 200, {}, b'hello world'
     resource = StringStubbingResource(_response_for)
     treq = StubTreq(resource)
     request = documint_request_factory(treq.request)
     self.assertThat(
         request(b'GET', b'http://example.com/success'),
         succeeded(
             AfterPreprocessing(
                 treq.content,
                 succeeded(Equals(b'hello world')))))
    def test_listen_events_attach_initial_sync(self):
        """
        When we listen for events from Marathon, and we receive a subscribe
        event from ourselves subscribing, an initial sync should be performed
        and certificates issued for any new domains.
        """
        self.fake_marathon.add_app({
            'id': '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com'
            },
            'portDefinitions': [
                {'port': 9000, 'protocol': 'tcp', 'labels': {}}
            ]
        })

        marathon_acme = self.mk_marathon_acme()
        marathon_acme.listen_events()

        # Observe that the certificate was stored and marathon-lb notified
        assert_that(self.cert_store.as_dict(), succeeded(MatchesDict({
            'example.com': Not(Is(None))
        })))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(True))
    def test_sync_other_issue_failure(self):
        """
        When a sync is run and we try to issue a certificate for a domain but
        some non-ACME server error occurs, the sync should fail.
        """
        self.fake_marathon.add_app({
            'id': '/my-app_1',
            'labels': {
                'HAPROXY_GROUP': 'external',
                'MARATHON_ACME_0_DOMAIN': 'example.com'
            },
            'portDefinitions': [
                {'port': 9000, 'protocol': 'tcp', 'labels': {}}
            ]
        })
        self.txacme_client.issuance_error = RuntimeError('Something bad')

        marathon_acme = self.mk_marathon_acme()
        d = marathon_acme.sync()

        assert_that(d, failed(MatchesStructure(
            value=MatchesStructure(subFailure=MatchesStructure(
                value=MatchesAll(
                    IsInstance(RuntimeError),
                    MatchesPredicate(str, 'Something bad')
                )
            ))
        )))
        # Nothing stored, nothing notified
        assert_that(self.cert_store.as_dict(), succeeded(Equals({})))
        assert_that(self.fake_marathon_lb.check_signalled_usr1(),
                    Equals(False))
    def test_dispatch_with_return_fails(self):
        dispatcher = EventDispatcher()

        test_callback_prio_0_cb_0 = Mock(return_value='hello')
        test_callback_prio_0_cb_1 = Mock(side_effect=RuntimeError('boom!'))
        test_callback_prio_1_cb_0 = Mock(return_value='world')

        dispatcher.add_listener(TestEvent, 0, test_callback_prio_0_cb_0)
        dispatcher.add_listener(TestEvent, 0, test_callback_prio_0_cb_1)
        dispatcher.add_listener(TestEvent, 1, test_callback_prio_1_cb_0)

        event = TestEvent()
        d = dispatcher.dispatch(event, fail_mode=FailMode.RETURN)

        matcher = matchers.MatchesListwise([
            matchers.MatchesListwise([
                matchers.Equals(0), matchers.MatchesListwise([
                    matchers.Equals((True, 'hello')),
                    matchers.MatchesListwise([
                        matchers.Equals(False),
                        matchers.AfterPreprocessing(lambda f: f.value, matchers.IsInstance(HandlerError)),
                    ])
                ]),
            ]),
            matchers.MatchesListwise([
                matchers.Equals(1), matchers.MatchesListwise([
                    matchers.Equals((True, 'world')),
                ]),
            ]),
        ])

        assert_that(d, twistedsupport.succeeded(matcher))
예제 #58
0
    def test_errors(self):
        """
        If a cert renewal fails within the panic interval, the panic callback
        is invoked; otherwise the error is logged normally.
        """
        now = datetime(2000, 1, 1, 0, 0, 0)
        certs = {
            u'a' * 100: _generate_cert(
                u'example.com',
                not_valid_before=now - timedelta(seconds=1),
                not_valid_after=now + timedelta(days=31)),
            }
        panics = []
        with AcmeFixture(now=now, certs=certs,
                         panic=lambda *a: panics.append(a)) as fixture:
            fixture.service.startService()
            self.assertThat(
                fixture.service.when_certs_valid(),
                succeeded(Is(None)))
            self.assertThat(fixture.responder.challenges, HasLength(0))

            fixture.clock.advance(36 * 60 * 60)
            self.assertThat(flush_logged_errors(), HasLength(1))
            self.assertThat(panics, Equals([]))
            self.assertThat(fixture.responder.challenges, HasLength(0))

            fixture.clock.advance(15 * 24 * 60 * 60)
            self.assertThat(
                panics,
                MatchesListwise([
                    MatchesListwise([IsInstance(Failure), Equals(u'a' * 100)]),
                    ]))
            self.assertThat(fixture.responder.challenges, HasLength(0))
예제 #59
0
 def test_insert_twice(self, server_name, pem_objects, pem_objects2):
     """
     Inserting an entry a second time overwrites the first entry.
     """
     self.assertThat(
         self.cert_store.store(server_name, pem_objects),
         succeeded(Is(None)))
     self.assertThat(
         self.cert_store.store(server_name, pem_objects2),
         succeeded(Is(None)))
     self.assertThat(
         self.cert_store.get(server_name),
         succeeded(Equals(pem_objects2)))
     self.assertThat(
         self.cert_store.as_dict(),
         succeeded(ContainsDict({server_name: Equals(pem_objects2)})))
예제 #60
0
 def test_issue_one_cert(self, server_name):
     """
     ``issue_cert`` will (re)issue a single certificate unconditionally.
     """
     with AcmeFixture() as fixture:
         fixture.service.startService()
         self.assertThat(
             fixture.cert_store.as_dict(),
             succeeded(
                 Not(Contains(server_name))))
         self.assertThat(
             fixture.service.issue_cert(server_name),
             succeeded(Always()))
         self.assertThat(
             fixture.cert_store.as_dict(),
             succeeded(
                 MatchesDict({server_name: Not(Equals([]))})))