cfn-custom-resource-provider
TL;DR One Custom Resource provider to Rule Them All, inspect the code, read the blog, try some examples and consider contributing 🤓
TOC
CloudFormation demo stacks
mock requests
client
- ACM
- S3
- AWS Backup
- Directory Services
- IAM
- KMS
- Relational Database Service
- Database Migration Service
- EC2
- KMS
resources
about
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.
CloudFormation
All shell-fu is Bash;
git
,pip
,awscli
andjq
required.
init
git clone --recurse-submodules --remote-submodules\
https://github.com/ab77/cfn-generic-custom-resource\
&& cd cfn-generic-custom-resource
create bucket
📝 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}
install requirements (venv)
📝 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
compile dependencies
docker ps && pushd generic_provider && make && popd
Client VPN demo
☢️ beware of the currently eye-watering Client VPN pricing
certificates
📜 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 assets
📦 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
deploy stack
📝 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
download profile
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
connect
Cognito demo
📝 make sure to create bucket and install requirements first
update bucket policy
⚠️ public read access required for access to
MetadataURL
, 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}
download metadata
- login to Google Apps Admin
- navigate to
Apps -> SAML Apps --> + --> SETUP MY OWN CUSTOM APP
- select
(Option 2) IDP metadata
, download and save
copy metadata
domain_name='foo.bar'
aws s3 cp GoogleIDPMetadata-${domain_name}.xml s3://${bucket}/
package assets
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
deploy stack
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}"
configure G Suite
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
VPC peering demo
creates a peering connection between source and destination VPCs, including tags and routes in both directions
package assets
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
create IAM role
☢ 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: '/'
...
update IAM role
☢ 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'
...
deploy stack
📝 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
AWS Backup (EFS)
also see mock examples below
package assets
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
deploy stack
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
ACM Private CA
also see mock examples below
package assets
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
deploy stack
⚠️ 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
mock client requests
🐞 useful to debug resource creation of AWS resources from a local workstation
ACM
ACM API reference
request_certificate
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
ACM-PCA API reference
create_certificate_authority
mock CloudFormation request to create_certificate_authority
(⚠️ ensure to clean-up the CA resources to avoid $400/month surprise on your next AWS bill)
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\": \"foo@bar.com\"
}
},
\"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
create_self_signed_cert
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
get_certificate_authority_csr
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
sign_csr
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
import_certificate_authority_certificate
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
S3 API reference
put-bucket-notification-configuration
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
Backup API reference
create-backup-vault
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
create-backup-plan
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
create-backup-selection
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
Directory Services API reference
AD Connector
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
IAM API reference
SSH public key
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
KMS API reference
encrypt
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
Relational Database Service
RDS API reference
modify-db-cluster
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
modify-db-instance
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
Database Migration Service
DMS API reference
describe-replication-tasks
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
stop-replication-task
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
EC2 API reference
create_launch_template_from_configuration
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
update_auto_scaling_group
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
create-tags
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
authorize-security-group-ingress
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
modify-subnet-attribute
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
get-parameter
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
put-parameter
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
EKS API reference
get addon version(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\": \"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
mock resources requests
EC2 API reference
network-interfaces-attribute
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 😬