TL;DR One Custom Resource provider to Rule Them All, inspect the code and try some examples 🤓
Generic CloudFormation Custom Resources provider. All shell-fu is Bash;
git
,pip
,awscli
andjq
required.
git clone https://github.com/ab77/cfn-generic-custom-resource\
&& cd cfn-generic-custom-resource\
&& git pull --recurse-submodules\
&& git submodule update --remote --recursive
📝 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
pushd generic_provider\
&& pip install --upgrade -r requirements.txt -t .\
&& 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'
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=vpc-abcdef1234567890
subnets=(subnet-abcdef1234567890 subnet-1234567890abcdef)
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\
--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\
--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\
--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
🐞 useful to debug resource creation of AWS resources from a local workstation
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 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 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\": \"get_parameter\",
\"AgentWaitQueryExpr\": \"$.Parameter.Value\",
\"AgentCreateArgs\": {
\"Name\": \"/foo/bar\",
\"WithDecryption\": true
}
}
}" | 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 😬