Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DatabaseProxy] Model validation failed (#: required key [TargetGroupName] not found) #8885

Closed
tbrand opened this issue Jul 3, 2020 · 20 comments · Fixed by #8896
Closed
Labels
bug This issue is a bug. needs-triage This issue or PR still needs to be triaged.

Comments

@tbrand
Copy link

tbrand commented Jul 3, 2020

I tried DatabaseProxy with Database Cluster (Aurora, Postgres) but it's failed as titled.

Reproduction Steps

    const databaseUser = 'test';
    const secret = new secretsmanager.Secret(this, 'secret', {
      generateSecretString: {
        generateStringKey: 'password',
        secretStringTemplate: `{"username": "${databaseUser}"}`,
        excludePunctuation: true
      }
    });

    const cluster = new rds.DatabaseCluster(this, 'Database', {
      engine: rds.DatabaseClusterEngine.AURORA_POSTGRESQL,
      engineVersion: '11.7',
      defaultDatabaseName: 'mydatabase',
      masterUser: {
        username: databaseUser,
        password: secret.secretValueFromJson('password')
      },
      instanceProps: {
        instanceType: ec2.InstanceType.of(
          ec2.InstanceClass.R5,
          ec2.InstanceSize.LARGE
        ),
        vpcSubnets: {
          subnetType: ec2.SubnetType.PRIVATE
        },
        vpc
      },
      parameterGroup: new rds.ClusterParameterGroup(this, 'ParameterGroup', {
        family: 'aurora-postgresql11',
        parameters: {
          application_name: 'test',
        },
      }),
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const proxy = new rds.DatabaseProxy(this, 'DatabaseProxy', {
      dbProxyName: 'test-proxy',
      debugLogging: true,
      iamAuth: false,
      requireTLS: true,
      secret,
      vpc,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE,
      },
      proxyTarget: rds.ProxyTarget.fromCluster(cluster),
    });

Error Log

Model validation failed (#: required key [TargetGroupName] not found)

Environment

  • CLI Version : 1.49.1
  • Framework Version: 1.49.1
  • Node.js Version: v13.7.0
  • OS : 10.14.6
  • Language (Version): TypeScript (3.7.5)

Other


This is 🐛 Bug Report

@tbrand tbrand added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Jul 3, 2020
@tbrand
Copy link
Author

tbrand commented Jul 3, 2020

I also tried below but still failed.

    cluster.addProxy('DatabaseProxy', {
      secret,
      vpc,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE,
      },
    });

@nideveloper
Copy link
Contributor

I have hit the same issue, tried DatabaseCluster and DatabaseInstance (MySQL, Postgres and Aurora MySQL) - no difference

@nideveloper
Copy link
Contributor

Incase it helps whoever picks this up, when digging in earlier. The only place I can see TargetGroupName is set is inside CfnDBProxyTargetGroup as cdk.Token.asString(this.getAtt('TargetGroupName'));

@civilizeddev
Copy link
Contributor

I'll take a look.

@tbrand
Copy link
Author

tbrand commented Jul 3, 2020

Thanks!

@guydelta
Copy link

guydelta commented Jul 3, 2020

This was my original workaround:
Prior to rds.DatabaseProxy's introduction in 1.49 I had to create the DB Proxy with CfnDBProxy.
This route required me to set the proxy's target group as follow:

const rdsProxy = new CfnDBProxy(this, id+'proxy',options);
const targetGroup = new CfnDBProxyTargetGroup(this, id + 'dbproxytarget', {
    dbProxyName: rdsProxy.ref,
    connectionPoolConfigurationInfo: {
        connectionBorrowTimeout: 120,
        maxConnectionsPercent: 99,
    },
    dbInstanceIdentifiers: [props.rds.instanceIdentifier]
});

This caused the same required key [TargetGroupName] error. After many hours of struggling and diving into the cdk source I realized there is no place where TargetGroupName* seems to be assigned to the target group, expept has has been pointed out, in cdk.Token.asString(this.getAtt('TargetGroupName')) which we can't use, and no way to pass it into the constructor as a property. My workaround was to add the following line immediately after creating the target group:

targetGroup.addOverride('Properties.TargetGroupName', 'default');

TargetGroupName is not documented and assigning anything but default caused an enum error.

In cdk 1.49.0 the DatabaseProxy class was finally added, alongside the very helpful rdsInstance.addProxy(...) for which I am very thankful. However, I notice in the new proxy.ts file that was introduced calls the following

    new CfnDBProxyTargetGroup(this, 'ProxyTargetGroup', {
      dbProxyName: this.dbProxyName,
      dbInstanceIdentifiers,
      dbClusterIdentifiers: bindResult.dbClusters?.map((c) => c.clusterIdentifier),
      connectionPoolConfigurationInfo: toConnectionPoolConfigurationInfo(props),
    });

yet without again assigning the TargetGroupName. Hence the error still remaining.

You can thus solve the error by adding the following at the end of your code after const proxy

const proxy = ...

const targetGroup = proxy.node.findChild('ProxyTargetGroup') as CfnDBProxyTargetGroup;
targetGroup.addOverride('Properties.TargetGroupName', 'default');

I just tested these two lines on the new implementation of the cdk rds proxy class and it is working with my new db stack. I hope this helps.

@tbrand
Copy link
Author

tbrand commented Jul 3, 2020

That's awesome! I will try the workaround.

@civilizeddev
Copy link
Contributor

Sorry for inconvenience caused.

It seems something has suddenly changed in CloudFormation. (They implicitly set TargetGroupName before)

Fortunately, I can find the point early with your help.

I hope this would work.

@tbrand
Copy link
Author

tbrand commented Jul 4, 2020

Another problem has happened. After I applying this patch, the deployment was failed with the message "Timed out waiting for target group to become available.".
But I confirmed that the database proxy had been created successfully on the AWS Management Console.

Does anyone hit the same problem?
Should I open another issue?

@civilizeddev
Copy link
Contributor

To initialize DB Proxy Target Group,

DB Proxy to DB connection should be established.

Check if the security groups specified in the DB Proxy are allowed to access network that DB resides.

@civilizeddev
Copy link
Contributor

In AWS Management Console:

I found that DB Proxy target was staying unavailable when I put a security group which is not allowed to access RDS instance.

image

In CDK or AWS CloudFormation, It will time out.

@tbrand Can you open new issue for this?

@tbrand
Copy link
Author

tbrand commented Jul 4, 2020

@civilizeddev Thanks for the digging!

when I put a security group which is not allowed to access RDS instance.

Is that correct? I thought that the closed security group occurs the timeout. So opened security group would solve the issue. (I haven't try.)

@civilizeddev
Copy link
Contributor

This diagram is more descriptive.

image

@nideveloper
Copy link
Contributor

nideveloper commented Jul 5, 2020

This is still a work in progress, it will be launched on cdkpatterns in the next couple of days but if you or anyone else following this wants some working code for a MySQL DB Instance, an RDS Proxy and a Lambda function which runs several queries integrated with an API Gateway HTTP API - https://github.com/nideveloper/serverless/blob/master/the-rds-proxy/typescript/lib/the-rds-proxy-stack.ts

@d2kx
Copy link

d2kx commented Jul 5, 2020

I stumbled into this, and I also had to add another property override, because doing DBCluster#addProxy added both DBClusterIdentifiers and DBInstanceIdentifiers to the ProxyTargetGroup, which is not allowed. So my overrides look like this:

const dbProxyTargetGroup = dbProxy.node.findChild(
  'ProxyTargetGroup'
) as CfnDBProxyTargetGroup;
dbProxyTargetGroup.addPropertyDeletionOverride('DBInstanceIdentifiers');
dbProxyTargetGroup.addPropertyOverride('TargetGroupName', 'default');

which solves the problems.

@civilizeddev
Copy link
Contributor

@d2kx Thank you for reporting.

fixed by 84d0a4a

@tbrand
Copy link
Author

tbrand commented Jul 6, 2020

Thanks guys. Finally I successfully created the proxy! 🎉
Here is the code.

     this.proxy = new rds.DatabaseProxy(this, 'DatabaseProxy', {
      dbProxyName: 'database-proxy',
      debugLogging: true,
      iamAuth: false,
      requireTLS: true,
      secret: this.secret,
      vpc: props.vpc,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE,
      },
      proxyTarget: rds.ProxyTarget.fromCluster(this.cluster),
    });

    const targetGroup = this.proxy.node.findChild('ProxyTargetGroup') as rds.CfnDBProxyTargetGroup;
    targetGroup.addOverride('Properties.TargetGroupName', 'default');
    targetGroup.addOverride('Properties.DBClusterIdentifiers', [this.cluster.clusterIdentifier]);
    targetGroup.addOverride('Properties.DBInstanceIdentifiers', []);

@mergify mergify bot closed this as completed in #8896 Jul 6, 2020
mergify bot pushed a commit that referenced this issue Jul 6, 2020
fixes #8885
follows #8476

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
@bodokaiser
Copy link

Any updates on this issue?

I am using version 1.44 of aws-cdk and deploying a cloudformation stack with DatabaseProxy timeouts because the target group remains unavailable. I placed the proxy and the lambda into the same SecurityGroup and even created an extra role to confirm that the proxy has access to the database secret.

It would be very helpful if there would be an official tutorial or example to this as this seems to be a common problem.

import { App, Stack, StackProps, Duration } from '@aws-cdk/core'
import { Role, ServicePrincipal, PolicyStatement } from '@aws-cdk/aws-iam'
import {
  Vpc,
  SecurityGroup,
  SubnetType,
  InstanceType,
  InstanceClass,
  InstanceSize,
  Port,
  ISecurityGroup,
} from '@aws-cdk/aws-ec2'
import {
  CfnDBProxyTargetGroup,
  DatabaseInstance,
  DatabaseInstanceEngine,
  DatabaseProxy,
  PostgresEngineVersion,
} from '@aws-cdk/aws-rds'
import { Secret } from '@aws-cdk/aws-secretsmanager'

export class PostgresStack extends Stack {
  public readonly secret: Secret
  public readonly proxy: DatabaseProxy
  public readonly instance: DatabaseInstance
  public readonly proxySecurityGroup: ISecurityGroup

  constructor(scope: App, id: string, props?: StackProps) {
    super(scope, id, props)

    const vpc = new Vpc(this, 'VPC', {
      natGateways: 0,
      subnetConfiguration: [{ name: 'postgres', subnetType: SubnetType.PUBLIC }],
    })

    const proxySecurityGroup = new SecurityGroup(this, 'RDS Proxy Clients', {
      vpc,
    })

    const databaseSecurityGroup = new SecurityGroup(this, 'RDS Database Clients', {
      vpc,
    })
    databaseSecurityGroup.addIngressRule(databaseSecurityGroup, Port.tcp(5432), 'allow db conection')
    databaseSecurityGroup.addIngressRule(proxySecurityGroup, Port.tcp(5432), 'allow proxy connection')

    this.secret = new Secret(this, 'MasterSecret', {
      generateSecretString: {
        excludePunctuation: true,
      },
    })

    this.instance = new DatabaseInstance(this, 'PostgresInstance', {
      engine: DatabaseInstanceEngine.postgres({ version: PostgresEngineVersion.VER_11 }),
      masterUsername: 'master',
      masterUserPassword: this.secret.secretValue,
      vpc,
      vpcPlacement: { subnetType: SubnetType.PUBLIC },
      instanceType: InstanceType.of(InstanceClass.T2, InstanceSize.MICRO),
      instanceIdentifier: 'CoopSaas-Postgres',
      securityGroups: [databaseSecurityGroup],
    })
    this.instance.connections.allowDefaultPortFromAnyIpv4()

    const proxyRole = new Role(this, 'RdsProxyRole', {
      assumedBy: new ServicePrincipal('rds.amazonaws.com'),
    })
    proxyRole.addToPolicy(
      new PolicyStatement({
        actions: [
          'secretsmanager:GetResourcePolicy',
          'secretsmanager:GetSecretValue',
          'secretsmanager:DescribeSecret',
          'secretsmanager:ListSecretVersionIds',
        ],
        resources: [this.secret.secretArn],
      }),
    )

    this.proxy = this.instance.addProxy('PostgresProxy', {
      secrets: [this.secret],
      vpc,
      debugLogging: true,
      iamAuth: true,
      role: proxyRole,
      securityGroups: [databaseSecurityGroup],
      borrowTimeout: Duration.seconds(30),
    })
    let targetGroup = this.proxy.node.children.find((child: any) => {
      return child instanceof CfnDBProxyTargetGroup
    }) as CfnDBProxyTargetGroup
    targetGroup.addPropertyOverride('TargetGroupName', 'default')
  }
}

@tbrand
Copy link
Author

tbrand commented Sep 27, 2020

I'm not a maintainer but I cloud say

  1. Update cdk to the latest version
  2. If (1) doesn't solve, try separating the stack of database and it's proxy as a workaround.

@bodokaiser
Copy link

@tbrand thank you for your response! I found in the CloudWatch LogGroups that RDSProxy requires the RDS credentials to be formated {"username":"...","password":"..."}, however, my previous cdk stack only stores the password as secret.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug. needs-triage This issue or PR still needs to be triaged.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants