Skip to content

Commit 86b9aca

Browse files
authored
Merge pull request #54 from cloudgraphdev/feature/CG-1139
feat: Add AWS IAM Access analyzer service
2 parents 13d0560 + cc3ae64 commit 86b9aca

15 files changed

Lines changed: 251 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi
117117
| glueJob | iamRole |
118118
| glueRegistry | |
119119
| guardDutyDetector | iamRole |
120+
| iamAccessAnalyzer | |
120121
| iamInstanceProfile | ec2, iamRole |
121122
| iamPasswordPolicy | |
122123
| iamSamlProvider | cognitoIdentityPool |

src/enums/schemasMap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export default {
5454
[services.emrCluster]: 'awsEmrCluster',
5555
[services.emrInstance]: 'awsEmrInstance',
5656
[services.emrStep]: 'awsEmrStep',
57+
[services.iamAccessAnalyzer]: 'awsIamAccessAnalyzer',
5758
[services.iamGroup]: 'awsIamGroup',
5859
[services.iamOpenIdConnectProvider]: 'awsIamOpenIdConnectProvider',
5960
[services.iamPasswordPolicy]: 'awsIamPasswordPolicy',

src/enums/serviceAliases.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default {
3636
[services.glueJob]: 'glueJobs',
3737
[services.glueRegistry]: 'glueRegistries',
3838
[services.guardDutyDetector]: 'guardDutyDetectors',
39+
[services.iamAccessAnalyzer]: 'iamAccessAnalyzers',
3940
[services.iamGroup]: 'iamGroups',
4041
[services.iamOpenIdConnectProvider]: 'iamOpenIdConnectProviders',
4142
[services.iamPasswordPolicy]: 'iamPasswordPolicies',

src/enums/serviceMap.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import SES from '../services/ses'
5858
import SQS from '../services/sqs'
5959
import VPC from '../services/vpc'
6060
import ECR from '../services/ecr'
61+
import IamAccessAnalyzer from '../services/iamAccessAnalyzer'
6162
import IamGroup from '../services/iamGroup'
6263
import IamUser from '../services/iamUser'
6364
import IamRole from '../services/iamRole'
@@ -171,6 +172,7 @@ export default {
171172
[services.s3]: S3,
172173
[services.secretsManager]: SecretsManager,
173174
[services.ses]: SES,
175+
[services.iamAccessAnalyzer]: IamAccessAnalyzer,
174176
[services.iamUser]: IamUser,
175177
[services.iamGroup]: IamGroup,
176178
[services.iamRole]: IamRole,

src/enums/services.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export default {
4848
emrCluster: 'emrCluster',
4949
emrInstance: 'emrInstance',
5050
emrStep: 'emrStep',
51+
iamAccessAnalyzer: 'iamAccessAnalyzer',
5152
iamUser: 'iamUser',
5253
iamGroup: 'iamGroup',
5354
iamRole: 'iamRole',

src/properties/logger.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,4 +668,8 @@ export default {
668668
* Configuration Recorder Status
669669
*/
670670
fetchedConfigurationRecorderStatus: (num: number): string => `Fetched ${num} Configuration Recorder Status`,
671+
/**
672+
* Access Analyzers
673+
*/
674+
fetchedaccessAnalyzers: (num: number): string => `Found ${num} Access Analyzers`,
671675
}

src/services/account/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type awsAccount implements awsOptionalService @key(fields: "id") {
4949
glueJobs: [awsGlueJob]
5050
glueRegistries: [awsGlueRegistry]
5151
guardDutyDetectors: [awsGuardDutyDetector]
52+
iamAccessAnalyzers: [awsIamAccessAnalyzer]
5253
iamGroups: [awsIamGroup]
5354
iamOpenIdConnectProviders: [awsIamOpenIdConnectProvider]
5455
iamPasswordPolicies: [awsIamPasswordPolicy]
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import CloudGraph from '@cloudgraph/sdk'
2+
import groupBy from 'lodash/groupBy'
3+
import isEmpty from 'lodash/isEmpty'
4+
import AccessAnalyzer, {
5+
ListAnalyzersRequest,
6+
ListAnalyzersResponse,
7+
AnalyzerSummary,
8+
AnalyzersList,
9+
} from 'aws-sdk/clients/accessanalyzer'
10+
11+
import { Config } from 'aws-sdk/lib/config'
12+
import { AWSError } from 'aws-sdk/lib/error'
13+
import awsLoggerText from '../../properties/logger'
14+
import { initTestEndpoint } from '../../utils'
15+
import AwsErrorLog from '../../utils/errorLog'
16+
import { TagMap } from '../../types'
17+
18+
const lt = { ...awsLoggerText }
19+
const { logger } = CloudGraph
20+
const serviceName = 'IAM Access Analyzer'
21+
const errorLog = new AwsErrorLog(serviceName)
22+
const endpoint = initTestEndpoint(serviceName)
23+
24+
const listAnalyzersData = async ({
25+
accessAnalyzer,
26+
region,
27+
nextToken: NextToken = '',
28+
}: {
29+
accessAnalyzer: AccessAnalyzer
30+
region: string
31+
nextToken?: string
32+
}): Promise<(AnalyzerSummary & { region: string })[]> =>
33+
new Promise<(AnalyzerSummary & { region: string })[]>(resolve => {
34+
let analyzerSummarytData: (AnalyzerSummary & {
35+
region: string
36+
})[] = []
37+
38+
const analyzersList: AnalyzersList = []
39+
let args: ListAnalyzersRequest = {}
40+
41+
if (NextToken) {
42+
args = { ...args, nextToken: NextToken }
43+
}
44+
45+
accessAnalyzer.listAnalyzers(
46+
args,
47+
(err: AWSError, data: ListAnalyzersResponse) => {
48+
if (err) {
49+
errorLog.generateAwsErrorLog({
50+
functionName: 'accessAnalyzer:listAnalyzers',
51+
err,
52+
})
53+
}
54+
55+
if (!isEmpty(data)) {
56+
const { nextToken, analyzers: analyzersData = [] } = data
57+
58+
analyzersList.push(...analyzersData)
59+
60+
logger.debug(lt.fetchedaccessAnalyzers(analyzersList.length))
61+
62+
if (nextToken) {
63+
listAnalyzersData({ accessAnalyzer, region, nextToken })
64+
}
65+
66+
analyzerSummarytData = analyzersList.map(analyzer => ({
67+
...analyzer,
68+
region,
69+
}))
70+
}
71+
72+
resolve(analyzerSummarytData)
73+
}
74+
)
75+
})
76+
77+
/**
78+
* IAM Access Analyzer
79+
*/
80+
81+
export interface RawAwsAnalyzerSummary extends Omit<AnalyzerSummary, 'Tags'> {
82+
region: string
83+
Tags?: TagMap
84+
}
85+
86+
export default async ({
87+
regions,
88+
config,
89+
}: {
90+
regions: string
91+
config: Config
92+
}): Promise<{
93+
[region: string]: RawAwsAnalyzerSummary[]
94+
}> =>
95+
new Promise(async resolve => {
96+
const analyzerSummaryResult: RawAwsAnalyzerSummary[] = []
97+
98+
const regionPromises = regions.split(',').map(region => {
99+
const accessAnalyzer = new AccessAnalyzer({ ...config, region, endpoint })
100+
101+
return new Promise<void>(async resolveAnalyzerSummaryData => {
102+
const analyzerSummaryData = await listAnalyzersData({
103+
accessAnalyzer,
104+
region,
105+
})
106+
107+
if (!isEmpty(analyzerSummaryData)) {
108+
for (const analyzer of analyzerSummaryData) {
109+
analyzerSummaryResult.push({
110+
...analyzer,
111+
region,
112+
Tags: analyzer.tags || {}
113+
})
114+
}
115+
}
116+
117+
resolveAnalyzerSummaryData()
118+
})
119+
})
120+
121+
await Promise.all(regionPromises)
122+
errorLog.reset()
123+
124+
resolve(groupBy(analyzerSummaryResult, 'region'))
125+
})
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { AwsIamAccessAnalyzer } from '../../types/generated'
2+
import { formatTagsFromMap } from '../../utils/format'
3+
4+
import { RawAwsAnalyzerSummary } from './data'
5+
6+
/**
7+
* IAM Access Analyzer
8+
*/
9+
10+
export default ({
11+
service: rawData,
12+
account,
13+
}: {
14+
service: RawAwsAnalyzerSummary
15+
account: string
16+
region: string
17+
}): AwsIamAccessAnalyzer => {
18+
const {
19+
arn,
20+
createdAt,
21+
lastResourceAnalyzed,
22+
lastResourceAnalyzedAt,
23+
name,
24+
status,
25+
statusReason,
26+
Tags: tags = {},
27+
type,
28+
region,
29+
} = rawData
30+
31+
return {
32+
id: arn,
33+
arn,
34+
accountId: account,
35+
region,
36+
createdAt: createdAt?.toISOString(),
37+
lastResourceAnalyzed,
38+
lastResourceAnalyzedAt: lastResourceAnalyzedAt?.toISOString(),
39+
name,
40+
status,
41+
statusReasonCode: statusReason?.code || '',
42+
type,
43+
tags: formatTagsFromMap(tags),
44+
}
45+
}
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 IamAccessAnalyzer 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)