Skip to content

Commit c0226b8

Browse files
committed
Merge branch 'main' of github.com:SpecterOps/BloodHound into pg-migrate
2 parents 3ffa5b9 + d9e67f9 commit c0226b8

File tree

47 files changed

+1152
-722
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1152
-722
lines changed

.github/workflows/jira-issue-transfer.yml

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ name: Jira Issue Transfer
1717

1818
on:
1919
issues:
20-
types:
21-
- opened
20+
types:
21+
- labeled
2222
jobs:
2323
build:
2424
runs-on: self-hosted
@@ -29,13 +29,25 @@ jobs:
2929
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
3030
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
3131
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
32-
- name: Jira Create
33-
uses: atlassian/gajira-create@v3
34-
with:
35-
project: BP
36-
issuetype: 'Bug Report'
37-
summary: ${{ github.event.issue.title }}
38-
description: "Github Issue Link: ${{ github.event.issue.url}} \r\n ${{ github.event.issue.body }}"
39-
fields: '{"labels":["GitHubReport"]}'
40-
env:
4132
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33+
if: github.event.label.name == 'ticketed' && !contains(github.event.issue.labels.*.name, 'ticketed')
34+
if: contains(github.event.issue.labels.*.name, 'bug')
35+
- name: Jira Create
36+
uses: atlassian/gajira-create@v3
37+
with:
38+
project: BP
39+
issuetype: 'Bug Report'
40+
summary: ${{ github.event.issue.title }}
41+
description: "Github Issue Link: ${{ github.event.issue.html_url}} \r\n ${{ github.event.issue.body }}"
42+
fields: '{"labels":["GitHubReport"]}'
43+
44+
if: contains(github.event.issue.labels.*.name, 'enhancement')
45+
- name: Jira Create
46+
uses: atlassian/gajira-create@v3
47+
with:
48+
project: BP
49+
issuetype: 'Product Feature'
50+
summary: ${{ github.event.issue.title }}
51+
description: "Github Issue Link: ${{ github.event.issue.html_url}} \r\n ${{ github.event.issue.body }}"
52+
fields: '{"labels":["GitHubReport"]}'
53+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright 2023 Specter Ops, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
name: PR-Ticket-OoOrg
17+
18+
on:
19+
pull_request:
20+
types: [opened, synchronize]
21+
22+
jobs:
23+
build:
24+
runs-on: self-hosted
25+
steps:
26+
- name: Check if PR is from within the organization
27+
id: check_org
28+
run:
29+
PR_URL=$(jq -r .pull_request.url "$GITHUB_EVENT_PATH")
30+
PR_NUMBER=$(jq -r .pull_request.number "$GITHUB_EVENT_PATH")
31+
TEAM_SLUG="bloodhound-engineering" # Replace with your team's slug
32+
GITHUB_TOKEN="${{ secrets.GITHUB_TOKEN }}"
33+
PR_ORG=$(jq -r .pull_request.head.repo.organization.login "$GITHUB_EVENT_PATH")
34+
PR_USER=$(jq -r .pull_request.user.login "$GITHUB_EVENT_PATH")
35+
TEAM_MEMBERSHIP=$(curl -s -X GET -H "Authorization: token $GITHUB_TOKEN" \
36+
"https://api.github.com/orgs/$PR_ORG/teams/$TEAM_SLUG/memberships/$PR_USER")
37+
38+
env:
39+
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
40+
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
41+
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
42+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43+
44+
if [ -z "$TEAM_MEMBERSHIP" ]; then
45+
- name: Login
46+
uses: atlassian/gajira-login@v3
47+
- name: Jira Create
48+
uses: atlassian/gajira-create@v3
49+
with:
50+
project: BP
51+
issuetype: 'Product Feature'
52+
summary: ${{ github.event.pulls.title }}
53+
description: "Github Pull Request Link: ${{ github.event.pulls.html_url}} \r\n ${{ github.event.pulls.body }}"
54+
fields: '{"labels":["GitHubReport"]}'
55+

cmd/api/src/analysis/ad/adcs_integration_test.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package ad_test
2121

