1

I'm modifying the code from this workshop to do cross-account rather than cross-region. https://cdk-advanced.workshop.aws/start.html

The first thing I did was install and configure cdk-assume-role-credential-plugin and bootstrapped.

In the workshop they use a AwsCustomResource to get around the cross-region limitation of StringParameter.valueFromLookup.

static valueFromLookup(scope, parameterName) Requires that the stack this scope is defined in will have explicit account/region information. Otherwise, it will fail during synthesis.Using an AwsCustomResource allows them to override the region from stack.

const targetKeyLookupCR = new cr.AwsCustomResource(this, 'TargetKeyLookup', {
      onUpdate: {   // will also be called for a CREATE event
        service: 'SSM',
        action: 'getParameter',
        parameters: {
          Name: props.targetKeyIdSsmParameterName
        },
        region: props.targetRegion,
        physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString())
      },
      policy: cr.AwsCustomResourcePolicy.fromSdkCalls({resources: [parameterArn]})
    });

Unfortunately you cannot override account of stack in a similar fashion. Adding an assumedRole does not help either. It always looks for parameter in stack's account.

I tried to use SecretsManager & AwsCustomResource to get around that cross-account limitation:

const targetKeyLookupCR = new cr.AwsCustomResource(this, 'TargetKeyLookup', {
      onUpdate: {
        service: 'SecretsManager',
        action: 'getSecretValue',
        parameters: {
          SecretId: props.targetKeyIdSsmParameterName
        },
        assumedRoleArn: 'arn:aws:iam::111111111111:role/MultiRegionS3CrrKmsCmkXacct',
        region: props.targetRegion,
        physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString())
      },
      policy: cr.AwsCustomResourcePolicy.fromSdkCalls({resources: [parameterArn]})
    });
    const secretString = targetKeyLookupCR.getResponseField(dataPath);

The code above returns a string that looks like this.

"{"password":"/];asdf(5;{ASDF=L%UuVxasDFHg`(:l","MyKeyID":"arn:aws:kms:us-west-1: 111111111111:key/1111a22b-3c44-5ddd-66e7-f8f9999a1111"}"

I can see it in CloudWatch:

2021-08-14T04:34:15.641Z c5ae47f7-1e4a-43a3-a475-d0d16ea7e1be INFO Responding {"Status":"SUCCESS","Reason":"OK",... "SecretString":"{"password":"/];asdf(5;{ASDF=L%UuVxasDFHg`(:l","MyKeyID":"arn:aws:kms:us-west-1: 111111111111:key/1111a22b-3c44-5ddd-66e7-f8f9999a1111}"...}

In the workshop they use the response directly:

 role.addToPolicy(new iam.PolicyStatement({
   resources: [targetKeyLookupCR.getResponseField('Parameter.Value')],
   actions: ['kms:Encrypt'] }));

To get the Value of MyKeyID, I've tried...

JSON.parse and splitting the string. No matter what I try ends up with an error like 'Unexpected token $ in JSON at position 0' or 'TypeError: Cannot read property 'split' of undefined'

This led me to https://docs.aws.amazon.com/cdk/latest/guide/tokens.html#tokens_json, which I definitely thought was going to be the answer! It was not. More 'Unexpected token $ in JSON at position 0' errors.

const stack = cdk.Stack.of(this);    
const secretString = targetKeyLookupCR.getResponseField('SecretString');
const jsonString = stack.toJsonString(secretString);
const jsonObj = JSON.parse(jsonString);

Update: From this https://docs.aws.amazon.com/cdk/api/latest/docs/core-readme.html#cfnjson I got a different error that indicates it has the right string, but just can't parse it properly. Unexpected token p in JSON at position 3 at JSON.parse ().

const secretString = targetKeyLookupCR.getResponseField('SecretString');
const cfnJson = new CfnJson(this, 'cfn-json', {
  value: secretString,
});
// @ts-ignore
const myKeyId = cfnJson.MyKeyID || '*';

This is because CfnJson adds quotes around string:

"Value": {
      "Fn::Join": [
        "",
        [
          "\"",
          {
            "Fn::GetAtt": [
              "MySourceTargetKeyLookupACF65EAE",
              "SecretString"
            ]
          },
          "\""
        ]
      ]
    }

I'm not sure how to proceed. How would I get the ARN from secretString as plain text?

bravogolfgolf
  • 357
  • 2
  • 11

1 Answers1

0

I guess I was looking for a more elegant solution, but this works...

const secretString = targetKeyLookupCR.getResponseField('SecretString');
const myKeyId = cdk.Fn.select(7,cdk.Fn.split('"', secretString));

It splits the secretString into an array using double quote as delimiter

cdk.Fn.split('"', secretString)

and selects the 7th item from array produced by split

cdk.Fn.select(7, <array>)
bravogolfgolf
  • 357
  • 2
  • 11