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))))
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)
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]}))))))
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))
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))
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))
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))
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))
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)), )
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)))
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))
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, }), ), ), ), )
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_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))
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), )
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))
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))
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))
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))
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), )
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'}))) )))
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': []}))))))
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, ), ), ), ), ), )
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)})))
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_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_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))
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")), )))
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)
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' }))) )))
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)})))
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')}) ])))
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 :("}))) )))
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))
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)})))
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'}))))
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)))
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'))
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))
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))
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)})))
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([]))})))