Skip to content

Commit

Permalink
Integrate publishing to eventbridge (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
Grunet authored Jul 21, 2023
1 parent 6a3e4e7 commit fcd186f
Show file tree
Hide file tree
Showing 20 changed files with 1,259 additions and 36 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"python.linting.flake8Args": [
"--max-line-length=88",
"--ignore=E203"
] // These are duplicated in the .flake8 config file
], // These are duplicated in the .flake8 config file
"python.defaultInterpreterPath": "/home/vscode/.cache/pypoetry/virtualenvs/honeypot-gX_fOwtd-py3.11/bin/python" // Found by running "poetry show -v" and looking at the first emitted line
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-honeypot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
id: build-and-push
uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
with:
context: ./packages/honeypot/src/
context: ./packages/honeypot/
push: true
tags: |
${{ steps.meta.outputs.tags }}
Expand Down
8 changes: 8 additions & 0 deletions packages/honeypot/.pyre_configuration
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,13 @@
"site_package_search_strategy": "pep561",
"source_directories": [
"./src"
],
"search_path": [
{
"site-package": "boto3"
},
{
"site-package": "moto"
}
]
}
5 changes: 5 additions & 0 deletions packages/honeypot/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ What is in scope for breaking changes includes
- Environment variables for the docker image
- Ports exposed by the docker image
- Container healthcheck routes exposed by the docker image
- Metadata and details of published events

## Changes

### v0.3.0

- Updated the simple http server to publish an event to an Eventbridge event bus on all GET requests, if the functionality is enabled (via environment variables)

### v0.2.3

- Try fix an issue with cosign prompting in the release workflow
Expand Down
22 changes: 22 additions & 0 deletions packages/honeypot/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Adapted from https://edu.chainguard.dev/chainguard/chainguard-images/reference/python/overview/#usage

# Python 3.11.4, dev base image. See https://www.chainguard.dev/unchained/a-guide-on-how-to-use-chainguard-images-for-public-catalog-tier-users for more details on how to update this
FROM cgr.dev/chainguard/python@sha256:ed93b11743a002aa79f7f084d2f23b7040a8c35ce26a34b7918f15db16fbc8f2 as builder

WORKDIR /app

COPY requirements.txt .

RUN pip install -r requirements.txt --user

# Python 3.11.4, production base image. See https://www.chainguard.dev/unchained/a-guide-on-how-to-use-chainguard-images-for-public-catalog-tier-users for more details on how to update this
FROM cgr.dev/chainguard/python@sha256:bbaba40f4dfff902af5ec49793a8d42478cae07ad9fcd6eace93a55c348a2aa6

# Make sure you update Python version in path
COPY --from=builder /home/nonroot/.local/lib/python3.11/site-packages /home/nonroot/.local/lib/python3.11/site-packages

COPY ./src/ .

# Arbitrary non-root id, something just below the 65535 limit https://unix.stackexchange.com/questions/191663/any-concerns-with-using-high-uid-numbers-3000-on-rhel5
USER 65532
ENTRYPOINT [ "python", "main.py" ]
8 changes: 4 additions & 4 deletions packages/honeypot/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ static-analysis-pyre:
static-analysis-mypy:
poetry run mypy --strict .
test-unit:
python -m unittest discover -s ./src/
poetry run python -m unittest discover -s ./src/
start-server-simple-http:
ENABLE_SERVER_SIMPLE_HTTP=true python ./src/main.py
ENABLE_SERVER_SIMPLE_HTTP=true poetry run python ./src/main.py
start-docker-simple-http:
cd ./src/ && docker compose up -d --build
docker compose up -d --build
stop-docker-simple-http:
cd ./src/ && docker compose down
docker compose down
20 changes: 19 additions & 1 deletion packages/honeypot/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
TODO
# Honeypot

## Contributing

### Dependency Management

When adding a new pypi dependency, first add it to Poetry like

```
poetry add (--dev) <name-of-dependency>
```

Then regenerate `requirements.txt` from the Poetry lockfile like

```
poetry export -f requirements.txt --output requirements.txt
```

This will make sure it's included in the Docker image build (requirements.txt) as well as the local dev environment (Poetry).
File renamed without changes.
958 changes: 957 additions & 1 deletion packages/honeypot/poetry.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions packages/honeypot/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ readme = "README.md"

