Skip to content

Commit 4e9e1e1

Browse files
zinicjeff-matthews
andcommitted
feat: document multi-property matching for OpenGraph Ingest - BED-7451 (#217)
* feat: document multi-property matching for OpenGraph Ingest - BED-7451 * Apply suggestions from code review from @docs Co-authored-by: Jeff Matthews <jmatthews@specterops.io> --------- Co-authored-by: Jeff Matthews <jmatthews@specterops.io>
1 parent cbd039f commit 4e9e1e1

3 files changed

Lines changed: 521 additions & 239 deletions

File tree

docs/assets/opengraph/opengraph-edge.json

Lines changed: 109 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,54 @@
22
"title": "Generic Ingest Edge",
33
"description": "Defines an edge between two nodes in a generic graph ingestion system. Each edge specifies a start and end node using either a unique identifier (id) or a name-based lookup. A kind is required to indicate the relationship type. Optional properties may include custom attributes. You may optionally constrain the start or end node to a specific kind using the kind field inside each reference.",
44
"type": "object",
5-
"properties": {
6-
"start": {
5+
"$defs": {
6+
"property_map": {
7+
"type": ["object", "null"],
8+
"description": "A key-value map of edge attributes. Values must not be objects. If a value is an array, it must contain only primitive types (e.g., strings, numbers, booleans) and must be homogeneous (all items must be of the same type).",
9+
"additionalProperties": {
10+
"anyOf": [
11+
{ "type": "string" },
12+
{ "type": "number" },
13+
{ "type": "boolean" },
14+
{
15+
"type": "array",
16+
"anyOf": [
17+
{ "items": { "type": "string" } },
18+
{ "items": { "type": "number" } },
19+
{ "items": { "type": "boolean" } }
20+
]
21+
}
22+
]
23+
}
24+
},
25+
"endpoint": {
726
"type": "object",
827
"properties": {
928
"match_by": {
1029
"type": "string",
11-
"enum": ["id", "name"],
30+
"enum": ["id", "name", "property"],
1231
"default": "id",
13-
"description": "Whether to match the start node by its unique object ID or by its name property."
32+
"description": "Whether to match the start node by its unique object ID or by a series of property matches. Note that the name value here is deprecated and will be removed in future versions. Users are advised to use the multi-property match strategy moving forward."
33+
},
34+
"property_matchers": {
35+
"type": "array",
36+
"minItems": 1,
37+
"items": {
38+
"type": "object",
39+
"properties": {
40+
"key": {
41+
"type": "string"
42+
},
43+
"operator": {
44+
"type": "string",
45+
"enum": ["equals"]
46+
},
47+
"value": {
48+
"type": ["string", "number", "boolean"]
49+
}
50+
},
51+
"required": ["key", "operator", "value"]
52+
}
1453
},
1554
"value": {
1655
"type": "string",
@@ -21,44 +60,55 @@
2160
"description": "Optional kind filter; the referenced node must have this kind."
2261
}
2362
},
24-
"required": ["value"]
25-
},
26-
"end": {
27-
"type": "object",
28-
"properties": {
29-
"match_by": {
30-
"type": "string",
31-
"enum": ["id", "name"],
32-
"default": "id",
33-
"description": "Whether to match the end node by its unique object ID or by its name property."
34-
},
35-
"value": {
36-
"type": "string",
37-
"description": "The value used for matching — either an object ID or a name, depending on match_by."
38-
},
39-
"kind": {
40-
"type": "string",
41-
"description": "Optional kind filter; the referenced node must have this kind."
63+
"if": {
64+
"allOf": [
65+
{
66+
"properties": {
67+
"match_by": {
68+
"type": "string",
69+
"const": "property"
70+
}
71+
}
72+
},
73+
{
74+
"not": {
75+
"properties": {
76+
"match_by": {
77+
"type": "null"
78+
}
79+
}
80+
}
81+
}
82+
]
83+
},
84+
"then": {
85+
"required": ["property_matchers"],
86+
"not": {
87+
"required": ["value"]
4288
}
4389
},
44-
"required": ["value"]
90+
"else": {
91+
"required": ["value"],
92+
"not": {
93+
"required": ["property_matchers"]
94+
}
95+
}
96+
}
97+
},
98+
"properties": {
99+
"start": {
100+
"$ref": "#/$defs/endpoint"
101+
},
102+
"end": {
103+
"$ref": "#/$defs/endpoint"
45104
},
46105
"kind": {
47106
"type": "string",
48-
"description": "The relationship type. Must contain only letters, numbers, and underscores.",
107+
"description": "Edge kind name must contain only alphanumeric characters and underscores.",
49108
"pattern": "^[A-Za-z0-9_]+$"
50109
},
51110
"properties": {
52-
"type": ["object", "null"],
53-
"description": "A key-value map of edge attributes. Values must not be objects. If a value is an array, it must contain only primitive types (e.g., strings, numbers, booleans) and must be homogeneous (all items must be of the same type).",
54-
"additionalProperties": {
55-
"type": ["string", "number", "boolean", "array"],
56-
"items": {
57-
"not": {
58-
"type": "object"
59-
}
60-
}
61-
}
111+
"$ref": "#/$defs/property_map"
62112
}
63113
},
64114
"required": ["start", "end", "kind"],
@@ -72,7 +122,28 @@
72122
"match_by": "id",
73123
"value": "server-5678"
74124
},
75-
"kind": "HasSession",
125+
"kind": "has_session",
126+
"properties": {
127+
"timestamp": "2025-04-16T12:00:00Z",
128+
"duration_minutes": 45
129+
}
130+
},
131+
{
132+
"start": {
133+
"match_by": "property",
134+
"property_matchers": [
135+
{
136+
"key": "prop_1",
137+
"operator": "equals",
138+
"value": "value"
139+
}
140+
]
141+
},
142+
"end": {
143+
"match_by": "id",
144+
"value": "server-5678"
145+
},
146+
"kind": "has_session",
76147
"properties": {
77148
"timestamp": "2025-04-16T12:00:00Z",
78149
"duration_minutes": 45
@@ -89,7 +160,7 @@
89160
"value": "file-server-1",
90161
"kind": "Server"
91162
},
92-
"kind": "AccessedResource",
163+
"kind": "accessed_resource",
93164
"properties": {
94165
"via": "SMB",
95166
"sensitive": true
@@ -102,7 +173,7 @@
102173
"end": {
103174
"value": "domain-controller-9"
104175
},
105-
"kind": "AdminTo",
176+
"kind": "admin_to",
106177
"properties": {
107178
"reason": "elevated_permissions",
108179
"confirmed": false
@@ -117,7 +188,7 @@
117188
"match_by": "id",
118189
"value": "network-42"
119190
},
120-
"kind": "ConnectedTo",
191+
"kind": "connected_to",
121192
"properties": null
122193
}
123194
]

docs/assets/opengraph/opengraph-node.json

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,43 @@
22
"title": "Generic Ingest Node",
33
"description": "A node used in a generic graph ingestion system. Each node must have a unique identifier (`id`) and at least one kind describing its role or type. Nodes may also include a `properties` object containing custom attributes.",
44
"type": "object",
5-
"properties": {
6-
"id": { "type": "string" },
7-
"properties": {
5+
"$defs": {
6+
"property_map": {
87
"type": ["object", "null"],
9-
"description": "A key-value map of node attributes. Values must not be objects. If a value is an array, it must contain only primitive types (e.g., strings, numbers, booleans) and must be homogeneous (all items must be of the same type).",
8+
"description": "A key-value map of entity attributes. Values must not be objects. If a value is an array, it must contain only primitive types (e.g., strings, numbers, booleans) and must be homogeneous (all items must be of the same type).",
109
"additionalProperties": {
11-
"type": ["string", "number", "boolean", "array"],
12-
"items": {
13-
"not": {
14-
"type": "object"
10+
"anyOf": [
11+
{ "type": "string" },
12+
{ "type": "number" },
13+
{ "type": "boolean" },
14+
{
15+
"type": "array",
16+
"anyOf": [
17+
{ "items": { "type": "string" } },
18+
{ "items": { "type": "number" } },
19+
{ "items": { "type": "boolean" } }
20+
]
1521
}
16-
}
22+
]
23+
},
24+
25+
"not": {
26+
"required": ["objectid"]
1727
}
28+
}
29+
},
30+
"properties": {
31+
"id": {
32+
"type": "string"
33+
},
34+
"properties": {
35+
"$ref": "#/$defs/property_map"
1836
},
1937
"kinds": {
2038
"type": ["array"],
2139
"items": { "type": "string" },
40+
"minItems": 0,
2241
"maxItems": 3,
23-
"minItems": 1,
2442
"description": "An array of kind labels for the node. The first element is treated as the node's primary kind and is used to determine which icon to display in the graph UI. This primary kind is only used for visual representation and has no semantic significance for data processing."
2543
}
2644
},
@@ -35,8 +53,8 @@
3553
"properties": {
3654
"manufacturer": "Brandon Corp",
3755
"model": "4000x",
38-
"isactive": true,
39-
"rating": 43.50
56+
"is_active": true,
57+
"rating": 43.5
4058
},
4159
"kinds": ["Device", "Asset"]
4260
},

0 commit comments

Comments
 (0)