Skip to content

Commit 9048ada

Browse files
authored
Merge pull request #23 from cloudgraphdev/feature/CG-1072
feat(secretsManager): Add connections to kms and lambda.
2 parents a4d4ed0 + cf03793 commit 9048ada

File tree

12 files changed

+306
-27
lines changed

12 files changed

+306
-27
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi
130130
| iot | |
131131
| kinesisFirehose | kinesisStream, s3, iamRole |
132132
| kinesisStream | kinesisFirehose |
133-
| kms | cloudtrail, cloudwatchLog, codebuild, ecsCluster, efs, eksCluster, elastiCacheReplicationGroup, elasticSearchDomain, emrCluster, lambda, rdsClusterSnapshot, sns, sageMakerNotebookInstance, dmsReplicationInstance, redshiftCluster, rdsCluster |
134-
| lambda | appSync, cognitoUserPool, kms, securityGroup, subnet, vpc, iamRole |
133+
| 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 |
135135
| managedAirflow | iamRole, securityGroups, subnet, s3 |
136136
| nacl | vpc |
137137
| natGateway | networkInterface, subnet, vpc |
@@ -148,7 +148,7 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi
148148
| sageMakerNotebookInstance | iamRole, kms, networkInterface, subnet, securityGroup |
149149
| sageMakerProject | |
150150
| s3 | cloudfront, cloudtrail, ecsCluster, kinesisFirehose, managedAirflow |
151-
| secretsManager | |
151+
| secretsManager | kms, lambda |
152152
| securityGroup | alb, asg, clientVpnEndpoint, codebuild, dmsReplicationInstance, ecsService, lambda, ec2, elasticSearchDomain, elb, rdsCluster, rdsDbInstance, eksCluster, elastiCacheCluster, managedAirflow, sageMakerNotebookInstance |
153153
| ses | |
154154
| sns | kms, cloudtrail, cloudwatch |

