Skip to content

Commit d955f35

Browse files
committed
feat(services): add new service guardDutyDetector
1 parent f0d1b64 commit d955f35

13 files changed

Lines changed: 457 additions & 142 deletions

File tree

src/enums/schemasMap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export default {
4747
[services.flowLog]: 'awsFlowLog',
4848
[services.glueJob]: 'awsGlueJob',
4949
[services.glueRegistry]: 'awsGlueRegistry',
50+
[services.guardDutyDetector]: 'awsGuardDutyDetector',
5051
[services.emrCluster]: 'awsEmrCluster',
5152
[services.emrInstance]: 'awsEmrInstance',
5253
[services.emrStep]: 'awsEmrStep',

src/enums/serviceMap.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import SageMakerProject from '../services/sageMakerProject'
8686
import SageMakerExperiment from '../services/sageMakerExperiment'
8787
import ManagedAirflow from '../services/managedAirflow'
8888
import WafV2WebAcl from '../services/wafV2WebAcl'
89+
import GuardDutyDetector from '../services/guardDutyDetector'
8990

9091
/**
9192
* serviceMap is an object that contains all currently supported services for AWS
@@ -126,6 +127,7 @@ export default {
126127
[services.elb]: ELB,
127128
[services.flowLog]: FlowLog,
128129
[services.glueJob]: GlueJob,
130+
[services.guardDutyDetector]: GuardDutyDetector,
129131
[services.glueRegistry]: GlueRegistry,
130132
[services.emrCluster]: EmrCluster,
131133
[services.emrInstance]: EmrInstance,

src/enums/services.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export default {
4242
flowLog: 'flowLog',
4343
glueJob: 'glueJob',
4444
glueRegistry: 'glueRegistry',
45+
guardDutyDetector: 'guardDutyDetector',
4546
emrCluster: 'emrCluster',
4647
emrInstance: 'emrInstance',
4748
emrStep: 'emrStep',
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Config } from 'aws-sdk/lib/config'
2+
import GUARDDUTY from 'aws-sdk/clients/guardduty'
3+
import isEmpty from 'lodash/isEmpty'
4+
import groupBy from 'lodash/groupBy'
5+
import { convertToPromise, fetchAllPaginatedData } from '../../utils/fetchUtils'
6+
import { initTestEndpoint } from '../../utils'
7+
import ErrorLog from '../../utils/errorLog'
8+
9+
const serviceName = 'guardDuty'
10+
const errorLog = new ErrorLog(serviceName)
11+
const endpoint = initTestEndpoint(serviceName)
12+
13+
export interface RawAwsGuardDutyDetector extends GUARDDUTY.GetDetectorResponse {
14+
id: string
15+
region: string
16+
members: GUARDDUTY.Members
17+
}
18+
19+
/**
20+
* GuardDutyDetector
21+
*/
22+
23+
export default async ({
24+
regions,
25+
config,
26+
}: {
27+
regions: string
28+
config: Config
29+
}): Promise<{ [region: string]: RawAwsGuardDutyDetector[] }> => {
30+
const result: RawAwsGuardDutyDetector[] = []
31+
32+
const activeRegions = regions.split(',')
33+
34+
for (const region of activeRegions) {
35+
let guardDutyDetectorList: GUARDDUTY.DetectorId[]
36+
const client = new GUARDDUTY({ ...config, region, endpoint })
37+
try {
38+
guardDutyDetectorList = await fetchAllPaginatedData({
39+
getResourcesFn: convertToPromise({
40+
sdkContext: client,
41+
fnName: 'listDetectors',
42+
}),
43+
accessor: '',
44+
})
45+
} catch (err) {
46+
errorLog.generateAwsErrorLog({ functionName: 'listDetectors', err })
47+
}
48+
49+
if (!isEmpty(guardDutyDetectorList)) {
50+
for (const detector of guardDutyDetectorList) {
51+
let detectorData: GUARDDUTY.GetDetectorResponse
52+
let members: GUARDDUTY.Members
53+
try {
54+
detectorData = await client
55+
.getDetector({ DetectorId: detector })
56+
.promise()
57+
members = await fetchAllPaginatedData({
58+
getResourcesFn: convertToPromise({
59+
sdkContext: client,
60+
fnName: 'listMembers',
61+
}),
62+
accessor: '',
63+
initialParams: {
64+
DetectorId: detector,
65+
},
66+
})
67+
} catch (err) {
68+
errorLog.generateAwsErrorLog({ functionName: 'getDetector', err })
69+
}
70+
result.push({
71+
id: detector,
72+
...detectorData,
73+
members,
74+
region,
75+
})
76+
}
77+
}
78+
}
79+
80+
return groupBy(result, 'region')
81+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import cuid from 'cuid'
2+
import { formatTagsFromMap } from '../../utils/format'
3+
import { AwsGuardDutyDetector } from '../../types/generated'
4+
import { RawAwsGuardDutyDetector } from './data'
5+
6+
/**
7+
* GuardDutyDetector
8+
*/
9+
10+
export default ({
11+
account,
12+
service: rawData,
13+
region,
14+
}: {
15+
account: string
16+
service: RawAwsGuardDutyDetector
17+
region: string
18+
}): AwsGuardDutyDetector => {
19+
const {
20+
id,
21+
CreatedAt: createdAt,
22+
FindingPublishingFrequency: findingPublishingFrequency,
23+
ServiceRole: serviceRole,
24+
Status: status,
25+
UpdatedAt: updatedAt,
26+
DataSources: dataSources,
27+
members = [],
28+
Tags
29+
} = rawData
30+
31+
const formattedDataSources = {
32+
cloudTrail: {
33+
status: dataSources?.CloudTrail?.Status
34+
},
35+
dnsLogs: {
36+
status: dataSources?.DNSLogs?.Status
37+
},
38+
flowLogs: {
39+
status: dataSources?.FlowLogs?.Status
40+
},
41+
s3Logs: {
42+
status: dataSources?.S3Logs?.Status
43+
},
44+
// kubernetes: { TODO: k8s logs support, maybe need to update aws sdk?
45+
// auditLogs: {
46+
// status: dataSources?.
47+
// }
48+
// }
49+
}
50+
51+
const mappedMembers = members?.map(member => ({
52+
id: cuid(),
53+
accountId: member?.AccountId,
54+
detectorId: member?.DetectorId,
55+
masterId: member?.MasterId,
56+
email: member?.Email,
57+
relationshipStatus: member?.RelationshipStatus,
58+
invitedAt: new Date(member?.InvitedAt)?.toISOString(),
59+
updatedAt: new Date(member?.UpdatedAt)?.toISOString()
60+
}))
61+
62+
return {
63+
id,
64+
region,
65+
accountId: account,
66+
createdAt: new Date(createdAt)?.toISOString(),
67+
updatedAt: new Date(updatedAt)?.toISOString(),
68+
findingPublishingFrequency,
69+
serviceRole,
70+
status,
71+
members: mappedMembers,
72+
dataSources: formattedDataSources,
73+
tags: formatTagsFromMap(Tags ?? {})
74+
}
75+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Service } from '@cloudgraph/sdk'
2+
import BaseService from '../base'
3+
import format from './format'
4+
import getData from './data'
5+
import mutation from './mutation'
6+
7+
export default class GuardDutyDetector extends BaseService implements Service {
8+
format = format.bind(this)
9+
10+
getData = getData.bind(this)
11+
12+
mutation = mutation
13+
}
14+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default `mutation($input: [AddawsGuardDutyDetectorInput!]!) {
2+
addawsGuardDutyDetector(input: $input, upsert: true) {
3+
numUids
4+
}
5+
}`;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
type awsGuardDutyDetector @key(fields: "id") {
2+
id: String! @id @search(by: [hash])
3+
accountId: String! @search(by: [hash])
4+
region: String @search(by: [hash, regexp])
5+
createdAt: DateTime
6+
updatedAt: DateTime
7+
findingPublishingFrequency: String @search(by: [hash, regexp])
8+
serviceRole: String @search(by: [hash, regexp])
9+
status: String @search(by: [hash, regexp])
10+
members: [awsGuardDutyMember]
11+
dataSources: awsGuardDutyDataSources
12+
tags: [awsRawTag]
13+
iamRole: awsIamRole @hasInverse(field: guardDutyDetectors)
14+
}
15+
16+
type awsGuardDutyMember {
17+
id: String! @id @search(by: [hash])
18+
accountId: String! @search(by: [hash])
19+
detectorId: String @search(by: [hash, regexp])
20+
masterId: String @search(by: [hash, regexp])
21+
email: String @search(by: [hash, regexp])
22+
relationshipStatus: String @search(by: [hash, regexp])
23+
invitedAt: DateTime
24+
updatedAt: DateTime
25+
}
26+
27+
type awsGuardDutyDataSources {
28+
cloudTrail: awsGuardDutyDataSource
29+
dnsLogs: awsGuardDutyDataSource
30+
flowLogs: awsGuardDutyDataSource
31+
s3Logs: awsGuardDutyDataSource
32+
}
33+
34+
type awsGuardDutyDataSource {
35+
status: String @search(by: [hash, regexp])
36+
}

src/services/iamRole/connections.ts

Lines changed: 76 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { RawAwsCodeBuild } from '../codeBuild/data'
1212
import { RawAwsGlueJob } from '../glueJob/data'
1313
import { glueJobArn } from '../../utils/generateArns'
1414
import { RawAwsManagedAirflow } from '../managedAirflow/data'
15+
import { RawAwsGuardDutyDetector } from '../guardDutyDetector/data'
1516

1617
/**
1718
* IAM Role
@@ -106,60 +107,86 @@ export default ({
106107
/**
107108
* Find any CodeBuild related data
108109
*/
109-
const codebuild = data.find(({ name }) => name === services.codebuild)
110-
if (codebuild?.data?.[region]) {
111-
const dataAtRegion: RawAwsCodeBuild[] = codebuild.data[region].filter(
112-
({ serviceRole, resourceAccessRole }: RawAwsCodeBuild) =>
113-
serviceRole === role.Arn || resourceAccessRole === role.Arn
114-
)
115-
for (const cb of dataAtRegion) {
116-
connections.push({
117-
id: cb.arn,
118-
resourceType: services.codebuild,
119-
relation: 'child',
120-
field: 'codebuilds',
121-
})
122-
}
123-
}
124-
125-
/**
110+
const codebuild = data.find(({ name }) => name === services.codebuild)
111+
if (codebuild?.data?.[region]) {
112+
const dataAtRegion: RawAwsCodeBuild[] = codebuild.data[region].filter(
113+
({ serviceRole, resourceAccessRole }: RawAwsCodeBuild) =>
114+
serviceRole === role.Arn || resourceAccessRole === role.Arn
115+
)
116+
for (const cb of dataAtRegion) {
117+
connections.push({
118+
id: cb.arn,
119+
resourceType: services.codebuild,
120+
relation: 'child',
121+
field: 'codebuilds',
122+
})
123+
}
124+
}
125+
126+
/**
126127
* Find any glueJob related data
127128
*/
128-
const jobs = data.find(({ name }) => name === services.glueJob)
129-
if (jobs?.data?.[region]) {
130-
const dataAtRegion: RawAwsGlueJob[] = jobs.data[region].filter(
131-
({ Role }: RawAwsGlueJob) =>
132-
Role === role.Arn
133-
)
134-
for (const job of dataAtRegion) {
135-
const arn = glueJobArn({ region, account, name: job.Name })
136-
connections.push({
137-
id: arn,
138-
resourceType: services.glueJob,
139-
relation: 'child',
140-
field: 'glueJobs',
141-
})
142-
}
129+
const jobs = data.find(({ name }) => name === services.glueJob)
130+
if (jobs?.data?.[region]) {
131+
const dataAtRegion: RawAwsGlueJob[] = jobs.data[region].filter(
132+
({ Role }: RawAwsGlueJob) => Role === role.Arn
133+
)
134+
for (const job of dataAtRegion) {
135+
const arn = glueJobArn({ region, account, name: job.Name })
136+
connections.push({
137+
id: arn,
138+
resourceType: services.glueJob,
139+
relation: 'child',
140+
field: 'glueJobs',
141+
})
143142
}
144-
145-
/**
143+
}
144+
145+
/**
146146
* Find any managedAirflow related data
147147
*/
148-
const managedAirflow = data.find(({ name }) => name === services.managedAirflow)
149-
if (managedAirflow?.data?.[region]) {
150-
const dataAtRegion: RawAwsManagedAirflow[] = managedAirflow.data[region].filter(
151-
({ ServiceRoleArn, ExecutionRoleArn }: RawAwsManagedAirflow) =>
152-
ServiceRoleArn === role.Arn || ExecutionRoleArn === role.Arn
153-
)
154-
for (const airflow of dataAtRegion) {
155-
connections.push({
156-
id: airflow.Arn,
157-
resourceType: services.managedAirflow,
158-
relation: 'child',
159-
field: 'managedAirflows',
160-
})
161-
}
162-
}
148+
const managedAirflow = data.find(
149+
({ name }) => name === services.managedAirflow
150+
)
151+
if (managedAirflow?.data?.[region]) {
152+
const dataAtRegion: RawAwsManagedAirflow[] = managedAirflow.data[
153+
region
154+
].filter(
155+
({ ServiceRoleArn, ExecutionRoleArn }: RawAwsManagedAirflow) =>
156+
ServiceRoleArn === role.Arn || ExecutionRoleArn === role.Arn
157+
)
158+
for (const airflow of dataAtRegion) {
159+
connections.push({
160+
id: airflow.Arn,
161+
resourceType: services.managedAirflow,
162+
relation: 'child',
163+
field: 'managedAirflows',
164+
})
165+
}
166+
}
167+
168+
/**
169+
* Find any guardDutyDetector related data
170+
*/
171+
const detectors = data.find(
172+
({ name }) => name === services.guardDutyDetector
173+
)
174+
if (detectors?.data?.[region]) {
175+
const dataAtRegion: RawAwsGuardDutyDetector[] = detectors.data[
176+
region
177+
].filter(
178+
({ ServiceRole }: RawAwsGuardDutyDetector) =>
179+
ServiceRole === role.Arn
180+
)
181+
for (const detector of dataAtRegion) {
182+
connections.push({
183+
id: detector.id,
184+
resourceType: services.guardDutyDetector,
185+
relation: 'child',
186+
field: 'guardDutyDetectors',
187+
})
188+
}
189+
}
163190

164191
return {
165192
[id]: connections,

src/services/iamRole/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ type awsIamRole @key(fields: "id") {
1919
codebuilds: [awsCodebuild] @hasInverse(field: iamRoles)
2020
glueJobs: [awsGlueJob] @hasInverse(field: iamRole)
2121
managedAirflows: [awsManagedAirflow] @hasInverse(field: iamRoles)
22+
guardDutyDetectors: [awsGuardDutyDetector] @hasInverse(field: iamRole)
2223
}

0 commit comments

Comments
 (0)