Skip to content

Commit 0b103fd

Browse files
committed
supports in path to leaf attack chain calculation
1 parent 8e021e0 commit 0b103fd

File tree

5 files changed

+683
-12
lines changed

5 files changed

+683
-12
lines changed

reporthandling/attacktrack/v1alpha1/attacktrackmethods.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package v1alpha1
22

33
import (
4+
"reflect"
5+
46
"github.com/kubescape/k8s-interface/workloadinterface"
57
"go.uber.org/zap"
68
)
@@ -172,6 +174,156 @@ func (handler *AttackTrackAllPathsHandler) CalculateAllPaths() [][]IAttackTrackS
172174
return allPaths
173175
}
174176

177+
// CalculatePathsRootToLeaf calculates all paths from the root to the leaf nodes of the attack track
178+
// The paths are returned as a slice of slices of IAttackTrackStep
179+
// Each path is a slice of IAttackTrackStep
180+
// The first element of each path is the root of the attack track
181+
// The last element of each path is a leaf node
182+
// The paths are calculated using DFS
183+
184+
func (handler *AttackTrackAllPathsHandler) CalculatePathsRootToLeaf() [][]IAttackTrackStep {
185+
handler.visited = make(map[string]bool)
186+
var paths [][]IAttackTrackStep
187+
currentPath := []IAttackTrackStep{}
188+
189+
var traverse func(step IAttackTrackStep)
190+
traverse = func(step IAttackTrackStep) {
191+
if len(step.GetControls()) > 0 {
192+
currentPath = append(currentPath, step)
193+
}
194+
195+
if step.Length() == 0 {
196+
// Reached a leaf node
197+
if len(step.GetControls()) > 0 {
198+
// Add current path to paths only if controls are not empty
199+
path := make([]IAttackTrackStep, len(currentPath))
200+
copy(path, currentPath)
201+
paths = append(paths, path)
202+
}
203+
} else {
204+
205+
// Traverse substeps recursively
206+
// Keep track of whether any substep with controls was traversed
207+
traversedSubstepWithControls := false
208+
for i := 0; i < step.Length(); i++ {
209+
subStep := step.SubStepAt(i)
210+
211+
// Check if the subStep has been visited before
212+
if !handler.visited[subStep.GetName()] {
213+
handler.visited[subStep.GetName()] = true
214+
215+
// Only include nodes with controls in the path
216+
if len(subStep.GetControls()) > 0 {
217+
traverse(subStep)
218+
traversedSubstepWithControls = true
219+
} else {
220+
// Exclude the substep and its connected path
221+
handler.visited[subStep.GetName()] = false
222+
continue
223+
}
224+
}
225+
}
226+
227+
// Add the current path to paths only if it ends with a leaf node with controls
228+
if len(step.GetControls()) > 0 && !traversedSubstepWithControls && step.Length() == 0 {
229+
path := make([]IAttackTrackStep, len(currentPath))
230+
copy(path, currentPath)
231+
paths = append(paths, path)
232+
}
233+
}
234+
235+
if len(step.GetControls()) > 0 {
236+
currentPath = currentPath[:len(currentPath)-1] // Remove last step from current path in order to explore other paths
237+
}
238+
}
239+
240+
traverse(handler.attackTrack.GetData())
241+
242+
return paths
243+
}
244+
245+
// GenerateAttackTrackFromPaths - generates a new attack track from the given paths
246+
// The new attack track will contain only nodes that have controls
247+
func (handler *AttackTrackAllPathsHandler) GenerateAttackTrackFromPaths(paths [][]IAttackTrackStep) *AttackTrack {
248+
if len(paths) == 0 {
249+
return nil
250+
}
251+
data := handler.filterNodesWithControls(handler.attackTrack.GetData(), paths)
252+
// Create a new AttackTrack with only nodes that have controls
253+
updatedAttackTrack := AttackTrack{
254+
ApiVersion: handler.attackTrack.GetApiVersion(),
255+
Kind: handler.attackTrack.GetKind(),
256+
Metadata: map[string]interface{}{"name": handler.attackTrack.GetName()},
257+
Spec: AttackTrackSpecification{
258+
Version: handler.attackTrack.GetVersion(),
259+
Description: handler.attackTrack.GetDescription(),
260+
Data: *data,
261+
},
262+
}
263+
264+
return &updatedAttackTrack
265+
}
266+
267+
// filterNodesWithControls - filters out nodes that do not have controls
268+
func (handler *AttackTrackAllPathsHandler) filterNodesWithControls(step IAttackTrackStep, paths [][]IAttackTrackStep) *AttackTrackStep {
269+
filteredStep := AttackTrackStep{
270+
Name: step.GetName(),
271+
Description: step.GetDescription(),
272+
SubSteps: nil,
273+
Controls: step.GetControls(),
274+
}
275+
276+
if step.Length() == 0 {
277+
if len(step.GetControls()) > 0 {
278+
return &filteredStep
279+
}
280+
281+
return nil
282+
}
283+
284+
subSteps := make([]AttackTrackStep, 0, step.Length())
285+
286+
for i := 0; i < step.Length(); i++ {
287+
subStep := step.SubStepAt(i)
288+
289+
// Check if the subStep is present in any of the paths
290+
isInPath := false
291+
for _, path := range paths {
292+
if containsStep(path, subStep) {
293+
isInPath = true
294+
break
295+
}
296+
}
297+
298+
// Only include substeps that have controls or are present in a path
299+
if len(subStep.GetControls()) > 0 || isInPath {
300+
filteredSubStep := handler.filterNodesWithControls(subStep, paths)
301+
if filteredSubStep != nil {
302+
subSteps = append(subSteps, *filteredSubStep)
303+
}
304+
}
305+
}
306+
if len(subSteps) > 0 {
307+
filteredStep.SubSteps = subSteps
308+
}
309+
310+
if len(filteredStep.Controls) == 0 && len(filteredStep.SubSteps) == 0 {
311+
return nil
312+
}
313+
314+
return &filteredStep
315+
}
316+
317+
// containsStep - checks if the given step is present in the given path
318+
func containsStep(path []IAttackTrackStep, step IAttackTrackStep) bool {
319+
for _, s := range path {
320+
if reflect.DeepEqual(s, step) {
321+
return true
322+
}
323+
}
324+
return false
325+
}
326+
175327
func NewAttackTrackControlsLookup(attackTracks []IAttackTrack, failedControlIds []string, allControls map[string]IAttackTrackControl) AttackTrackControlsLookup {
176328
lookup := make(AttackTrackControlsLookup)
177329

0 commit comments

Comments
 (0)