Skip to content

Commit

Permalink
Merge branch 'main' of github.com:SpecterOps/BloodHound into BED-5070
Browse files Browse the repository at this point in the history
  • Loading branch information
ALCooper12 committed Dec 23, 2024
2 parents b433b28 + c062f0f commit 7f34518
Show file tree
Hide file tree
Showing 100 changed files with 2,044 additions and 1,585 deletions.
1 change: 1 addition & 0 deletions cmd/api/src/analysis/azure/queries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (

func TestAnalysisAzure_GraphStats(t *testing.T) {
testCtx := integration.NewGraphTestContext(t, schema.DefaultGraphSchema())
testCtx.SetupAzure()
testCtx.DatabaseTest(func(harness integration.HarnessDetails, db graph.Database) {

_, agg, err := azure2.GraphStats(context.TODO(), testCtx.Graph.Database)
Expand Down
1 change: 1 addition & 0 deletions cmd/api/src/api/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const (
ErrorResponseAGNameTagEmpty = "asset group name or tag must not be empty"
ErrorResponseAGDuplicateName = "asset group name must be unique"
ErrorResponseAGDuplicateTag = "asset group tag must be unique"
ErrorResponseSSOProviderDuplicateName = "sso provider name must be unique"
ErrorResponseUserDuplicatePrincipal = "principal name must be unique"
ErrorResponseDetailsUniqueViolation = "unique constraint was violated"
ErrorResponseDetailsNotImplemented = "All good things to those who wait. Not implemented."
Expand Down
2 changes: 2 additions & 0 deletions cmd/api/src/api/registration/v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func registerV2Auth(resources v2.Resources, routerInst *router.Router, permissio
routerInst.PathPrefix(fmt.Sprintf("/api/{version}/login/saml/{%s}", api.URIPathVariableSSOProviderSlug), http.HandlerFunc(managementResource.SAMLLoginRedirect)),

// SAML resources
// DEPRECATED as of v6.4.0: Please use /api/v2/sso-providers/* endpoints instead of /api/v2/saml/*
routerInst.GET("/api/v2/saml", managementResource.ListSAMLProviders).RequirePermissions(permissions.AuthManageProviders),
routerInst.GET("/api/v2/saml/sso", managementResource.ListSAMLSignOnEndpoints),
routerInst.POST("/api/v2/saml/providers", managementResource.CreateSAMLProviderMultipart).RequirePermissions(permissions.AuthManageProviders),
Expand All @@ -57,6 +58,7 @@ func registerV2Auth(resources v2.Resources, routerInst *router.Router, permissio

// SSO
routerInst.GET("/api/v2/sso-providers", managementResource.ListAuthProviders),
routerInst.POST("/api/v2/sso-providers/saml", managementResource.CreateSAMLProviderMultipart).RequirePermissions(permissions.AuthManageProviders),
routerInst.POST("/api/v2/sso-providers/oidc", managementResource.CreateOIDCProvider).CheckFeatureFlag(resources.DB, appcfg.FeatureOIDCSupport).RequirePermissions(permissions.AuthManageProviders),
routerInst.DELETE(fmt.Sprintf("/api/v2/sso-providers/{%s}", api.URIPathVariableSSOProviderID), managementResource.DeleteSSOProvider).RequirePermissions(permissions.AuthManageProviders),
routerInst.PATCH(fmt.Sprintf("/api/v2/sso-providers/{%s}", api.URIPathVariableSSOProviderID), managementResource.UpdateSSOProvider).RequirePermissions(permissions.AuthManageProviders),
Expand Down
6 changes: 2 additions & 4 deletions cmd/api/src/api/tools/pg.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,8 @@ func migrateTypes(ctx context.Context, neoDB, pgDB graph.Database) error {
return err
}

return pgDB.WriteTransaction(ctx, func(tx graph.Transaction) error {
_, err := pgDB.(*pg.Driver).KindMapper().AssertKinds(tx, append(neoNodeKinds, neoEdgeKinds...))
return err
})
_, err := pgDB.(*pg.Driver).KindMapper().AssertKinds(ctx, append(neoNodeKinds, neoEdgeKinds...))
return err
}

func convertNeo4jProperties(properties *graph.Properties) error {
Expand Down
13 changes: 11 additions & 2 deletions cmd/api/src/api/v2/auth/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package auth

import (
"errors"
"fmt"
"net/http"
"time"
Expand Down Expand Up @@ -84,7 +85,11 @@ func (s ManagementResource) UpdateOIDCProviderRequest(response http.ResponseWrit
}

if oidcProvider, err := s.db.UpdateOIDCProvider(request.Context(), ssoProvider); err != nil {
api.HandleDatabaseError(request, response, err)
if errors.Is(err, database.ErrDuplicateSSOProviderName) {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusConflict, api.ErrorResponseSSOProviderDuplicateName, request), response)
} else {
api.HandleDatabaseError(request, response, err)
}
} else {
api.WriteBasicResponse(request.Context(), oidcProvider, http.StatusOK, response)
}
Expand All @@ -109,7 +114,11 @@ func (s ManagementResource) CreateOIDCProvider(response http.ResponseWriter, req
}

if oidcProvider, err := s.db.CreateOIDCProvider(request.Context(), upsertReq.Name, upsertReq.Issuer, upsertReq.ClientID, *upsertReq.Config); err != nil {
api.HandleDatabaseError(request, response, err)
if errors.Is(err, database.ErrDuplicateSSOProviderName) {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusConflict, api.ErrorResponseSSOProviderDuplicateName, request), response)
} else {
api.HandleDatabaseError(request, response, err)
}
} else {
api.WriteBasicResponse(request.Context(), oidcProvider, http.StatusCreated, response)
}
Expand Down
12 changes: 10 additions & 2 deletions cmd/api/src/api/v2/auth/saml.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,11 @@ func (s ManagementResource) CreateSAMLProviderMultipart(response http.ResponseWr
samlIdentityProvider.SingleSignOnURI = ssoURL

if newSAMLProvider, err := s.db.CreateSAMLIdentityProvider(request.Context(), samlIdentityProvider, config); err != nil {
api.HandleDatabaseError(request, response, err)
if errors.Is(err, database.ErrDuplicateSSOProviderName) {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusConflict, api.ErrorResponseSSOProviderDuplicateName, request), response)
} else {
api.HandleDatabaseError(request, response, err)
}
} else {
api.WriteBasicResponse(request.Context(), newSAMLProvider, http.StatusOK, response)
}
Expand Down Expand Up @@ -311,7 +315,11 @@ func (s ManagementResource) UpdateSAMLProviderRequest(response http.ResponseWrit
}

if newSAMLProvider, err := s.db.UpdateSAMLIdentityProvider(request.Context(), ssoProvider); err != nil {
api.HandleDatabaseError(request, response, err)
if errors.Is(err, database.ErrDuplicateSSOProviderName) {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusConflict, api.ErrorResponseSSOProviderDuplicateName, request), response)
} else {
api.HandleDatabaseError(request, response, err)
}
} else {
api.WriteBasicResponse(request.Context(), newSAMLProvider, http.StatusOK, response)
}
Expand Down
6 changes: 4 additions & 2 deletions cmd/api/src/api/v2/integration/ingest.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"strings"
"time"

"github.com/specterops/bloodhound/graphschema"

"github.com/specterops/bloodhound/dawgs/graph"
"github.com/specterops/bloodhound/src/model"
"github.com/specterops/bloodhound/src/model/appcfg"
Expand Down Expand Up @@ -198,7 +200,7 @@ func (s *Context) WaitForDatapipeAnalysis(timeout time.Duration, originalWrapper
type IngestAssertion func(testCtrl test.Controller, tx graph.Transaction)

func (s *Context) AssertIngest(assertion IngestAssertion) {
graphDB := integration.OpenGraphDB(s.TestCtrl)
graphDB := integration.OpenGraphDB(s.TestCtrl, graphschema.DefaultGraphSchema())
defer graphDB.Close(s.ctx)

require.Nil(s.TestCtrl, graphDB.ReadTransaction(s.ctx, func(tx graph.Transaction) error {
Expand All @@ -208,7 +210,7 @@ func (s *Context) AssertIngest(assertion IngestAssertion) {
}

func (s *Context) AssertIngestProperties(assertion IngestAssertion) {
graphDB := integration.OpenGraphDB(s.TestCtrl)
graphDB := integration.OpenGraphDB(s.TestCtrl, graphschema.DefaultGraphSchema())
defer graphDB.Close(s.ctx)

require.Nil(s.TestCtrl, graphDB.ReadTransaction(s.ctx, func(tx graph.Transaction) error {
Expand Down
3 changes: 2 additions & 1 deletion cmd/api/src/api/v2/integration/reconciliation.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package integration

import (
"github.com/specterops/bloodhound/dawgs/graph"
"github.com/specterops/bloodhound/graphschema"
"github.com/specterops/bloodhound/src/test"
"github.com/specterops/bloodhound/src/test/integration"
"github.com/stretchr/testify/require"
Expand All @@ -26,7 +27,7 @@ import (
type ReconciliationAssertion func(testCtrl test.Controller, tx graph.Transaction)

func (s *Context) AssertReconciliation(assertion ReconciliationAssertion) {
graphDB := integration.OpenGraphDB(s.TestCtrl)
graphDB := integration.OpenGraphDB(s.TestCtrl, graphschema.DefaultGraphSchema())
defer graphDB.Close(s.ctx)

require.Nil(s.TestCtrl, graphDB.ReadTransaction(s.ctx, func(tx graph.Transaction) error {
Expand Down
7 changes: 4 additions & 3 deletions cmd/api/src/database/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ const (
)

var (
ErrDuplicateAGName = errors.New("duplicate asset group name")
ErrDuplicateAGTag = errors.New("duplicate asset group tag")
ErrDuplicateUserPrincipal = errors.New("duplicate user principal name")
ErrDuplicateAGName = errors.New("duplicate asset group name")
ErrDuplicateAGTag = errors.New("duplicate asset group tag")
ErrDuplicateSSOProviderName = errors.New("duplicate sso provider name")
ErrDuplicateUserPrincipal = errors.New("duplicate user principal name")
)

func IsUnexpectedDatabaseError(err error) bool {
Expand Down
24 changes: 22 additions & 2 deletions cmd/api/src/database/sso_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,17 @@ func (s *BloodhoundDB) CreateSSOProvider(ctx context.Context, name string, authP
)

err := s.AuditableTransaction(ctx, auditEntry, func(tx *gorm.DB) error {
return CheckError(tx.Table(ssoProviderTableName).Create(&provider))
result := tx.Table(ssoProviderTableName).Create(&provider)

if result.Error != nil {
if strings.Contains(result.Error.Error(), "duplicate key value violates unique constraint \"sso_providers_name_key\"") {
return fmt.Errorf("%w: %v", ErrDuplicateSSOProviderName, tx.Error)
} else if strings.Contains(result.Error.Error(), "duplicate key value violates unique constraint \"sso_providers_slug_key\"") {
return fmt.Errorf("%w: %v", ErrDuplicateSSOProviderName, tx.Error)
}
}

return CheckError(result)
})

return provider, err
Expand Down Expand Up @@ -184,7 +194,17 @@ func (s *BloodhoundDB) UpdateSSOProvider(ctx context.Context, ssoProvider model.
}

err := s.AuditableTransaction(ctx, auditEntry, func(tx *gorm.DB) error {
return CheckError(tx.WithContext(ctx).Exec(fmt.Sprintf("UPDATE %s SET name = ?, slug = ?, updated_at = ?, config = ? WHERE id = ?;", ssoProviderTableName), ssoProvider.Name, ssoProvider.Slug, time.Now().UTC(), ssoProvider.Config, ssoProvider.ID))
result := tx.WithContext(ctx).Exec(fmt.Sprintf("UPDATE %s SET name = ?, slug = ?, updated_at = ?, config = ? WHERE id = ?;", ssoProviderTableName), ssoProvider.Name, ssoProvider.Slug, time.Now().UTC(), ssoProvider.Config, ssoProvider.ID)

if result.Error != nil {
if strings.Contains(result.Error.Error(), "duplicate key value violates unique constraint \"sso_providers_name_key\"") {
return fmt.Errorf("%w: %v", ErrDuplicateSSOProviderName, tx.Error)
} else if strings.Contains(result.Error.Error(), "duplicate key value violates unique constraint \"sso_providers_slug_key\"") {
return fmt.Errorf("%w: %v", ErrDuplicateSSOProviderName, tx.Error)
}
}

return CheckError(result)
})

return ssoProvider, err
Expand Down
1 change: 1 addition & 0 deletions cmd/api/src/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ require (
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
Expand Down
2 changes: 1 addition & 1 deletion cmd/api/src/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys=
github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
4 changes: 4 additions & 0 deletions cmd/api/src/queries/graph_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ func TestGetEntityResults(t *testing.T) {
queryCache, err := cache.NewCache(cache.Config{MaxSize: 1})
require.Nil(t, err)

testContext.SetupActiveDirectory()
testContext.DatabaseTest(func(harness integration.HarnessDetails, db graph.Database) {
objectID, err := harness.InboundControl.ControlledUser.Properties.Get(common.ObjectID.String()).String()
require.Nil(t, err)
Expand Down Expand Up @@ -197,6 +198,7 @@ func TestGetEntityResults_QueryShorterThanSlowQueryThreshold(t *testing.T) {
queryCache, err := cache.NewCache(cache.Config{MaxSize: 1})
require.Nil(t, err)

testContext.SetupActiveDirectory()
testContext.DatabaseTest(func(harness integration.HarnessDetails, db graph.Database) {
objectID, err := harness.InboundControl.ControlledUser.Properties.Get(common.ObjectID.String()).String()
require.Nil(t, err)
Expand Down Expand Up @@ -230,6 +232,7 @@ func TestGetEntityResults_Cache(t *testing.T) {
queryCache, err := cache.NewCache(cache.Config{MaxSize: 2})
require.Nil(t, err)

testContext.SetupActiveDirectory()
testContext.DatabaseTest(func(harness integration.HarnessDetails, db graph.Database) {
objectID, err := harness.InboundControl.ControlledUser.Properties.Get(common.ObjectID.String()).String()
require.Nil(t, err)
Expand Down Expand Up @@ -270,6 +273,7 @@ func TestGetEntityResults_Cache(t *testing.T) {

func TestGetAssetGroupComboNode(t *testing.T) {
testContext := integration.NewGraphTestContext(t, schema.DefaultGraphSchema())
testContext.SetupActiveDirectory()
testContext.DatabaseTest(func(harness integration.HarnessDetails, db graph.Database) {
graphQuery := queries.NewGraphQuery(db, cache.Cache{}, config.Configuration{})
comboNode, err := graphQuery.GetAssetGroupComboNode(context.Background(), "", ad.AdminTierZero)
Expand Down
2 changes: 1 addition & 1 deletion cmd/api/src/test/integration/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (s *GraphContext) End(t test.Context) {
func NewGraphContext(ctx test.Context, schema graph.Schema) *GraphContext {
graphContext := &GraphContext{
schema: schema,
Database: OpenGraphDB(ctx),
Database: OpenGraphDB(ctx, schema),
}

// Initialize the graph context
Expand Down
5 changes: 2 additions & 3 deletions cmd/api/src/test/integration/dawgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"github.com/specterops/bloodhound/dawgs/drivers/neo4j"
"github.com/specterops/bloodhound/dawgs/drivers/pg"
"github.com/specterops/bloodhound/dawgs/graph"
schema "github.com/specterops/bloodhound/graphschema"
"github.com/specterops/bloodhound/src/config"
"github.com/specterops/bloodhound/src/test"
"github.com/specterops/bloodhound/src/test/integration/utils"
Expand All @@ -39,7 +38,7 @@ func LoadConfiguration(testCtrl test.Controller) config.Configuration {
return cfg
}

func OpenGraphDB(testCtrl test.Controller) graph.Database {
func OpenGraphDB(testCtrl test.Controller, schema graph.Schema) graph.Database {
var (
cfg = LoadConfiguration(testCtrl)
graphDatabase graph.Database
Expand All @@ -62,7 +61,7 @@ func OpenGraphDB(testCtrl test.Controller) graph.Database {
}

test.RequireNilErrf(testCtrl, err, "Failed connecting to graph database: %v", err)
test.RequireNilErr(testCtrl, graphDatabase.AssertSchema(context.Background(), schema.DefaultGraphSchema()))
test.RequireNilErr(testCtrl, graphDatabase.AssertSchema(context.Background(), schema))

return graphDatabase
}
45 changes: 34 additions & 11 deletions cmd/api/src/test/integration/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,14 @@ func (s *GraphTestContext) UpdateNode(node *graph.Node) {
})
}

func (s *GraphTestContext) DatabaseTest(dbDelegate func(harness HarnessDetails, db graph.Database)) {
s.setupActiveDirectory()
s.setupAzure()
func (s *GraphTestContext) InitializeHarness(harness GraphTestHarness) {
s.Graph.WriteTransaction(s.testCtx, func(tx graph.Transaction) error {
harness.Setup(s)
return nil
})
}

func (s *GraphTestContext) DatabaseTest(dbDelegate func(harness HarnessDetails, db graph.Database)) {
dbDelegate(s.Harness, s.Graph.Database)
}

Expand All @@ -109,8 +113,7 @@ func (s *GraphTestContext) DatabaseTestWithSetup(setup func(harness *HarnessDeta
}

func (s *GraphTestContext) BatchTest(batchDelegate func(harness HarnessDetails, batch graph.Batch), assertionDelegate func(details HarnessDetails, tx graph.Transaction)) {
s.setupActiveDirectory()
s.setupAzure()
s.SetupAzureAndActiveDirectory()

s.Graph.BatchOperation(s.testCtx, func(batch graph.Batch) error {
batchDelegate(s.Harness, batch)
Expand All @@ -124,8 +127,7 @@ func (s *GraphTestContext) BatchTest(batchDelegate func(harness HarnessDetails,
}

func (s *GraphTestContext) TransactionalTest(txDelegate func(harness HarnessDetails, tx graph.Transaction)) {
s.setupActiveDirectory()
s.setupAzure()
s.SetupAzureAndActiveDirectory()

s.Graph.WriteTransaction(s.testCtx, func(tx graph.Transaction) error {
txDelegate(s.Harness, tx)
Expand Down Expand Up @@ -319,6 +321,7 @@ func (s *GraphTestContext) NewAzureTenant(tenantID string) *graph.Node {
return s.NewNode(graph.AsProperties(graph.PropertyMap{
common.Name: "New Tenant",
common.ObjectID: tenantID,
azure.TenantID: tenantID,
azure.License: "license",
}), azure.Entity, azure.Tenant)
}
Expand All @@ -345,12 +348,27 @@ func (s *GraphTestContext) NewActiveDirectoryComputer(name, domainSID string) *g
}), ad.Entity, ad.Computer)
}

func (s *GraphTestContext) NewActiveDirectoryUser(name, domainSID string, isTierZero ...bool) *graph.Node {
func (s *GraphTestContext) NewActiveDirectoryContainer(name, domainSID string) *graph.Node {
return s.NewNode(graph.AsProperties(graph.PropertyMap{
common.Name: name,
common.ObjectID: must.NewUUIDv4().String(),
ad.DomainSID: domainSID,
}), ad.Entity, ad.Container)
}

func (s *GraphTestContext) NewActiveDirectoryUser(name, domainSID string, isTierZero ...bool) *graph.Node {

propertyMap := graph.PropertyMap{
common.Name: name,
common.ObjectID: strings.ToUpper(must.NewUUIDv4().String()),
ad.DomainSID: domainSID,
}), ad.Entity, ad.User)
}

if isTierZero != nil && isTierZero[0] {
propertyMap[common.SystemTags] = ad.AdminTierZero
}

return s.NewNode(graph.AsProperties(propertyMap), ad.Entity, ad.User)
}

func (s *GraphTestContext) NewCustomActiveDirectoryUser(properties *graph.Properties) *graph.Node {
Expand Down Expand Up @@ -515,7 +533,12 @@ type CertTemplateData struct {
CertificatePolicy []string
}

func (s *GraphTestContext) setupAzure() {
func (s *GraphTestContext) SetupAzureAndActiveDirectory() {
s.SetupAzure()
s.SetupActiveDirectory()
}

func (s *GraphTestContext) SetupAzure() {
s.Harness.AZBaseHarness.Setup(s)
s.Harness.AZGroupMembership.Setup(s)
s.Harness.AZEntityPanelHarness.Setup(s)
Expand All @@ -530,7 +553,7 @@ func (s *GraphTestContext) setupAzure() {
s.Harness.AZManagementGroup.Setup(s)
}

func (s *GraphTestContext) setupActiveDirectory() {
func (s *GraphTestContext) SetupActiveDirectory() {
// startServer a host of Tier Zero tagged assets
s.Harness.RootADHarness.Setup(s)

Expand Down
Loading

0 comments on commit 7f34518

Please sign in to comment.