Skip to content

Commit

Permalink
fix: filter node property at ingest and for entity panel display (#810)
Browse files Browse the repository at this point in the history
  • Loading branch information
urangel committed Sep 10, 2024
1 parent c9d5755 commit ba22f44
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 16 deletions.
33 changes: 19 additions & 14 deletions cmd/api/src/daemons/datapipe/ingest.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (

const (
IngestCountThreshold = 500
ReconcileProperty = "reconcile"
)

func ReadFileForIngest(batch graph.Batch, reader io.ReadSeeker, adcsEnabled bool) error {
Expand Down Expand Up @@ -134,40 +135,44 @@ func IngestWrapper(batch graph.Batch, reader io.ReadSeeker, meta ingest.Metadata
return nil
}

func IngestNode(batch graph.Batch, nowUTC time.Time, identityKind graph.Kind, nextNode ein.IngestibleNode) error {
//Ensure object id is upper case
nextNode.ObjectID = strings.ToUpper(nextNode.ObjectID)

nextNode.PropertyMap[common.LastSeen.String()] = nowUTC
nextNode.PropertyMap[common.ObjectID.String()] = nextNode.ObjectID
func NormalizeEinNodeProperties(properties map[string]any, objectID string, nowUTC time.Time) map[string]any {
delete(properties, ReconcileProperty)
properties[common.LastSeen.String()] = nowUTC
properties[common.ObjectID.String()] = strings.ToUpper(objectID)

//Ensure that name, operatingsystem, and distinguishedname properties are upper case
if rawName, hasName := nextNode.PropertyMap[common.Name.String()]; hasName && rawName != nil {
// Ensure that name, operatingsystem, and distinguishedname properties are upper case
if rawName, hasName := properties[common.Name.String()]; hasName && rawName != nil {
if name, typeMatches := rawName.(string); typeMatches {
nextNode.PropertyMap[common.Name.String()] = strings.ToUpper(name)
properties[common.Name.String()] = strings.ToUpper(name)
} else {
log.Errorf("Bad type found for node name property during ingest. Expected string, got %T", rawName)
}
}

if rawOS, hasOS := nextNode.PropertyMap[common.OperatingSystem.String()]; hasOS && rawOS != nil {
if rawOS, hasOS := properties[common.OperatingSystem.String()]; hasOS && rawOS != nil {
if os, typeMatches := rawOS.(string); typeMatches {
nextNode.PropertyMap[common.OperatingSystem.String()] = strings.ToUpper(os)
properties[common.OperatingSystem.String()] = strings.ToUpper(os)
} else {
log.Errorf("Bad type found for node operating system property during ingest. Expected string, got %T", rawOS)
}
}

if rawDN, hasDN := nextNode.PropertyMap[ad.DistinguishedName.String()]; hasDN && rawDN != nil {
if rawDN, hasDN := properties[ad.DistinguishedName.String()]; hasDN && rawDN != nil {
if dn, typeMatches := rawDN.(string); typeMatches {
nextNode.PropertyMap[ad.DistinguishedName.String()] = strings.ToUpper(dn)
properties[ad.DistinguishedName.String()] = strings.ToUpper(dn)
} else {
log.Errorf("Bad type found for node distinguished name property during ingest. Expected string, got %T", rawDN)
}
}

return properties
}

func IngestNode(batch graph.Batch, nowUTC time.Time, identityKind graph.Kind, nextNode ein.IngestibleNode) error {
normalizedProperties := NormalizeEinNodeProperties(nextNode.PropertyMap, nextNode.ObjectID, nowUTC)

return batch.UpdateNodeBy(graph.NodeUpdate{
Node: graph.PrepareNode(graph.AsProperties(nextNode.PropertyMap), nextNode.Label),
Node: graph.PrepareNode(graph.AsProperties(normalizedProperties), nextNode.Label),
IdentityKind: identityKind,
IdentityProperties: []string{
common.ObjectID.String(),
Expand Down
48 changes: 48 additions & 0 deletions cmd/api/src/daemons/datapipe/ingest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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

package datapipe_test

import (
"testing"
"time"

"github.com/specterops/bloodhound/graphschema/ad"
"github.com/specterops/bloodhound/graphschema/common"
"github.com/specterops/bloodhound/src/daemons/datapipe"
"github.com/stretchr/testify/assert"
)

func TestNormalizeEinNodeProperties(t *testing.T) {
var (
nowUTC = time.Now().UTC()
objectID = "objectid"
properties = map[string]any{
datapipe.ReconcileProperty: false,
common.Name.String(): "name",
common.OperatingSystem.String(): "temple",
ad.DistinguishedName.String(): "distinguished-name",
}
normalizedProperties = datapipe.NormalizeEinNodeProperties(properties, objectID, nowUTC)
)

assert.Nil(t, normalizedProperties[datapipe.ReconcileProperty])
assert.NotNil(t, normalizedProperties[common.LastSeen.String()])
assert.Equal(t, "OBJECTID", normalizedProperties[common.ObjectID.String()])
assert.Equal(t, "NAME", normalizedProperties[common.Name.String()])
assert.Equal(t, "DISTINGUISHED-NAME", normalizedProperties[ad.DistinguishedName.String()])
assert.Equal(t, "TEMPLE", normalizedProperties[common.OperatingSystem.String()])
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
//
// SPDX-License-Identifier: Apache-2.0

import { Field } from './fragments';
import { render, screen } from '../../test-utils';
import { EntityField } from '../../utils';
import { exclusionList, Field, ObjectInfoFields } from './fragments';

describe('Field', () => {
it('should render a Field when the provided value is false', () => {
Expand Down Expand Up @@ -44,3 +45,21 @@ describe('Field', () => {
expect(container.innerHTML).toBe('');
});
});

describe('ObjectInfoFields', () => {
it('should filter properties that we do not want to display in the entity panel', () => {
const properties: EntityField[] = exclusionList.map((key) => {
return { label: key, value: 'test', keyprop: key };
});
const validProperty: EntityField = { label: 'Object ID', value: 'OBJECT_ID' };
properties.push(validProperty);

render(<ObjectInfoFields fields={properties} />);

expect(screen.getByText('Object ID')).toBeInTheDocument();

exclusionList.forEach((property) => {
expect(screen.queryByText(property)).not.toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import useCollapsibleSectionStyles from './InfoStyles/CollapsibleSection';
import React, { PropsWithChildren } from 'react';
import { EntityField, format } from '../../utils';

const exclusionList = [
export const exclusionList = [
'gid',
'admin_rights_count',
'admin_rights_risk_percent',
Expand All @@ -32,6 +32,7 @@ const exclusionList = [
'displayname',
'service_principal_id',
'highvalue',
'reconcile',
];

const filterNegatedFields = (fields: EntityField[]): EntityField[] =>
Expand Down

0 comments on commit ba22f44

Please sign in to comment.