2222
import (
2323
"context"
24+
2425
"github.com/specterops/bloodhound/analysis"
2526
ad2 "github.com/specterops/bloodhound/analysis/ad"
2627
"github.com/specterops/bloodhound/analysis/impact"
@@ -2928,42 +2929,42 @@ func TestExtendedByPolicyBinding(t *testing.T) {
29282929
operation.Done()
29292930

29302931
db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
2931-
if edge, err := analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.IssuancePolicy0.ID, harness.ExtendedByPolicyHarness.CertTemplate1.ID, ad.ExtendedByPolicy); err != nil {
2932+
if edge, err := analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.CertTemplate1.ID, harness.ExtendedByPolicyHarness.IssuancePolicy0.ID, ad.ExtendedByPolicy); err != nil {
29322933
t.Fatalf("error fetching ExtendedByPolicy edge (1) in integration test; %v", err)
29332934
} else {
29342935
require.NotNil(t, edge)
29352936
}
29362937

2937-
if edge, err := analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.IssuancePolicy1.ID, harness.ExtendedByPolicyHarness.CertTemplate1.ID, ad.ExtendedByPolicy); err != nil {
2938+
if edge, err := analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.CertTemplate1.ID, harness.ExtendedByPolicyHarness.IssuancePolicy1.ID, ad.ExtendedByPolicy); err != nil {
29382939
t.Fatalf("error fetching ExtendedByPolicy edge (2) in integration test; %v", err)
29392940
} else {
29402941
require.NotNil(t, edge)
29412942
}
29422943

2943-
if edge, err := analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.IssuancePolicy0.ID, harness.ExtendedByPolicyHarness.CertTemplate2.ID, ad.ExtendedByPolicy); err != nil {
2944+
if edge, err := analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.CertTemplate2.ID, harness.ExtendedByPolicyHarness.IssuancePolicy0.ID, ad.ExtendedByPolicy); err != nil {
29442945
t.Fatalf("error fetching ExtendedByPolicy edge (3) in integration test; %v", err)
29452946
} else {
29462947
require.NotNil(t, edge)
29472948
}
29482949

2949-
if edge, err := analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.IssuancePolicy2.ID, harness.ExtendedByPolicyHarness.CertTemplate2.ID, ad.ExtendedByPolicy); err != nil {
2950+
if edge, err := analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.CertTemplate2.ID, harness.ExtendedByPolicyHarness.IssuancePolicy2.ID, ad.ExtendedByPolicy); err != nil {
29502951
t.Fatalf("error fetching ExtendedByPolicy edge (4) in integration test; %v", err)
29512952
} else {
29522953
require.NotNil(t, edge)
29532954
}
29542955

2955-
if edge, err := analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.IssuancePolicy3.ID, harness.ExtendedByPolicyHarness.CertTemplate3.ID, ad.ExtendedByPolicy); err != nil {
2956+
if edge, err := analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.CertTemplate3.ID, harness.ExtendedByPolicyHarness.IssuancePolicy3.ID, ad.ExtendedByPolicy); err != nil {
29562957
t.Fatalf("error fetching ExtendedByPolicy edge (5) in integration test; %v", err)
29572958
} else {
29582959
require.NotNil(t, edge)
29592960
}
29602961

29612962
// CertificatePolicy doesn't match CertTemplateOID
2962-
edge, _ := analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.IssuancePolicy1.ID, harness.ExtendedByPolicyHarness.CertTemplate2.ID, ad.ExtendedByPolicy)
2963+
edge, _ := analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.CertTemplate2.ID, harness.ExtendedByPolicyHarness.IssuancePolicy1.ID, ad.ExtendedByPolicy)
29632964
require.Nil(t, edge, "ExtendedByPolicy edge exists between IssuancePolicy1 and CertTemplate2 where it shouldn't")
29642965

29652966
// Different domains, no edge
2966-
edge, _ = analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.IssuancePolicy4.ID, harness.ExtendedByPolicyHarness.CertTemplate4.ID, ad.ExtendedByPolicy)
2967+
edge, _ = analysis.FetchEdgeByStartAndEnd(testContext.Context(), db, harness.ExtendedByPolicyHarness.CertTemplate4.ID, harness.ExtendedByPolicyHarness.IssuancePolicy4.ID, ad.ExtendedByPolicy)
29672968
require.Nil(t, edge, "ExtendedByPolicy edge bridges domains where it shouldn't")
29682969

29692970
return nil

cmd/api/src/api/middleware/middleware.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ import (
4040
"github.com/unrolled/secure"
4141
)
4242

43+
const (
44+
// Default timeout for any request is thirty seconds
45+
defaultTimeout = 30 * time.Second
46+
)
47+
4348
// Wrapper is an iterator for middleware function application that wraps around a http.Handler.
4449
type Wrapper struct {
4550
middleware []mux.MiddlewareFunc

cmd/api/src/api/signature.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,16 @@ func SignRequestAtTime(hasher func() hash.Hash, id string, token string, datetim
208208
func SignRequest(tokenID, token string, request *http.Request) error {
209209
return SignRequestAtTime(sha256.New, tokenID, token, time.Now(), request)
210210
}
211+
212+
type readerDelegatedCloser struct {
213+
source io.Reader
214+
closer io.Closer
215+
}
216+
217+
func (s readerDelegatedCloser) Read(p []byte) (n int, err error) {
218+
return s.source.Read(p)
219+
}
220+
221+
func (s readerDelegatedCloser) Close() error {
222+
return s.closer.Close()
223+
}

cmd/api/src/api/v2/cypherquery.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ func (s Resources) CypherQuery(response http.ResponseWriter, request *http.Reque
6969
} else {
7070
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, err.Error(), request), response)
7171
}
72+
} else if !preparedQuery.HasMutation && len(graphResponse.Nodes)+len(graphResponse.Edges) == 0 {
73+
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusNotFound, "resource not found", request), response)
7274
} else {
7375
api.WriteBasicResponse(request.Context(), graphResponse, http.StatusOK, response)
7476
}

