TL;DR One Custom Resource provider to Rule Them All, inspect the code, read the blog, try some examples and consider contributing 🤓
- ACM
- S3
- AWS Backup
- Directory Services
- IAM
- KMS
- Relational Database Service
- Database Migration Service
- EC2
- KMS
The idea behind this project was to make available a flexible and simple tool to enable creation of any AWS resource supported by the API. We implement this functionality via a generic CloudFormation Custom Resources provider in Python (3), using boto3. The word "generic" is used here in a sense of having just one Lambda function, which can be used to create different custom resources by varying the input parameters.
For more information, please read this blog post.
All shell-fu is Bash;
git
,pip
,awscli
andjq
required.
git clone --recurse-submodules --remote-submodules\
https://github.com/ab77/cfn-generic-custom-resource\
&& cd cfn-generic-custom-resource
📝 creates a new bucket with a random GUID; ensure
~/.aws/credentials
and~/.aws/config
are configured (runaws configure ...
) and exportAWS_PROFILE
andAWS_REGION
environment variables
bucket=$(uuid)
aws s3 mb s3://${bucket}
📝 AWS Lambda provided boto3 library doesn't support Client VPN resources at the time of writing, so we need to package it with the code
pip install virtualenv --user
pushd generic_provider
python -m virtualenv venv
source venv/bin/activate
pip install --upgrade pip
pip install --upgrade -r requirements.txt -t .
popd
docker ps && pushd generic_provider && make && popd
☢️ beware of the currently eye-watering Client VPN pricing
📜 issue certificates with easy-rsa and upload to ACM, using fictional domain
foo.bar
domain_name='foo.bar'
git clone https://github.com/OpenVPN/easy-rsa
pushd easy-rsa/easyrsa3
./easyrsa init-pki
./easyrsa build-ca nopass
./easyrsa build-server-full server.${domain_name} nopass\
&& ./easyrsa build-client-full client1.${domain_name} nopass
popd
server_certificate=$(aws acm import-certificate\
--certificate file://easy-rsa/easyrsa3/pki/issued/server.${domain_name}.crt\
--private-key file://easy-rsa/easyrsa3/pki/private/server.${domain_name}.key\
--certificate-chain file://easy-rsa/easyrsa3/pki/ca.crt | jq -r '.CertificateArn')
client_certificate=$(aws acm import-certificate\
--certificate file://easy-rsa/easyrsa3/pki/issued/client1.${domain_name}.crt\
--private-key file://easy-rsa/easyrsa3/pki/private/client1.${domain_name}.key\
--certificate-chain file://easy-rsa/easyrsa3/pki/ca.crt | jq -r '.CertificateArn')
📦 package CloudFormation templates and Lambda function(s) and upload to S3
pushd client-vpn; for template in lambda main client-vpn; do
aws cloudformation package\
--template-file ${template}-template.yaml\
--s3-bucket ${bucket}\
--output-template-file ${template}.yaml
done; popd
📝 creates Client VPN endpoint with
certificate-authentication
; fordirectory-service-authentication
or both, specify additionalDirectoryId
parameter
stack_name='client-vpn-demo'
vpc_id=$(aws ec2 describe-vpcs | jq -r .Vpcs[0].VpcId)
subnets=(
$(aws ec2 describe-subnets | jq -r ".Subnets[0] | select(.VpcId==\"${vpc_id}\").SubnetId")
$(aws ec2 describe-subnets | jq -r ".Subnets[1] | select(.VpcId==\"${vpc_id}\").SubnetId")
)
subnet_count=${#subnets[@]}
cidr=172.16.0.0/22
pushd client-vpn; aws cloudformation deploy\
--template-file main.yaml\
--stack-name ${stack_name}\
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM\
--parameter-overrides\
VpcId=${vpc_id}\
CidrBlock=${cidr}\
SubnetIds=$(echo ${subnets[*]} | tr ' ' ',')\
SubnetCount=${subnet_count}\
ServerCertificateArn=${server_certificate}\
ClientRootCertificateChainArn=${client_certificate}\
--tags\
Name=${stack_name}\
Region=${AWS_REGION}\
Profile=${AWS_PROFILE}\
AccountId=$(aws sts get-caller-identity | jq -r '.Account'); popd
vpn_stack=$(aws cloudformation list-exports\
| jq -r ".Exports[] | select(.Name==\"VPNStackName-${stack_name}\").Value")
client_vpn_endpoint=$(aws cloudformation list-exports\
| jq -r ".Exports[] | select(.Name | startswith(\"ClientVpnEndpointId-${vpn_stack}\")).Value")
aws ec2 export-client-vpn-client-configuration\
--client-vpn-endpoint-id ${client_vpn_endpoint} | jq -r '.ClientConfiguration' > client.ovpn
📝 make sure to create bucket and install requirements first
⚠️ public read access required for access toMetadataURL
, adjust as necessary
tmpfile=$(mktemp)
cat << EOF > ${tmpfile}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "$(date +%s)",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::${bucket}/*"
]
}
]
}
EOF
aws s3api put-bucket-policy\
--bucket ${bucket}\
--policy file://${tmpfile}\
&& rm ${tmpfile}
- login to Google Apps Admin
- navigate to
Apps -> SAML Apps --> + --> SETUP MY OWN CUSTOM APP
- select
(Option 2) IDP metadata
, download and save
domain_name='foo.bar'
aws s3 cp GoogleIDPMetadata-${domain_name}.xml s3://${bucket}/
pushd cognito-idp; for template in lambda main cognito; do
aws cloudformation package\
--template-file ${template}-template.yaml\
--s3-bucket ${bucket}\
--output-template-file ${template}.yaml
done; popd
stack_name='c0gn1t0-demo'
metadata_url=https://${bucket}.s3.amazonaws.com/GoogleIDPMetadata-${domain_name}.xml
pushd cognito-idp; aws cloudformation deploy\
--template-file main.yaml\
--stack-name ${stack_name}\
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM\
--parameter-overrides\
DomainName=${domain_name}\
MetadataURL=${metadata_url}\
--tags\
Name=${stack_name}\
Region=${AWS_REGION}\
Profile=${AWS_PROFILE}\
AccountId=$(aws sts get-caller-identity | jq -r '.Account'); popd
cognito_stack=$(aws cloudformation list-exports\
| jq -r ".Exports[] | select(.Name==\"CognitoStackName-${stack_name}\").Value")
user_pool_id=$(aws cloudformation list-exports\
| jq -r ".Exports[] | select(.Name | startswith(\"UserPoolId-${cognito_stack}\")).Value")
echo "ACS URL: https://${stack_name}.auth.${AWS_REGION}.amazoncognito.com/saml2/idpresponse"
echo "Entity ID: urn:amazon:cognito:sp:${user_pool_id}"
Cognito IdP with Google SAML
- login to Google Apps Admin
- navigate to
Apps -> SAML Apps --> + --> SETUP MY OWN CUSTOM APP
- set
ACS URL
as per above - set
Entity ID
as per above - continue with ALB configuration
creates a peering connection between source and destination VPCs, including tags and routes in both directions
pushd vpc-peering; for template in lambda main; do
aws cloudformation package\
--template-file ${template}-template.yaml\
--s3-bucket ${bucket}\
--output-template-file ${template}.yaml
done; popd
☢ ensure appropriate VPCPeeringRole exists in the VPC accepter AWS account and review IAM role permissions
VPCPeeringRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: 'VPCPeeringRole'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS:
# list your VPC peering requester (source) AWS accounts here
- '123456789000'
...
Action: sts:AssumeRole
Path: '/'
...
☢ add VPC requester AWS accounts to CustomResourceLambdaRole under the
AmazonSTSPolicy
policy and review IAM role permissions
- PolicyName: AmazonSTSPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 'sts:AssumeRole'
- 'sts:PassRole'
Resource:
# list your VPC peering accepter (target) AWS accounts here
- !Sub 'arn:${AWS::Partition}:iam::123456789001:role/VPCPeeringRole'
...
📝 optionally enable EC2 nested stack and supply
SecurityGroup
in the accepter VPC as well asTargetPort
# peering between VPCs in this mock account 123456789000 (requester) and 123456789001 (accepter)
stack_name='vpc-peering-demo'
# create IPv6 routes (both VPCs must be IPv6)
ipv6='false'
# requester VPC
source_vpc='vpc-abcdef1234567890'
# comma separated list of one or more route table id(s) in the requester VPC'
source_route_table_ids='rtb-abcdef1234567890'
# accepter VPC
source_vpc='vpc-1234567890abcdef'
# VPC accepter AWS account
target_account_id=123456789001
# VPC accepter AWS region
target_region=${AWS_REGION}
# comma separated list of one or more route table id(s) in the accepter VPC'
target_route_table_ids='rtb-1234567890abcdef'
source_route_table_ids=($(echo ${source_route_table_ids} | sed 's/,/ /g' | tr ' ' '\n'))\
&& source_route_tables=${#source_route_table_ids[@]}\
&& source_route_table_ids="$(echo ${source_route_table_ids[*]} | tr ' ' ',')"
target_route_table_ids=($(echo ${target_route_table_ids} | sed 's/,/ /g' | tr ' ' '\n'))\
&& target_route_tables=${#target_route_table_ids[@]}\
&& target_route_table_ids="$(echo ${target_route_table_ids[*]} | tr ' ' ',')"
pushd vpc-peering; aws cloudformation deploy\
--template-file main.yaml\
--stack-name ${stack_name}\
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM\
--parameter-overrides\
SourceVpcId=${source_vpc}\
SourceRouteTableIds=${source_route_table_ids}\
SourceRouteTables=${source_route_tables}\
TargetRegion=${target_region}\
TargetAccountId=${target_account_id}\
TargetVpcId=${target_vpc}\
TargetRouteTableIds=${target_route_table_ids}\
TargetRouteTables=${target_route_tables}\
EC2Template=false\
--tags\
Name=${stack_name}\
Region=${AWS_REGION}\
Profile=${AWS_PROFILE}\
AccountId=$(aws sts get-caller-identity | jq -r '.Account'); popd
also see mock examples below
pushd aws-backup; for template in lambda; do
aws cloudformation package\
--template-file ${template}-template.yaml\
--s3-bucket ${bucket}\
--output-template-file ${template}.yaml
done; popd
see resource ARNs and namespaces for
ResourceId
parameter
stack_name='aws-backup-demo'
# specify resource ARN and name
resource_id="arn:aws:elasticfilesystem:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):file-system/fs-abcde1234"
resource_name='efs-resource-name'
pushd aws-backup; aws cloudformation deploy\
--template-file backup.yaml\
--stack-name ${stack_name}\
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM\
--parameter-overrides\
NameTag=${stack_name}\
ResourceId=${resource_id}\
ResourceName=${resource_name}\
--tags\
Name=${stack_name}\
Region=${AWS_REGION}\
Profile=${AWS_PROFILE}\
AccountId=$(aws sts get-caller-identity | jq -r '.Account'); popd
also see mock examples below
pushd acm-pca; for template in lambda main; do
aws cloudformation package\
--template-file ${template}-template.yaml\
--s3-bucket ${bucket}\
--output-template-file ${template}.yaml
done; popd
⚠️ ensure to clean-up the CA resources to avoid $400/month surprise on your next AWS bill
stack_name='acm-pca'
# change to a domain you own with postmaster and hostmaster email addresses forwarded for ACM SSL certificate validation
domain_name='belodetek.io'
pushd acm-pca; aws cloudformation deploy\
--template-file main.yaml\
--stack-name ${stack_name}\
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM\
--parameter-overrides\
NameTag=${stack_name}\
DomainWithoutDot=${domain_name}\
S3Template=true\
IAMTemplate=true\
R53Template=true\
ACMTemplate=true\
CFTemplate=true\
IAMTemplate=true\
PCATemplate=true\
--tags\
Name=${stack_name}\
Region=${AWS_REGION}\
Profile=${AWS_PROFILE}\
AccountId=$(aws sts get-caller-identity | jq -r '.Account'); popd
🐞 useful to debug resource creation of AWS resources from a local workstation
ACM API reference
mock CloudFormation request to request_certificate in a different account and/or region (e.g. for CloudFront)
# https://forums.aws.amazon.com/thread.jspa?messageID=912980
aws_region='us-east-1'
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"PhysicalResourceId\": \"$(uuid)\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"acm\",
\"AgentRegion\": \"${aws_region}\",
\"AgentCreateMethod\": \"request_certificate\",
\"AgentUpdateMethod\": \"update_certificate_options\",
\"AgentDeleteMethod\": \"delete_certificate\",
\"AgentWaitMethod\": \"certificate_validated\",
\"AgentWaitQueryExpr\": \"$.CertificateArn\",
\"AgentWaitResourceId\": \"CertificateArn\",
\"AgentResourceId\": \"CertificateArn\",
\"AgentCreateArgs\": {
\"DomainName\": \"foo.baz.com\",
\"ValidationMethod\": \"EMAIL\",
\"SubjectAlternativeNames\": [
\"bar.baz.com\"
],
\"DomainValidationOptions\": [
{
\"DomainName\": \"foo.baz.com\",
\"ValidationDomain\": \"baz.com\"
},
{
\"DomainName\": \"bar.baz.com\",
\"ValidationDomain\": \"baz.com\"
}
]
},
\"AgentUpdateArgs\": {
\"Options\": {
\"CertificateTransparencyLoggingPreference\": \"DISABLED\"
}
}
}
}" | jq -c | VERBOSE=1 ./generic_provider.py
popd
ACM-PCA API reference
mock CloudFormation request to create_certificate_authority
(
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"PhysicalResourceId\": \"$(uuid)\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"acm-pca\",
\"AgentCreateMethod\": \"create_certificate_authority\",
\"AgentUpdateMethod\": \"update_certificate_authority\",
\"AgentDeleteMethod\": \"delete_certificate_authority\",
\"AgentWaitQueryExpr\": \"$.CertificateAuthorityArn\",
\"AgentWaitResourceId\": \"CertificateAuthorityArn\",
\"AgentWaitDeleteExceptions\": [
\"botocore.exceptions.WaiterError\"
],
\"AgentResourceId\": \"CertificateAuthorityArn\",
\"AgentCreateArgs\": {
\"CertificateAuthorityConfiguration\": {
\"KeyAlgorithm\": \"RSA_2048\",
\"SigningAlgorithm\": \"SHA256WITHRSA\",
\"Subject\": {
\"Country\": \"FB\",
\"Organization\": \"foo-bar\",
\"OrganizationalUnit\": \"foo-bar\",
\"CommonName\": \"[email protected]\"
}
},
\"RevocationConfiguration\": {
\"CrlConfiguration\": {
\"Enabled\": true,
\"ExpirationInDays\": 7,
\"CustomCname\": \"foo.bar.com\",
\"S3BucketName\": \"foo-bar\"
}
},
\"CertificateAuthorityType\": \"SUBORDINATE\",
\"Tags\": [
{
\"Key\": \"Name\",
\"Value\": \"foo-bar\"
}
]
},
\"AgentUpdateArgs\": {
\"RevocationConfiguration\": {
\"CrlConfiguration\": {
\"Enabled\": true,
\"ExpirationInDays\": 7,
\"CustomCname\": \"foo.bar.com\"
}
},
\"Status\": \"ACTIVE\"
},
\"AgentDeleteArgs\": {
\"PermanentDeletionTimeInDays\": 7
}
}
}" | jq -c | VERBOSE=1 ./generic_provider.py
popd
mock CloudFormation request to create_self_signed_cert
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"PhysicalResourceId\": \"$(uuid)\",
\"ResourceProperties\": {
\"AgentType\": \"custom\",
\"AgentService\": \"acm_pca\",
\"AgentCreateMethod\": \"create_self_signed_cert\",
\"AgentCreateArgs\": {
\"PrivateKey\": \"/rsa-private-keys/acm-pca/key_pair\",
\"Country\": \"US\",
\"Org\": \"foo\",
\"OrgUnit\": \"bar\",
\"CommonName\": \"foo-bar\",
\"Serial\": 1,
\"ValidityInSeconds\": 315360000,
\"Digest\": \"sha256\"
}
}
}" | jq -c | VERBOSE=1 ./generic_provider.py | jq -r .Data.Certificate > ca.crt\
&& openssl x509 -in ca.crt -text -noout
popd
mock CloudFormation request to get_certificate_authority_csr
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"PhysicalResourceId\": \"$(uuid)\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"acm-pca\",
\"AgentCreateMethod\": \"get_certificate_authority_csr\",
\"AgentCreateArgs\": {
\"CertificateAuthorityArn\": \"arn:aws:acm-pca:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):certificate-authority/$(uuid)\",
}
}
}" | jq -c | VERBOSE=1 ./generic_provider.py | jq -r .Data.Csr > csr.pem && openssl req -in csr.pem -text -noout
popd
mock CloudFormation request to sign_csr request
# upload RSA private (signing) key to the SSM Parameter Store
openssl genrsa -out signing.key 4096 && signing_key=$(cat signing.key)
aws ssm put-parameter --type SecureString\
--name '/rsa-private-keys/acm-pca/key_pair'\
--value ${signing_key}
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"PhysicalResourceId\": \"$(uuid)\",
\"ResourceProperties\": {
\"AgentType\": \"custom\",
\"AgentService\": \"acm_pca\",
\"AgentCreateMethod\": \"sign_csr\",
\"AgentCreateArgs\": {
\"PrivateKey\": \"/rsa-private-keys/acm-pca/key_pair",
\"Csr\": \"$(cat csr.pem | base64)\",
\"ValidityInSeconds\": 315360000,
\"Digest\": \"sha256\"
}
}
}" | jq -c | VERBOSE=1 ./generic_provider.py > test.crt && openssl x509 -in test.crt -text -noout
popd
mock CloudFormation request to import_certificate_authority_certificate
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"PhysicalResourceId\": \"$(uuid)\",
\"ResourceProperties\": {
\"AgentType\": \"custom\",
\"AgentService\": \"acm_pca\",
\"AgentCreateMethod\": \"import_certificate_authority_certificate\",
\"AgentCreateArgs\": {
\"CertificateAuthorityArn\": \"arn:aws:acm-pca:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):certificate-authority/$(uuid)\",
\"Certificate\": \"$(cat test.crt | base64)\"
\"CACertificate\": \"$(cat csr.pem | base64)\"
}
}
}" | jq -c | VERBOSE=1 ./generic_provider.py
popd
S3 API reference
mock CloudFormation request to add a bucket Lambda notification configuration
bucket='foo'
lambda_function='bar'
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"PhysicalResourceId\": \"$(uuid)\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"s3\",
\"AgentCreateMethod\": \"put_bucket_notification_configuration\",
\"AgentUpdateMethod\": \"put_bucket_notification_configuration\",
\"AgentDeleteMethod\": \"put_bucket_notification_configuration\",
\"AgentCreateArgs\": {
\"Bucket\": \"${bucket}\",
\"NotificationConfiguration\": {
\"LambdaFunctionConfigurations\": [{
\"LambdaFunctionArn\": \"arn:aws:lambda:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):function:${lambda_function}\",
\"Events\": [
\"s3:ObjectRemoved:*\"
],
\"Filter\": {
\"Key\": {
\"FilterRules\": [
{
\"Name\": \"prefix\",
\"Value\": \"foo/\"
},
{
\"Name\": \"suffix\",
\"Value\": \".bar\"
}
]
}
}
}]
}
},
\"AgentUpdateArgs\": {
\"Bucket\": \"${bucket}\",
\"NotificationConfiguration\": {
\"LambdaFunctionConfigurations\": [{
\"LambdaFunctionArn\": \"arn:aws:lambda:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):function:${lambda_function}\",
\"Events\": [
\"s3:ObjectRemoved:*\"
],
\"Filter\": {
\"Key\": {
\"FilterRules\": [
{
\"Name\": \"prefix\",
\"Value\": \"foo/\"
},
{
\"Name\": \"suffix\",
\"Value\": \".bar\"
}
]
}
}
}]
}
},
\"AgentDeleteArgs\": {
\"Bucket\": \"${bucket}\",
\"NotificationConfiguration\": {}
}
}
}" | jq -c | VERBOSE=1 ./generic_provider.py
popd
Backup API reference
mock CloudFormation request to create a backup vault
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"PhysicalResourceId\": \"$(uuid)\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"backup\",
\"AgentCreateMethod\": \"create_backup_vault\",
\"AgentDeleteMethod\": \"delete_backup_vault\",
\"AgentCreateArgs\": {
\"BackupVaultName\": \"foo-bar\",
\"EncryptionKeyArn\": \"arn:aws:kms:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):key/$(uuid)\",
\"BackupVaultTags\": {
\"Name\": \"foo-bar\"
}
},
\"AgentDeleteArgs\": {
\"BackupVaultName\": \"foo-bar\"
}
}
}" | jq -c | VERBOSE=1 ./generic_provider.py
popd
mock CloudFormation request to create a backup plan
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"backup\",
\"AgentCreateMethod\": \"create_backup_plan\",
\"AgentUpdateMethod\": \"update_backup_plan\",
\"AgentDeleteMethod\": \"delete_backup_plan\",
\"AgentResourceId\": \"BackupPlanId\",
\"AgentWaitQueryExpr\": \"$.BackupPlanId\",
\"AgentCreateArgs\": {
\"BackupPlan\": {
\"BackupPlanName\": \"foo-bar\",
\"Rules\": [
{
\"RuleName\": \"foo-bar\",
\"TargetBackupVaultName\": \"Default\",
\"ScheduleExpression\": \"cron(0 2 * * ? *)\",
\"StartWindowMinutes\": 60,
\"CompletionWindowMinutes\": 180,
\"Lifecycle\": {
\"MoveToColdStorageAfterDays\": 30,
\"DeleteAfterDays\": 365
}
}
]
},
\"BackupPlanTags\": {
\"Name\": \"foo-bar\"
}
},
\"AgentUpdateArgs\": {
\"BackupPlan\": {
\"BackupPlanName\": \"foo-bar\",
\"Rules\": [
{
\"RuleName\": \"foo-bar\",
\"TargetBackupVaultName\": \"Default\",
\"ScheduleExpression\": \"cron(0 2 * * ? *)\",
\"StartWindowMinutes\": 60,
\"CompletionWindowMinutes\": 180,
\"Lifecycle\": {
\"MoveToColdStorageAfterDays\": 30,
\"DeleteAfterDays\": 365
}
}
]
}
}
}
}
}" | jq -c | VERBOSE=1 ./generic_provider.py
popd
mock CloudFormation request to create a backup slection
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"backup\",
\"AgentCreateMethod\": \"create_backup_selection\",
\"AgentDeleteMethod\": \"delete_backup_selection\",
\"AgentResourceId\": \"SelectionId\",
\"AgentWaitQueryExpr\": \"$.SelectionId\",
\"AgentCreateArgs\": {
\"BackupPlanId\": \"$(uuid)\",
\"BackupSelection\": {
\"SelectionName\": \"foo-bar\",
\"IamRoleArn\": \"arn:aws:iam::$(aws sts get-caller-identity | jq -r '.Account'):role/service-role/AWSBackupDefaultServiceRole\",
\"Resources\": [
\"arn:aws:elasticfilesystem:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):file-system/fs-abcde1234\"
],
\"ListOfTags\": [
{
\"ConditionType\": \"STRINGEQUALS\",
\"ConditionKey\": \"AccountId\",
\"ConditionValue\": \"$(aws sts get-caller-identity | jq -r '.Account')\"
}
]
}
},
\"AgentDeleteArgs\": {
\"BackupPlanId\": \"$(uuid)\"
}
}
}
}" | jq -c | VERBOSE=1 ./generic_provider.py
popd
Directory Services API reference
mock CloudFormation request to create AD Connector
mock_lambda_event=$(echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentService\": \"ds\",
\"AgentType\": \"client\",
\"AgentCreateMethod\": \"connect_directory\",
\"AgentDeleteMethod\": \"delete_directory\",
\"AgentWaitMethod\": \"describe_directories\",
\"AgentWaitQueryExpr\": \"$.DirectoryDescriptions[].Stage\",
\"AgentWaitCreateQueryValues\": [
\"Active\"
],
\"AgentWaitUpdateQueryValues\": [],
\"AgentWaitDeleteQueryValues\": [],
\"AgentResourceId\": \"DirectoryId\",
\"AgentWaitResourceId\": [
\"DirectoryIds\"
],
\"AgentCreateArgs\": {
\"Size\": \"Small\",
\"Description\": \"Active Directory connection.\",
\"Name\": \"foo-bar.local\",
\"ShortName\": \"foo-bar\",
\"Password\": \"bar\",
\"ConnectSettings\": {
\"VpcId\": \"vpc-abcdef1234567890\",
\"SubnetIds\": [
\"subnet-1234567890abcdef\",
\"subnet-abcdef1234567890\"
],
\"CustomerDnsIps\": [
\"1.2.3.4\",
\"4.5.6.7\"
],
\"CustomerUserName\": \"foo\"
}
}
}
}" | jq -c)\
&& pushd generic_provider\
&& ./generic_provider.py "${mock_lambda_event}"\
&& popd
IAM API reference
mock CloudFormation request to upload SSH public key
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"iam\",
\"AgentResourceId\": \"SSHPublicKeyId\",
\"AgentWaitQueryExpr\": \"$.SSHPublicKey.SSHPublicKeyId\",
\"AgentCreateMethod\": \"upload_ssh_public_key\",
\"AgentCreateArgs\": {
\"UserName\": \"foo-bar\",
\"SSHPublicKeyBody\": \"$(cat ~/.ssh/id_rsa.pub | head -n 1)\"
},
\"AgentDeleteMethod\": \"delete_ssh_public_key\",
\"AgentDeleteArgs\": {
\"UserName\": \"foo-bar\"
}
}
}" | jq -c | ./generic_provider.py
popd
KMS API reference
mock CloudFormation request to encrypt with KMS
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentCreateArgs\": {
\"KeyId\": \"arn:aws:kms:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):key/$(uuid)\",
\"Plaintext\": \"foo-bar\"
},
\"AgentType\": \"client\",
\"AgentService\": \"kms\",
\"AgentCreateMethod\": \"encrypt\"
}
}" | jq -c | ./generic_provider.py
popd
RDS API reference
mock CloudFormation request to enable RDS CloudWatch metrics
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"PhysicalResourceId\": \"$(uuid)\",
\"ResourceProperties\": {
\"AgentService\": \"rds\",
\"AgentType\": \"client\",
\"AgentCreateMethod\": \"modify_db_cluster\",
\"AgentCreateArgs\": {
\"DBClusterIdentifier\": \"foo-bar\",
\"CloudwatchLogsExportConfiguration\": {
\"EnableLogTypes\": [
\"error\",
\"slowquery\"
],
\"DisableLogTypes\": []
}
},
\"AgentDeleteMethod\": \"modify_db_cluster\",
\"AgentDeleteArgs\": {
\"DBClusterIdentifier\": \"foo-bar\",
\"CloudwatchLogsExportConfiguration\": {
\"DisableLogTypes\": [
\"error\",
\"slowquery\"
],
\"EnableLogTypes\": []
}
},
\"AgentWaitMethod\": \"describe_db_instances\",
\"AgentWaitDelay\": \"60\",
\"AgentWaitArgs\": {
\"Filters\": [
{
\"Name\": \"db-cluster-id\",
\"Values\": [
\"foo-bar\"
]
},
{
\"Name\": \"db-instance-id\",
\"Values\": [
\"foo-bar\"
]
}
]
},
\"AgentWaitQueryExpr\": \"$.DBInstances[*].DBInstanceStatus\",
\"AgentWaitCreateQueryValues\": [
\"available\"
],
\"AgentWaitDeleteQueryValues\": [
\"available\"
]
}
}" | jq -c | ./generic_provider.py
popd
mock CloudFormation request to enable RDS Performance Insights
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"PhysicalResourceId\": \"$(uuid)\",
\"ResourceProperties\": {
\"AgentService\": \"rds\",
\"AgentType\": \"client\",
\"AgentCreateMethod\": \"modify_db_instance\",
\"AgentCreateArgs\": {
\"DBInstanceIdentifier\": \"abcdefghij1234\",
\"EnablePerformanceInsights\": true,
\"PerformanceInsightsKMSKeyId\": \"arn:aws:kms:${AWS_REGION}:1234567890:key/$(uuid)\",
\"PerformanceInsightsRetentionPeriod\": 7,
\"ApplyImmediately\": true
},
\"AgentDeleteMethod\": \"modify_db_instance\",
\"AgentDeleteArgs\": {
\"DBInstanceIdentifier\": \"1234abcdefghij\",
\"EnablePerformanceInsights\": false,
\"ApplyImmediately\": true
},
\"AgentWaitMethod\": \"describe_db_instances\",
\"AgentWaitDelay\": \"60\",
\"AgentWaitArgs\": {
\"Filters\": [
{
\"Name\": \"db-cluster-id\",
\"Values\": [
\"1234abcdefghij\"
]
},
{
\"Name\": \"db-instance-id\",
\"Values\": [
\"abcdefghij1234\"
]
}
]
},
\"AgentWaitQueryExpr\": \"$.DBInstances[*].DBInstanceStatus\",
\"AgentWaitCreateQueryValues\": [
\"available\"
],
\"AgentWaitDeleteQueryValues\": [
\"available\"
]
}
}" | jq -c | ./generic_provider.py
popd
DMS API reference
mock CloudFormation request to describe running replication tasks
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentService\": \"dms\",
\"AgentType\": \"client\",
\"AgentCreateMethod\": \"describe_replication_tasks\",
\"AgentCreateArgs\": {
\"Filters\": [
{
\"Name\": \"replication-instance-arn\",
\"Values\": [
\"arn:aws:dms:us-west-2:313347522657:rep:ABCDEFGHIJKLMNOPQRSTUVWXYZ\"
]
}
]
},
\"AgentWaitQueryExpr\": \"$.ReplicationTasks[?(@.Status=='running')].ReplicationTaskArn\"
}
}" | jq -c | ./generic_provider.py
popd
mock CloudFormation request to stop replication task
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentService\": \"dms\",
\"AgentType\": \"client\",
\"AgentWaitMethod\": \"replication_task_stopped\",
\"AgentWaitArgs\": {
\"Filters\": [
{
\"Name\": \"replication-task-arn\",
\"Values\": [
\"arn:aws:dms:${AWS_REGION}:1234567890:task:ABCDEFGHIJKLMNOPQRSTUVWXYZ\"
]
}
]
},
\"AgentCreateMethod\": \"stop_replication_task\",
\"AgentCreateExceptions\": [
\"agent.exceptions.InvalidResourceStateFault\",
\"agent.exceptions.ClientError\"
],
\"AgentWaitCreateExceptions\": [
\"botocore.exceptions.WaiterError\"
],
\"AgentCreateArgs\": {
\"ReplicationTaskArn\": \"arn:aws:dms:${AWS_REGION}:1234567890:task:ABCDEFGHIJKLMNOPQRSTUVWXYZ\"
}
}
}" | jq -c | ./generic_provider.py
popd
EC2 API reference
mock CloudFormation request to create_launch_template_from_configuration
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"PhysicalResourceId\": \"$(uuid)\",
\"ResourceProperties\": {
\"AgentType\": \"custom\",
\"AgentService\": \"autoscaling\",
\"AgentCreateMethod\": \"create_launch_template_from_configuration\",
\"AgentDeleteMethod\": \"delete_launch_template\",
\"AgentCreateArgs\": {
\"LaunchConfigurationName\": \"awseb-e-abcdef1234-stack-AWSEBAutoScalingLaunchConfiguration-99F00TRKDCBAR\",
\"LaunchTemplateName\": \"foo-bar\",
\"Description\": \"foo-bar\",
\"TagSpecifications\": [
{
\"ResourceType\": \"launch-template\",
\"Tags\": [
{
\"Key\": \"Name\",
\"Value\": \"foo-bar\"
}
]
}
]
},
\"AgentDeleteArgs\": {
\"LaunchTemplateName\": \"foo-bar\"
}
}
}" | jq -c | VERBOSE=1 ./generic_provider.py
popd
mock CloudFormation request to update ASGs created by Elastic Beanstalk with MixedInstancesPolicy
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"PhysicalResourceId\": \"$(uuid)\",
\"ResourceProperties\": {
\"AgentType\": \"custom\",
\"AgentService\": \"autoscaling\",
\"AgentResponseNode\": \"ResponseMetadata\",
\"AgentCreateMethod\": \"update_auto_scaling_group\",
\"AgentUpdateMethod\": \"update_auto_scaling_group\",
\"AgentCreateArgs\": {
\"AutoScalingGroupName\": \"awseb-e-abcdef1234-stack-AWSEBAutoScalingGroup-99F00TRKDCBAR\",
\"MixedInstancesPolicy\": {
\"LaunchTemplate\": {
\"LaunchTemplateSpecification\": {
\"LaunchTemplateId\": \"lt-abcdef1234567890\",
\"Version\": \"1\"
},
\"Overrides\": [
{
\"InstanceType\": \"t3a.nano\"
},
{
\"InstanceType\": \"t3a.micro\"
},
{
\"InstanceType\": \"t3a.small\"
},
{
\"InstanceType\": \"t3a.medium\"
},
{
\"InstanceType\": \"t3a.large\"
}
]
},
\"InstancesDistribution\": {
\"OnDemandBaseCapacity\": 0,
\"OnDemandPercentageAboveBaseCapacity\": 50
}
}
}
}
}
}" | jq -c | VERBOSE=1 ./generic_provider.py
popd
mock CloudFormation request to tag resources
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"ec2\",
\"AgentCreateMethod\": \"create_tags\",
\"AgentCreateArgs\": {
\"Resources\": [
\"eipalloc-12345677890\"
],
\"Tags\": [
{
\"Key\": \"foo\",
\"Value\": \"bar\"
}
]
},
\"AgentDeleteMethod\": \"delete_tags\",
\"AgentDeleteArgs\": {
\"Resources\": [
\"eipalloc-12345677890\"
],
\"Tags\": [
{
\"Key\": \"foo\",
\"Value\": \"bar\"
}
]
}
}
}" | jq -c | ./generic_provider.py
popd
mock CloudFormation request to authorize_security_group_ingress in another account
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"ec2\",
\"RoleArn\": \"arn:aws:iam::1234567890:role/CrossAccountRole\",
\"AgentRegion\": \"us-east-1\",
\"AgentCreateMethod\": \"authorize_security_group_ingress\",
\"AgentCreateArgs\": {
\"GroupId\": \"sg-1234567890abcdef\",
\"IpPermissions\": [
{
\"FromPort\": 22,
\"IpProtocol\": \"tcp\",
\"IpRanges\": [
{
\"CidrIp\": \"172.16.0.0/16\",
\"Description\": \"foo-bar\"
}
],
\"ToPort\": 22
}
]
},
\"AgentDeleteMethod\": \"revoke_security_group_ingress\",
\"AgentDeleteArgs\": {
\"GroupId\": \"sg-1234567890abcdef\",
\"IpPermissions\": [
{
\"FromPort\": 22,
\"IpProtocol\": \"tcp\",
\"IpRanges\": [
{
\"CidrIp\": \"172.16.0.0/16\",
\"Description\": \"foo-bar\"
}
],
\"ToPort\": 22
}
]
}
}
}" | jq -c | ./generic_provider.py
popd
mock CloudFormation request to modify subnet attribute(s)
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"ec2\",
\"AgentCreateMethod\": \"modify_subnet_attribute\",
\"AgentCreateArgs\": {
\"MapPublicIpOnLaunch\": {
\"Value\": true
},
\"SubnetId\": \"subnet-abcdef1234567890\"
}
}
}" | jq -c | ./generic_provider.py
popd
mock CloudFormation request to get existing SSM parameter (stored outside of stack)
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"ssm\",
\"AgentCreateMethod\": \"get_parameter\",
\"AgentWaitQueryExpr\": \"$.Parameter.Value\",
\"AgentCreateArgs\": {
\"Name\": \"/foo/bar\",
\"WithDecryption\": true
}
}
}" | jq -c | ./generic_provider.py
popd
mock CloudFormation request to put SSM parameter
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"ssm\",
\"AgentCreateMethod\": \"put_parameter\",
\"AgentUpdateMethod\": \"put_parameter\",
\"AgentDeleteMethod\": \"delete_parameter\",
\"AgentResourceId\": \"Name\",
\"AgentCreateArgs\": {
\"Name\": \"/foo/bar\",
\"Value\": \"foo-bar\",
\"Type\": \"SecureString\",
\"Overwrite\": false
},
\"AgentUpdateArgs\": {
\"Name\": \"/foo/bar\",
\"Value\": \"foo-bar\",
\"Type\": \"SecureString\",
\"Overwrite\": true
},
\"AgentDeleteArgs\": {
\"Name\": \"/foo/bar\"
}
}
}" | jq -c | ./generic_provider.py
popd
EKS API reference
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentType\": \"client\",
\"AgentService\": \"eks\",
\"AgentCreateMethod\": \"describe_addon_versions\",
\"AgentWaitQueryExpr\": \"$.[0].addonVersions[0].addonVersion\",
\"AgentResourceId\": \"addonVersion\",
\"AgentResponseNode\": \"addons\",
\"AgentCreateArgs\": {
\"kubernetesVersion\": \"1.31\",
\"addonName\": \"kube-proxy\"
}
}
}" | jq -c | ./generic_provider.py
popd
EC2 API reference
mock CloudFormation request to obtain instance public IPv6 address
pushd generic_provider
echo "{
\"RequestType\": \"Create\",
\"ResponseURL\": \"https://cloudformation-custom-resource-response-${AWS_REGION}.s3.amazonaws.com/\",
\"StackId\": \"arn:aws:cloudformation:${AWS_REGION}:$(aws sts get-caller-identity | jq -r '.Account'):stack/MockStack/$(uuid)\",
\"RequestId\": \"$(uuid)\",
\"ResourceType\": \"Custom::MockResource\",
\"LogicalResourceId\": \"MockResource\",
\"ResourceProperties\": {
\"AgentService\": \"ec2\",
\"AgentType\": \"resource\",
\"AgentWaitQueryExpr\": \"$..Ipv6Address\",
\"AgentResourceId\": \"Ipv6Address\",
\"AgentCreateMethod\": \"network_interfaces_attribute\",
\"AgentCreateArgs\": {
\"ResourceName\": \"Instance\",
\"ResourceId\": \"i-abcdef1234567890\"
}
}
}" | jq -c | ./generic_provider.py
popd
--belodetek 😬