Skip to content

Commit

Permalink
feat(iot-alpha): support for account audit configuration (#31661)
Browse files Browse the repository at this point in the history
### 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*
  • Loading branch information
badmintoncryer authored Oct 7, 2024
1 parent cff1fcd commit fc19571
Show file tree
Hide file tree
Showing 15 changed files with 1,244 additions and 1 deletion.
48 changes: 48 additions & 0 deletions packages/@aws-cdk/aws-iot-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,51 @@ new iot.Logging(this, 'Logging', {
```

**Note**: All logs are forwarded to the `AWSIotLogsV2` log group in CloudWatch.

## Audit

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.
An audit can help you detect any drifts from security best practices or access policies.

### Account Audit Configuration

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.

You can enable an account audit configuration with the following code:

```ts
// Audit notification are sent to the SNS topic
declare const targetTopic: sns.ITopic;

new iot.AccountAuditConfiguration(this, 'AuditConfiguration', {
targetTopic,
});
```

By default, all audit checks are enabled, but it is also possible to enable only specific audit checks.

```ts
new iot.AccountAuditConfiguration(this, 'AuditConfiguration', {
checkConfiguration: {
// enabled
authenticatedCognitoRoleOverlyPermissiveCheck: true,
// enabled by default
caCertificateExpiringCheck: undefined,
// disabled
caCertificateKeyQualityCheck: false,
conflictingClientIdsCheck: false,
deviceCertificateExpiringCheck: false,
deviceCertificateKeyQualityCheck: false,
deviceCertificateSharedCheck: false,
intermediateCaRevokedForActiveDeviceCertificatesCheck: false,
ioTPolicyPotentialMisConfigurationCheck: false,
iotPolicyOverlyPermissiveCheck: false,
iotRoleAliasAllowsAccessToUnusedServicesCheck: false,
iotRoleAliasOverlyPermissiveCheck: false,
loggingDisabledCheck: false,
revokedCaCertificateStillActiveCheck: false,
revokedDeviceCertificateStillActiveCheck: false,
unauthenticatedCognitoRoleOverlyPermissiveCheck: false,
},
});
```
3 changes: 2 additions & 1 deletion packages/@aws-cdk/aws-iot-alpha/awslint.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"exclude": [
"no-unused-type:@aws-cdk/aws-iot-alpha.ActionConfig",
"props-physical-name:@aws-cdk/aws-iot-alpha.LoggingProps"
"props-physical-name:@aws-cdk/aws-iot-alpha.LoggingProps",
"props-physical-name:@aws-cdk/aws-iot-alpha.AccountAuditConfigurationProps"
]
}
287 changes: 287 additions & 0 deletions packages/@aws-cdk/aws-iot-alpha/lib/audit-configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
import { Resource, Stack, IResource } from 'aws-cdk-lib/core';
import { Construct } from 'constructs';
import * as iot from 'aws-cdk-lib/aws-iot';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as sns from 'aws-cdk-lib/aws-sns';

/**
* Represents AWS IoT Audit Configuration
*/
export interface IAccountAuditConfiguration extends IResource {
/**
* The account ID
* @attribute
*/
readonly accountId: string;
}

/**
* The types of audit checks
*
* @see https://docs.aws.amazon.com/iot-device-defender/latest/devguide/device-defender-audit-checks.html
*/
export interface CheckConfiguration {
/**
* Checks the permissiveness of an authenticated Amazon Cognito identity pool role.
*
* 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
* during the 31 days before the audit is performed.
*
* @default true
*/
readonly authenticatedCognitoRoleOverlyPermissiveCheck?: boolean;

/**
* Checks if a CA certificate is expiring.
*
* This check applies to CA certificates expiring within 30 days or that have expired.
*
* @default true
*/
readonly caCertificateExpiringCheck?: boolean;

/**
* Checks the quality of the CA certificate key.
*
* The quality checks if the key is in a valid format, not expired, and if the key meets a minimum required size.
*
* This check applies to CA certificates that are ACTIVE or PENDING_TRANSFER.
*
* @default true
*/
readonly caCertificateKeyQualityCheck?: boolean;

/**
* Checks if multiple devices connect using the same client ID.
*
* @default true
*/
readonly conflictingClientIdsCheck?: boolean;

/**
* Checks if a device certificate is expiring.
*
* This check applies to device certificates expiring within 30 days or that have expired.
*
* @default true
*/
readonly deviceCertificateExpiringCheck?: boolean;

/**
* Checks the quality of the device certificate key.
*
* The quality checks if the key is in a valid format, not expired, signed by a registered certificate authority,
* and if the key meets a minimum required size.
*
* @default true
*/
readonly deviceCertificateKeyQualityCheck?: boolean;

/**
* Checks if multiple concurrent connections use the same X.509 certificate to authenticate with AWS IoT.
*
* @default true
*/
readonly deviceCertificateSharedCheck?: boolean;

/**
* Checks if device certificates are still active despite being revoked by an intermediate CA.
*
* @default true
*/
readonly intermediateCaRevokedForActiveDeviceCertificatesCheck?: boolean;

/**
* Checks the permissiveness of a policy attached to an authenticated Amazon Cognito identity pool role.
*
* @default true
*/
readonly iotPolicyOverlyPermissiveCheck?: boolean;

/**
* Checks if an AWS IoT policy is potentially misconfigured.
*
* Misconfigured policies, including overly permissive policies, can cause security incidents like allowing devices access to unintended resources.
*
* This check is a warning for you to make sure that only intended actions are allowed before updating the policy.
*
* @default true
*/
readonly ioTPolicyPotentialMisConfigurationCheck?: boolean;

/**
* Checks if a role alias has access to services that haven't been used for the AWS IoT device in the last year.
*
* @default true
*/
readonly iotRoleAliasAllowsAccessToUnusedServicesCheck?: boolean;

/**
* Checks if the temporary credentials provided by AWS IoT role aliases are overly permissive.
*
* @default true
*/
readonly iotRoleAliasOverlyPermissiveCheck?: boolean;

/**
* Checks if AWS IoT logs are disabled.
*
* @default true
*/
readonly loggingDisabledCheck?: boolean;

/**
* Checks if a revoked CA certificate is still active.
*
* @default true
*/
readonly revokedCaCertificateStillActiveCheck?: boolean;

/**
* Checks if a revoked device certificate is still active.
*
* @default true
*/
readonly revokedDeviceCertificateStillActiveCheck?: boolean;

/**
* Checks if policy attached to an unauthenticated Amazon Cognito identity pool role is too permissive.
*
* @default true
*/
readonly unauthenticatedCognitoRoleOverlyPermissiveCheck?: boolean;
}

/**
* Properties for defining AWS IoT Audit Configuration
*/
export interface AccountAuditConfigurationProps {
/**
* The target SNS topic to which audit notifications are sent.
*
* @default - no notifications are sent
*/
readonly targetTopic?: sns.ITopic;

/**
* Specifies which audit checks are enabled and disabled for this account.
*
* @default - all checks are enabled
*/
readonly checkConfiguration?: CheckConfiguration;
}

/**
* Defines AWS IoT Audit Configuration
*/
export class AccountAuditConfiguration extends Resource implements IAccountAuditConfiguration {
/**
* Import an existing AWS IoT Audit Configuration
*
* @param scope The parent creating construct (usually `this`)
* @param id The construct's name
* @param accountId The account ID
*/
public static fromAccountId(scope: Construct, id: string, accountId: string): IAccountAuditConfiguration {
class Import extends Resource implements IAccountAuditConfiguration {
public readonly accountId = accountId;
}
return new Import(scope, id);
}

/**
* The account ID
* @attribute
*/
public readonly accountId: string;

constructor(scope: Construct, id: string, props?: AccountAuditConfigurationProps) {
super(scope, id);

this.accountId = Stack.of(this).account;

const auditRole = new iam.Role(this, 'AuditRole', {
assumedBy: new iam.ServicePrincipal('iot.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSIoTDeviceDefenderAudit'),
],
});

new iot.CfnAccountAuditConfiguration(this, 'Resource', {
accountId: this.accountId,
auditCheckConfigurations: this.renderAuditCheckConfigurations(props?.checkConfiguration),
auditNotificationTargetConfigurations: this.renderAuditNotificationTargetConfigurations(props?.targetTopic),
roleArn: auditRole.roleArn,
});
}

/**
* Render the audit notification target configurations
*/
private renderAuditNotificationTargetConfigurations(
targetTopic?: sns.ITopic,
): iot.CfnAccountAuditConfiguration.AuditNotificationTargetConfigurationsProperty | undefined {
if (!targetTopic) {
return undefined;
}

const notificationRole = new iam.Role(this, 'NotificationRole', {
assumedBy: new iam.ServicePrincipal('iot.amazonaws.com'),
inlinePolicies: {
NotificationPolicy: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
actions: ['sns:Publish'],
resources: [targetTopic.topicArn],
}),
],
}),
},
});

return {
sns: {
enabled: true,
targetArn: targetTopic.topicArn,
roleArn: notificationRole.roleArn,
},
};
}

/**
* Render the audit check configurations
*/
private renderAuditCheckConfigurations(checkConfiguration?: CheckConfiguration): iot.CfnAccountAuditConfiguration.AuditCheckConfigurationsProperty {
return {
authenticatedCognitoRoleOverlyPermissiveCheck:
this.renderAuditCheckConfiguration(checkConfiguration?.authenticatedCognitoRoleOverlyPermissiveCheck),
caCertificateExpiringCheck: this.renderAuditCheckConfiguration(checkConfiguration?.caCertificateExpiringCheck),
caCertificateKeyQualityCheck: this.renderAuditCheckConfiguration(checkConfiguration?.caCertificateKeyQualityCheck),
conflictingClientIdsCheck: this.renderAuditCheckConfiguration(checkConfiguration?.conflictingClientIdsCheck),
deviceCertificateExpiringCheck: this.renderAuditCheckConfiguration(checkConfiguration?.deviceCertificateExpiringCheck),
deviceCertificateKeyQualityCheck: this.renderAuditCheckConfiguration(checkConfiguration?.deviceCertificateKeyQualityCheck),
deviceCertificateSharedCheck: this.renderAuditCheckConfiguration(checkConfiguration?.deviceCertificateSharedCheck),
intermediateCaRevokedForActiveDeviceCertificatesCheck:
this.renderAuditCheckConfiguration(checkConfiguration?.intermediateCaRevokedForActiveDeviceCertificatesCheck),
iotPolicyOverlyPermissiveCheck: this.renderAuditCheckConfiguration(checkConfiguration?.iotPolicyOverlyPermissiveCheck),
ioTPolicyPotentialMisConfigurationCheck: this.renderAuditCheckConfiguration(checkConfiguration?.ioTPolicyPotentialMisConfigurationCheck),
iotRoleAliasAllowsAccessToUnusedServicesCheck:
this.renderAuditCheckConfiguration(checkConfiguration?.iotRoleAliasAllowsAccessToUnusedServicesCheck),
iotRoleAliasOverlyPermissiveCheck: this.renderAuditCheckConfiguration(checkConfiguration?.iotRoleAliasOverlyPermissiveCheck),
loggingDisabledCheck: this.renderAuditCheckConfiguration(checkConfiguration?.loggingDisabledCheck),
revokedCaCertificateStillActiveCheck: this.renderAuditCheckConfiguration(checkConfiguration?.revokedCaCertificateStillActiveCheck),
revokedDeviceCertificateStillActiveCheck:
this.renderAuditCheckConfiguration(checkConfiguration?.revokedDeviceCertificateStillActiveCheck),
unauthenticatedCognitoRoleOverlyPermissiveCheck:
this.renderAuditCheckConfiguration(checkConfiguration?.unauthenticatedCognitoRoleOverlyPermissiveCheck),
};
}

/**
* Render the audit check configuration
*/
private renderAuditCheckConfiguration(check?: boolean): iot.CfnAccountAuditConfiguration.AuditCheckConfigurationProperty | undefined {
return check === false ? undefined : { enabled: true };
}
}

1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-iot-alpha/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './action';
export * from './audit-configuration';
export * from './iot-sql';
export * from './logging';
export * from './topic-rule';
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-iot-alpha/rosetta/default.ts-fixture
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as actions from '@aws-cdk/aws-iot-actions-alpha';
import * as iot from '@aws-cdk/aws-iot-alpha';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as sns from 'aws-cdk-lib/aws-sns'

class Fixture extends Stack {
constructor(scope: Construct, id: string) {
Expand Down
Loading

0 comments on commit fc19571

Please sign in to comment.