Package: pytest-hpfeeds
pytest-hpfeedspytest-hpfeeds is a collection of boilerplate to help with smoke/integration testing of honeypots against a hpfeeds broker. It leverages pytest-docker-tools to manage running a test broker inside docker. It provides ahpfeeds_clientfixture to provide your pytest with a client connected to that broker.hpfeeds_brokerThis package provides ahpfeeds_brokerfixture. By referencing this fixture from a test pytest-hpfeeds will automatically start a broker (in a container) before your test and destroy it after the test is completed.deftest_my_broker(hpfeeds_broker):asserthpfeeds_broker.ips.primaryisnotNoneBy default the broker is configured with a single user (testwith a secret oftest) and a single channel calledtest.hpfeeds_clientThe package also provides ahpfeeds_clientfixture. This is an instance ofhpfeeds.asyncio.ClientSessionthat is already connected to your broker. Because the client depends on thehpfeeds_brokeryou don't need to reference it, pytest will still automatically start and stop the broker as needed.asyncdeftest_my_client(hpfeeds_client):hpfeeds_client.subscribe('test')hpfeeds_client.publish('test','hello')assertawaithpfeeds_client.read()==('test','test',b'hello')hpfeeds_broker_channelsYou can implement this fixture in yourconftest.pyto change which channels your broker knows about.importpytest@pytest.fixture()defhpfeeds_broker_channels():return["cowrie.sessions"]asyncdeftest_my_client(hpfeeds_client):hpfeeds_client.subscribe('cowrie.sessions"')hpfeeds_client.publish('cowrie.sessions"','hello')assertawaithpfeeds_client.read()==('test','cowrie.sessions"',b'hello')Testing a honeypot in practiceYou have packaged a honeypot and you want to write an end to end test to make sure that it functions as expected.If you have a honeypot in the current directory with aDockerfileyou can write aconftest.pylike this:importpathlibfrompytest_docker_toolsimportimage_or_buildCURRENT_DIR=pathlib.Path(__file__).parentimage=image_or_build(environ_key='IMAGE_ID',path=str(CURRENT_DIR),)honeypot=container(image=image,environment={"OUTPUT_HPFEEDS_HOST":"{hpfeeds_broker.ips.primary}","OUTPUT_HPFEEDS_PORT":"20000","OUTPUT_HPFEEDS_IDENT":"test","OUTPUT_HPFEEDS_SECRET":"test","OUTPUT_HPFEEDS_CHANNEL":"test",},ports={"8443/tcp":None},user="nobody",read_only=True,)To learn more about what this is doing, you should read the pytest-docker-toolsREADME. But some key points are:Variables are automatically interpolated against pytest fixtures. So"{hpfeeds_broker.ips.primary}"resolves thehpfeeds_brokerfixture (causing an ephemeral broker container to be started) and gets its main IP to pass to your honeypot image.Theimagefixture lets you test an existing image (one that exists locally). Thebuildfixture lets you do iterative development - it effectively doesdocker buildevery time you run your tests. Sometimes you want both. You want your development environment to use thebuldfixture, but your release pipeline should use theimagefixture so that it is testing the exact image (bit for bit) that will be deployed. That's what theimage_or_buildfixture is for. If your CI pipeline sets theIMAGE_IDenvironment variable then the existing image is tested. Otherwise pytest willdocker builda new image.Now to test this honeypot you can write a test:importjsonimporthttpxasyncdeftest_honeypot_logs_data(honeypot,hpfeeds_client):hpfeeds_client.subscribe("test")ip,port=honeypot.get_addr("8443/tcp")# Simulate simulating an attack on the honeypotasyncwithhttpx.AsyncClient()asclient:response=awaitclient.get(f"http://{ip}:{port}/some-endpoint")assertr.status_code==200ident,channel,event=awaithpfeeds_client.read()# Verify the event is correct and that the structure hasn't changedassertjson.loads(event)=={"event":"http.get",# ....}By usingpytest-hpfeedsandpytest-docker-toolsmost of the heavy lifting of build and starting your containerised honeypot and connecting it to a hpfeeds broker is hidden away. You can concentrating on simulating attacks against the honeypot and verifying the hpfeeds output, making it safe to rapidly deploy to your production environment without regressing your event processing backend.