Skip to content

Commit fc19571

Browse files
feat(iot-alpha): support for account audit configuration (#31661)
### Issue # (if applicable) Closes #31663. ### Reason for this change Cloudformation supports create an account audit configuration but AWS CDK doesn't support it. ### Description of changes - Define `IAccountAuditConfiguration` - Define `AccountAuditConfiguration` class and `AccountAuditConfigurationProps` I couldn't find documentation regarding the required policies for the role when executing an audit, but when enabling the audit configuration through the management console, it was set to use a "AWSIoTDeviceDefenderAudit" managed policy. This implementation follows that same approach. <img width="624" alt="スクリーンショット 2024-10-05 15 05 10" src="https://github.com/user-attachments/assets/61f9d0bb-2606-4b2d-9c8f-8245f7f47c68"> ### Description of how you validated changes Add both unit and integ tests ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent cff1fcd commit fc19571

15 files changed

+1244
-1
lines changed

Diff for: packages/@aws-cdk/aws-iot-alpha/README.md

+48
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,51 @@ new iot.Logging(this, 'Logging', {
9191
```
9292

9393
**Note**: All logs are forwarded to the `AWSIotLogsV2` log group in CloudWatch.
94+
95+
## Audit
96+
97+
An [AWS IoT Device Defender audit looks](https://docs.aws.amazon.com/iot-device-defender/latest/devguide/device-defender-audit.html) at account- and device-related settings and policies to ensure security measures are in place.
98+
An audit can help you detect any drifts from security best practices or access policies.
99+
100+
### Account Audit Configuration
101+
102+
The IoT audit includes [various audit checks](https://docs.aws.amazon.com/iot-device-defender/latest/devguide/device-defender-audit-checks.html), and it is necessary to configure settings to enable those checks.
103+
104+
You can enable an account audit configuration with the following code:
105+
106+
```ts
107+
// Audit notification are sent to the SNS topic
108+
declare const targetTopic: sns.ITopic;
109+
110+
new iot.AccountAuditConfiguration(this, 'AuditConfiguration', {
111+
targetTopic,
112+
});
113+
```
114+
115+
By default, all audit checks are enabled, but it is also possible to enable only specific audit checks.
116+
117+
```ts
118+
new iot.AccountAuditConfiguration(this, 'AuditConfiguration', {
119+
checkConfiguration: {
120+
// enabled
121+
authenticatedCognitoRoleOverlyPermissiveCheck: true,
122+
// enabled by default
123+
caCertificateExpiringCheck: undefined,
124+
// disabled
125+
caCertificateKeyQualityCheck: false,
126+
conflictingClientIdsCheck: false,
127+
deviceCertificateExpiringCheck: false,
128+
deviceCertificateKeyQualityCheck: false,
129+
deviceCertificateSharedCheck: false,
130+
intermediateCaRevokedForActiveDeviceCertificatesCheck: false,
131+
ioTPolicyPotentialMisConfigurationCheck: false,
132+
iotPolicyOverlyPermissiveCheck: false,
133+
iotRoleAliasAllowsAccessToUnusedServicesCheck: false,
134+
iotRoleAliasOverlyPermissiveCheck: false,
135+
loggingDisabledCheck: false,
136+
revokedCaCertificateStillActiveCheck: false,
137+
revokedDeviceCertificateStillActiveCheck: false,
138+
unauthenticatedCognitoRoleOverlyPermissiveCheck: false,
139+
},
140+
});
141+
```

Diff for: packages/@aws-cdk/aws-iot-alpha/awslint.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"exclude": [
33
"no-unused-type:@aws-cdk/aws-iot-alpha.ActionConfig",
4-
"props-physical-name:@aws-cdk/aws-iot-alpha.LoggingProps"
4+
"props-physical-name:@aws-cdk/aws-iot-alpha.LoggingProps",
5+
"props-physical-name:@aws-cdk/aws-iot-alpha.AccountAuditConfigurationProps"
56
]
67
}
+287
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
import { Resource, Stack, IResource } from 'aws-cdk-lib/core';
2+
import { Construct } from 'constructs';
3+
import * as iot from 'aws-cdk-lib/aws-iot';
4+
import * as iam from 'aws-cdk-lib/aws-iam';
5+
import * as sns from 'aws-cdk-lib/aws-sns';
6+
7+
/**
8+
* Represents AWS IoT Audit Configuration
9+
*/
10+
export interface IAccountAuditConfiguration extends IResource {
11+
/**
12+
* The account ID
13+
* @attribute
14+
*/
15+
readonly accountId: string;
16+
}
17+
18+
/**
19+
* The types of audit checks
20+
*
21+
* @see https://docs.aws.amazon.com/iot-device-defender/latest/devguide/device-defender-audit-checks.html
22+
*/
23+
export interface CheckConfiguration {
24+
/**
25+
* Checks the permissiveness of an authenticated Amazon Cognito identity pool role.
26+
*
27+
* For this check, AWS IoT Device Defender audits all Amazon Cognito identity pools that have been used to connect to the AWS IoT message broker
28+
* during the 31 days before the audit is performed.
29+
*
30+
* @default true
31+
*/
32+
readonly authenticatedCognitoRoleOverlyPermissiveCheck?: boolean;
33+
34+
/**
35+
* Checks if a CA certificate is expiring.
36+
*
37+
* This check applies to CA certificates expiring within 30 days or that have expired.
38+
*
39+
* @default true
40+
*/
41+
readonly caCertificateExpiringCheck?: boolean;
42+
43+
/**
44+
* Checks the quality of the CA certificate key.
45+
*
46+
* The quality checks if the key is in a valid format, not expired, and if the key meets a minimum required size.
47+
*
48+
* This check applies to CA certificates that are ACTIVE or PENDING_TRANSFER.
49+
*
50+
* @default true
51+
*/
52+
readonly caCertificateKeyQualityCheck?: boolean;
53+
54+
/**
55+
* Checks if multiple devices connect using the same client ID.
56+
*
57+
* @default true
58+
*/
59+
readonly conflictingClientIdsCheck?: boolean;
60+
61+
/**
62+
* Checks if a device certificate is expiring.
63+
*
64+
* This check applies to device certificates expiring within 30 days or that have expired.
65+
*
66+
* @default true
67+
*/
68+
readonly deviceCertificateExpiringCheck?: boolean;
69+
70+
/**
71+
* Checks the quality of the device certificate key.
72+
*
73+
* The quality checks if the key is in a valid format, not expired, signed by a registered certificate authority,
74+
* and if the key meets a minimum required size.
75+
*
76+
* @default true
77+
*/
78+
readonly deviceCertificateKeyQualityCheck?: boolean;
79+
80+
/**
81+
* Checks if multiple concurrent connections use the same X.509 certificate to authenticate with AWS IoT.
82+
*
83+
* @default true
84+
*/
85+
readonly deviceCertificateSharedCheck?: boolean;
86+
87+
/**
88+
* Checks if device certificates are still active despite being revoked by an intermediate CA.
89+
*
90+
* @default true
91+
*/
92+
readonly intermediateCaRevokedForActiveDeviceCertificatesCheck?: boolean;
93+
94+
/**
95+
* Checks the permissiveness of a policy attached to an authenticated Amazon Cognito identity pool role.
96+
*
97+
* @default true
98+
*/
99+
readonly iotPolicyOverlyPermissiveCheck?: boolean;
100+
101+
/**
102+
* Checks if an AWS IoT policy is potentially misconfigured.
103+
*
104+
* Misconfigured policies, including overly permissive policies, can cause security incidents like allowing devices access to unintended resources.
105+
*
106+
* This check is a warning for you to make sure that only intended actions are allowed before updating the policy.
107+
*
108+
* @default true
109+
*/
110+
readonly ioTPolicyPotentialMisConfigurationCheck?: boolean;
111+
112+
/**
113+
* Checks if a role alias has access to services that haven't been used for the AWS IoT device in the last year.
114+
*
115+
* @default true
116+
*/
117+
readonly iotRoleAliasAllowsAccessToUnusedServicesCheck?: boolean;
118+
119+
/**
120+
* Checks if the temporary credentials provided by AWS IoT role aliases are overly permissive.
121+
*
122+
* @default true
123+
*/
124+
readonly iotRoleAliasOverlyPermissiveCheck?: boolean;
125+
126+
/**
127+
* Checks if AWS IoT logs are disabled.
128+
*
129+
* @default true
130+
*/
131+
readonly loggingDisabledCheck?: boolean;
132+
133+
/**
134+
* Checks if a revoked CA certificate is still active.
135+
*
136+
* @default true
137+
*/
138+
readonly revokedCaCertificateStillActiveCheck?: boolean;
139+
140+
/**
141+
* Checks if a revoked device certificate is still active.
142+
*
143+
* @default true
144+
*/
145+
readonly revokedDeviceCertificateStillActiveCheck?: boolean;
146+
147+
/**
148+
* Checks if policy attached to an unauthenticated Amazon Cognito identity pool role is too permissive.
149+
*
150+
* @default true
151+
*/
152+
readonly unauthenticatedCognitoRoleOverlyPermissiveCheck?: boolean;
153+
}
154+
155+
/**
156+
* Properties for defining AWS IoT Audit Configuration
157+
*/
158+
export interface AccountAuditConfigurationProps {
159+
/**
160+
* The target SNS topic to which audit notifications are sent.
161+
*
162+
* @default - no notifications are sent
163+
*/
164+
readonly targetTopic?: sns.ITopic;
165+
166+
/**
167+
* Specifies which audit checks are enabled and disabled for this account.
168+
*
169+
* @default - all checks are enabled
170+
*/
171+
readonly checkConfiguration?: CheckConfiguration;
172+
}
173+
174+
/**
175+
* Defines AWS IoT Audit Configuration
176+
*/
177+
export class AccountAuditConfiguration extends Resource implements IAccountAuditConfiguration {
178+
/**
179+
* Import an existing AWS IoT Audit Configuration
180+
*
181+
* @param scope The parent creating construct (usually `this`)
182+
* @param id The construct's name
183+
* @param accountId The account ID
184+
*/
185+
public static fromAccountId(scope: Construct, id: string, accountId: string): IAccountAuditConfiguration {
186+
class Import extends Resource implements IAccountAuditConfiguration {
187+
public readonly accountId = accountId;
188+
}
189+
return new Import(scope, id);
190+
}
191+
192+
/**
193+
* The account ID
194+
* @attribute
195+
*/
196+
public readonly accountId: string;
197+
198+
constructor(scope: Construct, id: string, props?: AccountAuditConfigurationProps) {
199+
super(scope, id);
200+
201+
this.accountId = Stack.of(this).account;
202+
203+
const auditRole = new iam.Role(this, 'AuditRole', {
204+
assumedBy: new iam.ServicePrincipal('iot.amazonaws.com'),
205+
managedPolicies: [
206+
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSIoTDeviceDefenderAudit'),
207+
],
208+
});
209+
210+
new iot.CfnAccountAuditConfiguration(this, 'Resource', {
211+
accountId: this.accountId,
212+
auditCheckConfigurations: this.renderAuditCheckConfigurations(props?.checkConfiguration),
213+
auditNotificationTargetConfigurations: this.renderAuditNotificationTargetConfigurations(props?.targetTopic),
214+
roleArn: auditRole.roleArn,
215+
});
216+
}
217+
218+
/**
219+
* Render the audit notification target configurations
220+
*/
221+
private renderAuditNotificationTargetConfigurations(
222+
targetTopic?: sns.ITopic,
223+
): iot.CfnAccountAuditConfiguration.AuditNotificationTargetConfigurationsProperty | undefined {
224+
if (!targetTopic) {
225+
return undefined;
226+
}
227+
228+
const notificationRole = new iam.Role(this, 'NotificationRole', {
229+
assumedBy: new iam.ServicePrincipal('iot.amazonaws.com'),
230+
inlinePolicies: {
231+
NotificationPolicy: new iam.PolicyDocument({
232+
statements: [
233+
new iam.PolicyStatement({
234+
actions: ['sns:Publish'],
235+
resources: [targetTopic.topicArn],
236+
}),
237+
],
238+
}),
239+
},
240+
});
241+
242+
return {
243+
sns: {
244+
enabled: true,
245+
targetArn: targetTopic.topicArn,
246+
roleArn: notificationRole.roleArn,
247+
},
248+
};
249+
}
250+
251+
/**
252+
* Render the audit check configurations
253+
*/
254+
private renderAuditCheckConfigurations(checkConfiguration?: CheckConfiguration): iot.CfnAccountAuditConfiguration.AuditCheckConfigurationsProperty {
255+
return {
256+
authenticatedCognitoRoleOverlyPermissiveCheck:
257+
this.renderAuditCheckConfiguration(checkConfiguration?.authenticatedCognitoRoleOverlyPermissiveCheck),
258+
caCertificateExpiringCheck: this.renderAuditCheckConfiguration(checkConfiguration?.caCertificateExpiringCheck),
259+
caCertificateKeyQualityCheck: this.renderAuditCheckConfiguration(checkConfiguration?.caCertificateKeyQualityCheck),
260+
conflictingClientIdsCheck: this.renderAuditCheckConfiguration(checkConfiguration?.conflictingClientIdsCheck),
261+
deviceCertificateExpiringCheck: this.renderAuditCheckConfiguration(checkConfiguration?.deviceCertificateExpiringCheck),
262+
deviceCertificateKeyQualityCheck: this.renderAuditCheckConfiguration(checkConfiguration?.deviceCertificateKeyQualityCheck),
263+
deviceCertificateSharedCheck: this.renderAuditCheckConfiguration(checkConfiguration?.deviceCertificateSharedCheck),
264+
intermediateCaRevokedForActiveDeviceCertificatesCheck:
265+
this.renderAuditCheckConfiguration(checkConfiguration?.intermediateCaRevokedForActiveDeviceCertificatesCheck),
266+
iotPolicyOverlyPermissiveCheck: this.renderAuditCheckConfiguration(checkConfiguration?.iotPolicyOverlyPermissiveCheck),
267+
ioTPolicyPotentialMisConfigurationCheck: this.renderAuditCheckConfiguration(checkConfiguration?.ioTPolicyPotentialMisConfigurationCheck),
268+
iotRoleAliasAllowsAccessToUnusedServicesCheck:
269+
this.renderAuditCheckConfiguration(checkConfiguration?.iotRoleAliasAllowsAccessToUnusedServicesCheck),
270+
iotRoleAliasOverlyPermissiveCheck: this.renderAuditCheckConfiguration(checkConfiguration?.iotRoleAliasOverlyPermissiveCheck),
271+
loggingDisabledCheck: this.renderAuditCheckConfiguration(checkConfiguration?.loggingDisabledCheck),
272+
revokedCaCertificateStillActiveCheck: this.renderAuditCheckConfiguration(checkConfiguration?.revokedCaCertificateStillActiveCheck),
273+
revokedDeviceCertificateStillActiveCheck:
274+
this.renderAuditCheckConfiguration(checkConfiguration?.revokedDeviceCertificateStillActiveCheck),
275+
unauthenticatedCognitoRoleOverlyPermissiveCheck:
276+
this.renderAuditCheckConfiguration(checkConfiguration?.unauthenticatedCognitoRoleOverlyPermissiveCheck),
277+
};
278+
}
279+
280+
/**
281+
* Render the audit check configuration
282+
*/
283+
private renderAuditCheckConfiguration(check?: boolean): iot.CfnAccountAuditConfiguration.AuditCheckConfigurationProperty | undefined {
284+
return check === false ? undefined : { enabled: true };
285+
}
286+
}
287+

Diff for: packages/@aws-cdk/aws-iot-alpha/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './action';
2+
export * from './audit-configuration';
23
export * from './iot-sql';
34
export * from './logging';
45
export * from './topic-rule';

Diff for: packages/@aws-cdk/aws-iot-alpha/rosetta/default.ts-fixture

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as actions from '@aws-cdk/aws-iot-actions-alpha';
55
import * as iot from '@aws-cdk/aws-iot-alpha';
66
import * as lambda from 'aws-cdk-lib/aws-lambda';
77
import * as s3 from 'aws-cdk-lib/aws-s3';
8+
import * as sns from 'aws-cdk-lib/aws-sns'
89

910
class Fixture extends Stack {
1011
constructor(scope: Construct, id: string) {

0 commit comments

Comments
 (0)