Skip to content

Commit 1b41c48

Browse files
authored
Merge pull request #27 from cloudgraphdev/feature/CG-1071
feat(s3): Add connections to iamRole, lambda, sns and sqs services
2 parents 4029c61 + 01d2295 commit 1b41c48

File tree

13 files changed

+346
-23
lines changed

13 files changed

+346
-23
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,14 +124,14 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi
124124
| iamServerCertificate | |
125125
| iamUser | iamGroup |
126126
| iamPolicy | iamRole, iamGroup |
127-
| iamRole | appSync, cloudformationStackSet, codebuild, configurationRecorder, ec2, iamInstanceProfile, iamPolicy, eksCluster, ecsService, flowLog, glueJob, managedAirflow, sageMakerNotebookInstance, systemsManagerInstance guardDutyDetector, lambda, kinesisFirehose, rdsCluster |
127+
| iamRole | appSync, cloudformationStackSet, codebuild, configurationRecorder, ec2, iamInstanceProfile, iamPolicy, eksCluster, ecsService, flowLog, glueJob, managedAirflow, s3, sageMakerNotebookInstance, systemsManagerInstance guardDutyDetector, lambda, kinesisFirehose, rdsCluster |
128128
| iamGroup | iamUser, iamPolicy |
129129
| igw | vpc |
130130
| iot | |
131131
| kinesisFirehose | kinesisStream, s3, iamRole |
132132
| kinesisStream | kinesisFirehose |
133133
| kms | cloudtrail, cloudwatchLog, codebuild, ecsCluster, efs, eksCluster, elastiCacheReplicationGroup, elasticSearchDomain, emrCluster, lambda, rdsClusterSnapshot, sns, sageMakerNotebookInstance, secretsManager, dmsReplicationInstance, redshiftCluster, rdsCluster |
134-
| lambda | appSync, cognitoUserPool, kms, secretsManager, securityGroup, subnet, vpc, iamRole |
134+
| lambda | appSync, cognitoUserPool, kms, s3, secretsManager, securityGroup, subnet, vpc, iamRole |
135135
| managedAirflow | iamRole, securityGroups, subnet, s3 |
136136
| nacl | vpc |
137137
| natGateway | networkInterface, subnet, vpc |
@@ -147,12 +147,12 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi
147147
| sageMakerExperiment | |
148148
| sageMakerNotebookInstance | iamRole, kms, networkInterface, subnet, securityGroup |
149149
| sageMakerProject | |
150-
| s3 | cloudfront, cloudtrail, ecsCluster, kinesisFirehose, managedAirflow |
150+
| s3 | cloudfront, cloudtrail, ecsCluster, iamRole, kinesisFirehose, lambda, managedAirflow, sns, sqs |
151151
| secretsManager | kms, lambda |
152152
| securityGroup | alb, asg, clientVpnEndpoint, codebuild, dmsReplicationInstance, ecsService, lambda, ec2, elasticSearchDomain, elb, rdsCluster, rdsDbInstance, eksCluster, elastiCacheCluster, managedAirflow, sageMakerNotebookInstance |
153153
| ses | |
154-
| sns | kms, cloudtrail, cloudwatch |
155-
| sqs | |
154+
| sns | kms, cloudtrail, cloudwatch, s3 |
155+
| sqs | s3 |
156156
| subnet | alb, asg, codebuild, dmsReplicationInstance, ec2, ecsService, efsMountTarget, elastiCacheCluster, elasticSearchDomain, elb, lambda, managedAirflow, natGateway, networkInterface, sageMakerNotebookInstance, routeTable, vpc, eksCluster, emrCluster, flowLog |
157157
| systemsManagerInstance | ec2, iamRole |
158158
| systemsManagerDocument | |

src/services/cloudfront/schema.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type awsCloudfront implements awsBaseService @key(fields: "id") {
1818
origins: [awsCloudfrontOriginData]
1919
logging: awsCloudfrontLoggingConfig
2020
elb: [awsElb] @hasInverse(field: cloudfrontDistribution)
21-
s3: [awsS3] @hasInverse(field: cloudfrontDistribution)
21+
s3: [awsS3] @hasInverse(field: cloudfrontDistributions)
2222
tags: [awsRawTag]
2323
webAcl: [awsWafV2WebAcl] @hasInverse(field: cloudfront)
2424
}

