Note: This is a sample project for demonstration and learning purposes.
This project demonstrates how to automate the process of exporting Amazon Certificate Manager (ACM) certificates and installing them on EC2 instances and on-premises servers. It also showcases automated certificate renewal, creating a seamless, event-driven workflow that requires minimal manual intervention. The solution is designed to be flexible and customizable for different operating systems and application types.
- Implement secure certificate management using AWS services
- Build event-driven automation with Step Functions and EventBridge
- Apply tag-based targeting for EC2 instance management
- Implement attribute-based access control (ABAC) for security
- Create a complete serverless workflow with proper error handling
Before setting up this solution, ensure you have:
- AWS CLI configured with appropriate permissions
- An AWS account with access to:
- ACM (Amazon Certificate Manager)
- Lambda
- Step Functions
- DynamoDB
- Systems Manager (SSM)
- Secrets Manager
- EventBridge
- API Gateway
- IAM
- At least one ACM certificate that you want to export (must have "Exportable" flag set to true)
- One or more EC2 instances with:
- SSM Agent installed and running
- Proper IAM instance profile attached
- Network connectivity to AWS services
- IAM permissions to create and manage:
- CloudFormation stacks
- Lambda functions
- IAM roles and policies
- DynamoDB tables
- Step Functions state machines
- EventBridge rules
- API Gateway endpoints
Deploy the solution quickly using the provided CloudFormation templates:
-
Deploy the CloudFormation stack to the central account where the solution will be installed:
aws cloudformation deploy \ --stack-name acm-cert-automation-crossaccount \ --template-file infra/cloudformation/acm-cert-export-cross-account.yaml \ --capabilities CAPABILITY_NAMED_IAM
This template includes:
- Enhanced DynamoDB table with composite key structure
- Secure SSM document for certificate installation
- Automatic certificate renewal via EventBridge
- IAM roles with least privilege permissions
-
Deploy the CloudFormation stack to the target account(s) where the EC2 instances reside:
Option A: Single Account Deployment
aws cloudformation deploy \ --stack-name acm-cert-target-iam \ --template-file infra/cloudformation/target-account-iam-simplified.yaml \ --parameter-overrides CentralAccountId=123456789012 \ --capabilities CAPABILITY_NAMED_IAM
Option B: Multi-Account Deployment using StackSets
# Create StackSet in the central account aws cloudformation create-stack-set \ --stack-set-name acm-cert-target-iam-stackset \ --template-body file://infra/cloudformation/target-account-iam-simplified.yaml \ --parameters ParameterKey=CentralAccountId,ParameterValue=123456789012 \ --capabilities CAPABILITY_NAMED_IAM \ --permission-model SERVICE_MANAGED \ --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false # Deploy to multiple target accounts aws cloudformation create-stack-instances \ --stack-set-name acm-cert-target-iam-stackset \ --deployment-targets OrganizationalUnitIds=ou-xxxx-xxxxxxxx \ --regions us-east-1
Note: StackSets require AWS Organizations and appropriate permissions. For specific account targeting, replace
OrganizationalUnitIdswithAccounts=111111111111,222222222222 -
Tag your target EC2 instances and their IAM instance profiles:
Tag the EC2 instance:
aws ec2 create-tags \ --resources i-1234567890abcdef0 \ --tags Key=<YOUR_TAG_KEY>,Value=<YOUR_TAG_VALUE>
Tag the IAM role attached to the instance profile:
aws iam tag-role \ --role-name <YOUR_INSTANCE_PROFILE_ROLE_NAME> \ --tags Key=<YOUR_TAG_KEY>,Value=<YOUR_TAG_VALUE>
Important: Both the EC2 instance and its IAM role must have matching tags for ABAC to work properly. Replace
<YOUR_TAG_KEY>and<YOUR_TAG_VALUE>with your chosen values (e.g.,Key=env,Value=dev) -
Update instance profiles of the EC2 instances with the required IAM policy:
Add the following ABAC policy to the IAM role attached to your EC2 instances:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowGetSecretWhenEnvMatches", "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret" ], "Resource": "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:acm-passphrase/*", "Condition": { "StringEquals": { "secretsmanager:ResourceTag/<YOUR_TAG_KEY>": "${aws:PrincipalTag/<YOUR_TAG_KEY>}" } } } ] }Important: Replace
<YOUR_TAG_KEY>with your chosen tag key (e.g.,env,environment,team). Tag the instance profile IAM Role with the same tag key and value as used in your deployment (e.g.,env=dev) -
Get the API Gateway endpoint URL from CloudFormation outputs:
aws cloudformation describe-stacks \ --stack-name acm-cert-automation-crossaccount \ --query "Stacks[0].Outputs[?OutputKey=='APIEndpoint'].OutputValue" \ --output text -
Prepare the payload for the API Gateway endpoint:
Create or update
templates/exportCertCrossAccount.json:{ "CertificateArn": "arn:aws:acm:us-east-1:<ACCOUNT_ID>:certificate/<CERTIFICATE_ID>", "CertName": "demo_cert", "TargetTagKey": "env", "TargetTagValue": "dev", "TargetAccountId": "<TARGET_ACCOUNT_ID>" } -
Invoke the API to export and install the certificate:
Using
awscurl(requires AWS credentials):awscurl -X POST https://your-api-id.execute-api.us-east-1.amazonaws.com/prod/export-cert \ -H "Content-Type: application/json" \ -d @templates/exportCertCrossAccount.jsonOr using standard
curlwith AWS SigV4 signing:aws apigateway test-invoke-method \ --rest-api-id your-api-id \ --resource-id your-resource-id \ --http-method POST \ --body file://templates/exportCertCrossAccount.json
-
Monitor the execution in the Step Functions console:
Navigate to the AWS Step Functions console to see the certificate export and installation progress in real-time.
After the Step Function completes, verify the certificate installation on your EC2 instances:
# SSH into your EC2 instance
ssh ec2-user@your-instance-ip
# Check certificate files
ls -la /etc/ssl/certs/ | grep your-cert-name
ls -la /etc/ssl/private/ | grep your-cert-name
# Verify certificate details
openssl x509 -in /etc/ssl/certs/your-cert-name.crt -text -noout
# Retrieve passphrase from Secrets Manager
PASSPHRASE=$(aws secretsmanager get-secret-value \
--secret-id acm-passphrase/your-cert-name \
--query SecretString \
--output text | jq -r '.passphrase')
# Verify private key details using the passphrase
sudo openssl rsa -in /etc/ssl/private/your-cert-name.key \
-passin pass:$PASSPHRASE -text -noout
# Verify the private key matches the certificate (both should output the same MD5 hash)
openssl x509 -noout -modulus -in /etc/ssl/certs/your-cert-name.crt | openssl md5
sudo openssl rsa -noout -modulus -in /etc/ssl/private/your-cert-name.key \
-passin pass:$PASSPHRASE | openssl md5You can test the renewal process by manually triggering an EventBridge event using the AWS CLI:
Note: For testing purposes, update the EventBridge rule to use
"Source": "custom.aws.acm"instead of"aws.acm". This is necessary because users typically don't have permissions to invoke actual ACM events and can only trigger custom events.
aws events put-events --entries '[
{
"Source": "custom.aws.acm",
"DetailType": "ACM Certificate Available",
"Detail": "{\"Action\": \"RENEWAL\", \"CertificateType\": \"AMAZON_ISSUED\", \"CommonName\": \"example.cipherclouds.com\", \"DomainValidationMethod\": \"DNS\", \"CertificateCreatedDate\": \"2025-05-18T02:22:05Z\", \"CertificateExpirationDate\": \"2026-06-16T23:59:59Z\", \"DaysToExpiry\": 395, \"InUse\": false, \"Exported\": true}",
"Resources": [
"arn:aws:acm:us-east-1:1234567890123:certificate/0762eb4c-f9a8-4cf2-bfd6-7715ff743942"
],
"EventBusName": "default"
}
]' --region us-east-1Prerequisites for testing:
- The certificate ARN must exist in your DynamoDB table
- The EventBridge rule must be configured to listen for
custom.aws.acmevents
The solution consists of two main parts:
- Export and Install: Triggered via API Gateway to export certificates from ACM and install them on EC2 or on-premises servers
- Certificate Renewal: Automatically handles certificate renewals via EventBridge events
- Initiate Certificate Issuance: The user triggers the certificate issuance process, ensuring that the certificate is in the "issued" state before proceeding.
- API Triggers Step Function: An API call triggers a Step Function to begin the certificate installation cycle.
- Invoke ACM Export Lambda: The Step Function calls the first step, invoking the acm-export Lambda, which generates a passphrase and securely stores it in AWS Secrets Manager.
- Export Certificate: The acm-export Lambda uses the generated passphrase to export the ACM certificate, completing the first step of the export process.
- Invoke SSM Run Lambda: The Step Function invokes the ssm-run Lambda, passing the secret name, passphrase, encrypted private key, and public key to the next step in the cycle, as provided by the acm-export Lambda.
- Update DynamoDB: The ssm-run Lambda updates the DynamoDB table with relevant details, including the certificate ARN, certificate name, tag values, issuance date, and expiration date.
- Execute SSM Automation Document: The ssm-run Lambda triggers an SSM automation document, running commands to install the certificate by passing the public key, encrypted private key, and the secret alias of the passphrase.
- Install Certificate on EC2 Instances: The SSM automation document executes the installation of the certificates and the private key on the EC2 instances, while also validating the passphrase. (Customer can enhance these steps as they use the sample)
- Wait for SSM Deployment Completion: The Lambda status check waits for SSM to complete the certificate deployment across all EC2 instances before concluding the Step Function.
- EventBridge Triggers Certificate Renewal: An EventBridge event is triggered, confirming that the certificate has been renewed.
- Invoke Renewal Lambda: The EventBridge event invokes the renew-acm Lambda function, which initiates the renewal process.
- Invoke ACM Export Lambda: The Step Function invokes the acm-export Lambda, which generates a passphrase and securely stores it in AWS Secrets Manager.
- Export Renewed Certificate: The acm-export Lambda uses the generated passphrase to export the renewed ACM certificate, completing the export process.
- Invoke SSM Run Lambda: The Step Function invokes the ssm-run Lambda, passing the secret name, passphrase, encrypted private key, and public key to the next step in the cycle, as provided by the acm-export Lambda.
- Update DynamoDB: The ssm-run Lambda updates the DynamoDB table with relevant details, including the certificate ARN, certificate name, tag values, issuance date, and expiration date for the renewed certificate.
- Execute SSM Automation Document: The ssm-run Lambda triggers an SSM automation document, running commands to install the renewed certificate by passing the public key, encrypted private key, and the secret alias of the passphrase.
- Install Certificate on EC2 Instances: The SSM automation document executes the installation of the renewed certificate and the private key on the EC2 instances, while also validating the passphrase.
- Wait for SSM Deployment Completion: The Lambda status check waits for SSM to complete the certificate deployment across all EC2 instances before concluding the Step Function.
The cross-account architecture separates certificate management (central account) from certificate deployment (target account) for enhanced security and isolation.
Central Account (Certificate Management):
- Hosts ACM certificates
- Runs Lambda functions to export certificates
- Manages Step Functions orchestration
- Stores DynamoDB table with certificate metadata
Target Account (Certificate Deployment):
- Contains EC2 instances that receive certificates
- Stores certificate passphrases in Secrets Manager
- Executes SSM automation documents
- Accesses secrets via ABAC-controlled IAM roles
- Central Account Lambda exports certificate:
acm-Export-CrossAccountgenerates passphrase and exports certificate from ACM - Target Account receives secret: Passphrase is stored in target account's Secrets Manager with matching tags
- Central Account triggers SSM:
updtDBstrtSSMAutomation-CrossAccountinvokes SSM automation in target account - Target Account EC2 retrieves secret: EC2 instance's IAM role (with matching tags) retrieves passphrase from target account's Secrets Manager
- Certificate installed locally: SSM automation installs certificate on EC2 instance using the retrieved passphrase
- API Gateway - Accepts requests to export and install certificates
- ACM (Amazon Certificate Manager) - Source of certificates and renewal events
- Amazon EventBridge - Listens for certificate renewal events
- Lambda Functions:
renewACMCert-CrossAccount- Handles renewal events and starts the Step Functionacm-Export-CrossAccount- Exports certificates from ACMupdtDBstrtSSMAutomation-CrossAccount- Updates DynamoDB and triggers SSM automationcheckAutomationStatus-CrossAccount- Monitors SSM automation execution
- Step Function - Orchestrates the export and installation process
- DynamoDB (CertTagMapping) - Stores certificate metadata and target information using a composite key structure:
TagKeyValue(Partition Key) - Combination of tag key and value for efficient querying by tagCertificateArn#CertName(Sort Key) - Enables range queries on certificate ARNs
- Secrets Manager - Securely stores certificate passphrases
- SSM - Runs the enhanced
Install-ACMCertificatedocument to securely install certificates on targets
The CertTagMapping table uses the following structure:
| Attribute | Type | Description |
|---|---|---|
TagKeyValue |
String (PK) | Format: {TagKey}:{TagValue} (e.g., env:dev) |
CertificateArn#CertName |
String (SK) | Format: {CertificateArn}#{CertName} |
CertificateArn |
String | Full ARN of the ACM certificate |
CertName |
String | Friendly name for the certificate |
IssuedDate |
String | ISO 8601 timestamp of certificate issuance |
ExpirationDate |
String | ISO 8601 timestamp of certificate expiration |
TargetAccountId |
String | AWS account ID where targets reside |
This solution uses the following AWS services that may incur costs:
- Lambda: Charged per invocation and execution duration
- Step Functions: Charged per state transition
- DynamoDB: On-demand or provisioned capacity pricing
- Secrets Manager: Charged per secret per month and per API call
- API Gateway: Charged per million API calls
- SSM: No additional charge for SSM Agent or Run Command
- EventBridge: Charged per custom event published
Estimated monthly cost for moderate usage (100 certificate operations): $5-15 USD
Start
ββββΊ ExportCertOnly (acm-Export-CrossAccount)
β
CheckAndUpdateMappingAndSendSSM (updtDBstrtSSMAutomation-CrossAccount)
β
WaitForStatus (Wait 15s)
β
CheckAutomationStatus (checkAutomationStatus-CrossAccount)
β
βββββββΊ IsCommandComplete? (Choice)
β ββββΊ SuccessState (Succeed)
β ββββΊ FailureState (Fail)
βββββββββββββββΊ WaitForStatus (loop back if still InProgress)
| Feature | Benefit |
|---|---|
| Tag-based targeting | Works with both EC2 and on-premises servers |
| Central certificate management | All certificate metadata stored in DynamoDB |
| Fully automated renewals | No manual intervention needed for renewals |
| SSM automation | Consistent installation across environments |
| Step Function orchestration | Reliable execution with visibility and logging |
- Target EC2 instances must be running Linux and have SSM Agent installed
- Certificates are installed in
/etc/ssl/certs/and/etc/ssl/private/folders - The API Gateway is secured with IAM authentication
- Each certificate requires a one-to-one mapping with EC2 instance tags
- You can customize the installation paths by modifying the SSM document
- Certificate passphrases are automatically generated and stored securely in Secrets Manager
- The solution uses ABAC (Attribute-Based Access Control) for enhanced security
The solution can be extended to support different operating systems and application types. Here's how:
Modify the Lambda function to use different SSM documents based on the target environment:
# Example: Select SSM document based on OS type
ssm_document = {
'linux': 'Install-ACMCertificate-Linux',
'windows': 'Install-ACMCertificate-Windows'
}.get(os_type, 'Install-ACMCertificate-Linux')Support various operating systems by adding an OSType parameter to your payload:
{
"CertificateArn": "arn:aws:acm:...",
"OSType": "windows",
"InstallPath": "C:\\Certificates"
}Adapt certificate installation for different application types:
{
"CertificateArn": "arn:aws:acm:...",
"ApplicationType": "nginx",
"PostInstallAction": "restart-nginx"
}Specify custom certificate installation paths:
{
"CertificateArn": "arn:aws:acm:...",
"CertPath": "/opt/app/certs",
"KeyPath": "/opt/app/private"
}Enable application-specific actions after certificate installation:
# Example SSM document step
- name: RestartService
action: aws:runShellScript
inputs:
runCommand:
- systemctl restart {{ ApplicationService }}EC2 instances that receive certificates must have an IAM role with the following policy to enable Attribute-Based Access Control (ABAC) for accessing certificate passphrases:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowGetSecretWhenEnvMatches",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:[REGION]:[ACCOUNT_ID]:secret:acm-passphrase/*",
"Condition": {
"StringEquals": {
"secretsmanager:ResourceTag/env": "${aws:PrincipalTag/env}"
}
}
}
]
}Important: The IAM role assigned to the EC2 instance (not the EC2 instance itself) must have the same tags as the secret for ABAC to work properly. In the policy above,
${aws:PrincipalTag/env}refers to the tag on the IAM role, not the EC2 instance. During execution, the system validates that the role's tag matches the secret's tag to allow access.
This ABAC policy ensures that:
- EC2 instances can only access secrets when their IAM role has matching environment tags
- The SSM automation can retrieve the passphrase to decrypt the private key during installation
- Security is maintained through role-based tag access control
Symptom: Step Function fails at the ExportCertOnly step
Possible Causes:
- Certificate is not in "ISSUED" state
- Certificate doesn't have "Exportable" flag set to true
- Insufficient IAM permissions
Solution:
# Check certificate status
aws acm describe-certificate --certificate-arn your-cert-arn
# Verify the certificate is exportable
aws acm get-certificate --certificate-arn your-cert-arnSymptom: SSM automation document execution fails
Possible Causes:
- SSM Agent not running on EC2 instance
- IAM instance profile missing required permissions
- Network connectivity issues
Solution:
# Check SSM Agent status
sudo systemctl status amazon-ssm-agent
# Verify instance is managed by SSM
aws ssm describe-instance-information --filters "Key=InstanceIds,Values=i-xxxxx"
# Check IAM instance profile
aws ec2 describe-instances --instance-ids i-xxxxx \
--query 'Reservations[0].Instances[0].IamInstanceProfile'Symptom: EC2 instance cannot retrieve passphrase from Secrets Manager
Possible Causes:
- IAM role tags don't match secret tags
- IAM role missing Secrets Manager permissions
Solution:
# Check IAM role tags
aws iam list-role-tags --role-name your-instance-role
# Check secret tags
aws secretsmanager describe-secret --secret-id acm-passphrase/your-secret
# Ensure tags match (e.g., env=dev on both)Symptom: Certificate renewals don't trigger automatically
Possible Causes:
- EventBridge rule is disabled
- Event pattern doesn't match ACM events
- Lambda function has insufficient permissions
Solution:
# Check EventBridge rule status
aws events describe-rule --name your-rule-name
# Enable the rule if disabled
aws events enable-rule --name your-rule-name
# Test with a custom event (see Testing section)Symptom: Certificate metadata not found in DynamoDB
Possible Causes:
- Certificate hasn't been processed yet
- Incorrect tag key/value format
- DynamoDB table permissions issue
Solution:
# Query DynamoDB table
aws dynamodb query \
--table-name CertTagMapping \
--key-condition-expression "TagKeyValue = :tkv" \
--expression-attribute-values '{":tkv":{"S":"env:dev"}}'To remove all resources created by this solution:
-
Delete the CloudFormation stacks:
# Delete target account stack aws cloudformation delete-stack --stack-name acm-cert-target-iam # Delete central account stack aws cloudformation delete-stack --stack-name acm-cert-automation-crossaccount
-
Remove secrets from Secrets Manager (if not automatically deleted):
# List all ACM passphrases aws secretsmanager list-secrets \ --filters Key=name,Values=acm-passphrase/ # Delete specific secrets aws secretsmanager delete-secret \ --secret-id acm-passphrase/your-secret \ --force-delete-without-recovery
-
Remove certificates from EC2 instances (optional):
# SSH into each instance and remove certificate files sudo rm /etc/ssl/certs/your-cert-name.crt sudo rm /etc/ssl/private/your-cert-name.key -
Verify all resources are deleted:
# Check CloudFormation stacks aws cloudformation list-stacks --stack-status-filter DELETE_COMPLETE # Check for remaining Lambda functions aws lambda list-functions --query 'Functions[?contains(FunctionName, `acm`)]'
This sample project has the following limitations to be aware of:
- Only works with ACM certificates that have the "Exportable" flag set to true
- Requires EC2 instances to have SSM Agent installed and proper IAM permissions
- Certificate private keys are temporarily stored in memory during the export process
- The solution is region-specific; cross-region deployments require additional configuration
- Error handling is basic and may require enhancement for production use
- No built-in monitoring dashboard (relies on CloudWatch Logs and Metrics)
- Currently supports Linux-based EC2 instances only (Windows support requires custom SSM document)
- One-to-one mapping between certificates and tag combinations (one cert per tag pair)
To learn more about the technologies used in this sample project:
- AWS Certificate Manager Documentation
- AWS Step Functions Documentation
- AWS Systems Manager Documentation
- Attribute-Based Access Control (ABAC) with AWS
- AWS Secrets Manager Documentation
- Amazon EventBridge Documentation
- AWS Lambda Best Practices
Questions or Issues? Please open an issue in the repository or refer to the Troubleshooting section above.



