Skip to content

KenHundley/cfn-generic-custom-resource

 
 

Repository files navigation

cfn-custom-resource-provider

TL;DR One Custom Resource provider to Rule Them All, inspect the code and try some examples 🤓

CloudFormation

Generic CloudFormation Custom Resources provider. All shell-fu is Bash; git, pip, awscli and jq required.

TOC

demo stacks

mock requests

init

git clone https://github.com/ab77/cfn-generic-custom-resource\
  && cd cfn-generic-custom-resource\
  && git pull --recurse-submodules\
  && git submodule update --remote --recursive

create bucket

📝 creates a new bucket with a random GUID; ensure ~/.aws/credentials and ~/.aws/config are configured (run aws configure ...) and export AWS_PROFILE and AWS_REGION environment variables

bucket=$(uuid)
aws s3 mb s3://${bucket}

install requirements

📝 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

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'


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; for directory-service-authentication or both, specify additional DirectoryId 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

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\
  --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 as TargetPort

# 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

mock client requests

🐞 useful to debug resource creation of AWS resources from a local workstation

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-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 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

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 😬

About

CloudFormation generic custom resource provider

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Python 99.7%
  • HTML 0.3%