Skip to content

Commit

Permalink
BED-4624: Single commit for PR
Browse files Browse the repository at this point in the history
  • Loading branch information
iustinum committed Dec 13, 2024
1 parent 7e90c5d commit 052e805
Show file tree
Hide file tree
Showing 124 changed files with 3,579 additions and 1,239 deletions.
Binary file not shown.
85 changes: 71 additions & 14 deletions cmd/api/src/analysis/ad/adcs_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,63 +447,75 @@ func TestTrustedForNTAuth(t *testing.T) {
func TestEnrollOnBehalfOf(t *testing.T) {
testContext := integration.NewGraphTestContext(t, graphschema.DefaultGraphSchema())
testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) error {
harness.EnrollOnBehalfOfHarnessOne.Setup(testContext)
harness.EnrollOnBehalfOfHarness1.Setup(testContext)
return nil
}, func(harness integration.HarnessDetails, db graph.Database) {
certTemplates, err := ad2.FetchNodesByKind(context.Background(), db, ad.CertTemplate)
v1Templates := make([]*graph.Node, 0)
v2Templates := make([]*graph.Node, 0)

for _, template := range certTemplates {
if version, err := template.Properties.Get(ad.SchemaVersion.String()).Float64(); err != nil {
continue
} else if version == 1 {
v1Templates = append(v1Templates, template)
} else if version >= 2 {
continue
v2Templates = append(v2Templates, template)
}
}

require.Nil(t, err)

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
results, err := ad2.EnrollOnBehalfOfVersionOne(tx, v1Templates, certTemplates)
results, err := ad2.EnrollOnBehalfOfVersionOne(tx, v1Templates, certTemplates, harness.EnrollOnBehalfOfHarness1.Domain1)
require.Nil(t, err)

require.Len(t, results, 3)

require.Contains(t, results, analysis.CreatePostRelationshipJob{
FromID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate11.ID,
ToID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate12.ID,
FromID: harness.EnrollOnBehalfOfHarness1.CertTemplate11.ID,
ToID: harness.EnrollOnBehalfOfHarness1.CertTemplate12.ID,
Kind: ad.EnrollOnBehalfOf,
})

require.Contains(t, results, analysis.CreatePostRelationshipJob{
FromID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate13.ID,
ToID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate12.ID,
FromID: harness.EnrollOnBehalfOfHarness1.CertTemplate13.ID,
ToID: harness.EnrollOnBehalfOfHarness1.CertTemplate12.ID,
Kind: ad.EnrollOnBehalfOf,
})

require.Contains(t, results, analysis.CreatePostRelationshipJob{
FromID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate12.ID,
ToID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate12.ID,
FromID: harness.EnrollOnBehalfOfHarness1.CertTemplate12.ID,
ToID: harness.EnrollOnBehalfOfHarness1.CertTemplate12.ID,
Kind: ad.EnrollOnBehalfOf,
})

return nil
})

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
results, err := ad2.EnrollOnBehalfOfVersionTwo(tx, v2Templates, certTemplates, harness.EnrollOnBehalfOfHarness1.Domain1)
require.Nil(t, err)

require.Len(t, results, 0)

return nil
})
})

testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) error {
harness.EnrollOnBehalfOfHarnessTwo.Setup(testContext)
harness.EnrollOnBehalfOfHarness2.Setup(testContext)
return nil
}, func(harness integration.HarnessDetails, db graph.Database) {
certTemplates, err := ad2.FetchNodesByKind(context.Background(), db, ad.CertTemplate)
v1Templates := make([]*graph.Node, 0)
v2Templates := make([]*graph.Node, 0)

for _, template := range certTemplates {
if version, err := template.Properties.Get(ad.SchemaVersion.String()).Float64(); err != nil {
continue
} else if version == 1 {
continue
v1Templates = append(v1Templates, template)
} else if version >= 2 {
v2Templates = append(v2Templates, template)
}
Expand All @@ -512,15 +524,60 @@ func TestEnrollOnBehalfOf(t *testing.T) {
require.Nil(t, err)

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
results, err := ad2.EnrollOnBehalfOfVersionTwo(tx, v2Templates, certTemplates)
results, err := ad2.EnrollOnBehalfOfVersionOne(tx, v1Templates, certTemplates, harness.EnrollOnBehalfOfHarness2.Domain2)
require.Nil(t, err)

require.Len(t, results, 0)
return nil
})

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
results, err := ad2.EnrollOnBehalfOfVersionTwo(tx, v2Templates, certTemplates, harness.EnrollOnBehalfOfHarness2.Domain2)
require.Nil(t, err)

require.Len(t, results, 1)
require.Contains(t, results, analysis.CreatePostRelationshipJob{
FromID: harness.EnrollOnBehalfOfHarnessTwo.CertTemplate21.ID,
ToID: harness.EnrollOnBehalfOfHarnessTwo.CertTemplate23.ID,
FromID: harness.EnrollOnBehalfOfHarness2.CertTemplate21.ID,
ToID: harness.EnrollOnBehalfOfHarness2.CertTemplate23.ID,
Kind: ad.EnrollOnBehalfOf,
})
return nil
})
})

testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) error {
harness.EnrollOnBehalfOfHarness3.Setup(testContext)
return nil
}, func(harness integration.HarnessDetails, db graph.Database) {
operation := analysis.NewPostRelationshipOperation(context.Background(), db, "ADCS Post Process Test - EnrollOnBehalfOf 3")

_, enterpriseCertAuthorities, certTemplates, domains, cache, err := FetchADCSPrereqs(db)
require.Nil(t, err)

if err := ad2.PostEnrollOnBehalfOf(domains, enterpriseCertAuthorities, certTemplates, cache, operation); err != nil {
t.Logf("failed post processing for %s: %v", ad.EnrollOnBehalfOf.String(), err)
}
err = operation.Done()
require.Nil(t, err)

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if startNodes, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.Kind(query.Relationship(), ad.EnrollOnBehalfOf)
})); err != nil {
t.Fatalf("error fetching EnrollOnBehalfOf edges in integration test; %v", err)
} else if endNodes, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.Kind(query.Relationship(), ad.EnrollOnBehalfOf)
})); err != nil {
t.Fatalf("error fetching EnrollOnBehalfOf edges in integration test; %v", err)
} else {
require.Len(t, startNodes, 2)
require.True(t, startNodes.Contains(harness.EnrollOnBehalfOfHarness3.CertTemplate11))
require.True(t, startNodes.Contains(harness.EnrollOnBehalfOfHarness3.CertTemplate12))

require.Len(t, endNodes, 2)
require.True(t, startNodes.Contains(harness.EnrollOnBehalfOfHarness3.CertTemplate12))
require.True(t, startNodes.Contains(harness.EnrollOnBehalfOfHarness3.CertTemplate12))
}

