Skip to content

Commit 4ff4422

Browse files
authored
Merge pull request #39 from cloudgraphdev/fix/CG-990
fix(waf): Added missing connections for webAcl
2 parents 6f99c44 + c45e2fc commit 4ff4422

11 files changed

Lines changed: 180 additions & 70 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi
6969

7070
| Service | Relations |
7171
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
72-
| alb | ec2, elasticBeanstalkEnv, route53Record, securityGroup, subnet, vpc |
72+
| alb | ec2, elasticBeanstalkEnv, route53Record, securityGroup, subnet, vpc, wafV2WebAcl |
7373
| apiGatewayRestApi | apiGatewayResource, apiGatewayStage, route53Record |
74-
| apiGatewayStage | apiGatewayRestApi |
74+
| apiGatewayStage | apiGatewayRestApi, wafV2WebAcl |
7575
| apiGatewayResource | apiGatewayRestApi |
7676
| appSync | cognitoUserPool, dynamodb, iamRole, lambda, rdsCluster, wafV2WebAcl |
7777
| asg | ebs, ec2, elasticBeanstalkEnv, iamRole, securityGroup, subnet |
@@ -161,4 +161,4 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi
161161
| vpc | alb, codebuild, dmsReplicationInstance, ec2, eip, elb, ecsService, efsMountTarget, eksCluster igw, elastiCacheCluster, elasticSearchDomain, lambda, nacl, natGateway, networkInterface, rdsClusterSnapshot, rdsDbInstance, redshiftCluster, route53HostedZone, routeTable, subnet, flowLog, vpnGateway, transitGatewayAttachment |
162162
| vpnConnection | customerGateway, transitGateway, transitGatewayAttachment, vpnGateway |
163163
| vpnGateway | vpc, vpnConnection |
164-
| wafV2WebAcl | appSync |
164+
| wafV2WebAcl | appSync, apiGatewayStage, alb |

src/services/alb/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type awsAlb implements awsBaseService @key(fields: "arn") {
2222
listeners: [awsAlbListener]
2323
subnet: [awsSubnet] @hasInverse(field: alb) #change to plural
2424
elasticBeanstalkEnvs: [awsElasticBeanstalkEnv] @hasInverse(field: albs)
25+
webAcl: [awsWafV2WebAcl] @hasInverse(field: albs)
2526
}
2627

2728
type awsAlbListener

src/services/apiGatewayStage/data.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface RawAwsApiGatewayStage extends Omit<Stage, 'Tags'> {
3939
restApiId: string
4040
Tags?: TagMap
4141
region: string
42+
arn: string
4243
}
4344

