cdklabs.cdk-enterprise-iac 0.1.0
@cdklabs/cdk-enterprise-iac
License: Apache-2.0
Requires Python: ~=3.8

<!--
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
-->

# CDK Enterprise IaC

Utilities for using CDK within enterprise constraints.

## Install

Typescript

```zsh
npm install @cdklabs/cdk-enterprise-iac
```

Python

```zsh
pip install cdklabs.cdk-enterprise-iac
```

## Who this project is for

Within large enterprises, builders can come up against enterprise imposed constraints when deploying on AWS.

This could be simple things such as "All IAM roles must have a specific Permissions Boundary attached".

This could also be more restrictive such as strict separation of duties, only allowing certain teams the ability to deploy specific AWS resources (e.g. Networking team can deploy VPCs, Subnets, and route tables. Security team can deploy IAM resources. Developers can deploy Compute. DBAs can deploy Databases, etc.)

Enterprises with very restrictive environments like these would benefit from taking a closer look at their policies to see how they can allow builders to safely deploy resources with less friction. However in many enterprises this is easier said than done, and builders are still expected to deliver.

This project is meant to reduce friction for builders working within these enterprise constraints while the enterprise determines what policies make the most sense for their organization, and is in no way prescriptive.

## Usage

There are many tools available, all detailed in [`API.md`](./API.md).

A few examples of these tools below:

### Adding permissions boundaries to all generated IAM roles

Example for `AddPermissionBoundary` in Typescript project.

```python
# Example automatically generated from non-compiling source. May contain errors.
import aws_cdk as cdk
from ...lib.my_project_stack import MyStack
from aws_cdk import Aspects
from cdklabs.cdk_enterprise_iac import AddPermissionBoundary


app = cdk.App()
MyStack(app, "MyStack")

Aspects.of(app).add(
    AddPermissionBoundary(
        permissions_boundary_policy_name="MyPermissionBoundaryName",
        instance_profile_prefix="MY_PREFIX_",  # optional, Defaults to ''
        policy_prefix="MY_POLICY_PREFIX_",  # optional, Defaults to ''
        role_prefix="MY_ROLE_PREFIX_"
    ))
```

Example for `AddPermissionBoundary` in Python project.

```python
import aws_cdk as cdk
from cdklabs.cdk_enterprise_iac import AddPermissionBoundary
from test_py.test_py_stack import TestPyStack


app = cdk.App()
TestPyStack(app, "TestPyStack")

cdk.Aspects.of(app).add(AddPermissionBoundary(
    permissions_boundary_policy_name="MyPermissionBoundaryName",
    instance_profile_prefix="MY_PREFIX_",  # optional, Defaults to ""
    policy_prefix="MY_POLICY_PREFIX_",  # optional, Defaults to ""
    role_prefix="MY_ROLE_PREFIX_"  # optional, Defaults to ""
))

app.synth()
```

### Resource extraction

