Skip to content

Commit 8f45e82

Browse files
committed
Feat: Add protocol-specific event types
1 parent 6215fcc commit 8f45e82

4 files changed

Lines changed: 234 additions & 83 deletions

File tree

.github/workflows/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
strategy:
77
fail-fast: false
88
matrix:
9-
python-version: ["3.13"]
9+
python-version: ["3.10", "3.11", "3.12", "3.13"]
1010
poetry-version: ["2.3.1"]
1111
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
1212
runs-on: ${{ matrix.os }}

l9format/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.3.2"
1+
__version__ = "1.4.0"

l9format/l9format.py

Lines changed: 229 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
from serde import Model, fields
44

55

6-
# Import from https://github.com/rossmacarthur/serde/commit/304884bca3c80e8b9a22a054b64dd94e3324b593#diff-dd2f65f16516311f0183377201a3d0b7894438787da732fc2bbc9c28cbde9fb8
7-
# A helper function...
86
def round_decimal(
97
decimal_obj: decimal.Decimal, num_of_places: int = 6
108
) -> decimal.Decimal:
@@ -16,19 +14,18 @@ def round_decimal(
1614
class Decimal(fields.Instance):
1715
"""
1816
A `~decimal.Decimal` field.
17+
1918
This field serializes `~decimal.Decimal` objects as strings and
2019
deserializes string representations of Decimals as `~decimal.Decimal`
2120
objects.
22-
The resolution of the decimal can be specified. When not specified, the number
23-
is not rounded. When it is specified, the decimal is rounded to this number of
24-
decimal places upon serialization and deserialization.
25-
Note: When float type numbers are not rounded before serialization,
26-
they will be serialized in exact form, which as they are floats,
27-
is almost never the exact intended value,
28-
e.g. 0.2 = 0.20000000000000000000023
21+
22+
The resolution of the decimal can be specified. When not specified,
23+
the number is not rounded. When it is specified, the decimal is rounded
24+
to this number of decimal places upon serialization and deserialization.
25+
2926
Args:
30-
resolution (Union[int, bool]): The number of decimal places to round to.
31-
When None, rounding is disabled.
27+
resolution (int | None): The number of decimal places to round to.
28+
When None, rounding is disabled.
3229
**kwargs: keyword arguments for the `Field` constructor.
3330
"""
3431

@@ -49,44 +46,33 @@ def deserialize(self, value: object) -> decimal.Decimal:
4946
return round_decimal(
5047
decimal.Decimal(str(value)), num_of_places=self.resolution
5148
)
52-
5349
return decimal.Decimal(str(value))
5450
except decimal.DecimalException as e:
5551
raise ValueError(f"invalid decimal: {value}") from e
5652

5753

58-
class L9HttpEvent(Model):
59-
root: fields.Str()
60-
url: fields.Str()
61-
status: fields.Int()
62-
# FIXME: must be int64
63-
length: fields.Int()
64-
header: fields.Optional(fields.Dict(key=fields.Str(), value=fields.Str()))
65-
title: fields.Str()
66-
favicon_hash: fields.Str()
54+
# --- Base Models ---
6755

6856

69-
class ServiceCredentials(Model):
70-
noauth: fields.Bool()
71-
username: fields.Str()
72-
password: fields.Str()
73-
key: fields.Str()
74-
# Bytes
75-
raw: fields.Optional(fields.Str())
57+
class GeoPoint(Model):
58+
lat: Decimal()
59+
lon: Decimal()
7660

7761

78-
class SoftwareModule(Model):
79-
name: fields.Str()
80-
version: fields.Str()
81-
fingerprint: fields.Str()
62+
class GeoLocation(Model):
63+
continent_name: fields.Optional(fields.Str())
64+
region_iso_code: fields.Optional(fields.Str())
65+
city_name: fields.Optional(fields.Str())
66+
country_iso_code: fields.Optional(fields.Str())
67+
country_name: fields.Optional(fields.Str())
68+
region_name: fields.Optional(fields.Str())
69+
location: fields.Optional(fields.Nested(GeoPoint))
8270

8371

84-
class Software(Model):
85-
name: fields.Str()
86-
version: fields.Str()
87-
os: fields.Str()
88-
modules: fields.Optional(fields.List(fields.Nested(SoftwareModule)))
89-
fingerprint: fields.Str()
72+
class Network(Model):
73+
organization_name: fields.Str()
74+
asn: fields.Int()
75+
network: fields.Str()
9076

9177

9278
class Certificate(Model):
@@ -101,26 +87,26 @@ class Certificate(Model):
10187
valid: fields.Bool()
10288

10389

104-
class GeoPoint(Model):
105-
lat: Decimal()
106-
lon: Decimal()
90+
class SoftwareModule(Model):
91+
name: fields.Str()
92+
version: fields.Str()
93+
fingerprint: fields.Str()
10794

10895

109-
class GeoLocation(Model):
110-
continent_name: fields.Optional(fields.Str())
111-
region_iso_code: fields.Optional(fields.Str())
112-
city_name: fields.Optional(fields.Str())
113-
country_iso_code: fields.Optional(fields.Str())
114-
country_name: fields.Optional(fields.Str())
115-
region_name: fields.Optional(fields.Str())
116-
location: fields.Optional(fields.Nested(GeoPoint))
96+
class Software(Model):
97+
name: fields.Str()
98+
version: fields.Str()
99+
os: fields.Str()
100+
modules: fields.Optional(fields.List(fields.Nested(SoftwareModule)))
101+
fingerprint: fields.Str()
117102

118103

119-
class L9SSHEvent(Model):
120-
fingerprint: fields.Str()
121-
version: fields.Int()
122-
banner: fields.Str()
123-
motd: fields.Str()
104+
class ServiceCredentials(Model):
105+
noauth: fields.Bool()
106+
username: fields.Str()
107+
password: fields.Str()
108+
key: fields.Str()
109+
raw: fields.Optional(fields.Str())
124110

125111

126112
class DatasetSummary(Model):
@@ -132,11 +118,17 @@ class DatasetSummary(Model):
132118
ransom_notes: fields.Optional(fields.List(fields.Str()))
133119

134120

135-
class L9LeakEvent(Model):
136-
stage: fields.Str()
137-
type: fields.Str()
138-
severity: fields.Str()
139-
dataset: fields.Nested(DatasetSummary)
121+
# --- Service Events ---
122+
123+
124+
class L9HttpEvent(Model):
125+
root: fields.Str()
126+
url: fields.Str()
127+
status: fields.Int()
128+
length: fields.Int()
129+
header: fields.Optional(fields.Dict(key=fields.Str(), value=fields.Str()))
130+
title: fields.Str()
131+
favicon_hash: fields.Str()
140132

141133

142134
class L9SSLEvent(Model):
@@ -153,10 +145,149 @@ class L9ServiceEvent(Model):
153145
software: fields.Nested(Software)
154146

155147

156-
class Network(Model):
157-
organization_name: fields.Str()
158-
asn: fields.Int()
159-
network: fields.Str()
148+
class L9LeakEvent(Model):
149+
stage: fields.Str()
150+
type: fields.Str()
151+
severity: fields.Str()
152+
dataset: fields.Nested(DatasetSummary)
153+
154+
155+
# --- Protocol Events ---
156+
157+
158+
class L9SSHEvent(Model):
159+
fingerprint: fields.Optional(fields.Str())
160+
version: fields.Optional(fields.Int())
161+
banner: fields.Optional(fields.Str())
162+
motd: fields.Optional(fields.Str())
163+
key_type: fields.Optional(fields.Str())
164+
key: fields.Optional(fields.Str())
165+
kex_algorithms: fields.Optional(fields.List(fields.Str()))
166+
host_key_algorithms: fields.Optional(fields.List(fields.Str()))
167+
encryption_algorithms: fields.Optional(fields.List(fields.Str()))
168+
mac_algorithms: fields.Optional(fields.List(fields.Str()))
169+
compression_algorithms: fields.Optional(fields.List(fields.Str()))
170+
auth_methods: fields.Optional(fields.List(fields.Str()))
171+
172+
173+
class L9VNCEvent(Model):
174+
version: fields.Optional(fields.Str())
175+
security_types: fields.Optional(fields.List(fields.Str()))
176+
noauth: fields.Optional(fields.Bool())
177+
178+
179+
class L9FTPEvent(Model):
180+
banner: fields.Optional(fields.Str())
181+
tls_supported: fields.Optional(fields.Bool())
182+
anonymous: fields.Optional(fields.Bool())
183+
184+
185+
class L9SMTPEvent(Model):
186+
banner: fields.Optional(fields.Str())
187+
starttls: fields.Optional(fields.Bool())
188+
extensions: fields.Optional(fields.List(fields.Str()))
189+
190+
191+
class L9TelnetEvent(Model):
192+
banner: fields.Optional(fields.Str())
193+
options: fields.Optional(fields.List(fields.Str()))
194+
auth_required: fields.Optional(fields.Bool())
195+
196+
197+
class L9RedisEvent(Model):
198+
version: fields.Optional(fields.Str())
199+
mode: fields.Optional(fields.Str())
200+
os: fields.Optional(fields.Str())
201+
auth_required: fields.Optional(fields.Bool())
202+
203+
204+
class L9MySQLEvent(Model):
205+
version: fields.Optional(fields.Str())
206+
protocol_version: fields.Optional(fields.Int())
207+
auth_plugin: fields.Optional(fields.Str())
208+
server_status: fields.Optional(fields.Str())
209+
210+
211+
class L9PostgreSQLEvent(Model):
212+
version: fields.Optional(fields.Str())
213+
databases: fields.Optional(fields.List(fields.Str()))
214+
ssl_enabled: fields.Optional(fields.Bool())
215+
auth_method: fields.Optional(fields.Str())
216+
server_encoding: fields.Optional(fields.Str())
217+
client_encoding: fields.Optional(fields.Str())
218+
timezone: fields.Optional(fields.Str())
219+
max_connections: fields.Optional(fields.Int())
220+
221+
222+
class L9MongoDBEvent(Model):
223+
version: fields.Optional(fields.Str())
224+
databases: fields.Optional(fields.List(fields.Str()))
225+
auth_required: fields.Optional(fields.Bool())
226+
wire_version: fields.Optional(fields.Int())
227+
228+
229+
class L9MemcachedEvent(Model):
230+
version: fields.Optional(fields.Str())
231+
libevent: fields.Optional(fields.Str())
232+
curr_items: fields.Optional(fields.Int())
233+
total_items: fields.Optional(fields.Int())
234+
bytes: fields.Optional(fields.Int())
235+
max_bytes: fields.Optional(fields.Int())
236+
cmd_get: fields.Optional(fields.Int())
237+
cmd_set: fields.Optional(fields.Int())
238+
get_hits: fields.Optional(fields.Int())
239+
get_misses: fields.Optional(fields.Int())
240+
threads: fields.Optional(fields.Int())
241+
242+
243+
class L9AMQPEvent(Model):
244+
protocol_major: fields.Optional(fields.Int())
245+
protocol_minor: fields.Optional(fields.Int())
246+
product: fields.Optional(fields.Str())
247+
version: fields.Optional(fields.Str())
248+
platform: fields.Optional(fields.Str())
249+
250+
251+
class L9LDAPEvent(Model):
252+
naming_contexts: fields.Optional(fields.List(fields.Str()))
253+
supported_versions: fields.Optional(fields.List(fields.Str()))
254+
vendor_name: fields.Optional(fields.Str())
255+
vendor_version: fields.Optional(fields.Str())
256+
supported_sasl: fields.Optional(fields.List(fields.Str()))
257+
anonymous_bind: fields.Optional(fields.Bool())
258+
can_enumerate: fields.Optional(fields.Bool())
259+
260+
261+
class L9SIPEvent(Model):
262+
version: fields.Optional(fields.Str())
263+
user_agent: fields.Optional(fields.Str())
264+
server: fields.Optional(fields.Str())
265+
allow: fields.Optional(fields.List(fields.Str()))
266+
supported: fields.Optional(fields.List(fields.Str()))
267+
268+
269+
class L9RDPEvent(Model):
270+
product_version: fields.Optional(fields.Str())
271+
nla_required: fields.Optional(fields.Bool())
272+
ssl_enabled: fields.Optional(fields.Bool())
273+
hostname: fields.Optional(fields.Str())
274+
275+
276+
class L9DNSEvent(Model):
277+
software: fields.Optional(fields.Str())
278+
version: fields.Optional(fields.Str())
279+
recursion: fields.Optional(fields.Bool())
280+
dnssec: fields.Optional(fields.Bool())
281+
zone_transfer: fields.Optional(fields.Bool())
282+
nameservers: fields.Optional(fields.List(fields.Str()))
283+
284+
285+
class L9RTSPEvent(Model):
286+
server: fields.Optional(fields.Str())
287+
methods: fields.Optional(fields.List(fields.Str()))
288+
289+
290+
# --- Main Event ---
160291

161292

162293
class L9Event(Model):
@@ -176,25 +307,45 @@ class L9Event(Model):
176307
summary: fields.Str()
177308
time: fields.DateTime()
178309
ssl: fields.Optional(fields.Nested(L9SSLEvent))
179-
ssh: fields.Nested(L9SSHEvent)
310+
# Protocol-specific events
311+
ssh: fields.Optional(fields.Nested(L9SSHEvent))
312+
vnc: fields.Optional(fields.Nested(L9VNCEvent))
313+
ftp: fields.Optional(fields.Nested(L9FTPEvent))
314+
smtp: fields.Optional(fields.Nested(L9SMTPEvent))
315+
telnet: fields.Optional(fields.Nested(L9TelnetEvent))
316+
redis: fields.Optional(fields.Nested(L9RedisEvent))
317+
mysql: fields.Optional(fields.Nested(L9MySQLEvent))
318+
postgresql: fields.Optional(fields.Nested(L9PostgreSQLEvent))
319+
mongodb: fields.Optional(fields.Nested(L9MongoDBEvent))
320+
memcached: fields.Optional(fields.Nested(L9MemcachedEvent))
321+
amqp: fields.Optional(fields.Nested(L9AMQPEvent))
322+
ldap: fields.Optional(fields.Nested(L9LDAPEvent))
323+
sip: fields.Optional(fields.Nested(L9SIPEvent))
324+
rdp: fields.Optional(fields.Nested(L9RDPEvent))
325+
dns: fields.Optional(fields.Nested(L9DNSEvent))
326+
rtsp: fields.Optional(fields.Nested(L9RTSPEvent))
327+
# Service events
180328
service: fields.Nested(L9ServiceEvent)
181-
leak: fields.Optional(fields.Nested((L9LeakEvent)))
329+
leak: fields.Optional(fields.Nested(L9LeakEvent))
182330
tags: fields.Optional(fields.List(fields.Str()))
183331
geoip: fields.Nested(GeoLocation)
184332
network: fields.Nested(Network)
185333

186334

335+
# --- Aggregation ---
336+
337+
187338
class L9Aggregation(Model):
188-
summary = fields.Optional(fields.Str())
189-
ip = fields.Str()
190-
resource_id = fields.Str()
191-
open_ports = fields.List(fields.Str())
192-
leak_count = fields.Int()
193-
leak_event_count = fields.Int()
194-
events = fields.List(fields.Nested(L9Event))
195-
plugins = fields.List(fields.Str())
196-
geoip = fields.Nested(GeoLocation)
197-
network = fields.Nested(Network)
198-
creation_date = fields.DateTime()
199-
update_date = fields.DateTime()
200-
fresh = fields.Bool()
339+
summary: fields.Optional(fields.Str())
340+
ip: fields.Str()
341+
resource_id: fields.Str()
342+
open_ports: fields.List(fields.Str())
343+
leak_count: fields.Int()
344+
leak_event_count: fields.Int()
345+
events: fields.List(fields.Nested(L9Event))
346+
plugins: fields.List(fields.Str())
347+
geoip: fields.Nested(GeoLocation)
348+
network: fields.Nested(Network)
349+
creation_date: fields.DateTime()
350+
update_date: fields.DateTime()
351+
fresh: fields.Bool()

0 commit comments

Comments
 (0)