[tool.poetry.dependencies]
python = "^3.11"
boto3 = "^1.28.5"


[tool.poetry.group.dev.dependencies]
black = "^23.3.0"
flake8 = "^6.0.0"
pyre-check = "^0.9.18"
mypy = "^1.4.1"
boto3-stubs = "^1.28.5"
moto = {extras = ["events"], version = "^4.1.13"}

[build-system]
requires = ["poetry-core"]
Expand Down
21 changes: 21 additions & 0 deletions packages/honeypot/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
boto3==1.28.5 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:2c76db4a1208b8d09814261fc5e530fc36b3b952ef807312495e6869fa6eaad5 \
--hash=sha256:a5c815ab81219a606f20362c9d9c190f5c224bf33c5dc4c20501036cc4a9034f
botocore==1.31.5 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:8aec97512587a5475036a982785e406c52efd260457b809846985f849c3d7cf3 \
--hash=sha256:b35114dae9c451895a11fef13d76881e2bb5428e5de8a702cc8589a28fb34c7a
jmespath==1.0.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 \
--hash=sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe
python-dateutil==2.8.2 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
--hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
s3transfer==0.6.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:3c0da2d074bf35d6870ef157158641178a4204a6e689e82546083e31e0311346 \
--hash=sha256:640bb492711f4c0c0905e1f62b6aaeb771881935ad27884852411f8e9cacbca9
six==1.16.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
urllib3==1.26.16 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f \
--hash=sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14
12 changes: 0 additions & 12 deletions packages/honeypot/src/Dockerfile

This file was deleted.

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from typing import Protocol
from abc import abstractmethod


class EventClientAdapterProtocol(Protocol):
@abstractmethod
def sendEvent(self, eventDetails: object) -> None:
raise NotImplementedError
48 changes: 48 additions & 0 deletions packages/honeypot/src/eventClients/eventbridge_client_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from .event_client_adapter_protocol import EventClientAdapterProtocol

import boto3

from dataclasses import dataclass
import json


class EventbridgeClientAdapter(EventClientAdapterProtocol):
def __init__(self, eventBusNameOrArn: str) -> None:
self.__eventBusNameOrArn: str = eventBusNameOrArn

self.__eventbridgeClient = boto3.client("events")

def sendEvent(self, eventDetails: object) -> None:
response = self.__eventbridgeClient.put_events(
Entries=[
{
"Source": "cloud-native-honeypot",
"DetailType": "cloudNativeHoneypotTriggered",
"Detail": json.dumps(eventDetails),
"EventBusName": self.__eventBusNameOrArn,
}
]
)

if response["FailedEntryCount"] == 0:
print("Eventbridge event published successfully.")
else:
print(
f"""Failed to publish {response['FailedEntryCount']} event(s) to
Eventbridge."""
)


@dataclass
class EventbridgeClientAdapterInputs:
eventBusNameOrArn: str


def createEventClientAdapter(
inputs: EventbridgeClientAdapterInputs,
) -> EventClientAdapterProtocol:
eventBusNameOrArn = inputs.eventBusNameOrArn
if not eventBusNameOrArn:
raise ValueError("eventBusNameOrArn must be a non-empty string")

return EventbridgeClientAdapter(eventBusNameOrArn=eventBusNameOrArn)
62 changes: 53 additions & 9 deletions packages/honeypot/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,65 @@
import os
from typing import Any

from eventClients.event_client_adapter_protocol import EventClientAdapterProtocol
from eventClients.eventbridge_client_adapter import (
createEventClientAdapter,
EventbridgeClientAdapterInputs,
)
from servers import simple_http
from servers.server_adapter_protocol import ServerAdapterProtocol

serverNameToEnvVarDict = {"simple_http": "ENABLE_SERVER_SIMPLE_HTTP"}


class ServerAdaptersManager:
def __init__(self) -> None:
self.__serverAdapters: list[ServerAdapterProtocol] = []

def startServers(self) -> None:
eventClient = tryCreateEventbrigeClient()

if isServerEnabled("simple_http"):
sa = simple_http.createServerAdapter(
simple_http.ServerAdapterInputs(eventClient=eventClient)
)
self.__serverAdapters.append(sa)

for serverAdapter in self.__serverAdapters:
serverAdapter.start()

