Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bed 4752 #873

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions cmd/api/src/api/saml/saml.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,7 @@ func assertionFindString(assertion *saml.Assertion, names ...string) (string, er
return value.Value, nil
}
}

log.Errorf("[SAML] Found attribute values for attribute %s however none of the values have an XML type of %s. Choosing the first value.", bhsaml.ObjectIDAttributeNameFormat, bhsaml.XMLTypeString)
log.Warnf("[SAML] Found attribute values for attribute %s however none of the values have an XML type of %s. Choosing the first value.", bhsaml.ObjectIDAttributeNameFormat, bhsaml.XMLTypeString)
return attribute.Values[0].Value, nil
}
}
Expand Down
208 changes: 104 additions & 104 deletions cmd/api/src/api/v2/audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,122 +243,122 @@ func TestResources_ListAuditLogs_Filtered(t *testing.T) {
}

func TestResources_ListAuditLogs_SkipAndOffset(t *testing.T) {
var (
mockCtrl = gomock.NewController(t)
mockDB = mocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()

mockDB.EXPECT().ListAuditLogs(gomock.Any(), gomock.Any(), gomock.Any(), 10, gomock.Any(), "", model.SQLFilter{}).Return(model.AuditLogs{}, 1000, nil)

endpoint := "/api/v2/audit"

if req, err := http.NewRequest("GET", endpoint, nil); err != nil {
t.Fatal(err)
} else {
q := url.Values{}
q.Add("skip", "10")
q.Add("offset", "20")

req.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String())
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
require.Equal(t, http.StatusOK, response.Code)
}
var (
mockCtrl = gomock.NewController(t)
mockDB = mocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()

mockDB.EXPECT().ListAuditLogs(gomock.Any(), gomock.Any(), gomock.Any(), 10, gomock.Any(), "", model.SQLFilter{}).Return(model.AuditLogs{}, 1000, nil)

endpoint := "/api/v2/audit"

if req, err := http.NewRequest("GET", endpoint, nil); err != nil {
t.Fatal(err)
} else {
q := url.Values{}
q.Add("skip", "10")
q.Add("offset", "20")

req.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String())
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
require.Equal(t, http.StatusOK, response.Code)
}
}

func TestResources_ListAuditLogs_OnlyOffset(t *testing.T) {
var (
mockCtrl = gomock.NewController(t)
mockDB = mocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()
var (
mockCtrl = gomock.NewController(t)
mockDB = mocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()

mockDB.EXPECT().ListAuditLogs(gomock.Any(), gomock.Any(), gomock.Any(), 20, gomock.Any(), "", model.SQLFilter{}).Return(model.AuditLogs{}, 1000, nil)
mockDB.EXPECT().ListAuditLogs(gomock.Any(), gomock.Any(), gomock.Any(), 20, gomock.Any(), "", model.SQLFilter{}).Return(model.AuditLogs{}, 1000, nil)

endpoint := "/api/v2/audit"
endpoint := "/api/v2/audit"

if req, err := http.NewRequest("GET", endpoint, nil); err != nil {
t.Fatal(err)
} else {
q := url.Values{}
q.Add("offset", "20")
if req, err := http.NewRequest("GET", endpoint, nil); err != nil {
t.Fatal(err)
} else {
q := url.Values{}
q.Add("offset", "20")

req.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String())
req.URL.RawQuery = q.Encode()
req.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String())
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")
router := mux.NewRouter()
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
require.Equal(t, http.StatusOK, response.Code)
}
response := httptest.NewRecorder()
router.ServeHTTP(response, req)
require.Equal(t, http.StatusOK, response.Code)
}
}

