diff --git a/packages/@aws-cdk/aws-iot-alpha/README.md b/packages/@aws-cdk/aws-iot-alpha/README.md index ffd905dc5a2ed..65a0c740bf9ee 100644 --- a/packages/@aws-cdk/aws-iot-alpha/README.md +++ b/packages/@aws-cdk/aws-iot-alpha/README.md @@ -139,3 +139,40 @@ new iot.AccountAuditConfiguration(this, 'AuditConfiguration', { }, }); ``` + +### Scheduled Audit + +You can create a [scheduled audit](https://docs.aws.amazon.com/iot-device-defender/latest/devguide/AuditCommands.html#device-defender-AuditCommandsManageSchedules) that is run at a specified time interval. Checks must be enabled for your account by creating `AccountAuditConfiguration`. + +```ts +declare const config: iot.AccountAuditConfiguration; + +// Daily audit +const dailyAudit = new iot.ScheduledAudit(this, 'DailyAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.DAILY, + auditChecks: [ + iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK, + ], +}) + +// Weekly audit +const weeklyAudit = new iot.ScheduledAudit(this, 'WeeklyAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.WEEKLY, + dayOfWeek: iot.DayOfWeek.SUNDAY, + auditChecks: [ + iot.AuditCheck.CA_CERTIFICATE_EXPIRING_CHECK, + ], +}); + +// Monthly audit +const monthlyAudit = new iot.ScheduledAudit(this, 'MonthlyAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.MONTHLY, + dayOfMonth: iot.DayOfMonth.of(1), + auditChecks: [ + iot.AuditCheck.CA_CERTIFICATE_KEY_QUALITY_CHECK, + ], +}); +``` diff --git a/packages/@aws-cdk/aws-iot-alpha/lib/index.ts b/packages/@aws-cdk/aws-iot-alpha/lib/index.ts index 8fe633369a5a5..ed616f70bf433 100644 --- a/packages/@aws-cdk/aws-iot-alpha/lib/index.ts +++ b/packages/@aws-cdk/aws-iot-alpha/lib/index.ts @@ -2,6 +2,7 @@ export * from './action'; export * from './audit-configuration'; export * from './iot-sql'; export * from './logging'; +export * from './scheduled-audit'; export * from './topic-rule'; // AWS::IoT CloudFormation Resources: diff --git a/packages/@aws-cdk/aws-iot-alpha/lib/scheduled-audit.ts b/packages/@aws-cdk/aws-iot-alpha/lib/scheduled-audit.ts new file mode 100644 index 0000000000000..7b69887cae327 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-alpha/lib/scheduled-audit.ts @@ -0,0 +1,378 @@ +import { Resource, Stack, IResource, Token, ArnFormat } from 'aws-cdk-lib/core'; +import { Construct } from 'constructs'; +import * as iot from 'aws-cdk-lib/aws-iot'; +import { IAccountAuditConfiguration } from './audit-configuration'; + +/** + * Represents AWS IoT Scheduled Audit + */ +export interface IScheduledAudit extends IResource { + /** + * The scheduled audit name + * @attribute + */ + readonly scheduledAuditName: string; + + /** + * The ARN of the scheduled audit + * @attribute + */ + readonly scheduledAuditArn: string; +} + +/** + * Construction properties for a Scheduled Audit + */ +export interface ScheduledAuditAttributes { + /** + * The scheduled audit name + */ + readonly scheduledAuditName: string; + + /** + * The ARN of the scheduled audit + */ + readonly scheduledAuditArn: string; +} + +/** + * The AWS IoT Device Defender audit checks + * + * @see https://docs.aws.amazon.com/iot-device-defender/latest/devguide/device-defender-audit-checks.html + */ +export enum AuditCheck { + /** + * 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. + */ + AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK = 'AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK', + + /** + * Checks if a CA certificate is expiring. + * + * This check applies to CA certificates expiring within 30 days or that have expired. + */ + CA_CERTIFICATE_EXPIRING_CHECK = 'CA_CERTIFICATE_EXPIRING_CHECK', + + /** + * 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. + */ + CA_CERTIFICATE_KEY_QUALITY_CHECK = 'CA_CERTIFICATE_KEY_QUALITY_CHECK', + + /** + * Checks if multiple devices connect using the same client ID. + */ + CONFLICTING_CLIENT_IDS_CHECK = 'CONFLICTING_CLIENT_IDS_CHECK', + + /** + * Checks if a device certificate is expiring. + * + * This check applies to device certificates expiring within 30 days or that have expired. + */ + DEVICE_CERTIFICATE_EXPIRING_CHECK = 'DEVICE_CERTIFICATE_EXPIRING_CHECK', + + /** + * 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. + */ + DEVICE_CERTIFICATE_KEY_QUALITY_CHECK = 'DEVICE_CERTIFICATE_KEY_QUALITY_CHECK', + + /** + * Checks if multiple concurrent connections use the same X.509 certificate to authenticate with AWS IoT. + */ + DEVICE_CERTIFICATE_SHARED_CHECK = 'DEVICE_CERTIFICATE_SHARED_CHECK', + + /** + * Checks the permissiveness of a policy attached to an authenticated Amazon Cognito identity pool role. + */ + IOT_POLICY_OVERLY_PERMISSIVE_CHECK = 'IOT_POLICY_OVERLY_PERMISSIVE_CHECK', + + /** + * Checks if a role alias has access to services that haven't been used for the AWS IoT device in the last year. + */ + IOT_ROLE_ALIAS_ALLOWS_ACCESS_TO_UNUSED_SERVICES_CHECK = 'IOT_ROLE_ALIAS_ALLOWS_ACCESS_TO_UNUSED_SERVICES_CHECK', + + /** + * Checks if the temporary credentials provided by AWS IoT role aliases are overly permissive. + */ + IOT_ROLE_ALIAS_OVERLY_PERMISSIVE_CHECK = 'IOT_ROLE_ALIAS_OVERLY_PERMISSIVE_CHECK', + + /** + * Checks if AWS IoT logs are disabled. + */ + LOGGING_DISABLED_CHECK = 'LOGGING_DISABLED_CHECK', + + /** + * Checks if a revoked CA certificate is still active. + */ + REVOKED_CA_CERTIFICATE_STILL_ACTIVE_CHECK = 'REVOKED_CA_CERTIFICATE_STILL_ACTIVE_CHECK', + + /** + * Checks if a revoked device certificate is still active. + */ + REVOKED_DEVICE_CERTIFICATE_STILL_ACTIVE_CHECK = 'REVOKED_DEVICE_CERTIFICATE_STILL_ACTIVE_CHECK', + + /** + * Checks if policy attached to an unauthenticated Amazon Cognito identity pool role is too permissive. + */ + UNAUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK = 'UNAUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK', +} + +/** + * The day of the week on which the scheduled audit takes place. + */ +export enum DayOfWeek { + /** + * Sunday + */ + SUNDAY = 'SUN', + + /** + * Monday + */ + MONDAY = 'MON', + + /** + * Tuesday + */ + TUESDAY = 'TUE', + + /** + * Wednesday + */ + WEDNESDAY = 'WED', + + /** + * Thursday + */ + THURSDAY = 'THU', + + /** + * Friday + */ + FRIDAY = 'FRI', + + /** + * Saturday + */ + SATURDAY = 'SAT', +} + +/** + * The day of the month on which the scheduled audit takes place. + */ +export class DayOfMonth { + /** + * The last day of the month + */ + public static readonly LAST_DAY = new DayOfMonth('LAST'); + + /** + * Custom day of the month + * @param day the day of the month + */ + public static of(day: number): DayOfMonth { + if (day < 1 || day > 31) { + throw new Error(`Day of month must be between 1 and 31, got: ${day}`); + } + if (!Number.isInteger(day)) { + throw new Error(`Day of month must be an integer, got: ${day}`); + } + return new DayOfMonth(String(day)); + } + + /** + * + * @param day The day of the month + */ + private constructor(public readonly day: string) {} +} + +/** + * The frequency at which the scheduled audit takes place. + */ +export enum Frequency { + /** + * Daily + */ + DAILY = 'DAILY', + + /** + * Weekly + */ + WEEKLY = 'WEEKLY', + + /** + * Bi-weekly + */ + BI_WEEKLY = 'BIWEEKLY', + + /** + * Monthly + */ + MONTHLY = 'MONTHLY', +} + +/** + * Properties for defining AWS IoT Scheduled Audit + */ +export interface ScheduledAuditProps { + /** + * Which checks are performed during the scheduled audit. + * + * Checks must be enabled for your account. + */ + readonly auditChecks: AuditCheck[]; + + /** + * Account audit configuration. + * + * The audit checks specified in `auditChecks` must be enabled in this configuration. + */ + readonly accountAuditConfiguration: IAccountAuditConfiguration; + + /** + * The day of the week on which the scheduled audit is run (if the frequency is "WEEKLY" or "BIWEEKLY"). + * + * @default - required if frequency is "WEEKLY" or "BIWEEKLY", not allowed otherwise + */ + readonly dayOfWeek?: DayOfWeek; + + /** + * The day of the month on which the scheduled audit is run (if the frequency is "MONTHLY"). + * + * If days 29-31 are specified, and the month does not have that many days, the audit takes place on the "LAST" day of the month. + * + * @default - required if frequency is "MONTHLY", not allowed otherwise + */ + readonly dayOfMonth?: DayOfMonth; + + /** + * How often the scheduled audit occurs. + */ + readonly frequency: Frequency; + + /** + * The name of the scheduled audit. + * + * @default - auto generated name + */ + readonly scheduledAuditName?: string; +} + +/** + * Defines AWS IoT Scheduled Audit + */ +export class ScheduledAudit extends Resource implements IScheduledAudit { + /** + * Import an existing AWS IoT Scheduled Audit from its ARN. + * + * @param scope The parent creating construct (usually `this`) + * @param id The construct's name + * @param scheduledAuditArn The ARN of the scheduled audit + */ + public static fromScheduledAuditArn(scope: Construct, id: string, scheduledAuditArn: string): IScheduledAudit { + const name = Stack.of(scope).splitArn(scheduledAuditArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName; + if (!name) { + throw new Error(`No scheduled audit name found in ARN: '${scheduledAuditArn}'`); + } + + return this.fromScheduledAuditAttributes(scope, id, { scheduledAuditArn: scheduledAuditArn, scheduledAuditName: name }); + } + + /** + * Import an existing AWS IoT Scheduled Audit from its attributes. + * + * @param scope The parent creating construct (usually `this`) + * @param id The construct's name + * @param attrs The scheduled audit attributes + */ + public static fromScheduledAuditAttributes(scope: Construct, id: string, attrs: ScheduledAuditAttributes): IScheduledAudit { + class Import extends Resource implements IScheduledAudit { + public readonly scheduledAuditArn = attrs.scheduledAuditArn; + public readonly scheduledAuditName = attrs.scheduledAuditName; + } + return new Import(scope, id); + } + + /** + * The scheduled audit name + * @attribute + */ + public readonly scheduledAuditName: string; + + /** + * The ARN of the scheduled audit + * @attribute + */ + public readonly scheduledAuditArn: string; + + constructor(scope: Construct, id: string, props: ScheduledAuditProps) { + super(scope, id); + + if (props.auditChecks.length === 0) { + throw new Error('At least one \'auditChecks\' must be specified.'); + } + + switch (props.frequency) { + case Frequency.DAILY: + if (props.dayOfWeek) { + throw new Error('Day of the week must not be specified for daily audits.'); + } + if (props.dayOfMonth) { + throw new Error('Day of the month must not be specified for daily audits.'); + } + break; + case Frequency.WEEKLY: + case Frequency.BI_WEEKLY: + if (!props.dayOfWeek) { + throw new Error('Day of the week must be specified for weekly or bi-weekly audits.'); + } + if (props.dayOfMonth) { + throw new Error('Day of the month must not be specified for weekly or bi-weekly audits.'); + } + break; + case Frequency.MONTHLY: + if (!props.dayOfMonth) { + throw new Error('Day of the month must be specified for monthly audits.'); + } + if (props.dayOfWeek) { + throw new Error('Day of the week must not be specified for monthly audits.'); + } + break; + default: + throw new Error('Invalid frequency specified.'); + } + + if (props.scheduledAuditName !== undefined && !Token.isUnresolved(props.scheduledAuditName)) { + if (props.scheduledAuditName.length < 1 || props.scheduledAuditName.length > 128) { + throw new Error(`Scheduled audit name must be between 1 and 128 characters, got: ${props.scheduledAuditName.length}`); + } + if (!/^[a-zA-Z0-9:_-]+$/.test(props.scheduledAuditName)) { + throw new Error(`Scheduled audit name must be alphanumeric and may include colons, underscores, and hyphens, got: ${props.scheduledAuditName}`); + } + } + + const resource = new iot.CfnScheduledAudit(this, 'Resource', { + scheduledAuditName: props.scheduledAuditName, + targetCheckNames: props.auditChecks, + dayOfWeek: props.dayOfWeek, + dayOfMonth: props.dayOfMonth?.day, + frequency: props.frequency, + }); + + this.scheduledAuditName = resource.ref; + this.scheduledAuditArn = resource.attrScheduledAuditArn; + + resource.node.addDependency(props.accountAuditConfiguration); + } +} + diff --git a/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/IotAuditConfigurationTestStack.assets.json b/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/IotAuditConfigurationTestStack.assets.json index 621c14e610285..770e7ed409613 100644 --- a/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/IotAuditConfigurationTestStack.assets.json +++ b/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/IotAuditConfigurationTestStack.assets.json @@ -1,7 +1,7 @@ { "version": "38.0.1", "files": { - "d809d9222ee845df66ea2b3540b3dffe1098b00da280f913784b983e7e4ddf35": { + "c093a5b4a568daafc27fab102fea007eaf70c883b8e02171441d44e702e0cebc": { "source": { "path": "IotAuditConfigurationTestStack.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "d809d9222ee845df66ea2b3540b3dffe1098b00da280f913784b983e7e4ddf35.json", + "objectKey": "c093a5b4a568daafc27fab102fea007eaf70c883b8e02171441d44e702e0cebc.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/IotAuditConfigurationTestStack.template.json b/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/IotAuditConfigurationTestStack.template.json index 16606b73febd4..ed4e8c63400f8 100644 --- a/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/IotAuditConfigurationTestStack.template.json +++ b/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/IotAuditConfigurationTestStack.template.json @@ -145,6 +145,61 @@ ] } } + }, + "DailyAudit1160906D": { + "Type": "AWS::IoT::ScheduledAudit", + "Properties": { + "Frequency": "DAILY", + "TargetCheckNames": [ + "AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK" + ] + }, + "DependsOn": [ + "AuditConfigurationAuditRole0FFA1461", + "AuditConfigurationNotificationRole9774BAD4", + "AuditConfiguration8C793652" + ] + }, + "WeeklyAudit5489D5FF": { + "Type": "AWS::IoT::ScheduledAudit", + "Properties": { + "DayOfWeek": "SUN", + "Frequency": "WEEKLY", + "TargetCheckNames": [ + "CA_CERTIFICATE_EXPIRING_CHECK" + ] + }, + "DependsOn": [ + "AuditConfigurationAuditRole0FFA1461", + "AuditConfigurationNotificationRole9774BAD4", + "AuditConfiguration8C793652" + ] + }, + "MonthlyAudit11A7B28C": { + "Type": "AWS::IoT::ScheduledAudit", + "Properties": { + "DayOfMonth": "LAST", + "Frequency": "MONTHLY", + "TargetCheckNames": [ + "CA_CERTIFICATE_KEY_QUALITY_CHECK", + "CONFLICTING_CLIENT_IDS_CHECK", + "DEVICE_CERTIFICATE_EXPIRING_CHECK", + "DEVICE_CERTIFICATE_KEY_QUALITY_CHECK", + "DEVICE_CERTIFICATE_SHARED_CHECK", + "IOT_POLICY_OVERLY_PERMISSIVE_CHECK", + "IOT_ROLE_ALIAS_ALLOWS_ACCESS_TO_UNUSED_SERVICES_CHECK", + "IOT_ROLE_ALIAS_OVERLY_PERMISSIVE_CHECK", + "LOGGING_DISABLED_CHECK", + "REVOKED_CA_CERTIFICATE_STILL_ACTIVE_CHECK", + "REVOKED_DEVICE_CERTIFICATE_STILL_ACTIVE_CHECK", + "UNAUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK" + ] + }, + "DependsOn": [ + "AuditConfigurationAuditRole0FFA1461", + "AuditConfigurationNotificationRole9774BAD4", + "AuditConfiguration8C793652" + ] } }, "Parameters": { diff --git a/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/manifest.json b/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/manifest.json index f8743822993c2..da95d1a0a5749 100644 --- a/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/manifest.json @@ -19,7 +19,7 @@ "notificationArns": [], "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/d809d9222ee845df66ea2b3540b3dffe1098b00da280f913784b983e7e4ddf35.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/c093a5b4a568daafc27fab102fea007eaf70c883b8e02171441d44e702e0cebc.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -59,6 +59,24 @@ "data": "AuditConfiguration8C793652" } ], + "/IotAuditConfigurationTestStack/DailyAudit/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DailyAudit1160906D" + } + ], + "/IotAuditConfigurationTestStack/WeeklyAudit/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "WeeklyAudit5489D5FF" + } + ], + "/IotAuditConfigurationTestStack/MonthlyAudit/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MonthlyAudit11A7B28C" + } + ], "/IotAuditConfigurationTestStack/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/tree.json b/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/tree.json index 1748d3c01b041..a8a4569a1201f 100644 --- a/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.js.snapshot/tree.json @@ -243,6 +243,100 @@ "version": "0.0.0" } }, + "DailyAudit": { + "id": "DailyAudit", + "path": "IotAuditConfigurationTestStack/DailyAudit", + "children": { + "Resource": { + "id": "Resource", + "path": "IotAuditConfigurationTestStack/DailyAudit/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IoT::ScheduledAudit", + "aws:cdk:cloudformation:props": { + "frequency": "DAILY", + "targetCheckNames": [ + "AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK" + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iot-alpha.ScheduledAudit", + "version": "0.0.0" + } + }, + "WeeklyAudit": { + "id": "WeeklyAudit", + "path": "IotAuditConfigurationTestStack/WeeklyAudit", + "children": { + "Resource": { + "id": "Resource", + "path": "IotAuditConfigurationTestStack/WeeklyAudit/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IoT::ScheduledAudit", + "aws:cdk:cloudformation:props": { + "dayOfWeek": "SUN", + "frequency": "WEEKLY", + "targetCheckNames": [ + "CA_CERTIFICATE_EXPIRING_CHECK" + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iot-alpha.ScheduledAudit", + "version": "0.0.0" + } + }, + "MonthlyAudit": { + "id": "MonthlyAudit", + "path": "IotAuditConfigurationTestStack/MonthlyAudit", + "children": { + "Resource": { + "id": "Resource", + "path": "IotAuditConfigurationTestStack/MonthlyAudit/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IoT::ScheduledAudit", + "aws:cdk:cloudformation:props": { + "dayOfMonth": "LAST", + "frequency": "MONTHLY", + "targetCheckNames": [ + "CA_CERTIFICATE_KEY_QUALITY_CHECK", + "CONFLICTING_CLIENT_IDS_CHECK", + "DEVICE_CERTIFICATE_EXPIRING_CHECK", + "DEVICE_CERTIFICATE_KEY_QUALITY_CHECK", + "DEVICE_CERTIFICATE_SHARED_CHECK", + "IOT_POLICY_OVERLY_PERMISSIVE_CHECK", + "IOT_ROLE_ALIAS_ALLOWS_ACCESS_TO_UNUSED_SERVICES_CHECK", + "IOT_ROLE_ALIAS_OVERLY_PERMISSIVE_CHECK", + "LOGGING_DISABLED_CHECK", + "REVOKED_CA_CERTIFICATE_STILL_ACTIVE_CHECK", + "REVOKED_DEVICE_CERTIFICATE_STILL_ACTIVE_CHECK", + "UNAUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK" + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iot-alpha.ScheduledAudit", + "version": "0.0.0" + } + }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "IotAuditConfigurationTestStack/BootstrapVersion", diff --git a/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.ts b/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.ts index ce8535d25d0a0..6d37bf3b59df0 100644 --- a/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.ts +++ b/packages/@aws-cdk/aws-iot-alpha/test/integ.audit-configuration.ts @@ -9,9 +9,46 @@ class TestStack extends cdk.Stack { const targetTopic = new sns.Topic(this, 'Topic'); - new iot.AccountAuditConfiguration(this, 'AuditConfiguration', { + const config = new iot.AccountAuditConfiguration(this, 'AuditConfiguration', { targetTopic, }); + + new iot.ScheduledAudit(this, 'DailyAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.DAILY, + auditChecks: [ + iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK, + ], + }); + + new iot.ScheduledAudit(this, 'WeeklyAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.WEEKLY, + dayOfWeek: iot.DayOfWeek.SUNDAY, + auditChecks: [ + iot.AuditCheck.CA_CERTIFICATE_EXPIRING_CHECK, + ], + }); + + new iot.ScheduledAudit(this, 'MonthlyAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.MONTHLY, + dayOfMonth: iot.DayOfMonth.LAST_DAY, + auditChecks: [ + iot.AuditCheck.CA_CERTIFICATE_KEY_QUALITY_CHECK, + iot.AuditCheck.CONFLICTING_CLIENT_IDS_CHECK, + iot.AuditCheck.DEVICE_CERTIFICATE_EXPIRING_CHECK, + iot.AuditCheck.DEVICE_CERTIFICATE_KEY_QUALITY_CHECK, + iot.AuditCheck.DEVICE_CERTIFICATE_SHARED_CHECK, + iot.AuditCheck.IOT_POLICY_OVERLY_PERMISSIVE_CHECK, + iot.AuditCheck.IOT_ROLE_ALIAS_ALLOWS_ACCESS_TO_UNUSED_SERVICES_CHECK, + iot.AuditCheck.IOT_ROLE_ALIAS_OVERLY_PERMISSIVE_CHECK, + iot.AuditCheck.LOGGING_DISABLED_CHECK, + iot.AuditCheck.REVOKED_CA_CERTIFICATE_STILL_ACTIVE_CHECK, + iot.AuditCheck.REVOKED_DEVICE_CERTIFICATE_STILL_ACTIVE_CHECK, + iot.AuditCheck.UNAUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK, + ], + }); } } diff --git a/packages/@aws-cdk/aws-iot-alpha/test/scheduled-audit.test.ts b/packages/@aws-cdk/aws-iot-alpha/test/scheduled-audit.test.ts new file mode 100644 index 0000000000000..85972b4571483 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-alpha/test/scheduled-audit.test.ts @@ -0,0 +1,231 @@ +import { Template } from 'aws-cdk-lib/assertions'; +import * as cdk from 'aws-cdk-lib'; +import * as iot from '../lib'; + +let stack: cdk.Stack; +let config: iot.AccountAuditConfiguration; + +beforeEach(() => { + stack = new cdk.Stack(); + config = new iot.AccountAuditConfiguration(stack, 'AccountAuditConfiguration'); +}); + +test('Default property', () => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.DAILY, + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + }); + + Template.fromStack(stack).hasResource('AWS::IoT::ScheduledAudit', { + DependsOn: ['AccountAuditConfigurationAuditRoleBEFDE978', 'AccountAuditConfigurationA87E7758'], + Properties: { + Frequency: 'DAILY', + TargetCheckNames: ['AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK'], + }, + }); +}); + +test('full settings', () => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.DAILY, + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + scheduledAuditName: 'MyScheduledAudit', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IoT::ScheduledAudit', { + Frequency: 'DAILY', + ScheduledAuditName: 'MyScheduledAudit', + TargetCheckNames: ['AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK'], + }); +}); + +describe('daily audit', () => { + test('throw error for specifying day of week', () => { + expect(() => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.DAILY, + dayOfWeek: iot.DayOfWeek.MONDAY, + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + }); + }).toThrow('Day of the week must not be specified for daily audits.'); + }); + + test('throw error for specifying day of month', () => { + expect(() => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.DAILY, + dayOfMonth: iot.DayOfMonth.of(29), + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + }); + }).toThrow('Day of the month must not be specified for daily audits.'); + }); +}); + +describe('weekly audit', () => { + test('set day of week', () => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.WEEKLY, + dayOfWeek: iot.DayOfWeek.MONDAY, + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IoT::ScheduledAudit', { + Frequency: 'WEEKLY', + DayOfWeek: 'MON', + TargetCheckNames: ['AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK'], + }); + }); + + test('throw error for missing day of week', () => { + expect(() => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.WEEKLY, + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + }); + }).toThrow('Day of the week must be specified for weekly or bi-weekly audits.'); + }); + + test('throw error for specifying both day of week and day of month', () => { + expect(() => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.WEEKLY, + dayOfWeek: iot.DayOfWeek.MONDAY, + dayOfMonth: iot.DayOfMonth.of(29), + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + }); + }).toThrow('Day of the month must not be specified for weekly or bi-weekly audits.'); + }); +}); + +describe('bi-weekly audit', () => { + test('set day of week', () => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.BI_WEEKLY, + dayOfWeek: iot.DayOfWeek.MONDAY, + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IoT::ScheduledAudit', { + Frequency: 'BIWEEKLY', + DayOfWeek: 'MON', + TargetCheckNames: ['AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK'], + }); + }); + + test('throw error for missing day of week', () => { + expect(() => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.BI_WEEKLY, + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + }); + }).toThrow('Day of the week must be specified for weekly or bi-weekly audits.'); + }); + + test('throw error for specifying both day of week and day of month', () => { + expect(() => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.BI_WEEKLY, + dayOfWeek: iot.DayOfWeek.MONDAY, + dayOfMonth: iot.DayOfMonth.of(29), + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + }); + }).toThrow('Day of the month must not be specified for weekly or bi-weekly audits.'); + }); +}); + +describe('monthly audit', () => { + test('set day of month', () => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.MONTHLY, + dayOfMonth: iot.DayOfMonth.of(29), + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IoT::ScheduledAudit', { + Frequency: 'MONTHLY', + DayOfMonth: '29', + TargetCheckNames: ['AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK'], + }); + }); + + test('throw error for missing day of month', () => { + expect(() => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.MONTHLY, + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + }); + }).toThrow('Day of the month must be specified for monthly audits.'); + }); + + test('throw error for specifying both day of week and day of month', () => { + expect(() => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.MONTHLY, + dayOfWeek: iot.DayOfWeek.MONDAY, + dayOfMonth: iot.DayOfMonth.of(29), + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + }); + }).toThrow('Day of the week must not be specified for monthly audits.'); + }); +}); + +test.each(['', 'a'.repeat(129)])('throw error for invalid length of scheduled audit name %s', (name) => { + expect(() => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.DAILY, + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + scheduledAuditName: name, + }); + }).toThrow(`Scheduled audit name must be between 1 and 128 characters, got: ${name.length}`); +}); + +test('throw error for invalid scheduled audit name', () => { + expect(() => { + new iot.ScheduledAudit(stack, 'ScheduledAudit', { + accountAuditConfiguration: config, + frequency: iot.Frequency.DAILY, + auditChecks: [iot.AuditCheck.AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK], + scheduledAuditName: '*!()', + }); + }).toThrow('Scheduled audit name must be alphanumeric and may include colons, underscores, and hyphens, got: *!()'); +}); + +test('import by attributes', () => { + const name = 'scheduled-audit-name'; + const arn = 'arn:aws:iot:us-east-1:123456789012:scheduledaudit/scheduled-audit-name'; + + const scheduledAudit = iot.ScheduledAudit.fromScheduledAuditAttributes(stack, 'AccountAuditConfigurationFromId', { + scheduledAuditName: name, + scheduledAuditArn: arn, + }); + + expect(scheduledAudit).toMatchObject({ + scheduledAuditName: name, + scheduledAuditArn: arn, + }); +}); + +test('import by arn', () => { + const arn = 'arn:aws:iot:us-east-1:123456789012:scheduledaudit/scheduled-audit-name'; + + const scheduledAudit = iot.ScheduledAudit.fromScheduledAuditArn(stack, 'AccountAuditConfigurationFromArn', arn); + + expect(scheduledAudit).toMatchObject({ + scheduledAuditArn: arn, + scheduledAuditName: 'scheduled-audit-name', + }); +});