Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Akamai/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## 2026-07-09 - 1.1.0

### Changed

- Migrated module from Poetry to uv

## 2026-04-16 - 1.0.1

### Changed
Expand Down
20 changes: 16 additions & 4 deletions Akamai/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
FROM python:3.11
FROM python:3.14

WORKDIR /app

RUN pip install poetry
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

# Install dependencies
COPY poetry.lock pyproject.toml /app/
RUN poetry config virtualenvs.create false && poetry install --only main
COPY pyproject.toml uv.lock /app/
RUN uv sync --frozen --no-dev --no-install-project

# Make the project virtual environment the default interpreter
ENV PATH="/app/.venv/bin:$PATH"

COPY . .

RUN useradd -ms /bin/bash sekoiaio-runtime

# Some dependencies (e.g. pyrate-limiter, used by requests-ratelimiter) resolve a temp
# directory at import time via tempfile.gettempdir(). The default system temp directories
# (/tmp, /var/tmp, /usr/tmp) may not be writable by the non-root runtime user in the
# deployment environment, so provide a dedicated writable directory and point TMPDIR at it.
RUN mkdir -p /app/tmp && chown sekoiaio-runtime:sekoiaio-runtime /app/tmp
ENV TMPDIR=/app/tmp

USER sekoiaio-runtime

ENTRYPOINT [ "python", "./main.py" ]
15 changes: 8 additions & 7 deletions Akamai/akamai_modules/connector_akamai_waf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
import re
import time
import urllib.parse
from datetime import datetime, timedelta, timezone
from collections.abc import Generator
from datetime import UTC, datetime, timedelta
from functools import cached_property
from typing import Any, Generator
from typing import Any

import orjson
import requests
from cachetools import Cache, LRUCache
from pydantic.v1 import Field
from pydantic import Field
from sekoia_automation.checkpoint import CheckpointTimestamp, TimeUnit
from sekoia_automation.connector import Connector, DefaultConnectorConfiguration

Expand Down Expand Up @@ -136,7 +137,7 @@ def process_event(cls, event: dict[str, Any]) -> None:
if "responseHeaders" in event.get("httpMessage", {}):
event["httpMessage"]["responseHeaders"] = response_headers

def __fetch_next_events(self, from_date: int) -> Generator[list, None, None]:
def __fetch_next_events(self, from_date: int) -> Generator[list]:
url = f"{self.module.configuration.base_url}/siem/v1/configs/{self.configuration.config_id}"
response = self.client.get(
url=url, params={"from": from_date, "limit": self.page_size}, timeout=60, stream=True
Comment on lines +140 to 143

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Clarify the generator element types for better static checking and readability.

Since you’re updating the hints, please specify the yielded types more precisely. For example, __fetch_next_events could be Generator[list[dict[str, Any]], None, None], and fetch_events / filter_processed_events Generator[dict[str, Any], None, None]. This helps mypy and readers understand the data shape flowing through the connector.

Suggested implementation:

from cachetools import Cache, LRUCache
from pydantic import Field
from typing import Any, Generator
from sekoia_automation.checkpoint import CheckpointTimestamp, TimeUnit
from sekoia_automation.connector import Connector, DefaultConnectorConfiguration
        if "responseHeaders" in event.get("httpMessage", {}):
            event["httpMessage"]["responseHeaders"] = response_headers

    def __fetch_next_events(self, from_date: int) -> Generator[list[dict[str, Any]], None, None]:
            )

    def fetch_events(self) -> Generator[dict[str, Any], None, None]:
        most_recent_date_seen: int = self.from_timestamp

Expand Down Expand Up @@ -186,7 +187,7 @@ def __fetch_next_events(self, from_date: int) -> Generator[list, None, None]:
url=url, params={"offset": offset, "limit": self.page_size}, timeout=60, stream=True
)

def fetch_events(self) -> Generator[list, None, None]:
def fetch_events(self) -> Generator[list]:
most_recent_date_seen: int = self.from_timestamp

for next_events in self.__fetch_next_events(most_recent_date_seen):
Expand All @@ -205,7 +206,7 @@ def fetch_events(self) -> Generator[list, None, None]:
self.from_timestamp = most_recent_date_seen
self.cursor.offset = most_recent_date_seen

delta_time = datetime.now(timezone.utc).timestamp() - most_recent_date_seen
delta_time = datetime.now(UTC).timestamp() - most_recent_date_seen
current_lag = int(delta_time)
EVENTS_LAG.labels(intake_key=self.configuration.intake_key).set(current_lag)

Expand Down Expand Up @@ -239,7 +240,7 @@ def __handle_response_error(self, response: requests.Response):

response.raise_for_status()

def filter_processed_events(self, events: list[dict]) -> Generator[dict, None, None]:
def filter_processed_events(self, events: list[dict]) -> Generator[dict]:
for event in events:
event_id = event["httpMessage"]["requestId"]
if event_id in self.events_cache:
Expand Down
6 changes: 3 additions & 3 deletions Akamai/akamai_modules/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from pydantic.v1 import BaseModel, Field
from pydantic import BaseModel, Field


class AkamaiModuleConfiguration(BaseModel):
host: str = Field(..., description="Host of the tenant")
client_token: str = Field(..., description="Client token")
client_secret: str = Field(..., description="Client secret", secret=True)
access_token: str = Field(..., description="Access Token", secret=True)
client_secret: str = Field(..., description="Client secret", json_schema_extra={"secret": True})
access_token: str = Field(..., description="Access Token", json_schema_extra={"secret": True})

@property
def base_url(self):
Expand Down
2 changes: 1 addition & 1 deletion Akamai/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@
"slug": "akamai",
"name": "Akamai",
"uuid": "6f4e254f-9f1b-4760-8af5-f6639779e25a",
"version": "1.0.1"
"version": "1.1.0"
}
10 changes: 10 additions & 0 deletions Akamai/mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Module-specific mise config.
# Common tools and tasks (uv, test, lint, format) are inherited from the
# repository root mise.toml. Only the Python version and the local .venv are
# declared here, since both are module-specific.

[tools]
python = "3.14"

[env]
_.python.venv = { path = ".venv", create = false }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_.python.venv = { path = ".venv", create = false }
_.python.venv = { path = ".venv", create = false }
[tasks.test]
description = "Run tests"
run = "uv run pytest tests/"
[tasks.lint]
description = "Check code style and types"
run = [
"uv run ruff check .",
"uv run ruff format --check .",
"uv run mypy .",
]
[tasks.format]
description = "Format and auto-fix code"
run = [
"uv run ruff check --fix .",
"uv run ruff format .",
]
[tasks.check]
description = "Check typing"
run = "uvx mypy --install-types --non-interactive --ignore-missing-imports --show-column-numbers --hide-error-context ."
[tasks.compliance]
description = "check the compliance of the automation module"
dir = "../utils"
run = "uv run python compliance check --changes"
[tasks.validate]
description = "Check the automation module"
depends = ["lint", "format", "check", "compliance"]

Loading