def stopServers(self) -> None:
for serverAdapter in self.__serverAdapters:
serverAdapter.stop()


def tryCreateEventbrigeClient() -> EventClientAdapterProtocol | None:
if not isEnvironmentVariableTruthy("ENABLE_EVENT_CLIENT_EVENTBRIDGE"):
return None

try:
eventBusNameOrArn = os.environ.get("EVENTBRIDGE_EVENT_BUS_NAME_OR_ARN")

if not eventBusNameOrArn:
print("Missing EVENTBRIDGE_EVENT_BUS_NAME_OR_ARN")
return None

return createEventClientAdapter(
EventbridgeClientAdapterInputs(eventBusNameOrArn=eventBusNameOrArn)
)
except Exception as ex:
print("Failed to create eventbridge client")
print(ex)
return None


def isServerEnabled(serverName: str) -> bool:
envVarName = serverNameToEnvVarDict[serverName]

return isEnvironmentVariableTruthy(envVarName)


def isEnvironmentVariableTruthy(envVarName: str) -> bool:
envVarValue = os.environ.get(envVarName)

if (envVarValue) and (len(envVarValue) > 0):
Expand All @@ -20,20 +70,14 @@ def isServerEnabled(serverName: str) -> bool:


if __name__ == "__main__":
serverAdapters: list[ServerAdapterProtocol] = []
serverAdaptersManager = ServerAdaptersManager()

def terminationHandler(sig: int, frame: Any) -> None:
for serverAdapter in serverAdapters:
serverAdapter.stop()
serverAdaptersManager.stopServers()

print("Exiting the process")

signal.signal(signal.SIGINT, terminationHandler)
signal.signal(signal.SIGTERM, terminationHandler)

if isServerEnabled("simple_http"):
sa = simple_http.createServerAdapter()
serverAdapters.append(sa)

for serverAdapter in serverAdapters:
serverAdapter.start()
serverAdaptersManager.startServers()
39 changes: 36 additions & 3 deletions packages/honeypot/src/servers/simple_http.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,52 @@
from .server_adapter_protocol import ServerAdapterProtocol
from eventClients.event_client_adapter_protocol import EventClientAdapterProtocol

from dataclasses import dataclass
from http.server import HTTPServer, BaseHTTPRequestHandler
from threading import Thread
from typing import Any


class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def __init__(self, request: Any, client_address: Any, server: Any) -> None:
self.__eventClient: EventClientAdapterProtocol | None = server.eventClient

super().__init__(request, client_address, server)

def do_GET(self) -> None:
if self.__eventClient:
try:
self.__eventClient.sendEvent(
{
"server": "simple_http",
"requestMethod": "GET",
"clientIp": self.client_address[0],
"userAgent": self.headers.get("User-Agent"),
}
)
except Exception as ex:
print("Failed to send event in response to GET request")
print(ex)

self.send_response(200)
self.end_headers()
self.wfile.write(b"Hello, World!")


class SimpleHttpServerAdapter(ServerAdapterProtocol):
def __init__(self) -> None:
def __init__(self, eventClient: EventClientAdapterProtocol | None) -> None:
self.__eventClient: EventClientAdapterProtocol | None = eventClient

self.__httpServer: HTTPServer | None = None

def start(self) -> None:
print("Starting simple HTTP server on port 8000")

self.__httpServer = HTTPServer(("0.0.0.0", 8000), SimpleHTTPRequestHandler)
# Only clear way to inject the client into request handling
# is by appending it onto the server object
# and reading it later on
self.__httpServer.eventClient = self.__eventClient # type: ignore[attr-defined]

def start_in_separate_thread(httpServer: HTTPServer | None) -> None:
if httpServer:
Expand All @@ -39,5 +67,10 @@ def stop_in_separate_thread(httpServer: HTTPServer | None) -> None:
thread.join()


def createServerAdapter() -> ServerAdapterProtocol:
return SimpleHttpServerAdapter()
@dataclass
class ServerAdapterInputs:
eventClient: EventClientAdapterProtocol | None


def createServerAdapter(inputs: ServerAdapterInputs) -> ServerAdapterProtocol:
return SimpleHttpServerAdapter(eventClient=inputs.eventClient)
Loading

0 comments on commit fcd186f

Please sign in to comment.