cmd/api/src/api/v2/cypherquery_integration_test.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,12 @@ package v2_test
2121

2222
import (
2323
"bytes"
24-
"fmt"
24+
"testing"
25+
2526
"github.com/specterops/bloodhound/cypher/backend/cypher"
2627
"github.com/specterops/bloodhound/src/auth"
2728
"github.com/specterops/bloodhound/src/model"
2829
"github.com/specterops/bloodhound/src/utils/test"
29-
"net/http"
30-
"testing"
3130

3231
"github.com/specterops/bloodhound/cypher/frontend"
3332
"github.com/specterops/bloodhound/graphschema/common"
@@ -144,7 +143,7 @@ func Test_CypherSearch_WithCypherMutationsEnabled(t *testing.T) {
144143
assert.True(ok)
145144

146145
var (
147-
query = "match (w) where w.name = 'vldmrt' remove w.name return w"
146+
query = "match (w: Wizard) where w.name = 'vldmrt' remove w.name return w"
148147
strippedQuery = &bytes.Buffer{}
149148
)
150149
parsedQuery, err := frontend.ParseCypher(parseCtx, query)
@@ -169,7 +168,7 @@ func Test_CypherSearch_WithCypherMutationsEnabled(t *testing.T) {
169168
assert.True(ok)
170169

171170
var (
172-
query = "match (w) where w.name = 'harryp' delete w"
171+
query = "match (w: Wizard) where w.name = 'harryp' delete w"
173172
strippedQuery = &bytes.Buffer{}
174173
)
175174
parsedQuery, err := frontend.ParseCypher(parseCtx, query)
@@ -192,12 +191,12 @@ func Test_CypherSearch_WithCypherMutationsEnabled(t *testing.T) {
192191
assert.True(found)
193192
}),
194193

195-
lab.TestCase("adds failed mutation attempts to audit log", func(assert *require.Assertions, harness *lab.Harness) {
194+
lab.TestCase("adds mutation attempts to audit log", func(assert *require.Assertions, harness *lab.Harness) {
196195
apiClient, ok := lab.Unpack(harness, adminApiClientFixture)
197196
assert.True(ok)
198197

199198
var (
200-
query = "match (w) set w.wizard = true return w.wizard"
199+
query = "match (w: Wizard) set w.wizard = true return w.wizard"
201200
strippedQuery = &bytes.Buffer{}
202201
)
203202

@@ -207,12 +206,12 @@ func Test_CypherSearch_WithCypherMutationsEnabled(t *testing.T) {
207206
require.Nil(t, err)
208207

209208
_, err = apiClient.CypherQuery(v2.CypherQueryPayload{Query: query})
210-
assert.ErrorContains(err, fmt.Sprintf("%d", http.StatusInternalServerError))
209+
require.Nil(t, err)
211210

212211
auditLogs, err := apiClient.GetLatestAuditLogs()
213212
assert.Nil(err)
214213

215-
err = test.AssertAuditLogs(auditLogs.Logs, model.AuditLogActionMutateGraph, model.AuditLogStatusFailure, model.AuditData{"query": strippedQuery.String()})
214+
err = test.AssertAuditLogs(auditLogs.Logs, model.AuditLogActionMutateGraph, model.AuditLogStatusSuccess, model.AuditData{"query": strippedQuery.String()})
216215
assert.Nil(err)
217216
}),
218217
)

cmd/api/src/api/v2/file_uploads.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func (s Resources) ProcessFileUpload(response http.ResponseWriter, request *http
131131
}
132132

133133
if !IsValidContentTypeForUpload(request.Header) {
134-
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "Content type must be application/json or application/zip", request), response)
134+
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, fmt.Sprintf("Content type must be application/json or application/zip"), request), response)
135135
} else if fileUploadJobID, err := strconv.Atoi(fileUploadJobIdString); err != nil {
136136
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, api.ErrorResponseDetailsIDMalformed, request), response)
137137
} else if fileUploadJob, err := fileupload.GetFileUploadJobByID(request.Context(), s.DB, int64(fileUploadJobID)); err != nil {

0 commit comments

Comments
 (0)