src/properties/logger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ export default {
427427
gettingRotationStatus: 'Checking rotation status for each key...',
428428
gettingPolicies: 'Fetching default Policy for each key...',
429429
gettingTags: 'Fetching Tags for each key...',
430+
gettingAliases: 'Fetching Aliases for each key...',
430431

431432
/**
432433
* EKS

src/services/kms/data.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import isEmpty from 'lodash/isEmpty'
66
import { AWSError, Request } from 'aws-sdk'
77
import { Config } from 'aws-sdk/lib/config'
88
import KMS, {
9+
AliasListEntry,
910
KeyListEntry,
1011
KeyMetadata,
1112
ListKeysRequest,
@@ -33,6 +34,7 @@ export type AwsKms = KeyListEntry &
3334
policy: string
3435
Tags: TagMap
3536
keyRotationEnabled: boolean
37+
Aliases?: AliasListEntry[]
3638
}
3739

3840
export default async ({
@@ -50,6 +52,7 @@ export default async ({
5052
const regionPromises = []
5153
const policyPromises = []
5254
const tagPromises = []
55+
const aliasesPromises = []
5356

5457
/**
5558
* Step 1) for all regions, list the kms keys
@@ -331,6 +334,50 @@ export default async ({
331334
})
332335

333336
await Promise.all(tagPromises)
337+
338+
/**
339+
* Step 6) get aliases for each key
340+
*/
341+
342+
logger.debug(lt.gettingAliases)
343+
344+
kmsData.map(({ region, KeyId }, idx) => {
345+
const kms = new KMS({ ...config, region, endpoint })
346+
347+
const aliasesPromise = new Promise<void>(resolveAliases =>
348+
kms.listAliases({ KeyId }, (err, data) => {
349+
if (err) {
350+
errorLog.generateAwsErrorLog({
351+
functionName: 'kms:listAliases',
352+
err,
353+
})
354+
resolveAliases()
355+
}
356+
357+
/**
358+
* No aliases data
359+
*/
360+
361+
if (isEmpty(data)) {
362+
return resolveAliases()
363+
}
364+
365+
/**
366+
* Add the aliases to the key
367+
*/
368+
369+
const { Aliases: aliases } = data || {}
370+
371+
kmsData[idx].Aliases = aliases
372+
373+
resolveAliases()
374+
})
375+
)
376+
377+
aliasesPromises.push(aliasesPromise)
378+
})
379+
380+
await Promise.all(aliasesPromises)
334381
errorLog.reset()
335382

336383
resolve(groupBy(kmsData, 'region'))

src/services/kms/format.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
1-
import t from '../../properties/translations'
1+
import { AliasListEntry } from 'aws-sdk/clients/kms'
2+
import cuid from 'cuid'
23
import { AwsKms } from './data'
3-
import { AwsKms as AwsKmsType } from '../../types/generated'
4+
import { AwsKms as AwsKmsType, AwsKmsAliasListEntry } from '../../types/generated'
45
import { formatTagsFromMap, formatIamJsonPolicy } from '../../utils/format'
56

7+
export const formatAliases = (
8+
aliases?: AliasListEntry[]
9+
): AwsKmsAliasListEntry[] => {
10+
return (
11+
aliases?.map(a => ({
12+
id: cuid(),
13+
aliasName: a.AliasName,
14+
aliasArn: a.AliasArn,
15+
targetKeyId: a.TargetKeyId,
16+
creationDate: a.CreationDate?.toISOString(),
17+
lastUpdatedDate: a.LastUpdatedDate?.toISOString(),
18+
})) || []
19+
)
20+
}
21+
622
/**
723
* KMS
824
*/
@@ -32,6 +48,7 @@ export default ({
3248
Origin: origin,
3349
DeletionDate: deletionDate,
3450
ValidTo: validTo,
51+
Aliases: aliases = []
3552
} = key
3653

3754
return {
@@ -47,10 +64,11 @@ export default ({
4764
keyState,
4865
customerMasterKeySpec,
4966
tags: formatTagsFromMap(Tags),
50-
creationDate: creationDate ? creationDate.toString() : undefined,
67+
creationDate: creationDate?.toISOString(),
5168
keyManager,
5269
origin,
53-
deletionDate: deletionDate ? deletionDate.toString() : undefined,
54-
validTo: validTo ? validTo.toString() : undefined,
70+
deletionDate: deletionDate?.toISOString(),
71+
validTo: validTo?.toISOString(),
72+
aliases: formatAliases(aliases),
5573
}
5674
}

src/services/kms/schema.graphql

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
type awsKmsAliasListEntry
2+
@generate(
3+
query: { get: false, query: true, aggregate: false }
4+
mutation: { add: false, delete: false }
5+
subscription: false
6+
) {
7+
id: String! @id
8+
aliasName: String @search(by: [hash, regexp])
9+
aliasArn: String @search(by: [hash, regexp])
10+
targetKeyId: String @search(by: [hash, regexp])
11+
creationDate: DateTime @search(by: [day])
12+
lastUpdatedDate: DateTime @search(by: [day])
13+
}
14+
115
type awsKms implements awsBaseService @key(fields: "id") {
216
description: String @search(by: [hash, regexp, fulltext])
317
keyRotationEnabled: Boolean @search
@@ -7,11 +21,12 @@ type awsKms implements awsBaseService @key(fields: "id") {
721
keyState: String @search(by: [hash, regexp])
822
customerMasterKeySpec: String @search(by: [hash, regexp])
923
tags: [awsRawTag]
10-
creationDate: String @search(by: [hash, regexp])
24+
creationDate: DateTime @search(by: [day])
1125
keyManager: String @search(by: [hash, regexp])
1226
origin: String @search(by: [hash, regexp])
13-
deletionDate: String @search(by: [hash, regexp])
14-
validTo: String @search(by: [hash, regexp])
27+
deletionDate: DateTime @search(by: [day])
28+
validTo: DateTime @search(by: [day])
29+
aliases: [awsKmsAliasListEntry]
1530
lambda: [awsLambda] @hasInverse(field: kms) #change to plural
1631
cloudtrail: [awsCloudtrail] @hasInverse(field: kms) #change to plural
1732
redshiftCluster: [awsRedshiftCluster] @hasInverse(field: kms) #change to plural
@@ -28,6 +43,7 @@ type awsKms implements awsBaseService @key(fields: "id") {
2843
sageMakerNotebookInstances: [awsSageMakerNotebookInstance]
2944
@hasInverse(field: kms)
3045
rdsClusterSnapshots: [awsRdsClusterSnapshot] @hasInverse(field: kms)
46+
secretsManager: [awsSecretsManager] @hasInverse(field: kms)
3147
ecsCluster: [awsEcsCluster] @hasInverse(field: kms)
3248
dynamodb: [awsDynamoDbTable] @hasInverse(field: kms)
3349
cognitoUserPools: [awsCognitoUserPool] @hasInverse(field: kms)

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+
secretsManager: [awsSecretsManager] @hasInverse(field: lambda)
2425
iamRole: [awsIamRole] @hasInverse(field: lambda)
2526
}
2627

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { ServiceConnection } from '@cloudgraph/sdk'
2+
import { isEmpty } from 'lodash'
3+
import { RawAwsSecretsManager } from './data'
4+
import { AwsKms } from '../kms/data'
5+
import { RawAwsLambdaFunction } from '../lambda/data'
6+
import services from '../../enums/services'
7+
8+
export default ({
9+
service,
10+
data,
11+
region,
12+
}: {
13+
account: string
14+
service: RawAwsSecretsManager
15+
data: Array<{ name: string; data: { [property: string]: any[] } }>
16+
region: string
17+
}): {
18+
[property: string]: ServiceConnection[]
19+
} => {
20+
const connections: ServiceConnection[] = []
21+
const {
22+
ARN: id,
23+
KmsKeyId: kmsKeyId,
24+
RotationLambdaARN: rotationLambdaARN,
25+
ReplicationStatus: replicationStatus,
26+
} = service
27+
28+
/**
29+
* Find KMS
30+
* related to this Secrets Manager
31+
*/
32+
const kmsKeyIds: string[] = replicationStatus?.map(rs => rs.KmsKeyId)
33+
const kmsKeys = data.find(({ name }) => name === services.kms)
34+
if (kmsKeys?.data?.[region]) {
35+
const kmsKeyInRegion: AwsKms[] = kmsKeys.data[region].filter(
36+
({ Arn: arn, KeyId: keyId, Aliases: aliases = [] }: AwsKms) =>
37+
kmsKeyId === arn ||
38+
kmsKeyIds?.includes(arn) ||
39+
kmsKeyIds?.includes(keyId) ||
40+
aliases?.some(a => kmsKeyIds?.includes(a.AliasName))
41+
)
42+
43+
if (!isEmpty(kmsKeyInRegion)) {
44+
for (const kms of kmsKeyInRegion) {
45+
const { KeyId: keyId }: AwsKms = kms
46+
connections.push({
47+
id: keyId,
48+
resourceType: services.kms,
49+
relation: 'child',
50+
field: 'kms',
51+
})
52+
}
53+
}
54+
}
55+
56+
/**
57+
* Find Lambda Functions
58+
* related to this Secrets Manager
59+
*/
60+
const lambdas = data.find(({ name }) => name === services.lambda)
61+
62+
if (rotationLambdaARN && lambdas?.data?.[region]) {
63+
const lambdaInRegion: RawAwsLambdaFunction = lambdas.data[region].find(
64+
({ FunctionArn: functionArn }: RawAwsLambdaFunction) =>
65+
rotationLambdaARN === functionArn
66+
)
67+
68+
if (lambdaInRegion) {
69+
const { FunctionArn: functionArn }: RawAwsLambdaFunction = lambdaInRegion
70+
71+
connections.push({
72+
id: functionArn,
73+
resourceType: services.lambda,
74+
relation: 'child',
75+
field: 'lambda',
76+
})
77+
}
78+
}
79+
80+
const snsResult = {
81+
[id]: connections,
82+
}
83+
return snsResult
84+
}

src/services/secretsManager/data.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import SM, { SecretListEntry } from 'aws-sdk/clients/secretsmanager'
1+
import SM, {
2+
SecretListEntry,
3+
DescribeSecretResponse,
4+
ReplicationStatusType,
5+
} from 'aws-sdk/clients/secretsmanager'
26
import { AWSError } from 'aws-sdk/lib/error'
37
import CloudGraph from '@cloudgraph/sdk'
48
import groupBy from 'lodash/groupBy'
@@ -22,8 +26,33 @@ const endpoint = initTestEndpoint(serviceName)
2226
export interface RawAwsSecretsManager extends Omit<SecretListEntry, 'Tags'> {
2327
region: string
2428
Tags: TagMap
29+
ReplicationStatus?: ReplicationStatusType[]
2530
}
2631

32+
const getSecretDetails = async (
33+
sm: SM,
34+
secretId: string
35+
): Promise<DescribeSecretResponse> =>
36+
new Promise(resolve => {
37+
sm.describeSecret(
38+
{
39+
SecretId: secretId,
40+
},
41+
(err: AWSError, data: DescribeSecretResponse) => {
42+
if (err) {
43+
errorLog.generateAwsErrorLog({
44+
functionName: 'sm:describeSecret',
45+
err,
46+
})
47+
}
48+
if (!isEmpty(data)) {
49+
resolve(data)
50+
}
51+
resolve({})
52+
}
53+
)
54+
})
55+
2756
export default async ({
2857
regions,
2958
config,
@@ -34,6 +63,7 @@ export default async ({
3463
new Promise(async resolve => {
3564
const smData: RawAwsSecretsManager[] = []
3665
const regionPromises = []
66+
const secretsDetailsPromises = []
3767

3868
regions.split(',').map(region => {
3969
const regionPromise = new Promise<void>(resolveSecretsManagerData => {
@@ -73,8 +103,24 @@ export default async ({
73103
})
74104
regionPromises.push(regionPromise)
75105
})
76-
77106
await Promise.all(regionPromises)
107+
108+
smData.map(({ ARN: arn, region }, idx) => {
109+
const sm = new SM({ ...config, region, endpoint })
110+
const secretDetailsPromise = new Promise<void>(
111+
async resolveSecretDetails => {
112+
const secretDetails: DescribeSecretResponse = await getSecretDetails(
113+
sm,
114+
arn
115+
)
116+
smData[idx].ReplicationStatus = secretDetails?.ReplicationStatus || []
117+
resolveSecretDetails()
118+
}
119+
)
120+
secretsDetailsPromises.push(secretDetailsPromise)
121+
})
122+
await Promise.all(secretsDetailsPromises)
123+
78124
errorLog.reset()
79125

80126
resolve(groupBy(smData, 'region'))

0 commit comments

Comments
 (0)