src/services/cloudtrail/schema.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type awsCloudtrail implements awsOptionalService @key(fields: "id") {
1515
status: awsCloudtrailStatus
1616
eventSelectors: [awsCloudtrailEventSelector]
1717
tags: [awsRawTag]
18-
s3: [awsS3] @hasInverse(field: cloudtrail)
18+
s3: [awsS3] @hasInverse(field: cloudtrails)
1919
sns: [awsSns] @hasInverse(field: cloudtrail)
2020
kms: [awsKms] @hasInverse(field: cloudtrail)
2121
cloudwatchLog: [awsCloudwatchLog] @hasInverse(field: cloudtrail)

src/services/iamRole/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type awsIamRole implements awsBaseService @key(fields: "id") {
2222
systemsManagerInstances: [awsSystemsManagerInstance]
2323
@hasInverse(field: iamRole)
2424
iamInstanceProfiles: [awsIamInstanceProfile] @hasInverse(field: iamRole)
25+
s3: [awsS3] @hasInverse(field: iamRole)
2526
dynamodb: [awsDynamoDbTable] @hasInverse(field: iamRoles)
2627
ec2Instances: [awsEc2] @hasInverse(field: iamRole)
2728
cognitoUserPools: [awsCognitoUserPool] @hasInverse(field: iamRole)

src/services/lambda/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type awsLambda implements awsBaseService @key(fields: "arn") {
2121
vpc: [awsVpc] @hasInverse(field: lambda)
2222
cognitoUserPools: [awsCognitoUserPool] @hasInverse(field: lambdas)
2323
appSync: [awsAppSync] @hasInverse(field: lambda)
24+
s3: [awsS3] @hasInverse(field: lambdas)
2425
secretsManager: [awsSecretsManager] @hasInverse(field: lambda)
2526
iamRole: [awsIamRole] @hasInverse(field: lambda)
2627
}

src/services/s3/connections.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { ServiceConnection } from '@cloudgraph/sdk'
2+
import isEmpty from 'lodash/isEmpty'
3+
import services from '../../enums/services'
4+
import { RawAwsS3 } from './data'
5+
import { RawAwsIamRole } from '../iamRole/data'
6+
import { RawAwsLambdaFunction } from '../lambda/data'
7+
import { RawAwsSns } from '../sns/data'
8+
import { AwsSqs } from '../sqs/data'
9+
import { globalRegionName } from '../../enums/regions'
10+
11+
/**
12+
* S3
13+
*/
14+
15+
export default ({
16+
service,
17+
data,
18+
region,
19+
}: {
20+
data: { name: string; data: { [property: string]: any[] } }[]
21+
service: RawAwsS3
22+
region: string
23+
}): { [key: string]: ServiceConnection[] } => {
24+
const connections: ServiceConnection[] = []
25+
26+
const {
27+
Id: id,
28+
AdditionalInfo: {
29+
ReplicationConfig: replicationConfig,
30+
NotificationConfiguration: {
31+
LambdaFunctionConfigurations: lambdaFunctionConfigurations,
32+
TopicConfigurations: topicConfigurations,
33+
QueueConfigurations: queueConfigurations,
34+
},
35+
},
36+
} = service
37+
38+
/**
39+
* Find IAM Roles
40+
* related to this S3
41+
*/
42+
const roles: { name: string; data: { [property: string]: any[] } } =
43+
data.find(({ name }) => name === services.iamRole)
44+
if (replicationConfig?.Role && roles?.data?.[globalRegionName]) {
45+
const dataAtRegion: RawAwsIamRole[] = roles.data[globalRegionName].filter(
46+
role => role.Arn === replicationConfig.Role
47+
)
48+
if (!isEmpty(dataAtRegion)) {
49+
for (const instance of dataAtRegion) {
50+
const { Arn: arn }: RawAwsIamRole = instance
51+
52+
connections.push({
53+
id: arn,
54+
resourceType: services.iamRole,
55+
relation: 'child',
56+
field: 'iamRole',
57+
})
58+
}
59+
}
60+
}
61+
62+
/**
63+
* Find lambda functions
64+
* related to this S3
65+
*/
66+
const lambdaFunctions: {
67+
name: string
68+
data: { [property: string]: any[] }
69+
} = data.find(({ name }) => name === services.lambda)
70+
71+
const functionArns = lambdaFunctionConfigurations?.map(
72+
lambdaConfig => lambdaConfig?.LambdaFunctionArn
73+
)
74+
75+
if (!isEmpty(functionArns) && lambdaFunctions?.data?.[region]) {
76+
const dataAtRegion: RawAwsLambdaFunction[] = lambdaFunctions.data[
77+
region
78+
].filter(({ FunctionArn }: RawAwsLambdaFunction) =>
79+
functionArns.includes(FunctionArn)
80+
)
81+
82+
if (!isEmpty(dataAtRegion)) {
83+
for (const lambdaFunction of dataAtRegion) {
84+
const { FunctionArn: functionArn }: RawAwsLambdaFunction =
85+
lambdaFunction
86+
connections.push({
87+
id: functionArn,
88+
resourceType: services.lambda,
89+
relation: 'child',
90+
field: 'lambdas',
91+
})
92+
}
93+
}
94+
}
95+
96+
/**
97+
* Find SNS topic
98+
* related to this S3
99+
*/
100+
const snsTopics = data.find(({ name }) => name === services.sns)
101+
const topicArns = topicConfigurations?.map(topic => topic?.TopicArn)
102+
if (!isEmpty(topicArns) && snsTopics?.data?.[region]) {
103+
const snsTopicsInRegion: RawAwsSns[] = snsTopics.data[region].filter(
104+
({ TopicArn: topicArn }: RawAwsSns) => topicArns.includes(topicArn)
105+
)
106+
107+
if (!isEmpty(snsTopicsInRegion)) {
108+
for (const topic of snsTopicsInRegion) {
109+
const { TopicArn: topicArn }: RawAwsSns = topic
110+
connections.push({
111+
id: topicArn,
112+
resourceType: services.sns,
113+
relation: 'child',
114+
field: 'sns',
115+
})
116+
}
117+
}
118+
}
119+
120+
/**
121+
* Find SQS
122+
* related to this S3
123+
*/
124+
const sqsQueues = data.find(({ name }) => name === services.sqs)
125+
const sqsArns = queueConfigurations?.map(queue => queue?.QueueArn)
126+
if (!isEmpty(sqsArns) && sqsQueues?.data?.[region]) {
127+
const dataAtRegion: AwsSqs[] = sqsQueues.data[region].filter(
128+
({ sqsAttributes: { QueueArn: queueArn } }: AwsSqs) =>
129+
sqsArns.includes(queueArn)
130+
)
131+
if (!isEmpty(dataAtRegion)) {
132+
for (const instance of dataAtRegion) {
133+
const {
134+
sqsAttributes: { QueueArn: queueArn },
135+
}: AwsSqs = instance
136+
137+
connections.push({
138+
id: queueArn,
139+
resourceType: services.sqs,
140+
relation: 'child',
141+
field: 'sqs',
142+
})
143+
}
144+
}
145+
}
146+
147+
const s3Result = {
148+
[id]: connections,
149+
}
150+
return s3Result
151+
}

src/services/s3/data.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import S3, {
3030
ListBucketsOutput,
3131
ListObjectsV2Output,
3232
LoggingEnabled,
33+
NotificationConfiguration,
3334
Object as S3Object,
3435
Owner,
3536
Payer,
@@ -301,6 +302,29 @@ const getBucketWebsite = async (s3: S3, name: BucketName) =>
301302
)
302303
})
303304