return nil
})
Expand Down
1 change: 1 addition & 0 deletions cmd/api/src/api/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const (
URIPathVariableAssetGroupSelectorID = "asset_group_selector_id"
URIPathVariableAttackPathID = "attack_path_id"
URIPathVariableClientID = "client_id"
URIPathVariableDataType = "data_type"
URIPathVariableDomainID = "domain_id"
URIPathVariableEventID = "event_id"
URIPathVariableFeatureID = "feature_id"
Expand Down
4 changes: 4 additions & 0 deletions cmd/api/src/api/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,19 @@ const (
ErrorResponseDetailsOTPInvalid = "one time password is invalid"
ErrorResponseDetailsResourceNotFound = "resource not found"
ErrorResponseDetailsToBeforeFrom = "to time cannot be before from time"
ErrorResponseDetailsTimeRangeInvalid = "time range provided is invalid"
ErrorResponseDetailsToMalformed = "to parameter should be formatted as RFC3339 i.e 2021-04-21T07:20:50.52Z"
ErrorResponseMultipleCollectionScopesProvided = "may only scope collection by exactly one of OU, Domain, or All Trusted Domains"
ErrorResponsePayloadUnmarshalError = "error unmarshalling JSON payload"
ErrorResponseRequestTimeout = "request timed out"
ErrorResponseUserSelfDisable = "user attempted to disable themselves"
ErrorResponseUserSelfRoleChange = "user attempted to change own role"
ErrorResponseUserSelfSSOProviderChange = "user attempted to change own SSO Provider"
ErrorResponseAGTagWhiteSpace = "asset group tags must not contain whitespace"
ErrorResponseAGNameTagEmpty = "asset group name or tag must not be empty"
ErrorResponseAGDuplicateName = "asset group name must be unique"
ErrorResponseAGDuplicateTag = "asset group tag 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
5 changes: 4 additions & 1 deletion cmd/api/src/api/registration/v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,12 @@ func registerV2Auth(resources v2.Resources, routerInst *router.Router, permissio
routerInst.GET("/api/v2/sso-providers", managementResource.ListAuthProviders),
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),
routerInst.GET(fmt.Sprintf("/api/v2/sso-providers/{%s}/signing-certificate", api.URIPathVariableSSOProviderID), managementResource.ServeSigningCertificate).RequirePermissions(permissions.AuthManageProviders),

routerInst.GET(fmt.Sprintf("/api/v2/sso/{%s}/login", api.URIPathVariableSSOProviderSlug), managementResource.SSOLoginHandler),
routerInst.GET(fmt.Sprintf("/api/v2/sso/{%s}/metadata", api.URIPathVariableSSOProviderSlug), managementResource.ServeMetadata),
routerInst.PathPrefix(fmt.Sprintf("/api/v2/sso/{%s}/callback", api.URIPathVariableSSOProviderSlug), http.HandlerFunc(managementResource.SSOCallbackHandler)),
routerInst.GET(fmt.Sprintf("/api/v2/sso/{%s}/metadata", api.URIPathVariableSSOProviderSlug), managementResource.ServeMetadata),

// Permissions
routerInst.GET("/api/v2/permissions", managementResource.ListPermissions).RequirePermissions(permissions.AuthManageSelf),
Expand Down
2 changes: 2 additions & 0 deletions cmd/api/src/api/tools/pg.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ func (s *PGMigrator) SwitchPostgreSQL(response http.ResponseWriter, request *htt
api.WriteJSONResponse(request.Context(), map[string]any{
"error": fmt.Errorf("failed connecting to PostgreSQL: %w", err),
}, http.StatusInternalServerError, response)
} else if err := pgDB.AssertSchema(request.Context(), s.graphSchema); err != nil {
log.Errorf("Unable to assert graph schema in PostgreSQL: %v", err)
} else if err := SetGraphDriver(request.Context(), s.cfg, pg.DriverName); err != nil {
api.WriteJSONResponse(request.Context(), map[string]any{
"error": fmt.Errorf("failed updating graph database driver preferences: %w", err),
Expand Down
4 changes: 1 addition & 3 deletions cmd/api/src/api/v2/analysisrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ package v2

import (
"database/sql"
"errors"
"net/http"

"github.com/specterops/bloodhound/errors"
"github.com/specterops/bloodhound/log"
"github.com/specterops/bloodhound/src/api"
"github.com/specterops/bloodhound/src/auth"
Expand All @@ -33,8 +33,6 @@ const ErrAnalysisScheduledMode = "analysis is configured to run on a schedule, u
func (s Resources) GetAnalysisRequest(response http.ResponseWriter, request *http.Request) {
if analRequest, err := s.DB.GetAnalysisRequest(request.Context()); err != nil && !errors.Is(err, sql.ErrNoRows) {
api.HandleDatabaseError(request, response, err)
} else if errors.Is(err, sql.ErrNoRows) {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusNotFound, api.ErrorResponseDetailsResourceNotFound, request), response)
} else {
api.WriteBasicResponse(request.Context(), analRequest, http.StatusOK, response)
}
Expand Down
39 changes: 39 additions & 0 deletions cmd/api/src/api/v2/analysisrequest_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2024 Specter Ops, Inc.
//
// Licensed under the Apache License, Version 2.0
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

//go:build integration
// +build integration

package v2_test

import (
"testing"

"github.com/specterops/bloodhound/src/api/v2/integration"
"github.com/specterops/bloodhound/src/model"
"github.com/stretchr/testify/require"
)

func TestRequestAnalysis(t *testing.T) {
testCtx := integration.NewFOSSContext(t)

err := testCtx.AdminClient().RequestAnalysis()
require.Nil(t, err)

analReq, err := testCtx.AdminClient().GetAnalysisRequest()
require.Nil(t, err)
require.Equal(t, analReq.RequestType, model.AnalysisRequestAnalysis)
}
57 changes: 45 additions & 12 deletions cmd/api/src/api/v2/analysisrequest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,59 @@
//
// SPDX-License-Identifier: Apache-2.0

//go:build integration
// +build integration

package v2_test

import (
"fmt"
"net/http"
"testing"
"time"

"github.com/specterops/bloodhound/src/api/v2/integration"
v2 "github.com/specterops/bloodhound/src/api/v2"
dbMocks "github.com/specterops/bloodhound/src/database/mocks"
"github.com/specterops/bloodhound/src/model"
"github.com/stretchr/testify/require"
"github.com/specterops/bloodhound/src/utils/test"
"go.uber.org/mock/gomock"
)

func TestRequestAnalysis(t *testing.T) {
testCtx := integration.NewFOSSContext(t)
func TestResources_GetAnalysisRequest(t *testing.T) {
const (
url = "api/v2/analysis/status"
)

var (
mockCtrl = gomock.NewController(t)
mockDB = dbMocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()

t.Run("success getting analysis", func(t *testing.T) {
analysisRequest := model.AnalysisRequest{
RequestedAt: time.Now(),
RequestedBy: "test",
RequestType: model.AnalysisRequestType("test-type"),
}

mockDB.EXPECT().GetAnalysisRequest(gomock.Any()).Return(analysisRequest, nil)

test.Request(t).
WithMethod(http.MethodGet).
WithURL(url).
OnHandlerFunc(resources.GetAnalysisRequest).
Require().
ResponseJSONBody(analysisRequest).
ResponseStatusCode(http.StatusOK)
})

err := testCtx.AdminClient().RequestAnalysis()
require.Nil(t, err)
t.Run("error getting analysis", func(t *testing.T) {
mockDB.EXPECT().GetAnalysisRequest(gomock.Any()).Return(model.AnalysisRequest{}, fmt.Errorf("an error"))

analReq, err := testCtx.AdminClient().GetAnalysisRequest()
require.Nil(t, err)
require.Equal(t, analReq.RequestType, model.AnalysisRequestAnalysis)
test.Request(t).
WithMethod(http.MethodGet).
WithURL(url).
OnHandlerFunc(resources.GetAnalysisRequest).
Require().
ResponseStatusCode(http.StatusInternalServerError)
})
}
1 change: 1 addition & 0 deletions cmd/api/src/api/v2/attack_path.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
ErrorNoFindingType = "no finding type specified"
ErrorInvalidFindingType = "invalid finding type specified: %v"
ErrorInvalidRFC3339 = "invalid RFC-3339 datetime format: %v"
ErrorNoDataType = "no data type specified in url"
)

type RiskAcceptRequest struct {
Expand Down
Loading

0 comments on commit 052e805

Please sign in to comment.