Man, I don't miss CloudFormation. If you want to do anything interesting at all (like being able to spin up turn-key ad-hoc stacks for developers that are mostly the same as your main environments), then you have to parameterize everything, and passing data around CloudFormation templates is just awful--what you want is functions and data structures but CloudFormation gives you "nested-stacks" and you have to encode most of your data structures as strings and "parse them out" with the YAML builtin functions that they provide (e.g., `{"Fn::Split": "foo,bar,baz"}` will evaluate to `["foo", "bar", "baz"]`, but good luck if you want a list of objects). Each stack also has a limit on the number of parameters you can pass, and since you can't really pass struct-like data (e.g., `"Foo": {"bar": "baz", "qux": "..."}`) you end up flattening that struct into N parameters (e.g., `"FooBar": "Baz", "FooQux": "..."`). You can try to plumb things through SSM or SecretsManager, but each stack also limits the number of parameters you can pass there as well.
Basically, CloudFormation is like the shittiest programming language you've ever encountered. People will argue that it's supposed to be simple because it's just YAML, but that's bogus--we clearly need to be able to do complex things in this space or else CF wouldn't provide these hacky functions. We pretty clearly need some sort of expression language if not something more robust, since that's the direction all of these "it's just YAML!" tools are going (CloudFormation, Terraform, etc). CloudFormation might actually make a decent "assembly language" for an infrastructure-as-code backend (although extending CloudFormation for third party services was still far too difficult last I checked--basically you had to run your own lambdas) that some higher-level tools might generate, but it's a mess for anything that isn't a toy.