Skip to content

Commit

Permalink
[CORE] Add concept of dependent edges (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
d0g0x01 authored Sep 14, 2023
1 parent 7d741c2 commit 8c3ba71
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 241 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ To query the KubeHound graph data requires using the [Gremlin](https://tinkerpop
+ Download and install the application from https://gdotv.com/
+ Create a connection to the local janusgraph instance by following the steps here https://docs.gdotv.com/connection-management/ and using `hostname=localhost`
+ Navigate to the query editor and enter a sample query e.g `g.V().count()`. See detailed instructions here: https://docs.gdotv.com/query-editor/#run-your-query
+ See the provided [cheatsheet](./pkg/kubehound/graph/CHEATSHEET.md) for examples of useful queries for various use cases.

## Development

Expand Down
3 changes: 3 additions & 0 deletions pkg/kubehound/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ func buildGraph(ctx context.Context, cfg *config.KubehoundConfig, storedb stored

log.I.Info("Loading graph edge definitions")
edges := edge.Registered()
if err := edges.Verify(); err != nil {
return fmt.Errorf("edge registry verification: %w", err)
}

log.I.Info("Loading graph builder")
builder, err := graph.NewBuilder(cfg, storedb, graphdb, cache, edges)
Expand Down
230 changes: 0 additions & 230 deletions pkg/kubehound/graph/CHEATSHEET.md

This file was deleted.

9 changes: 9 additions & 0 deletions pkg/kubehound/graph/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ func (b *Builder) Run(ctx context.Context) error {
return err
}

// Dependent edges must be built last, sequentially
l.Info("Starting dependent edge construction")
for label, e := range b.edges.Dependent() {
err := b.buildEdge(ctx, label, e, oic, l)
if err != nil {
return fmt.Errorf("building dependent edge %s: %w", label, err)
}
}

l.Info("Completed edge construction")
return nil
}
11 changes: 10 additions & 1 deletion pkg/kubehound/graph/edge/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
var __ = gremlin.T__
var P = gremlin.P

// Edge interface defines objects used to construct edges within our graph database through processing data from the intermediate store.
// Builder interface defines objects used to construct edges within our graph database through processing data from the intermediate store.

//go:generate mockery --name Builder --output mocks --case underscore --filename edge.go --with-expecter
type Builder interface {
Expand Down Expand Up @@ -43,3 +43,12 @@ type Builder interface {
Stream(ctx context.Context, store storedb.Provider, cache cache.CacheReader,
process types.ProcessEntryCallback, complete types.CompleteQueryCallback) error
}

// DependentBuilder interface defines objects used to construct edges with dependencies on other edges in the graph.
// Dependent edges are built last and their dependencies cannot be dependent edges themselves.
type DependentBuilder interface {
Builder

// Dependencies returns the edge labels of all dependencies.
Dependencies() []string
}
57 changes: 48 additions & 9 deletions pkg/kubehound/graph/edge/registry.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package edge

import (
"fmt"
"sync"

"github.com/DataDog/KubeHound/pkg/telemetry/log"
Expand All @@ -9,21 +10,24 @@ import (
type RegistrationFlag uint8

const (
RegisterDefault RegistrationFlag = 1 << iota // Default edge
RegisterGraphMutation // Edge can mutate the graph
RegisterDefault RegistrationFlag = 1 << iota // Default edge
RegisterGraphMutation // Edge can mutate the graph
RegisterGraphDependency // Edge has a dependency on default/mutating edges
)

// Registry holds details of edges (i.e attacks) registered in KubeHound.
type Registry struct {
mutating map[string]Builder
simple map[string]Builder
mutating map[string]Builder
simple map[string]Builder
dependent map[string]DependentBuilder
}

// newRegistry creates a new registry instance. This should not be called directly.
func newRegistry() *Registry {
r := &Registry{
mutating: make(map[string]Builder),
simple: make(map[string]Builder),
mutating: make(map[string]Builder),
simple: make(map[string]Builder),
dependent: make(map[string]DependentBuilder),
}

return r
Expand Down Expand Up @@ -52,18 +56,53 @@ func (r *Registry) Simple() map[string]Builder {
return r.simple
}

// Dependent returns the map of registered edge builders with default edge dependencies.
func (r *Registry) Dependent() map[string]DependentBuilder {
return r.dependent
}

// Verify verifies the integrity and consistency of the registry.
// Function should only be called once all edges have been registered via init() calls.
func (r *Registry) Verify() error {
// Ensure all dependent edges have dependencies registered in mutating or default collections
for name, builder := range r.dependent {
for _, d := range builder.Dependencies() {
_, depSimple := r.simple[d]
_, depMutating := r.mutating[d]

if !depSimple && !depMutating {
return fmt.Errorf("unregistered dependency (%s) for dependent edge %s", d, name)
}
}
}

return nil
}

// Register loads the provided edge into the registry.
func Register(edge Builder, flags RegistrationFlag) {
registry := Registered()
if flags&RegisterGraphMutation != 0 {
switch {
case flags&RegisterGraphMutation != 0:
log.I.Debugf("Registering mutating edge builder %s -> %s", edge.Name(), edge.Label())

if _, ok := registry.mutating[edge.Name()]; ok {
log.I.Fatalf("edge name collision: %s", edge.Name())
}

registry.mutating[edge.Name()] = edge
} else {
case flags&RegisterGraphDependency != 0:
log.I.Debugf("Registering dependent edge builder %s -> %s", edge.Name(), edge.Label())
if _, ok := registry.dependent[edge.Name()]; ok {
log.I.Fatalf("edge name collision: %s", edge.Name())
}

dependent, ok := edge.(DependentBuilder)
if !ok {
log.I.Fatalf("dependent edge must implement DependentBuilder: %s", edge.Name())
}

registry.dependent[edge.Name()] = dependent
default:
log.I.Debugf("Registering default edge builder %s -> %s", edge.Name(), edge.Label())
if _, ok := registry.simple[edge.Name()]; ok {
log.I.Fatalf("edge name collision: %s", edge.Name())
Expand Down

0 comments on commit 8c3ba71

Please sign in to comment.