Skip to content

Commit

Permalink
Merge pull request #1104 from SpecterOps/BED-5083
Browse files Browse the repository at this point in the history
Bed 5083: Posture page extension features
  • Loading branch information
sircodemane authored Jan 29, 2025
2 parents 2c8638b + f698ea9 commit 5c18b8b
Show file tree
Hide file tree
Showing 31 changed files with 579 additions and 114 deletions.
3 changes: 2 additions & 1 deletion cmd/api/src/api/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ const (
ErrorResponseDetailsUniqueViolation = "unique constraint was violated"
ErrorResponseDetailsNotImplemented = "All good things to those who wait. Not implemented."

FmtErrorResponseDetailsBadQueryParameters = "there are errors in the query parameters: %v"
FmtErrorResponseDetailsBadQueryParameters = "there are errors in the query parameters: %v"
FmtErrorResponseDetailsMissingRequiredQueryParameter = "missing required query parameter: %v"
)

func IsErrorResponse(response *http.Response) bool {
Expand Down
68 changes: 68 additions & 0 deletions cmd/api/src/database/dataquality.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,74 @@ func (s *BloodhoundDB) GetADDataQualityStats(ctx context.Context, domainSid stri
return adDataQualityStats, int(count), nil
}

// GetAggregateADDataQualityStats will aggregate AD Quality stats by
// summing the maximum asset counts per environment per day. Due to
// session and group completeness being percentages, it will return
// the single maximum value of all environments per day.
func (s *BloodhoundDB) GetAggregateADDataQualityStats(ctx context.Context, domainSIDs []string, start time.Time, end time.Time) (model.ADDataQualityStats, error) {
var (
adDataQualityStats model.ADDataQualityStats
params = map[string]any{
"ids": domainSIDs,
"start": start,
"end": end,
}
)

const aggregateAdDataQualityStatsSql = `
WITH aggregated_quality_stats AS (
SELECT
DATE_TRUNC('day', created_at) AS created_date,
MAX(users) AS max_users,
MAX(groups) AS max_groups,
MAX(computers) AS max_computers,
MAX(ous) AS max_ous,
MAX(containers) AS max_containers,
MAX(gpos) AS max_gpos,
MAX(acls) AS max_acls,
MAX(sessions) AS max_sessions,
MAX(relationships) AS max_relationships,
MAX(aiacas) AS max_aiacas,
MAX(rootcas) AS max_rootcas,
MAX(enterprisecas) AS max_enterprisecas,
MAX(ntauthstores) AS max_ntauthstores,
MAX(certtemplates) AS max_certtemplates,
MAX(issuancepolicies) AS max_issuancepolicies,
MAX(session_completeness) AS max_session_completeness,
MAX(local_group_completeness) AS max_local_group_completeness
FROM ad_data_quality_stats
WHERE domain_sid IN @ids
AND created_at BETWEEN @start AND @end
GROUP BY domain_sid, created_date
)
SELECT
created_date AS created_at,
SUM(max_users) AS users,
SUM(max_groups) AS groups,
SUM(max_computers) AS computers,
SUM(max_ous) AS ous,
SUM(max_containers) AS containers,
SUM(max_gpos) AS gpos,
SUM(max_acls) AS acls,
SUM(max_sessions) AS sessions,
SUM(max_relationships) AS relationships,
SUM(max_aiacas) AS aiacas,
SUM(max_rootcas) AS rootcas,
SUM(max_enterprisecas) AS enterprisecas,
SUM(max_ntauthstores) AS ntauthstores,
SUM(max_certtemplates) AS certtemplates,
SUM(max_issuancepolicies) AS issuancepolicies,
MAX(max_session_completeness) AS session_completeness,
MAX(max_local_group_completeness) AS local_group_completeness
FROM aggregated_quality_stats
GROUP BY created_at
ORDER BY created_at;`

result := s.db.WithContext(ctx).Raw(aggregateAdDataQualityStatsSql, params).Scan(&adDataQualityStats)

return adDataQualityStats, CheckError(result)
}

func (s *BloodhoundDB) CreateADDataQualityAggregation(ctx context.Context, aggregation model.ADDataQualityAggregation) (model.ADDataQualityAggregation, error) {
result := s.db.WithContext(ctx).Create(&aggregation)
return aggregation, CheckError(result)
Expand Down
1 change: 1 addition & 0 deletions cmd/api/src/database/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ type Database interface {
// Data Quality
dataquality.DataQualityData
GetADDataQualityStats(ctx context.Context, domainSid string, start time.Time, end time.Time, sort_by string, limit int, skip int) (model.ADDataQualityStats, int, error)
GetAggregateADDataQualityStats(ctx context.Context, domainSIDs []string, start time.Time, end time.Time) (model.ADDataQualityStats, error)
GetADDataQualityAggregations(ctx context.Context, start time.Time, end time.Time, sort_by string, limit int, skip int) (model.ADDataQualityAggregations, int, error)
GetAzureDataQualityStats(ctx context.Context, tenantId string, start time.Time, end time.Time, sort_by string, limit int, skip int) (model.AzureDataQualityStats, int, error)
GetAzureDataQualityAggregations(ctx context.Context, start time.Time, end time.Time, sort_by string, limit int, skip int) (model.AzureDataQualityAggregations, int, error)
Expand Down
15 changes: 15 additions & 0 deletions cmd/api/src/database/mocks/db.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions cmd/api/src/queries/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ type Graph interface {
GetNodesByKind(ctx context.Context, kinds ...graph.Kind) (graph.NodeSet, error)
GetFilteredAndSortedNodes(orderCriteria model.OrderCriteria, filterCriteria graph.Criteria) (graph.NodeSet, error)
FetchNodesByObjectIDs(ctx context.Context, objectIDs ...string) (graph.NodeSet, error)
FetchNodesByObjectIDsAndKinds(ctx context.Context, kinds graph.Kinds, objectIDs ...string) (graph.NodeSet, error)
ValidateOUs(ctx context.Context, ous []string) ([]string, error)
BatchNodeUpdate(ctx context.Context, nodeUpdate graph.NodeUpdate) error
RawCypherQuery(ctx context.Context, pQuery PreparedQuery, includeProperties bool) (model.UnifiedGraph, error)
Expand Down Expand Up @@ -726,6 +727,26 @@ func (s *GraphQuery) FetchNodesByObjectIDs(ctx context.Context, objectIDs ...str
})
}

func (s *GraphQuery) FetchNodesByObjectIDsAndKinds(ctx context.Context, kinds graph.Kinds, objectIDs ...string) (graph.NodeSet, error) {
var nodes graph.NodeSet

return nodes, s.Graph.ReadTransaction(ctx, func(tx graph.Transaction) error {
if fetchedNodes, err := ops.FetchNodeSet(tx.Nodes().Filterf(
func() graph.Criteria {
return query.And(
query.KindIn(query.Node(), kinds...),
query.In(query.NodeProperty(common.ObjectID.String()), objectIDs),
)
}),
); err != nil {
return err
} else {
nodes = fetchedNodes
return nil
}
})
}

func (s *GraphQuery) ValidateOUs(ctx context.Context, ous []string) ([]string, error) {
var validated = make([]string, 0)

Expand Down
20 changes: 20 additions & 0 deletions cmd/api/src/queries/mocks/graph.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion cmd/ui/src/ducks/global/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0
import { Domain } from 'js-client-library';
import { OptionsObject, SnackbarKey } from 'notistack';
import * as types from './types';

Expand Down Expand Up @@ -63,7 +64,7 @@ export const setExpanded = (expanded: { [key: string]: symbol[] }): types.Global
};
};

export const setDomain = (domain: types.Domain | null): types.GlobalOptionsActionTypes => {
export const setDomain = (domain: Domain | null): types.GlobalOptionsActionTypes => {
return {
type: types.GLOBAL_SET_DOMAIN,
domain,
Expand Down
9 changes: 1 addition & 8 deletions cmd/ui/src/ducks/global/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
//
// SPDX-License-Identifier: Apache-2.0
import { Notification } from 'bh-shared-ui';
import { Domain } from 'js-client-library';
import { SnackbarKey } from 'notistack';

const GLOBAL_ADD_SNACKBAR = 'app/global/ADDSNACKBAR';
Expand Down Expand Up @@ -107,14 +108,6 @@ export type GlobalOptionsActionTypes =
| SetAssetGroupIndexAction
| SetAssetGroupEditAction;

export interface Domain {
type: string;
impactValue: number;
name: string;
id: string;
collected: boolean;
}

export interface SetExpandedAction {
type: typeof GLOBAL_SET_EXPANDED;
expanded: { [key: string]: symbol[] };
Expand Down
1 change: 0 additions & 1 deletion cmd/ui/src/views/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ const Content: React.FC = () => {
}
}, [authState, isFullyAuthenticated, dispatch]);

// set inital domain/tenant once user is authenticated
useEffect(() => {
if (isFullyAuthenticated) {
const ctrl = new AbortController();
Expand Down
2 changes: 1 addition & 1 deletion cmd/ui/src/views/Explore/GraphView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ const GraphView: FC = () => {
</Grid>
<ContextMenu contextMenu={contextMenu} handleClose={handleCloseContextMenu} />
<GraphProgress loading={graphState.loading} />
<NoDataDialogWithLinks open={!data.length} />
<NoDataDialogWithLinks open={!data?.length} />
</Box>
);
};
Expand Down
3 changes: 2 additions & 1 deletion cmd/ui/src/views/QA/QA.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
ActiveDirectoryPlatformInfo,
AzurePlatformInfo,
DataSelector,
DataSelectorValueTypes,
DomainInfo,
PageWithTitle,
TenantInfo,
Expand All @@ -38,7 +39,7 @@ const useStyles = makeStyles((theme) => ({

const QualityAssurance: React.FC = () => {
const domain = useAppSelector((state) => state.global.options.domain);
const [contextType, setContextType] = useState(domain?.type || null);
const [contextType, setContextType] = useState<DataSelectorValueTypes | null>(domain?.type || null);
const [contextId, setContextId] = useState(domain?.id || null);
const [dataError, setDataError] = useState(false);
const classes = useStyles();
Expand Down
1 change: 0 additions & 1 deletion packages/go/graphschema/ad/ad.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion packages/go/graphschema/azure/azure.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion packages/go/graphschema/common/common.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 5c18b8b

Please sign in to comment.