4445
const getStages = async ({ apiGw, restApiId }): Promise<ListOfStage> =>
@@ -133,11 +134,19 @@ export default async ({
133134
})
134135
const additionalPromise = new Promise<void>(async resolveAdditional => {
135136
const stages = await getStages({ apiGw, restApiId })
137+
const arn = apiGatewayRestApiArn({
138+
restApiArn: apiGatewayArn({ region }),
139+
id: restApiId,
140+
})
136141
apiGatewayStages.push(
137142
...stages.map(stage => ({
138143
...stage,
139144
restApiId,
140145
region,
146+
arn: apiGatewayStageArn({
147+
restApiArn: arn,
148+
name: stage.stageName,
149+
}),
141150
}))
142151
)
143152

@@ -152,24 +161,17 @@ export default async ({
152161

153162
// get all tags for each stage
154163
apiGatewayStages.map(stage => {
155-
const { stageName, restApiId, region } = stage
164+
const { arn, region } = stage
156165
const apiGw = new APIGW({
157166
...config,
158167
region,
159168
endpoint,
160169
...customRetrySettings,
161170
})
162171
const tagsPromise = new Promise<void>(async resolveTags => {
163-
const arn = apiGatewayRestApiArn({
164-
restApiArn: apiGatewayArn({ region }),
165-
id: restApiId,
166-
})
167172
stage.Tags = await getTags({
168173
apiGw,
169-
arn: apiGatewayStageArn({
170-
restApiArn: arn,
171-
name: stageName,
172-
}),
174+
arn,
173175
})
174176
resolveTags()
175177
})

src/services/apiGatewayStage/format.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ import cuid from 'cuid'
22

33
import { RawAwsApiGatewayStage } from './data'
44
import { AwsApiGatewayStage as AwsAGStageType } from '../../types/generated'
5-
import {
6-
apiGatewayArn,
7-
apiGatewayRestApiArn,
8-
apiGatewayStageArn,
9-
} from '../../utils/generateArns'
105
import { formatTagsFromMap } from '../../utils/format'
116

127
export default ({
@@ -17,6 +12,7 @@ export default ({
1712
account: string
1813
}): AwsAGStageType => {
1914
const {
15+
arn,
2016
stageName: name,
2117
description,
2218
cacheClusterEnabled,
@@ -26,19 +22,10 @@ export default ({
2622
clientCertificateId,
2723
tracingEnabled,
2824
variables: vars = {},
29-
restApiId,
3025
tags = {},
3126
region,
3227
} = service
3328

34-
const arn = apiGatewayStageArn({
35-
restApiArn: apiGatewayRestApiArn({
36-
restApiArn: apiGatewayArn({ region }),
37-
id: restApiId,
38-
}),
39-
name,
40-
})
41-
4229
const variables = Object.entries(vars).map(([k, v]) => ({
4330
id: cuid(),
4431
key: k,

src/services/apiGatewayStage/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ type awsApiGatewayStage implements awsBaseService @key(fields: "arn") {
3232
variables: [awsApiGatewayStageVariable]
3333
tags: [awsRawTag]
3434
restApi: [awsApiGatewayRestApi] @hasInverse(field: stages)
35+
webAcl: [awsWafV2WebAcl] @hasInverse(field: apiGatewayStages)
3536
}

src/services/appSync/connections.ts

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { RawAwsCognitoUserPool } from '../cognitoUserPool/data'
1212
import { RawAwsRdsCluster } from '../rdsCluster/data'
1313
import { RawAwsIamRole } from '../iamRole/data'
1414
import { globalRegionName } from '../../enums/regions'
15-
import { RawAwsWafV2WebAcl } from '../wafV2WebAcl/data'
1615

1716
/**
1817
* AppSync
@@ -30,7 +29,7 @@ export default ({
3029
region: string
3130
}): { [key: string]: ServiceConnection[] } => {
3231
const connections: ServiceConnection[] = []
33-
const { apiId: id, awsDataSources, userPoolConfig, wafWebAclArn } = appSync
32+
const { apiId: id, awsDataSources, userPoolConfig } = appSync
3433

3534
/**
3635
* Find cognito user pools
@@ -162,9 +161,7 @@ export default ({
162161
const roles: { name: string; data: { [property: string]: any[] } } =
163162
data.find(({ name }) => name === services.iamRole)
164163

165-
const roleArns = awsDataSources?.map(
166-
({ serviceRoleArn }) => serviceRoleArn
167-
)
164+
const roleArns = awsDataSources?.map(({ serviceRoleArn }) => serviceRoleArn)
168165

169166
if (roles?.data?.[globalRegionName]) {
170167
const dataAtRegion: RawAwsIamRole[] = roles.data[globalRegionName].filter(
@@ -184,32 +181,6 @@ export default ({
184181
}
185182
}
186183

187-
/**
188-
* Find wafV2WebAcls
189-
*/
190-
const acls: {
191-
name: string
192-
data: { [property: string]: RawAwsWafV2WebAcl[] }
193-
} = data.find(({ name }) => name === services.wafV2WebAcl)
194-
195-
if (acls?.data) {
196-
const allAcls = Object.values(acls.data).flat()
197-
const dataInRegion: RawAwsWafV2WebAcl[] = allAcls.filter(
198-
({ ARN }: RawAwsWafV2WebAcl) => ARN === wafWebAclArn
199-
)
200-
201-
if (!isEmpty(dataInRegion)) {
202-
for (const acl of dataInRegion) {
203-
connections.push({
204-
id: acl.Id,
205-
resourceType: services.wafV2WebAcl,
206-
relation: 'child',
207-
field: 'webAcl',
208-
})
209-
}
210-
}
211-
}
212-
213184
const appSyncResult = {
214185
[id]: connections,
215186
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import isEmpty from 'lodash/isEmpty'
2+
3+
import { ServiceConnection } from '@cloudgraph/sdk'
4+
5+
import services from '../../enums/services'
6+
import { RawAwsAlb } from '../alb/data'
7+
import { RawAwsApiGatewayStage } from '../apiGatewayStage/data'
8+
import { RawAwsAppSync } from '../appSync/data'
9+
import { RawAwsWafV2WebAcl } from './data'
10+
11+
/**
12+
* WAF
13+
*/
14+
15+
export default ({
16+
service: waf,
17+
data,
18+
region,
19+
}: {
20+
account: string
21+
data: { name: string; data: { [property: string]: any[] } }[]
22+
service: RawAwsWafV2WebAcl
23+
region: string
24+
}): { [key: string]: ServiceConnection[] } => {
25+
const connections: ServiceConnection[] = []
26+
const { Id: id, wafResources } = waf
27+
28+
/**
29+
* Find ALBs
30+
* related to this WAF
31+
*/
32+
const albs: {
33+
name: string
34+
data: { [property: string]: any[] }
35+
} = data.find(({ name }) => name === services.alb)
36+
37+
if (albs?.data?.[region] && wafResources?.elasticloadbalancing?.length > 0) {
38+
const associatedALBs: RawAwsAlb[] = albs.data[region].filter(
39+
({ LoadBalancerArn }: RawAwsAlb) =>
40+
wafResources?.elasticloadbalancing?.find(arn => arn === LoadBalancerArn)
41+
)
42+
43+
if (!isEmpty(associatedALBs)) {
44+
for (const { LoadBalancerArn } of associatedALBs) {
45+
connections.push({
46+
id: LoadBalancerArn,
47+
resourceType: services.alb,
48+
relation: 'child',
49+
field: 'albs',
50+
})
51+
}
52+
}
53+
}
54+
/**
55+
* Find Rest API Stages
56+
* related to this WAF
57+
*/
58+
const stages: {
59+
name: string
60+
data: { [property: string]: any[] }
61+
} = data.find(({ name }) => name === services.apiGatewayStage)
62+
63+
if (stages?.data?.[region] && wafResources?.apigateway?.length > 0) {
64+
const associatedStages: RawAwsApiGatewayStage[] = stages.data[
65+
region
66+
].filter(({ arn: stageArn }: RawAwsApiGatewayStage) =>
67+
wafResources?.apigateway?.find(arn => arn === stageArn)
68+
)
69+
70+
if (!isEmpty(associatedStages)) {
71+
for (const { arn } of associatedStages) {
72+
connections.push({
73+
id: arn,
74+
resourceType: services.apiGatewayStage,
75+
relation: 'child',
76+
field: 'apiGatewayStages',
77+
})
78+
}
79+
}
80+
}
81+
82+
/**
83+
* Find Apps Sync
84+
* related to this WAF
85+
*/
86+
const apps: {
87+
name: string
88+
data: { [property: string]: any[] }
89+
} = data.find(({ name }) => name === services.appSync)
90+
91+
if (apps?.data?.[region] && wafResources?.appsync?.length > 0) {
92+
const associatedApps: RawAwsAppSync[] = apps.data[region].filter(
93+
({ arn: apiArn }: RawAwsAppSync) =>
94+
wafResources?.appsync?.find(arn => arn === apiArn)
95+
)
96+
97+
if (!isEmpty(associatedApps)) {
98+
for (const { apiId } of associatedApps) {
99+
connections.push({
100+
id: apiId,
101+
resourceType: services.appSync,
102+
relation: 'child',
103+
field: 'appSync',
104+
})
105+
}
106+
}
107+
}
108+
109+
return {
110+
[id]: connections,
111+
}
112+
}

src/services/wafV2WebAcl/data.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,34 @@ const scopes = {
1414
regional: 'REGIONAL',
1515
}
1616

17+
const resources = [
18+
{ name: 'elasticloadbalancing', type: 'APPLICATION_LOAD_BALANCER' },
19+
{ name: 'apigateway', type: 'API_GATEWAY' },
20+
{ name: 'appsync', type: 'APPSYNC' },
21+
]
22+
1723
export interface RawAwsWafV2WebAcl extends WAFV2.WebACL {
1824
region: string
1925
loggingConfiguration: WAFV2.LoggingConfiguration
26+
wafResources: { [resource: string]: string[] }
27+
}
28+
29+
const listResources = async (
30+
client: WAFV2,
31+
wafArn: string
32+
): Promise<{ [resource: string]: string[] }> => {
33+
const wafResources = {}
34+
for (const { name, type } of resources) {
35+
const { ResourceArns } = (await client
36+
.listResourcesForWebACL({
37+
WebACLArn: wafArn,
38+
ResourceType: type,
39+
})
40+
.promise()) ?? { ResourceArns: [] }
41+
42+
wafResources[name] = ResourceArns
43+
}
44+
return wafResources
2045
}
2146

2247
/**
@@ -72,12 +97,15 @@ export default async ({
7297
.promise()
7398
wafData.loggingConfiguration =
7499
loggingConfiguration.LoggingConfiguration
100+
101+
wafData.wafResources = await listResources(client, arn)
75102
} catch (err) {
76103
errorLog.generateAwsErrorLog({ functionName: 'getWebACL', err })
77104
}
78105
result.push({
79106
...wafData?.WebACL,
80107
loggingConfiguration: wafData?.loggingConfiguration,
108+
wafResources: wafData?.wafResources,
81109
region,
82110
})
83111
}

src/services/wafV2WebAcl/index.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
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 WafV2WebAcl extends BaseService implements Service {
8-
format = format.bind(this)
9-
10-
getData = getData.bind(this)
11-
12-
mutation = mutation
13-
}
14-
2+
import BaseService from '../base'
3+
import format from './format'
4+
import getData from './data'
5+
import getConnections from './connections'
6+
import mutation from './mutation'
7+
8+
export default class WafV2WebAcl extends BaseService implements Service {
9+
format = format.bind(this)
10+
11+
getData = getData.bind(this)
12+
13+
getConnections = getConnections.bind(this)
14+
15+
mutation = mutation
16+
}

src/services/wafV2WebAcl/schema.graphql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ type awsWafV2WebAcl implements awsBaseService @key(fields: "arn") {
1313
loggingConfiguration: awsWafV2LoggingConfig
1414
cloudfront: [awsCloudfront] @hasInverse(field: webAcl)
1515
appSync: [awsAppSync] @hasInverse(field: webAcl)
16+
apiGatewayStages: [awsApiGatewayStage] @hasInverse(field: webAcl)
17+
albs: [awsAlb] @hasInverse(field: webAcl)
1618
}
1719

1820
type awsWafV2Rule {

0 commit comments

Comments
 (0)