Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CORE] Add concept of dependent edges #111

Merged
merged 2 commits into from
Sep 14, 2023
Merged
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
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