Skip to content

Commit

Permalink
chore: gate graph clearing (#523)
Browse files Browse the repository at this point in the history
* chore: gate graph clearing

* chore: use feature flag for gating

* chore: update error messages

* chore: conform flag key

* chore: mock feature flag response in test

* chore: enable flag by default, update UI tests

* chore: make flag non user updatable

* chore: handle error earlier in if/else chain, return early on error

* chore: mock expected call to getFlagByKey in dbwipe tests
  • Loading branch information
urangel authored Mar 26, 2024
1 parent 8c3fa90 commit 18b69e6
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 14 deletions.
22 changes: 20 additions & 2 deletions cmd/api/src/api/v2/database_wipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/specterops/bloodhound/log"
"github.com/specterops/bloodhound/src/api"
"github.com/specterops/bloodhound/src/model"
"github.com/specterops/bloodhound/src/model/appcfg"
)

type DatabaseWipe struct {
Expand Down Expand Up @@ -95,8 +96,25 @@ func (s Resources) HandleDatabaseWipe(response http.ResponseWriter, request *htt

// delete graph
if payload.DeleteCollectedGraphData {
s.TaskNotifier.RequestDeletion()
s.handleAuditLogForDatabaseWipe(request.Context(), auditEntry, true, "collected graph data")
if clearGraphDataFlag, err := s.DB.GetFlagByKey(request.Context(), appcfg.FeatureClearGraphData); err != nil {
api.WriteErrorResponse(
request.Context(),
api.BuildErrorResponse(http.StatusInternalServerError, "unable to inspect the feature flag for clearing graph data", request),
response,
)
return
} else if !clearGraphDataFlag.Enabled {
api.WriteErrorResponse(
request.Context(),
api.BuildErrorResponse(http.StatusBadRequest, "deleting graph data is currently disabled", request),
response,
)
return
} else {
s.TaskNotifier.RequestDeletion()
s.handleAuditLogForDatabaseWipe(request.Context(), auditEntry, true, "collected graph data")
}

}

// delete asset group selectors
Expand Down
8 changes: 8 additions & 0 deletions cmd/api/src/api/v2/database_wipe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/specterops/bloodhound/src/api/v2/apitest"
taskerMocks "github.com/specterops/bloodhound/src/daemons/datapipe/mocks"
dbMocks "github.com/specterops/bloodhound/src/database/mocks"
"github.com/specterops/bloodhound/src/model/appcfg"
"go.uber.org/mock/gomock"
)

Expand Down Expand Up @@ -86,6 +87,10 @@ func TestDatabaseWipe(t *testing.T) {
apitest.BodyStruct(input, v2.DatabaseWipe{DeleteCollectedGraphData: true})
},
Setup: func() {
mockDB.EXPECT().GetFlagByKey(gomock.Any(), gomock.Any()).Return(appcfg.FeatureFlag{
Enabled: true,
}, nil)

taskerIntent := mockTasker.EXPECT().RequestDeletion().Times(1)
successfulAuditLogIntent := mockDB.EXPECT().AppendAuditLog(gomock.Any(), gomock.Any()).Return(nil).Times(1)
successfulAuditLogWipe := mockDB.EXPECT().AppendAuditLog(gomock.Any(), gomock.Any()).Return(nil).Times(1)
Expand Down Expand Up @@ -247,6 +252,9 @@ func TestDatabaseWipe(t *testing.T) {
successfulAuditLogIntent := mockDB.EXPECT().AppendAuditLog(gomock.Any(), gomock.Any()).Return(nil).Times(1)

// collected graph data operations
mockDB.EXPECT().GetFlagByKey(gomock.Any(), gomock.Any()).Return(appcfg.FeatureFlag{
Enabled: true,
}, nil)
taskerIntent := mockTasker.EXPECT().RequestDeletion().Times(1)
nodesDeletedAuditLog := mockDB.EXPECT().AppendAuditLog(gomock.Any(), gomock.Any()).Return(nil).Times(1)
gomock.InOrder(successfulAuditLogIntent, taskerIntent, nodesDeletedAuditLog)
Expand Down
9 changes: 9 additions & 0 deletions cmd/api/src/model/appcfg/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package appcfg

import (
"context"

"github.com/specterops/bloodhound/src/model"
)

Expand All @@ -29,6 +30,7 @@ const (
FeatureReconciliation = "reconciliation"
FeatureEntityPanelCaching = "entity_panel_cache"
FeatureAdcs = "adcs"
FeatureClearGraphData = "clear_graph_data"
)

// AvailableFlags returns a FeatureFlagSet of expected feature flags. Feature flag defaults introduced here will become the initial
Expand Down Expand Up @@ -84,6 +86,13 @@ func AvailableFlags() FeatureFlagSet {
Enabled: false,
UserUpdatable: false,
},
FeatureClearGraphData: {
Key: FeatureClearGraphData,
Name: "Clear Graph Data",
Description: "Enables the ability to delete all nodes and edges from the graph database.",
Enabled: true,
UserUpdatable: false,
},
}
}

Expand Down
28 changes: 23 additions & 5 deletions cmd/ui/src/views/DatabaseManagement/DatabaseManagement.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ describe('DatabaseManagement', () => {
const server = setupServer(
rest.post('/api/v2/clear-database', (req, res, ctx) => {
return res(ctx.status(204));
}),
rest.get('/api/v2/features', async (req, res, ctx) => {
return res(
ctx.json({
data: [
{
id: 1,
key: 'clear_graph_data',
name: 'Clear Graph Data',
description: 'Enables the ability to delete all nodes and edges from the graph database.',
enabled: true,
user_updatable: true,
},
],
})
);
})
);

Expand All @@ -35,12 +51,14 @@ describe('DatabaseManagement', () => {
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

it('renders', () => {
it('renders', async () => {
const title = screen.getByText(/clear bloodhound data/i);
const checkboxes = screen.getAllByRole('checkbox');
const button = screen.getByRole('button', { name: /proceed/i });

expect(title).toBeInTheDocument();
expect(await screen.findByRole('checkbox', { name: /Collected graph data/i })).toBeInTheDocument();

const checkboxes = screen.getAllByRole('checkbox');
expect(checkboxes.length).toEqual(5);
expect(button).toBeInTheDocument();
});
Expand All @@ -64,7 +82,7 @@ describe('DatabaseManagement', () => {
const errorMsg = screen.getByText(/please make a selection/i);
expect(errorMsg).toBeInTheDocument();

const checkbox = screen.getByRole('checkbox', { name: /collected graph data/i });
const checkbox = screen.getByRole('checkbox', { name: /All asset group selectors/i });
await user.click(checkbox);

expect(errorMsg).not.toBeInTheDocument();
Expand All @@ -73,7 +91,7 @@ describe('DatabaseManagement', () => {
it('open and closes dialog', async () => {
const user = userEvent.setup();

const checkbox = screen.getByRole('checkbox', { name: /collected graph data/i });
const checkbox = screen.getByRole('checkbox', { name: /All asset group selectors/i });
await user.click(checkbox);

const button = screen.getByRole('button', { name: /proceed/i });
Expand All @@ -91,7 +109,7 @@ describe('DatabaseManagement', () => {
it('handles posting a mutation', async () => {
const user = userEvent.setup();

const checkbox = screen.getByRole('checkbox', { name: /collected graph data/i });
const checkbox = screen.getByRole('checkbox', { name: /All asset group selectors/i });
await user.click(checkbox);

const proceedButton = screen.getByRole('button', { name: /proceed/i });
Expand Down
20 changes: 13 additions & 7 deletions cmd/ui/src/views/DatabaseManagement/DatabaseManagement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useMutation } from 'react-query';
import { useSelector } from 'react-redux';
import { selectAllAssetGroupIds, selectTierZeroAssetGroupId } from 'src/ducks/assetgroups/reducer';
import { ClearDatabaseRequest } from 'js-client-library';
import FeatureFlag from 'src/components/FeatureFlag';

const initialState: State = {
deleteCollectedGraphData: false,
Expand Down Expand Up @@ -249,13 +250,18 @@ const DatabaseManagement = () => {
) : null}

<FormGroup sx={{ paddingTop: 1 }}>
<FormControlLabel
label='Collected graph data (all nodes and edges)'
control={
<Checkbox
checked={deleteCollectedGraphData}
onChange={handleCheckbox}
name='deleteCollectedGraphData'
<FeatureFlag
flagKey='clear_graph_data'
enabled={
<FormControlLabel
label='Collected graph data (all nodes and edges)'
control={
<Checkbox
checked={deleteCollectedGraphData}
onChange={handleCheckbox}
name='deleteCollectedGraphData'
/>
}
/>
}
/>
Expand Down

0 comments on commit 18b69e6

Please sign in to comment.