305+
const getBucketNotificationConfiguration = async (s3: S3, name: BucketName) =>
306+
new Promise<NotificationConfiguration | any>(resolve => {
307+
s3.getBucketNotificationConfiguration(
308+
{
309+
Bucket: name,
310+
},
311+
(err: AWSError, data: NotificationConfiguration) => {
312+
if (err) {
313+
errorLog.generateAwsErrorLog({
314+
functionName: 's3:getBucketNotificationConfiguration',
315+
err,
316+
})
317+
}
318+
319+
if (!isEmpty(data)) {
320+
resolve(data)
321+
}
322+
323+
resolve({})
324+
}
325+
)
326+
})
327+
304328
const getBucketAdditionalInfo = async (s3: S3, name: BucketName) =>
305329
new Promise<any>(async resolve => {
306330
const promises = [
@@ -318,6 +342,7 @@ const getBucketAdditionalInfo = async (s3: S3, name: BucketName) =>
318342
getBucketTagging(s3, name),
319343
getBucketVersioning(s3, name),
320344
getBucketWebsite(s3, name),
345+
getBucketNotificationConfiguration(s3, name),
321346
]
322347

323348
const [
@@ -335,6 +360,7 @@ const getBucketAdditionalInfo = async (s3: S3, name: BucketName) =>
335360
Tags,
336361
VersioningInfo,
337362
WebsiteInfo,
363+
NotificationConfig,
338364
] = (await Promise.allSettled(promises)).map(
339365
/** We force the PromiseFulfilledResult interface
340366
* because all promises that we input to Promise.allSettled
@@ -364,6 +390,7 @@ const getBucketAdditionalInfo = async (s3: S3, name: BucketName) =>
364390
Tags: convertAwsTagsToTagMap(Tags),
365391
VersioningInfo,
366392
StaticWebsiteInfo: WebsiteInfo,
393+
NotificationConfiguration: NotificationConfig,
367394
})
368395
})
369396

@@ -455,6 +482,7 @@ export interface RawAwsS3 {
455482
ReqPaymentConfig: Payer
456483
StaticWebsiteInfo?: GetBucketWebsiteOutput
457484
VersioningInfo?: GetBucketVersioningOutput
485+
NotificationConfiguration?: NotificationConfiguration
458486
}
459487
Tags: TagMap
460488
Contents?: S3Object[]

0 commit comments

Comments
 (0)