Skip to content

Commit 5bb15c0

Browse files
authored
Merge pull request #125 from cloudgraphdev/feature/CG-1340-support-aws-acm-service
feat(CG-1340): support aws ACM service
2 parents 6770d72 + 76c4d55 commit 5bb15c0

File tree

13 files changed

+304
-0
lines changed

13 files changed

+304
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi
7070

7171
| Service | Relations |
7272
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
73+
| acm | |
7374
| alb | ec2, elasticBeanstalkEnv, route53Record, securityGroup, subnet, vpc, wafV2WebAcl |
7475
| apiGatewayDomainName | apiGatewayHttpApi, apiGatewayRestApi |
7576
| apiGatewayHttpApi | apiGatewayDomainName |

src/enums/resources.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export default {
2+
acm: 'aws_acm',
23
alb: 'aws_lb',
34
elb: 'aws_elb',
45
vpc: 'aws_vpc',

src/enums/schemasMap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import services from './services'
55
*/
66
export default {
77
account: 'awsAccount',
8+
[services.acm]: 'awsAcm',
89
[services.alb]: 'awsAlb',
910
[services.apiGatewayDomainName]: 'awsApiGatewayDomainName',
1011
[services.apiGatewayHttpApi]: 'awsApiGatewayHttpApi',

src/enums/serviceAliases.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import services from './services'
22

33
export default {
4+
[services.acm]: 'acms',
45
[services.alb]: 'albs',
56
[services.apiGatewayDomainName]: 'apiGatewayDomainNames',
67
[services.apiGatewayHttpApi]: 'apiGatewayHttpApis',

src/enums/serviceMap.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Account from '../services/account'
2+
import ACM from '../services/acm'
23
import ALB from '../services/alb'
34
import APIGatewayResource from '../services/apiGatewayResource'
45
import APIGatewayRestApi from '../services/apiGatewayRestApi'
@@ -113,6 +114,7 @@ import VpcPeeringConnection from '../services/vpcPeeringConnection'
113114
export default {
114115
account: Account,
115116
[services.appSync]: AppSync,
117+
[services.acm]: ACM,
116118
[services.alb]: ALB,
117119
[services.apiGatewayDomainName]: APIGatewayDomainName,
118120
[services.apiGatewayHttpApi]: APIGatewayHttpApi,

src/enums/services.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export default {
2+
acm: 'acm',
23
alb: 'alb',
34
apiGatewayDomainName: 'apiGatewayDomainName',
45
apiGatewayHttpApi: 'apiGatewayHttpApi',

src/properties/logger.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ export default {
1010
regionNotFound: (name: string): string =>
1111
`❌ The region ${name} was not found in the list of supported AWS regions ❌`,
1212
globalAwsRegion: 'Found Global AWS region, adding global resources',
13+
/**
14+
* ACM
15+
*/
16+
fetchedAcmCertificates: (num: number): string =>
17+
`Fetched ${num} ACM certificates`,
1318
/**
1419
* IAM
1520
*/

src/services/acm/data.ts

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import CloudGraph from '@cloudgraph/sdk'
2+
import ACM, { CertificateSummary, ListCertificatesRequest, ListCertificatesResponse, ListTagsForCertificateRequest, ListTagsForCertificateResponse, Tag } from 'aws-sdk/clients/acm'
3+
import { AWSError } from 'aws-sdk/lib/error'
4+
import { Config } from 'aws-sdk/lib/config'
5+
import isEmpty from 'lodash/isEmpty'
6+
import groupBy from 'lodash/groupBy'
7+
import awsLoggerText from '../../properties/logger'
8+
import { initTestEndpoint, setAwsRetryOptions } from '../../utils'
9+
import AwsErrorLog from '../../utils/errorLog'
10+
import { API_GATEWAY_CUSTOM_DELAY } from '../../config/constants'
11+
import { TagMap } from '../../types'
12+
13+
const lt = { ...awsLoggerText }
14+
const { logger } = CloudGraph
15+
const MAX_CERTIFICATES = 500
16+
const serviceName = 'ACM'
17+
const errorLog = new AwsErrorLog(serviceName)
18+
const endpoint = initTestEndpoint(serviceName)
19+
const customRetrySettings = setAwsRetryOptions({
20+
baseDelay: API_GATEWAY_CUSTOM_DELAY,
21+
})
22+
23+
export const getCertificatesForRegion = async (
24+
acm: ACM
25+
): Promise<CertificateSummary[]> =>
26+
new Promise(async resolve => {
27+
const certificateSummaryList: CertificateSummary[] = []
28+
const listCertificatesOpts: ListCertificatesRequest = {}
29+
const listAllCertificates = (token?: string): void => {
30+
listCertificatesOpts.MaxItems = MAX_CERTIFICATES
31+
if (token) {
32+
listCertificatesOpts.NextToken = token
33+
}
34+
try {
35+
acm.listCertificates(
36+
listCertificatesOpts,
37+
(err: AWSError, data: ListCertificatesResponse) => {
38+
if (err) {
39+
errorLog.generateAwsErrorLog({
40+
functionName: 'acm:listCertificates',
41+
err,
42+
})
43+
}
44+
45+
if (isEmpty(data)) {
46+
return resolve([])
47+
}
48+
49+
const { NextToken: nextToken, CertificateSummaryList: items = [] } = data || {}
50+
51+
if (isEmpty(items)) {
52+
return resolve([])
53+
}
54+
55+
logger.debug(lt.fetchedAcmCertificates(items.length))
56+
57+
certificateSummaryList.push(...items)
58+
59+
if (nextToken) {
60+
listAllCertificates(nextToken)
61+
} else {
62+
resolve(certificateSummaryList)
63+
}
64+
}
65+
)
66+
} catch (error) {
67+
resolve([])
68+
}
69+
}
70+
listAllCertificates()
71+
})
72+
73+
const getTagsForCertificate = (
74+
acm: ACM,
75+
certificateArn: string
76+
): Promise<{ certificateArn: string; tags: Tag[] }> =>
77+
new Promise<{ certificateArn: string; tags: Tag[] }>(resolve => {
78+
const args: ListTagsForCertificateRequest = { CertificateArn: certificateArn }
79+
const listTags = (): void => {
80+
try {
81+
acm.listTagsForCertificate(
82+
args,
83+
(err: AWSError, data: ListTagsForCertificateResponse) => {
84+
if (err) {
85+
errorLog.generateAwsErrorLog({
86+
functionName: 'acm:listTagsForCertificate',
87+
err,
88+
})
89+
}
90+
if (isEmpty(data)) {
91+
return resolve({
92+
certificateArn,
93+
tags: [],
94+
})
95+
}
96+
const { Tags: tags = [] } = data || {}
97+
98+
resolve({ certificateArn, tags })
99+
}
100+
);
101+
102+
} catch (error) {
103+
resolve({
104+
certificateArn,
105+
tags: [],
106+
})
107+
}
108+
}
109+
listTags();
110+
})
111+
112+
export interface RawAwsAcm extends CertificateSummary {
113+
region: string
114+
Tags: TagMap
115+
account
116+
}
117+
118+
export default async ({
119+
regions,
120+
config,
121+
account,
122+
}: {
123+
account: string
124+
regions: string
125+
config: Config
126+
}): Promise<{
127+
[region: string]: RawAwsAcm[]
128+
}> =>
129+
new Promise(async resolve => {
130+
const acmResult: RawAwsAcm[] = []
131+
132+
const regionPromises = regions.split(',').map(region => {
133+
const acm = new ACM({
134+
...config,
135+
region,
136+
endpoint,
137+
...customRetrySettings,
138+
})
139+
140+
return new Promise<void>(async resolveAcmData => {
141+
// Get ACM certificate summaries
142+
const certificates = await getCertificatesForRegion(acm)
143+
144+
const tagsPromises = certificates.map(
145+
({ CertificateArn: certificateArn }) => getTagsForCertificate(acm, certificateArn)
146+
)
147+
148+
const tagsData = await Promise.all(tagsPromises)
149+
150+
if (!isEmpty(certificates)) {
151+
for (const certificate of certificates) {
152+
acmResult.push({
153+
...certificate,
154+
Tags: tagsData?.find(t => t.certificateArn === certificate.CertificateArn)
155+
?.tags.reduce((tagMap, {Key, Value}) => {
156+
tagMap[Key] = Value;
157+
return tagMap;
158+
}, {}),
159+
region,
160+
account,
161+
})
162+
}
163+
}
164+
165+
resolveAcmData()
166+
})
167+
})
168+
169+
await Promise.all(regionPromises)
170+
errorLog.reset()
171+
172+
resolve(groupBy(acmResult, 'region'))
173+
})

src/services/acm/format.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { RawAwsAcm } from './data'
2+
import { AwsAcm } from '../../types/generated'
3+
import { formatTagsFromMap } from '../../utils/format'
4+
5+
export default ({
6+
service,
7+
account: accountId,
8+
region,
9+
}: {
10+
service: RawAwsAcm
11+
account: string
12+
region: string
13+
}): AwsAcm => {
14+
const {
15+
CertificateArn: arn,
16+
DomainName: domainName,
17+
SubjectAlternativeNameSummaries: subjectAlternativeNameSummaries,
18+
HasAdditionalSubjectAlternativeNames: hasAdditionalSubjectAlternativeNames,
19+
Status: status,
20+
Type: type,
21+
KeyAlgorithm: keyAlgorithm,
22+
KeyUsages: keyUsages,
23+
ExtendedKeyUsages: extendedKeyUsages,
24+
InUse: inUse,
25+
Exported: exported,
26+
RenewalEligibility: renewalEligibility,
27+
NotBefore: notBefore,
28+
NotAfter: notAfter,
29+
CreatedAt: createdAt,
30+
IssuedAt: issuedAt,
31+
ImportedAt: importedAt,
32+
RevokedAt: revokedAt,
33+
Tags: tags,
34+
} = service
35+
36+
return {
37+
id: arn,
38+
accountId,
39+
arn,
40+
region,
41+
domainName,
42+
subjectAlternativeNameSummaries,
43+
hasAdditionalSubjectAlternativeNames,
44+
status,
45+
type,
46+
keyAlgorithm,
47+
keyUsages,
48+
extendedKeyUsages,
49+
inUse,
50+
exported,
51+
renewalEligibility,
52+
notBefore: notBefore?.toISOString(),
53+
notAfter: notAfter?.toISOString(),
54+
createdAt: createdAt?.toISOString(),
55+
issuedAt: issuedAt?.toISOString(),
56+
importedAt: importedAt?.toISOString(),
57+
revokedAt: revokedAt?.toISOString(),
58+
tags: formatTagsFromMap(tags),
59+
}
60+
}

src/services/acm/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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 Acm extends BaseService implements Service {
8+
format = format.bind(this)
9+
10+
getData = getData.bind(this)
11+
12+
mutation = mutation
13+
}

0 commit comments

Comments
 (0)