CDK Aspects
A collection of AWS CDK Aspects for infrastructure compliance and tagging enforcement, featuring:
- Tag validation through the
RequiredTagsCheckeraspect - Automated tagging with the
addTagsToStackutility function - Policy enforcement at CDK synthesis time
- Flexible configuration supporting organisation-specific tagging requirements
- Error annotations for missing or incorrect tag configurations
- Type-safe tag definitions with TypeScript support
Features
- RequiredTagsChecker Aspect:
- Validates presence of mandatory tags on CDK stacks
- Ensures stacks have at least one tag applied
- Generates synthesis-time errors for missing required tags
- Supports custom required tag lists per organisation
- Tag Management Utilities:
addTagsToStack()- Bulk apply tags to CDK stacks- Type-safe tag key/value definitions
- Consistent tagging patterns across infrastructure
- Compliance: Enforce organizational tagging policies at build time
- Flexibility: Configurable required tags to match your governance requirements
- Integration: Works seamlessly with existing CDK constructs and stacks
Usage
Basic Tag Validation
import { App, Stack } from 'aws-cdk-lib';
import { Aspects } from 'aws-cdk-lib';
import { RequiredTagsChecker } from '@leighton-digital/cloud-blocks';
const app = new App();
const stack = new Stack(app, 'MyStack');
// Define required tags for your organisation
const requiredTags = ['Environment', 'Owner', 'Project', 'CostCenter'];
// Apply the aspect to enforce tagging policy
Aspects.of(app).add(new RequiredTagsChecker(requiredTags));
// This will cause a synthesis error if any required tags are missing
Adding Tags to Stacks
import { App, Stack } from 'aws-cdk-lib';
import { addTagsToStack } from '@leighton-digital/cloud-blocks';
const app = new App();
const stack = new Stack(app, 'MyStack');
// Define your tags
const commonTags = {
Environment: 'production',
Owner: 'platform-team',
Project: 'customer-portal',
CostCenter: 'engineering',
ManagedBy: 'cdk',
};
// Apply tags to the stack
addTagsToStack(stack, commonTags);
Complete Example with Validation
import { App, Stack, StackProps } from 'aws-cdk-lib';
import { Aspects } from 'aws-cdk-lib';
import { Bucket } from 'aws-cdk-lib/aws-s3';
import {
addTagsToStack,
RequiredTagsChecker
} from '@leighton-digital/cloud-blocks';
import type { Tags } from '@leighton-digital/cloud-blocks';
// Define organisation-wide required tags
const organizationRequiredTags = [
'Environment',
'Owner',
'Project',
'CostCenter',
'DataClassification'
];
// Define common tags for this application
const applicationTags: Tags = {
Environment: 'production',
Owner: 'platform-team',
Project: 'customer-portal',
CostCenter: 'engineering',
DataClassification: 'confidential',
Application: 'web-api',
Version: '1.2.0',
};
class MyApplicationStack extends Stack {
constructor(scope: App, id: string, props?: StackProps) {
super(scope, id, props);
// Apply standard tags to this stack
addTagsToStack(this, applicationTags);
// Create some resources - they will inherit the stack tags
new Bucket(this, 'DataBucket', {
bucketName: 'my-app-data-bucket',
});
}
}
const app = new App();
// Create the stack
new MyApplicationStack(app, 'CustomerPortalStack');
// Apply tag validation to the entire app
Aspects.of(app).add(new RequiredTagsChecker(organizationRequiredTags));
// Synthesize the app - will fail if required tags are missing
app.synth();
Environment-Specific Tagging
import { App, Stack } from 'aws-cdk-lib';
import { addTagsToStack } from '@leighton-digital/cloud-blocks';
import { getStage } from '@leighton-digital/cloud-blocks';
function createEnvironmentTags(stage: string): Tags {
const baseTags = {
Owner: 'platform-team',
Project: 'customer-portal',
ManagedBy: 'cdk',
};
switch (stage) {
case 'dev':
return {
...baseTags,
Environment: 'development',
CostCenter: 'development',
DataClassification: 'internal',
AutoShutdown: 'true',
};
case 'staging':
return {
...baseTags,
Environment: 'staging',
CostCenter: 'engineering',
DataClassification: 'internal',
AutoShutdown: 'false',
};
case 'prod':
return {
...baseTags,
Environment: 'production',
CostCenter: 'engineering',
DataClassification: 'confidential',
AutoShutdown: 'false',
BackupRequired: 'true',
};
default:
throw new Error(`Unknown stage: ${stage}`);
}
}
const app = new App();
const stage = getStage(app);
const stack = new Stack(app, `MyStack-${stage}`);
// Apply environment-specific tags
const environmentTags = createEnvironmentTags(stage);
addTagsToStack(stack, environmentTags);
Multi-Stack Application with Shared Tags
import { App, Stack } from 'aws-cdk-lib';
import { Aspects } from 'aws-cdk-lib';
import {
addTagsToStack,
RequiredTagsChecker
} from '@leighton-digital/cloud-blocks';
class DatabaseStack extends Stack {
constructor(scope: App, id: string) {
super(scope, id);
// Stack-specific tags
addTagsToStack(this, {
Component: 'database',
DataRetention: '7years',
BackupSchedule: 'daily',
});
}
}
class ApiStack extends Stack {
constructor(scope: App, id: string) {
super(scope, id);
// Stack-specific tags
addTagsToStack(this, {
Component: 'api',
Scaling: 'auto',
LoadBalanced: 'true',
});
}
}
const app = new App();
// Shared tags across all stacks
const sharedTags = {
Environment: 'production',
Owner: 'platform-team',
Project: 'e-commerce',
CostCenter: 'engineering',
};
// Create stacks
const dbStack = new DatabaseStack(app, 'DatabaseStack');
const apiStack = new ApiStack(app, 'ApiStack');
// Apply shared tags to all stacks
addTagsToStack(dbStack, sharedTags);
addTagsToStack(apiStack, sharedTags);
// Enforce required tags across the entire application
const requiredTags = ['Environment', 'Owner', 'Project', 'CostCenter', 'Component'];
Aspects.of(app).add(new RequiredTagsChecker(requiredTags));
Tag Types
TagKey and TagValue
// Basic tag types
export type TagKey = string;
export type TagValue = string;
// Individual tag
export interface Tag {
key: TagKey;
value: TagValue;
}
Tags Map
// Multiple tags as key/value pairs
export type Tags = Record<TagKey, TagValue>;
const myTags: Tags = {
Environment: 'production',
Owner: 'team-alpha',
Project: 'web-portal',
};
Required Tags
// List of mandatory tag keys
export type RequiredTags = TagKey[];
const organizationRequiredTags: RequiredTags = [
'Environment',
'Owner',
'Project',
'CostCenter',
'DataClassification'
];
Organizational Tagging Patterns
Standard Tag Categories
// Operational tags
const operationalTags = {
Environment: 'production', // dev, staging, prod
Owner: 'platform-team', // Responsible team/person
ManagedBy: 'cdk', // How resource is managed
};
// Business tags
const businessTags = {
Project: 'customer-portal', // Business project
CostCenter: 'engineering', // Billing/cost allocation
Application: 'web-api', // Application name
};
// Security tags
const securityTags = {
DataClassification: 'confidential', // public, internal, confidential, restricted
Compliance: 'sox', // Regulatory requirements
BackupRequired: 'true', // Backup policy
};
// Operational tags
const automationTags = {
AutoShutdown: 'false', // Automated shutdown policy
Monitoring: 'enabled', // Monitoring configuration
Scaling: 'auto', // Scaling policy
};
Best Practices
// Create reusable tag factories
export function createStandardTags(
environment: string,
owner: string,
project: string,
additionalTags: Tags = {}
): Tags {
return {
Environment: environment,
Owner: owner,
Project: project,
ManagedBy: 'cdk',
CreatedDate: new Date().toISOString().split('T')[0],
...additionalTags,
};
}
// Usage
const prodTags = createStandardTags('production', 'platform-team', 'customer-portal', {
CostCenter: 'engineering',
DataClassification: 'confidential',
});
Error Handling and Validation
Synthesis-Time Validation
When required tags are missing, the CDK synthesis will fail with clear error messages:
# Example error output
[ERROR] There are no tags on "MyStack"
[ERROR] "Environment" is missing from stack with id "MyStack"
[ERROR] "Owner" is missing from stack with id "MyStack"
[ERROR] "Project" is missing from stack with id "MyStack"
Common Validation Scenarios
The RequiredTagsChecker validates:
- No tags present: Ensures stacks have at least one tag
- Missing required tags: Checks all mandatory tags are present
- Stack-level validation: Only validates CDK stacks, ignores other constructs
What gets created
Aspect Behaviour
- RequiredTagsChecker:
- Visits all constructs during CDK synthesis
- Validates only
Stackconstructs - Adds error annotations for missing tags
- Causes synthesis to fail if validation fails
Tag Application
- addTagsToStack:
- Applies tags using
cdk.Tags.of(stack).add() - Tags propagate to all child resources by default
- Supports tag inheritance and overrides
- Applies tags using
Integration with CDK
Tag Inheritance
CDK tags applied to stacks automatically propagate to child resources:
// Tags applied to stack will appear on the bucket
addTagsToStack(stack, { Environment: 'prod' });
new Bucket(stack, 'MyBucket', {
// This bucket will have Environment=prod tag
bucketName: 'my-bucket',
});
Tag Overrides
Child resources can override inherited tags:
addTagsToStack(stack, { Environment: 'prod' });
new Bucket(stack, 'DevBucket', {
bucketName: 'dev-bucket',
});
// Override the Environment tag for this specific bucket
Tags.of(bucket).add('Environment', 'development');
Operational notes & caveats
- Synthesis-time validation: Tag validation occurs during
cdk synth, not at runtime - Stack-only validation: The aspect only validates CDK Stack constructs, not individual resources
- Tag propagation: Tags applied to stacks automatically propagate to child resources unless overridden
- Case sensitivity: Tag keys and values are case-sensitive
- AWS limits: AWS has limits on number of tags (50 per resource) and tag key/value length (128/256 characters)
- Cost implications: Tags are used for cost allocation and billing - ensure consistent tagging for accurate cost tracking
Troubleshooting
- "There are no tags on [StackName]" → Apply tags using
addTagsToStack()or CDK'sTags.of(stack).add() - "[TagKey] is missing from stack" → Add the missing required tag to your stack's tag configuration
- Synthesis fails with tag errors → Review the error messages and ensure all required tags are present
- Tags not appearing on resources → Verify tags are applied to the stack, not just individual constructs
- Tag inheritance issues → Check that child constructs haven't overridden inherited tags
Best Practices
- Define organisation-wide required tags early in your CDK adoption
- Use tag factories for consistent tag application across projects
- Apply aspects at the App level to enforce policies across all stacks
- Include cost allocation tags for accurate billing and cost tracking
- Use environment-specific tag values for proper resource classification
- Document your tagging strategy for team consistency
- Validate tags in CI/CD by running
cdk synthin your deployment pipeline