:warning: Resource extraction is in an experimental phase. Test and validate before using in production. Please open any issues found [here](https://github.com/cdklabs/cdk-enterprise-iac/).

> NOTE: You must have the AWS CLI installed and available for the Resource Extractor to work.

In many enterprises, there are separate teams with different IAM permissions than developers deploying CDK applications.

For example there might be a networking team with permissions to deploy `AWS::EC2::SecurityGroup` and `AWS::EC2::EIP`, or a security team with permissions to deploy `AWS::IAM::Role` and `AWS::IAM::Policy`, but the developers deploying the CDK don't have those permissions.

When a developer doesn't have permissions to deploy necessary resources in their CDK application, writing good code becomes difficult to manage when a cdk deploy will quickly error due to not being able to deploy something like an `AWS::IAM::Role` which is foundational to any project deployed into AWS.

An enterprise should *allow* builders to deploy these resources via CDK for [many reasons](https://github.com/aws/aws-cdk/wiki/Security-And-Safety-Dev-Guide#allowing-creation-of-iam-roles-without-privilege-escalation), and can use [Permissions Boundaries](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) to prevent privilege escalation. For enterprises that haven't yet utilized Permissions Boundaries, the `ResourceExtractor` can make it easier for builders to write good CDK while complying with enterprise policies.

Using the `ResourceExtractor` Aspect, developers can write their CDK code as though they had sufficient IAM permissions, but extract those resources into a separate stack for an external team to deploy on their behalf.

Take the following example stack:

```python
# Example automatically generated from non-compiling source. May contain errors.
from aws_cdk import App, Aspects, RemovalPolicy, Stack
from aws_cdk.aws_lambda import Code, Function, Runtime
from aws_cdk.aws_s3 import Bucket


app = App()
app_stack = Stack(app, "MyAppStack")

func = Function(app_stack, "TestLambda",
    code=Code.from_asset(path.join(__dirname, "lambda-handler")),
    handler="index.handler",
    runtime=Runtime.PYTHON_3_11
)
bucket = Bucket(app_stack, "TestBucket",
    auto_delete_objects=True,
    removal_policy=RemovalPolicy.DESTROY
)
bucket.grant_read_write(func)

app.synth()
```

The synthesized Cloudformation would include *all* AWS resources required, including resources a developer might not have permissions to deploy

The above example would include the following snippet in the synthesized Cloudformation

```yaml
TestLambdaServiceRoleC28C2D9C:
  Type: 'AWS::IAM::Role'
  Properties:
    AssumeRolePolicyDocument:
      Statement:
        - Action: 'sts:AssumeRole'
          Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
      Version: 2012-10-17
    # excluding remaining properties
  TestLambda2F70C45E:
    Type: 'AWS::Lambda::Function'
    Properties:
      Role: !GetAtt
        - TestLambdaServiceRoleC28C2D9C
        - Arn
      # excluding remaining properties
```

While including `bucket.grantReadWrite(func)` in the CDK application ensures an IAM role with least privilege IAM policies for the application, the creation of IAM resources such as Roles and Policies may be restricted to a security team, resulting in the synthesized Cloudformation template not being deployable by a developer.

Using the `ResourceExtractor`, we can pull out an arbitrary list of Cloudformation resources that a developer *doesn't* have permissions to provision, and create a separate stack that can be sent to a security team.

```python
# Example automatically generated from non-compiling source. May contain errors.
from aws_cdk import App, Aspects, RemovalPolicy, Stack
from aws_cdk.aws_lambda import Code, Function, Runtime
from aws_cdk.aws_s3 import Bucket
# Import ResourceExtractor
from cdklabs.cdk_enterprise_iac import ResourceExtractor


app = App()
app_stack = Stack(app, "MyAppStack")
# Set up a destination stack to extract resources to
extracted_stack = Stack(app, "ExtractedStack")

func = Function(app_stack, "TestLambda",
    code=Code.from_asset(path.join(__dirname, "lambda-handler")),
    handler="index.handler",
    runtime=Runtime.PYTHON_3_11
)
bucket = Bucket(app_stack, "TestBucket",
    auto_delete_objects=True,
    removal_policy=RemovalPolicy.DESTROY
)
bucket.grant_read_write(func)

# Capture the output of app.synth()
synthed_app = app.synth()
# Apply the ResourceExtractor Aspect
Aspects.of(app).add(
    ResourceExtractor(
        # synthesized stacks to examine
        stack_artifacts=synthed_app.stacks,
        # Array of Cloudformation resources to extract
        resource_types_to_extract=["AWS::IAM::Role", "AWS::IAM::Policy", "AWS::IAM::ManagedPolicy", "AWS::IAM::InstanceProfile"
        ],
        # Destination stack for extracted resources
        extract_destination_stack=extracted_stack
    ))
# Resynthing since ResourceExtractor has modified the app
app.synth(force=True)
```

In the example above, *all* resources are created in the `appStack`, and an empty `extractedStack` is also created.

We apply the `ResourceExtractor` Aspect, specifying the Cloudformation resource types the developer is unable to deploy due to insufficient IAM permissions.

Now when we list stacks in the CDK project, we can see an added stack

```zsh
$ cdk ls
MyAppStack
ExtractedStack
```

Taking a look at these synthesized stacks, in the `ExtractedStack` we'll find:

```yaml
Resources:
  TestLambdaServiceRoleC28C2D9C:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: 'sts:AssumeRole'
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
        Version: 2012-10-17
      # excluding remaining properties
Outputs:
  ExportAppStackTestLambdaServiceRoleC28C2D9C:
    Value:
      'Fn::GetAtt':
        - TestLambdaServiceRoleC28C2D9C
        - Arn
    Export:
      Name: 'AppStack:TestLambdaServiceRoleC28C2D9C'  # Exported name
```

And inside the synthesized `MyAppStack` template:

```yaml
Resources:
  TestLambda2F70C45E:
    Type: 'AWS::Lambda::Function'
    Properties:
      Role: !ImportValue 'AppStack:TestLambdaServiceRoleC28C2D9C'  # Using ImportValue instrinsic function to use pre-created IAM role
      # excluding remaining properties
```

In this scenario, a developer is able to provide an external security team with sufficient IAM privileges to deploy the `ExtractedStack`.

Once deployed, a developer can run `cdk deploy MyAppStack` without errors due to insufficient IAM privileges

#### Value Sharing methods

When resources are extracted from a stack, there must be a method to reference the resources that have been extracted.

There are three methods (see `ResourceExtractorShareMethod` enum)

* `CFN_OUTPUT`
* `SSM_PARAMETER`
* `API_LOOKUP`

##### `CFN_OUTPUT`

The default sharing method is `CFN_OUTPUT`, which uses Cloudformation Export/Import to Export values in the extracted stack (see [Outputs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html)), and use the [Fn::ImportValue](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html) intrinsic function to reference those values.

This works fine, but some teams may prefer a looser coupling between the extracted stack deployed by an external team and the rest of the CDK infrastructure.

##### `SSM_PARAMETER`

In this method, the extracted stack generates Parameters in AWS Systems Manager Parameter Store, and modifies the CDK application to look up the generated parameter using [`aws_ssm.StringParameter.valueFromLookup()`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ssm.StringParameter.html#static-valuewbrfromwbrlookupscope-parametername) at synthesis time.

Example on using this method:

```python
# Example automatically generated from non-compiling source. May contain errors.
from cdklabs.cdk_enterprise_iac import ResourceExtractor, ResourceExtractorShareMethod


Aspects.of(app).add(
    ResourceExtractor(
        stack_artifacts=synthed_app.stacks,
        resource_types_to_extract=["AWS::IAM::Role", "AWS::IAM::Policy", "AWS::IAM::ManagedPolicy", "AWS::IAM::InstanceProfile"
        ],
        extract_destination_stack=extracted_stack,
        value_share_method=ResourceExtractorShareMethod.SSM_PARAMETER
    ))
```

##### `API_LOOKUP`

The `API_LOOKUP` sharing method is a work in progress, and not yet supported

#### Resource Partials

Some resources that get extracted might reference resources that aren't yet created.

In our example CDK application we include the line

```python
# Example automatically generated from non-compiling source. May contain errors.
bucket.grant_read_write(func)
```

This creates an `AWS::IAM::Policy` that includes the necessary Actions scoped down to the S3 bucket.

When the `AWS::IAM::Policy` is extracted, it's unable to use `Ref` or `Fn::GetAtt` to reference the S3 bucket since the S3 bucket wasn't extracted.

In this case we substitute the reference with a "partial ARN" that makes a best effort to scope the resources in the IAM policy statement to the ARN of the yet-to-be created S3 bucket.

There are multiple resource types supported out of the box (found in [`createDefaultTransforms`](src/patches/resource-extractor/resourceTransformer.ts)). In the event you have a resource not yet supported, you'll receive a `MissingTransformError`. In this case you can either open an [issue](https://github.com/cdklabs/cdk-enterprise-iac/issues) with the resource in question, or you can include the `additionalTransforms` property.

Consider the following:

```python
# Example automatically generated from non-compiling source. May contain errors.
vpc = Vpc(stack, "TestVpc")
db = DatabaseInstance(stack, "TestDb",
    vpc=vpc,
    engine=DatabaseInstanceEngine.POSTGRES
)
func = Function(stack, "TestLambda", {
    "code": Code.from_asset(path.join(__dirname, "lambda-handler")),
    "handler": "index.handler",
    "runtime": Runtime.PYTHON_3_11
})
db.secret.grant_read(func)

synthed_app = app.synth()
Aspects.of(app).add(
    ResourceExtractor(
        extract_destination_stack=extracted_stack,
        stack_artifacts=synthed_app.stacks,
        value_share_method=ResourceExtractorShareMethod.CFN_OUTPUT,
        resource_types_to_extract=["AWS::IAM::Role", "AWS::IAM::Policy"],
        additional_transforms={
            "AWS::SecretsManager::SecretTargetAttachment": f"arn:{Aws.PARTITION}:secretsmanager:{Aws.REGION}:{Aws.ACCOUNT_ID}:secret:some-expected-value*"
        }
    ))
app.synth(force=True)
```

In this case, there is a `AWS::SecretsManager::SecretTargetAttachment` generated to complete the final link between a Secrets Manager secret and the associated database by adding the database connection information to the secret JSON, which returns the ARN of the generated secret.

In the context of extracting the IAM policy, we want to tell the `ResourceExtractor` how to handle the resource section of the IAM policy statement so that it is scoped down sufficiently.

In this case rather than using a `Ref: LogicalIdForTheSecretTargetAttachment` we construct the ARN we want to use.

Details in [API.md](API.md)

## Generated API.md

---


Generated API.md below:

<details>
    <summary>Expand to view API docs</summary><!--
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
-->

# CDK Enterprise IaC

Utilities for using CDK within enterprise constraints.

## Install

Typescript

```zsh
npm install @cdklabs/cdk-enterprise-iac
```

Python

```zsh
pip install cdklabs.cdk-enterprise-iac
```

## Who this project is for

Within large enterprises, builders can come up against enterprise imposed constraints when deploying on AWS.

This could be simple things such as "All IAM roles must have a specific Permissions Boundary attached".

This could also be more restrictive such as strict separation of duties, only allowing certain teams the ability to deploy specific AWS resources (e.g. Networking team can deploy VPCs, Subnets, and route tables. Security team can deploy IAM resources. Developers can deploy Compute. DBAs can deploy Databases, etc.)

Enterprises with very restrictive environments like these would benefit from taking a closer look at their policies to see how they can allow builders to safely deploy resources with less friction. However in many enterprises this is easier said than done, and builders are still expected to deliver.

This project is meant to reduce friction for builders working within these enterprise constraints while the enterprise determines what policies make the most sense for their organization, and is in no way prescriptive.

## Usage

There are many tools available, all detailed in [`API.md`](./API.md).

A few examples of these tools below:

### Adding permissions boundaries to all generated IAM roles

Example for `AddPermissionBoundary` in Typescript project.

```python
# Example automatically generated from non-compiling source. May contain errors.
import aws_cdk as cdk
from ...lib.my_project_stack import MyStack
from aws_cdk import Aspects
from cdklabs.cdk_enterprise_iac import AddPermissionBoundary


app = cdk.App()
MyStack(app, "MyStack")

Aspects.of(app).add(
    AddPermissionBoundary(
        permissions_boundary_policy_name="MyPermissionBoundaryName",
        instance_profile_prefix="MY_PREFIX_",  # optional, Defaults to ''
        policy_prefix="MY_POLICY_PREFIX_",  # optional, Defaults to ''
        role_prefix="MY_ROLE_PREFIX_"
    ))
```

Example for `AddPermissionBoundary` in Python project.

```python
import aws_cdk as cdk
from cdklabs.cdk_enterprise_iac import AddPermissionBoundary
from test_py.test_py_stack import TestPyStack


app = cdk.App()
TestPyStack(app, "TestPyStack")

cdk.Aspects.of(app).add(AddPermissionBoundary(
    permissions_boundary_policy_name="MyPermissionBoundaryName",
    instance_profile_prefix="MY_PREFIX_",  # optional, Defaults to ""
    policy_prefix="MY_POLICY_PREFIX_",  # optional, Defaults to ""
    role_prefix="MY_ROLE_PREFIX_"  # optional, Defaults to ""
))

app.synth()
```

### Resource extraction

:warning: Resource extraction is in an experimental phase. Test and validate before using in production. Please open any issues found [here](https://github.com/cdklabs/cdk-enterprise-iac/).

In many enterprises, there are separate teams with different IAM permissions than developers deploying CDK applications.

For example there might be a networking team with permissions to deploy `AWS::EC2::SecurityGroup` and `AWS::EC2::EIP`, or a security team with permissions to deploy `AWS::IAM::Role` and `AWS::IAM::Policy`, but the developers deploying the CDK don't have those permissions.

When a developer doesn't have permissions to deploy necessary resources in their CDK application, writing good code becomes difficult to manage when a cdk deploy will quickly error due to not being able to deploy something like an `AWS::IAM::Role` which is foundational to any project deployed into AWS.

An enterprise should *allow* builders to deploy these resources via CDK for [many reasons](https://github.com/aws/aws-cdk/wiki/Security-And-Safety-Dev-Guide#allowing-creation-of-iam-roles-without-privilege-escalation), and can use [Permissions Boundaries](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) to prevent privilege escalation. For enterprises that haven't yet utilized Permissions Boundaries, the `ResourceExtractor` can make it easier for builders to write good CDK while complying with enterprise policies.

Using the `ResourceExtractor` Aspect, developers can write their CDK code as though they had sufficient IAM permissions, but extract those resources into a separate stack for an external team to deploy on their behalf.

Take the following example stack:

```python
# Example automatically generated from non-compiling source. May contain errors.
from aws_cdk import App, Aspects, RemovalPolicy, Stack
from aws_cdk.aws_lambda import Code, Function, Runtime
from aws_cdk.aws_s3 import Bucket


app = App()
app_stack = Stack(app, "MyAppStack")

func = Function(app_stack, "TestLambda",
    code=Code.from_asset(path.join(__dirname, "lambda-handler")),
    handler="index.handler",
    runtime=Runtime.PYTHON_3_11
)
bucket = Bucket(app_stack, "TestBucket",
    auto_delete_objects=True,
    removal_policy=RemovalPolicy.DESTROY
)
bucket.grant_read_write(func)

app.synth()
```

The synthesized Cloudformation would include *all* AWS resources required, including resources a developer might not have permissions to deploy

The above example would include the following snippet in the synthesized Cloudformation

```yaml
TestLambdaServiceRoleC28C2D9C:
  Type: 'AWS::IAM::Role'
  Properties:
    AssumeRolePolicyDocument:
      Statement:
        - Action: 'sts:AssumeRole'
          Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
      Version: 2012-10-17
    # excluding remaining properties
  TestLambda2F70C45E:
    Type: 'AWS::Lambda::Function'
    Properties:
      Role: !GetAtt
        - TestLambdaServiceRoleC28C2D9C
        - Arn
      # excluding remaining properties
```

While including `bucket.grantReadWrite(func)` in the CDK application ensures an IAM role with least privilege IAM policies for the application, the creation of IAM resources such as Roles and Policies may be restricted to a security team, resulting in the synthesized Cloudformation template not being deployable by a developer.

Using the `ResourceExtractor`, we can pull out an arbitrary list of Cloudformation resources that a developer *doesn't* have permissions to provision, and create a separate stack that can be sent to a security team.

```python
# Example automatically generated from non-compiling source. May contain errors.
from aws_cdk import App, Aspects, RemovalPolicy, Stack
from aws_cdk.aws_lambda import Code, Function, Runtime
from aws_cdk.aws_s3 import Bucket
# Import ResourceExtractor
from cdklabs.cdk_enterprise_iac import ResourceExtractor


app = App()
app_stack = Stack(app, "MyAppStack")
# Set up a destination stack to extract resources to
extracted_stack = Stack(app, "ExtractedStack")

func = Function(app_stack, "TestLambda",
    code=Code.from_asset(path.join(__dirname, "lambda-handler")),
    handler="index.handler",
    runtime=Runtime.PYTHON_3_11
)
bucket = Bucket(app_stack, "TestBucket",
    auto_delete_objects=True,
    removal_policy=RemovalPolicy.DESTROY
)
bucket.grant_read_write(func)

# Capture the output of app.synth()
synthed_app = app.synth()
# Apply the ResourceExtractor Aspect
Aspects.of(app).add(
    ResourceExtractor(
        # synthesized stacks to examine
        stack_artifacts=synthed_app.stacks,
        # Array of Cloudformation resources to extract
        resource_types_to_extract=["AWS::IAM::Role", "AWS::IAM::Policy", "AWS::IAM::ManagedPolicy", "AWS::IAM::InstanceProfile"
        ],
        # Destination stack for extracted resources
        extract_destination_stack=extracted_stack
    ))
# Resynthing since ResourceExtractor has modified the app
app.synth(force=True)
```

In the example above, *all* resources are created in the `appStack`, and an empty `extractedStack` is also created.

We apply the `ResourceExtractor` Aspect, specifying the Cloudformation resource types the developer is unable to deploy due to insufficient IAM permissions.

Now when we list stacks in the CDK project, we can see an added stack

```zsh
$ cdk ls
MyAppStack
ExtractedStack
```

Taking a look at these synthesized stacks, in the `ExtractedStack` we'll find:

```yaml
Resources:
  TestLambdaServiceRoleC28C2D9C:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: 'sts:AssumeRole'
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
        Version: 2012-10-17
      # excluding remaining properties
Outputs:
  ExportAppStackTestLambdaServiceRoleC28C2D9C:
    Value:
      'Fn::GetAtt':
        - TestLambdaServiceRoleC28C2D9C
        - Arn
    Export:
      Name: 'AppStack:TestLambdaServiceRoleC28C2D9C'  # Exported name
```

And inside the synthesized `MyAppStack` template:

```yaml
Resources:
  TestLambda2F70C45E:
    Type: 'AWS::Lambda::Function'
    Properties:
      Role: !ImportValue 'AppStack:TestLambdaServiceRoleC28C2D9C'  # Using ImportValue instrinsic function to use pre-created IAM role
      # excluding remaining properties
```

In this scenario, a developer is able to provide an external security team with sufficient IAM privileges to deploy the `ExtractedStack`.

Once deployed, a developer can run `cdk deploy MyAppStack` without errors due to insufficient IAM privileges

#### Value Sharing methods

When resources are extracted from a stack, there must be a method to reference the resources that have been extracted.

There are three methods (see `ResourceExtractorShareMethod` enum)

* `CFN_OUTPUT`
* `SSM_PARAMETER`
* `API_LOOKUP`

##### `CFN_OUTPUT`

The default sharing method is `CFN_OUTPUT`, which uses Cloudformation Export/Import to Export values in the extracted stack (see [Outputs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html)), and use the [Fn::ImportValue](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html) intrinsic function to reference those values.

This works fine, but some teams may prefer a looser coupling between the extracted stack deployed by an external team and the rest of the CDK infrastructure.

##### `SSM_PARAMETER`

In this method, the extracted stack generates Parameters in AWS Systems Manager Parameter Store, and modifies the CDK application to look up the generated parameter using [`aws_ssm.StringParameter.valueFromLookup()`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ssm.StringParameter.html#static-valuewbrfromwbrlookupscope-parametername) at synthesis time.

Example on using this method:

```python
# Example automatically generated from non-compiling source. May contain errors.
from cdklabs.cdk_enterprise_iac import ResourceExtractor, ResourceExtractorShareMethod


Aspects.of(app).add(
    ResourceExtractor(
        stack_artifacts=synthed_app.stacks,
        resource_types_to_extract=["AWS::IAM::Role", "AWS::IAM::Policy", "AWS::IAM::ManagedPolicy", "AWS::IAM::InstanceProfile"
        ],
        extract_destination_stack=extracted_stack,
        value_share_method=ResourceExtractorShareMethod.SSM_PARAMETER
    ))
```

##### `API_LOOKUP`

The `API_LOOKUP` sharing method is a work in progress, and not yet supported

#### Resource Partials

Some resources that get extracted might reference resources that aren't yet created.

In our example CDK application we include the line

```python
# Example automatically generated from non-compiling source. May contain errors.
bucket.grant_read_write(func)
```

This creates an `AWS::IAM::Policy` that includes the necessary Actions scoped down to the S3 bucket.

When the `AWS::IAM::Policy` is extracted, it's unable to use `Ref` or `Fn::GetAtt` to reference the S3 bucket since the S3 bucket wasn't extracted.

In this case we substitute the reference with a "partial ARN" that makes a best effort to scope the resources in the IAM policy statement to the ARN of the yet-to-be created S3 bucket.

There are multiple resource types supported out of the box (found in [`createDefaultTransforms`](src/patches/resource-extractor/resourceTransformer.ts)). In the event you have a resource not yet supported, you'll receive a `MissingTransformError`. In this case you can either open an [issue](https://github.com/cdklabs/cdk-enterprise-iac/issues) with the resource in question, or you can include the `additionalTransforms` property.

Consider the following:

```python
# Example automatically generated from non-compiling source. May contain errors.
vpc = Vpc(stack, "TestVpc")
db = DatabaseInstance(stack, "TestDb",
    vpc=vpc,
    engine=DatabaseInstanceEngine.POSTGRES
)
func = Function(stack, "TestLambda", {
    "code": Code.from_asset(path.join(__dirname, "lambda-handler")),
    "handler": "index.handler",
    "runtime": Runtime.PYTHON_3_11
})
db.secret.grant_read(func)

synthed_app = app.synth()
Aspects.of(app).add(
    ResourceExtractor(
        extract_destination_stack=extracted_stack,
        stack_artifacts=synthed_app.stacks,
        value_share_method=ResourceExtractorShareMethod.CFN_OUTPUT,
        resource_types_to_extract=["AWS::IAM::Role", "AWS::IAM::Policy"],
        additional_transforms={
            "AWS::SecretsManager::SecretTargetAttachment": f"arn:{Aws.PARTITION}:secretsmanager:{Aws.REGION}:{Aws.ACCOUNT_ID}:secret:some-expected-value*"
        }
    ))
app.synth(force=True)
```

In this case, there is a `AWS::SecretsManager::SecretTargetAttachment` generated to complete the final link between a Secrets Manager secret and the associated database by adding the database connection information to the secret JSON, which returns the ARN of the generated secret.

In the context of extracting the IAM policy, we want to tell the `ResourceExtractor` how to handle the resource section of the IAM policy statement so that it is scoped down sufficiently.

In this case rather than using a `Ref: LogicalIdForTheSecretTargetAttachment` we construct the ARN we want to use.

Details in [API.md](API.md)

## Generated API.md

---


Generated API.md below:

<details>
    <summary>Expand to view API docs</summary>

# API Reference <a name="API Reference" id="api-reference"></a>

## Constructs <a name="Constructs" id="Constructs"></a>

### EcsIsoServiceAutoscaler <a name="EcsIsoServiceAutoscaler" id="@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler"></a>

Creates a EcsIsoServiceAutoscaler construct.

This construct allows you to scale an ECS service in an ISO
region where classic ECS Autoscaling may not be available.

#### Initializers <a name="Initializers" id="@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.Initializer"></a>

```python
# Example automatically generated from non-compiling source. May contain errors.
from cdklabs.cdk_enterprise_iac import EcsIsoServiceAutoscaler


EcsIsoServiceAutoscaler(scope, Construct, id, string, props, EcsIsoServiceAutoscalerProps)
```

| **Name** | **Type** | **Description** |
| --- | --- | --- |
| <code><a href="#@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.Initializer.parameter.scope">scope</a></code> | <code>constructs.Construct</code> | *No description.* |
| <code><a href="#@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.Initializer.parameter.id">id</a></code> | <code>string</code> | *No description.* |
| <code><a href="#@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.Initializer.parameter.props">props</a></code> | <code><a href="#@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscalerProps">EcsIsoServiceAutoscalerProps</a></code> | *No description.* |

---


##### `scope`<sup>Required</sup> <a name="scope" id="@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.Initializer.parameter.scope"></a>

* *Type:* constructs.Construct

---


##### `id`<sup>Required</sup> <a name="id" id="@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.Initializer.parameter.id"></a>

* *Type:* string

---


##### `props`<sup>Required</sup> <a name="props" id="@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.Initializer.parameter.props"></a>

* *Type:* <a href="#@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscalerProps">EcsIsoServiceAutoscalerProps</a>

---


#### Methods <a name="Methods" id="Methods"></a>

| **Name** | **Description** |
| --- | --- |
| <code><a href="#@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.toString">toString</a></code> | Returns a string representation of this construct. |

---


##### `toString` <a name="toString" id="@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.toString"></a>

```python
# Example automatically generated from non-compiling source. May contain errors.
to_string()string
```

Returns a string representation of this construct.

#### Static Functions <a name="Static Functions" id="Static Functions"></a>

| **Name** | **Description** |
| --- | --- |
| <code><a href="#@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.isConstruct">isConstruct</a></code> | Checks if `x` is a construct. |

---


##### ~~`isConstruct`~~ <a name="isConstruct" id="@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.isConstruct"></a>

```python
# Example automatically generated from non-compiling source. May contain errors.
from cdklabs.cdk_enterprise_iac import EcsIsoServiceAutoscaler


EcsIsoServiceAutoscaler.is_construct(x, any)
```

Checks if `x` is a construct.

###### `x`<sup>Required</sup> <a name="x" id="@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.isConstruct.parameter.x"></a>

* *Type:* any

Any object.

---


#### Properties <a name="Properties" id="Properties"></a>

| **Name** | **Type** | **Description** |
| --- | --- | --- |
| <code><a href="#@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.property.node">node</a></code> | <code>constructs.Node</code> | The tree node. |
| <code><a href="#@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.property.ecsScalingManagerFunction">ecsScalingManagerFunction</a></code> | <code>aws-cdk-lib.aws_lambda.Function</code> | *No description.* |

---


##### `node`<sup>Required</sup> <a name="node" id="@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.property.node"></a>

```python
# Example automatically generated from non-compiling source. May contain errors.
node: Node;
```

* *Type:* constructs.Node

The tree node.

---


##### `ecsScalingManagerFunction`<sup>Required</sup> <a name="ecsScalingManagerFunction" id="@cdklabs/cdk-enterprise-iac.EcsIsoServiceAutoscaler.property.ecsScalingManagerFunction"></a>

```python
ecsScalingManagerFunction: Function;
```

* *Type:* aws-cdk-lib.aws_lambda.Function

---


### PopulateWithConfig <a name="PopulateWithConfig" id="@cdklabs/cdk-enterprise-iac.PopulateWithConfig"></a>

Populate a provided VPC with subnets based on a provided configuration.

*Example*

```python
# Example automatically generated from non-compiling source. May contain errors.
my_subnet_config = [{
    "group_name": "app",
    "cidr_range": "172.31.0.0/27",
    "availability_zone": "a",
    "subnet_type": subnet_type.PUBLIC
}, {
    "group_name": "app",
    "cidr_range": "172.31.0.32/27",
    "availability_zone": "b",
    "subnet_type": subnet_type.PUBLIC
}, {
    "group_name": "db",
    "cidr_range": "172.31.0.64/27",
    "availability_zone": "a",
    "subnet_type": subnet_type.PRIVATE_WITH_EGRESS
}, {
    "group_name": "db",
    "cidr_range": "172.31.0.96/27",
    "availability_zone": "b",
    "subnet_type": subnet_type.PRIVATE_WITH_EGRESS
}, {
    "group_name": "iso",
    "cidr_range": "172.31.0.128/26",
    "availability_zone": "a",
    "subnet_type": subnet_type.PRIVATE_ISOLATED
}, {
    "group_name": "iso",
    "cidr_range": "172.31.0.196/26",
    "availability_zone": "b",
    "subnet_type": subnet_type.PRIVATE_ISOLATED
}
]
PopulateWithConfig(self, "vpcPopulater",
    vpc_id="vpc-abcdefg1234567",
    private_route_table_id="rt-abcdefg123456",
    local_route_table_id="rt-123456abcdefg",
    subnet_config=my_subnet_config
)
```

#### Initializers <a name="Initializers" id="@cdklabs/cdk-enterprise-iac.PopulateWithConfig.Initializer"></a>

```python
# Example automatically generated from non-compiling source. May contain errors.
from cdklabs.cdk_enterprise_iac import PopulateWithConfig


PopulateWithConfig(scope, Construct, id, string, props, PopulateWithConfigProps)
```

| **Name** | **Type** | **Description** |
| --- | --- | --- |
| <code><a href="#@cdklabs/cdk-enterprise-iac.PopulateWithConfig.Initializer.parameter.scope">scope</a></code> | <code>constructs.Construct</code> | *No description.* |
| <code><a href="#@cdklabs/cdk-enterprise-iac.PopulateWithConfig.Initializer.parameter.id">id</a></code> | <code>string</code> | *No description.* |
| <code><a href="#@cdklabs/cdk-enterprise-iac.PopulateWithConfig.Initializer.parameter.props">props</a></code> | <code><a href="#@cdklabs/cdk-enterprise-iac.PopulateWithConfigProps">PopulateWithConfigProps</a></code> | *No description.* |

---


##### `scope`<sup>Required</sup> <a name="scope" id="@cdklabs/cdk-enterprise-iac.PopulateWithConfig.Initializer.parameter.scope"></a>

* *Type:* constructs.Construct

---


##### `id`<sup>Required</sup> <a name="id" id="@cdklabs/cdk-enterprise-iac.PopulateWithConfig.Initializer.parameter.id"></a>

* *Type:* string

---


##### `props`<sup>Required</sup> <a name="props" id="@cdklabs/cdk-enterprise-iac.PopulateWithConfig.Initializer.parameter.props"></a>

* *Type:* <a href="#@cdklabs/cdk-enterprise-iac.PopulateWithConfigProps">PopulateWithConfigProps</a>

---


#### Methods <a name="Methods" id="Methods"></a>

| **Name** | **Description** |
| --- | --- |
| <code><a href="#@cdklabs/cdk-enterprise-iac.PopulateWithConfig.toString">toString</a></code> | Returns a string representation of this construct. |

---


##### `toString` <a name="toString" id="@cdklabs/cdk-enterprise-iac.PopulateWithConfig.toString"></a>

```python
# Example automatically generated from non-compiling source. May contain errors.
to_string()string
```

Returns a string representation of this construct.

#### Static Functions <a name="Static Functions" id="Static Functions"></a>

| **Name** | **Description** |
| --- | --- |
| <code><a href="#@cdklabs/cdk-enterprise-iac.PopulateWithConfig.isConstruct">isConstruct</a></code> | Checks if `x` is a construct. |

---


##### ~~`isConstruct`~~ <a name="isConstruct" id="@cdklabs/cdk-enterprise-iac.PopulateWithConfig.isConstruct"></a>

```python
# Example automatically generated from non-compiling source. May contain errors.
from cdklabs.cdk_enterprise_iac import PopulateWithConfig


PopulateWithConfig.is_construct(x, any)
```

Checks if `x` is a construct.

###### `x`<sup>Required</sup> <a name="x" id="@cdklabs/cdk-enterprise-iac.PopulateWithConfig.isConstruct.parameter.x"></a>

* *Type:* any

Any object.

---


#### Properties <a name="Properties" id="Properties"></a>

| **Name** | **Type** | **Description** |
| --- | --- | --- |
| <code><a href="#@cdklabs/cdk-enterprise-iac.PopulateWithConfig.property.node">node</a></code> | <code>constructs.Node</code> | The tree node. |

---


##### `node`<sup>Required</sup> <a name="node" id="@cdklabs/cdk-enterprise-iac.PopulateWithConfig.property.node"></a>

```python
# Example automatically generated from non-compiling source. May contain errors.
node: Node;
```

* *Type:* constructs.Node

The tree node.

---


### SplitVpcEvenly <a name="SplitVpcEvenly" id="@cdklabs/cdk-enterprise-iac.SplitVpcEvenly"></a>

Splits a VPC evenly between a provided number of AZs (3 if not defined), and attaches a provided route table to each, and labels.

*Example*

```python
# Example automatically generated from non-compiling source. May contain errors.
# with more specific properties
SplitVpcEvenly(self, "evenSplitVpc",
    vpc_id="vpc-abcdefg123456",
    vpc_cidr="172.16.0.0/16",
    route_table_id="rt-abcdefgh123456",
    cidr_bits="10",
    number_of_azs=4,
    subnet_type=subnet_type.PRIVATE_ISOLATED
)
```

#### Initializers <a name="Initializers" id="@cdklabs/cdk-enterprise-iac.SplitVpcEvenly.Initializer"></a>

```python
# Example automatically generated from non-compiling source. May contain errors.
from cdklabs.cdk_enterprise_iac import SplitVpcEvenly


SplitVpcEvenly(scope, Construct, id, string, props, SplitVpcEvenlyProps)
```

| **Name** | **Type** | **Description** |
| --- | --- | --- |
| <code><a href="#@cdklabs/cdk-enterprise-iac.SplitVpcEvenly.Initializer.parameter.scope">scope</a></code> | <code>constructs.Construct</code> | *No description.* |
| <code><a href="#@cdklabs/cdk-enterprise-iac.SplitVpcEvenly.Initializer.parameter.id">id</a></code> | <code>string</code> | *No description.* |
| <code><a href="#@cdklabs/cdk-enterprise-iac.SplitVpcEvenly.Initializer.parameter.props">props</a></code> | <code><a href="#@cdklabs/cdk-enterprise-iac.SplitVpcEvenlyProps">SplitVpcEvenlyProps</a></code> | *No description.* |

---


##### `scope`<sup>Required</sup> <a name="scope" id="@cdklabs/cdk-enterprise-iac.SplitVpcEvenly.Initializer.parameter.scope"></a>

* *Type:* constructs.Construct

---


##### `id`<sup>Required</sup> <a name="id" id="@cdklabs/cdk-enterprise-iac.SplitVpcEvenly.Initializer.parameter.id"></a>

* *Type:* string

---


##### `props`<sup>Required</sup> <a name="props" id="@cdklabs/cdk-enterprise-iac.SplitVpcEvenly.Initializer.parameter.props"></a>

* *Type:* <a href="#@cdklabs/cdk-enterprise-iac.SplitVpcEvenlyProps">SplitVpcEvenlyProps</a>

---


#### Methods <a name="Methods" id="Methods"></a>

| **Name** | **Description** |
| --- | --- |
| <code><a href="#@cdklabs/cdk-enterprise-iac.SplitV