func TestResources_ListAuditLogs_OnlySkip(t *testing.T) {
var (
mockCtrl = gomock.NewController(t)
mockDB = mocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()

// Expect skip to be 5 (from "skip" parameter)
mockDB.EXPECT().ListAuditLogs(gomock.Any(), gomock.Any(), gomock.Any(), 5, gomock.Any(), gomock.Any(), gomock.Any()).Return(model.AuditLogs{}, 1000, nil)

endpoint := "/api/v2/audit"

if req, err := http.NewRequest("GET", endpoint, nil); err != nil {
t.Fatal(err)
} else {
q := url.Values{}
q.Add("skip", "5")

req.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String())
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
require.Equal(t, http.StatusOK, response.Code)
}
var (
mockCtrl = gomock.NewController(t)
mockDB = mocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()

// Expect skip to be 5 (from "skip" parameter)
mockDB.EXPECT().ListAuditLogs(gomock.Any(), gomock.Any(), gomock.Any(), 5, gomock.Any(), gomock.Any(), gomock.Any()).Return(model.AuditLogs{}, 1000, nil)

endpoint := "/api/v2/audit"

if req, err := http.NewRequest("GET", endpoint, nil); err != nil {
t.Fatal(err)
} else {
q := url.Values{}
q.Add("skip", "5")

req.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String())
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
require.Equal(t, http.StatusOK, response.Code)
}
}

func TestResources_ListAuditLogs_InvalidSkip(t *testing.T) {
var (
mockCtrl = gomock.NewController(t)
mockDB = mocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()

endpoint := "/api/v2/audit"

if req, err := http.NewRequest("GET", endpoint, nil); err != nil {
t.Fatal(err)
} else {
q := url.Values{}
q.Add("skip", "invalid")

req.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String())
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
require.Equal(t, http.StatusBadRequest, response.Code)
require.Contains(t, response.Body.String(), "query parameter \\\"skip\\\" is malformed")
}
}
var (
mockCtrl = gomock.NewController(t)
mockDB = mocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()

endpoint := "/api/v2/audit"

if req, err := http.NewRequest("GET", endpoint, nil); err != nil {
t.Fatal(err)
} else {
q := url.Values{}
q.Add("skip", "invalid")

req.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String())
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
require.Equal(t, http.StatusBadRequest, response.Code)
require.Contains(t, response.Body.String(), "query parameter \\\"skip\\\" is malformed")
}
}
18 changes: 9 additions & 9 deletions cmd/api/src/api/v2/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ func ParseSkipQueryParameter(params url.Values, defaultValue int) (int, error) {
}

func ParseSkipQueryParameterWithKey(params url.Values, key string, defaultValue int) (int, error) {
if param := params.Get(key); param == "" {
return defaultValue, nil
} else if skip, err := strconv.Atoi(param); err != nil {
return 0, fmt.Errorf("error converting %s value %v to int: %v", key, param, err)
} else if skip < 0 {
return 0, fmt.Errorf(utils.ErrorInvalidSkip, skip)
} else {
return skip, nil
}
if param := params.Get(key); param == "" {
return defaultValue, nil
} else if skip, err := strconv.Atoi(param); err != nil {
return 0, fmt.Errorf("error converting %s value %v to int: %v", key, param, err)
} else if skip < 0 {
return 0, fmt.Errorf(utils.ErrorInvalidSkip, skip)
} else {
return skip, nil
}
}

func ParseLimitQueryParameter(params url.Values, defaultValue int) (int, error) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/api/src/bootstrap/initializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type Initializer[DBType database.Database, GraphType graph.Database] struct {
Configuration config.Configuration
PreMigrationDaemons InitializerLogic[DBType, GraphType]
Entrypoint InitializerLogic[DBType, GraphType]
DBConnector DatabaseConstructor[DBType, GraphType]
DBConnector DatabaseConstructor[DBType, GraphType]
}

func (s Initializer[DBType, GraphType]) Launch(parentCtx context.Context, handleSignals bool) error {
Expand Down
6 changes: 3 additions & 3 deletions cmd/api/src/cmd/bhapi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ func main() {
log.Fatalf("Unable to read configuration %s: %v", configFilePath, err)
} else {
initializer := bootstrap.Initializer[*database.BloodhoundDB, *graph.DatabaseSwitch]{
Configuration: cfg,
DBConnector: services.ConnectDatabases,
Configuration: cfg,
DBConnector: services.ConnectDatabases,
PreMigrationDaemons: services.PreMigrationDaemons,
Entrypoint: services.Entrypoint,
Entrypoint: services.Entrypoint,
}

if err := initializer.Launch(context.Background(), true); err != nil {
Expand Down
9 changes: 7 additions & 2 deletions cmd/api/src/daemons/datapipe/azure_convertors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package datapipe

import (
"encoding/json"
"errors"
"fmt"
"strings"

Expand Down Expand Up @@ -190,7 +191,9 @@ func convertAzureAppOwner(raw json.RawMessage, converted *ConvertedAzureData) {
)
if err := json.Unmarshal(raw.Owner, &owner); err != nil {
log.Errorf(SerialError, "app owner", err)
} else if ownerType, err := ein.ExtractTypeFromDirectoryObject(owner); err != nil {
} else if ownerType, err := ein.ExtractTypeFromDirectoryObject(owner); errors.Is(err, ein.InvalidTypeErr) {
log.Warnf(ExtractError, err)
} else if err != nil {
log.Errorf(ExtractError, err)
} else {
converted.RelProps = append(converted.RelProps, ein.ConvertAzureOwnerToRel(owner, ownerType, azure.App, data.AppId))
Expand Down Expand Up @@ -235,7 +238,9 @@ func convertAzureDeviceOwner(raw json.RawMessage, converted *ConvertedAzureData)
)
if err := json.Unmarshal(raw.Owner, &owner); err != nil {
log.Errorf(SerialError, "device owner", err)
} else if ownerType, err := ein.ExtractTypeFromDirectoryObject(owner); err != nil {
} else if ownerType, err := ein.ExtractTypeFromDirectoryObject(owner); errors.Is(err, ein.InvalidTypeErr) {
log.Warnf(ExtractError, err)
} else if err != nil {
log.Errorf(ExtractError, err)
} else {
converted.RelProps = append(converted.RelProps, ein.ConvertAzureOwnerToRel(owner, ownerType, azure.Device, data.DeviceId))
Expand Down
10 changes: 8 additions & 2 deletions cmd/api/src/daemons/datapipe/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ package datapipe
import (
"archive/zip"
"context"
"errors"
"fmt"
"io"
"io/fs"
"os"

"github.com/specterops/bloodhound/dawgs/graph"
Expand Down Expand Up @@ -214,7 +216,9 @@ func (s *Daemon) processIngestFile(ctx context.Context, path string, fileType mo

if err := file.Close(); err != nil {
log.Errorf("Error closing ingest file %s: %v", filePath, err)
} else if err := os.Remove(filePath); err != nil {
} else if err := os.Remove(filePath); errors.Is(err, fs.ErrNotExist) {
log.Warnf("Removing ingest file %s: %w", filePath, err)
} else if err != nil {
log.Errorf("Error removing ingest file %s: %v", filePath, err)
}
}
Expand Down Expand Up @@ -249,7 +253,9 @@ func (s *Daemon) processIngestTasks(ctx context.Context, ingestTasks model.Inges
log.Errorf("Failed to fetch job for ingest task %d: %v", ingestTask.ID, err)
}
total, failed, err := s.processIngestFile(ctx, ingestTask.FileName, ingestTask.FileType)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
log.Warnf("Did not process ingest task %d with file %s: %v", ingestTask.ID, ingestTask.FileName, err)
} else if err != nil {
log.Errorf("Failed processing ingest task %d with file %s: %v", ingestTask.ID, ingestTask.FileName, err)
}

Expand Down
16 changes: 16 additions & 0 deletions cmd/api/src/database/parameters_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
// 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 database_test

import (
Expand Down
16 changes: 16 additions & 0 deletions cmd/api/src/model/appcfg/parameter_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
// 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 appcfg_test

import (
Expand Down
Loading
Loading