cloudformation-coverage-roadmap icon indicating copy to clipboard operation
cloudformation-coverage-roadmap copied to clipboard

AWS::RDS::DBInstance - DbiResourceId accessible via Fn::GetAtt

Open akunszt opened this issue 6 years ago • 20 comments

Instructions for CloudFormation Coverage New Issues Template

  1. Title -> AWS::RDS::DBInstance-DbiResourceId accessible via Fn::GetAtt
  2. Scope of request -> Make the DbiResourceId accessible using Fn::GetAtt on an AWS::RDS::DBInstance resource.
  3. Expected behavior -> Get back the DbiResourceId.
  4. Test case recommendation (optional) ->
  5. Links to existing API doc (optional) ->
  6. Category tag (optional) -> Database
  7. Any additional context (optional)

The RDS instances now supports IAM authentication as described here. Unfortunately in the PolicyDocument you have to use the DbiResourceId as the reference to the target RDS instance. This is not known before and you can't query it from the CFn either. Currently I have to create the stack, fetch the DbiResourceId and then create a changeset to modify the Policy. It would so much simpler if we can do that in one step.

"DBInstance": {
  "Type": "AWS::RDS::DBInstance",
   "Properties": { }
},
"DBUserPolicy": {
  "Type": "AWS::IAM::ManagedPolicy",
  "Properties": {
      "Description": "blah blah not relevant",
      "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Action": [  "rds-db:connect" ],
            "Effect": "Allow",
            "Resource": [
              { "Fn::Sub": "arn:aws:rds-db:${AWS::Region}:${AWS::AccountId}:dbuser:db-KOUCHAID5AIS8GAN6XEIKOOTIZ/db_user_name" }
            ]
          }
        ]
      }
    }       
  }
}

I would like to change the Resource entry like this:

"Fn::Sub": [
"arn:aws:rds-db:${AWS::Region}:${AWS::AccountId}:dbuser:${DbiResourceId}/db_user_name",
{ "DbiResourceId": { "Fn::GetAtt": [ "DbInstance", "DbiResourceId" ] } } ]

akunszt avatar Aug 06 '19 13:08 akunszt

Any news?

qoomon avatar Apr 17 '20 12:04 qoomon

This is a daft omission. The fact that you can enable IAM authentication via CFN but can't get the resource ID to setup an IAM policy for it is absurd. Why was this not included in the CFN update providing for enabling IAM authentication in the first place?

Come on. This was raised nearly 10 months ago. We're not asking for much...

dannosaur avatar May 27 '20 18:05 dannosaur

Its really absurd, this issue is nearly 10 months old and nobody seems to care about this issue. The feature is completely useless i am not able to fetch the db-resource-id inside the cloudformation template.

amir-badar avatar Jun 02 '20 13:06 amir-badar

Any news? Are we not taking it as valuable feature to have?

amir-badar avatar Jun 10 '20 07:06 amir-badar

I would also like an update on this.

ssanpietro-gr avatar Jun 12 '20 21:06 ssanpietro-gr

Totally agree, it is absurd. I meditated on the architecture for a project for two days with IAM authorization in mind to discover, that can't automate policy creation with CF. Several workarounds are definitely possible here, but it will inevitably break "encapsulation". Hope this will be implemented soon.

dbolotin avatar Sep 25 '20 16:09 dbolotin

DbiResourceId should have been available from the start. I keep coming back to this Github issue every few months when I have the same headache for a different project.. Strong +1 for this to be added to enhancement!

kz974 avatar Oct 18 '20 02:10 kz974

Here's a snippet from our template to lookup the DbiResourceId via a CustomResource:

    "DBResourceId": {
      "Properties": {
        "DBInstanceIdentifier": {
          "Ref": "Instance"
        },
        "ServiceToken": {
          "Fn::GetAtt": [
            "DBResourceIdLookupLambda",
            "Arn"
          ]
        }
      },
      "Type": "AWS::CloudFormation::CustomResource"
    },
    "DBResourceIdLookupLambda": {
      "Properties": {
        "Code": {
          "ZipFile": "import logging\nimport boto3\nimport cfnresponse\n\nlog = logging.getLogger(__name__)\nlog.setLevel(logging.INFO)\n\n\ndef db_resource_name(instance_identifier: str):\n    rds = boto3.client(\"rds\")\n    return rds.describe_db_instances(DBInstanceIdentifier=instance_identifier)[\n        \"DBInstances\"\n    ][0][\"DbiResourceId\"]\n\n\ndef lambda_handler(event: dict, context) -> None:\n    physical_resource_id = None\n    if \"DBInstanceIdentifier\" not in event[\"ResourceProperties\"]:\n        log.error(\"DBInstanceIdentifier not provided in ResourceProperties\")\n        status = cfnresponse.FAILED\n    elif event[\"RequestType\"] == \"Delete\":\n        log.info(\"Stack is deleting. Skipping ID lookup.\")\n        status = cfnresponse.SUCCESS\n    else:\n        instance_identifier = event[\"ResourceProperties\"][\"DBInstanceIdentifier\"]\n        try:\n            physical_resource_id = db_resource_name(instance_identifier)\n            status = cfnresponse.SUCCESS\n        except Exception:\n            log.error(\n                \"Error looking up DB instance identifier for '%s'\",\n                instance_identifier,\n                exc_info=True,\n            )\n            status = cfnresponse.FAILED\n\n    cfnresponse.send(event, context, status, {}, physical_resource_id)\n"
        },
        "Handler": "index.lambda_handler",
        "Role": {
          "Fn::GetAtt": [
            "DBResourceIdLookupRole",
            "Arn"
          ]
        },
        "Runtime": "python3.7",
        "Timeout": 10
      },
      "Type": "AWS::Lambda::Function"
    },
    "DBResourceIdLookupRole": {
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [
            {
              "Action": "sts:AssumeRole",
              "Effect": "Allow",
              "Principal": {
                "Service": "lambda.amazonaws.com"
              },
              "Sid": ""
            }
          ],
          "Version": "2012-10-17"
        },
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
        ],
        "Policies": [
          {
            "PolicyDocument": {
              "Statement": [
                {
                  "Action": "rds:DescribeDBInstances",
                  "Effect": "Allow",
                  "Resource": [
                    {
                      "Fn::Sub": "arn:aws:rds:${AWS::Region}:${AWS::AccountId}:db:${Instance}"
                    },
                    {
                      "Fn::If": [
                        "IsMultiAz",
                        {
                          "Fn::Sub": "arn:aws:rds:${AWS::Region}:${AWS::AccountId}:db:${ReplicaInstance}"
                        },
                        {
                          "Ref": "AWS::NoValue"
                        }
                      ]
                    }
                  ],
                  "Sid": ""
                }
              ],
              "Version": "2012-10-17"
            },
            "PolicyName": {
              "Fn::Sub": "describe-db-instance"
            }
          }
        ]
      },
      "Type": "AWS::IAM::Role"
    },
    "DBResourceIdReplica": {
      "Condition": "IsMultiAz",
      "Properties": {
        "DBInstanceIdentifier": {
          "Ref": "ReplicaInstance"
        },
        "ServiceToken": {
          "Fn::GetAtt": [
            "DBResourceIdLookupLambda",
            "Arn"
          ]
        }
      },
      "Type": "AWS::CloudFormation::CustomResource"
    }

The actual code of the lambda is:

import logging
import boto3
import cfnresponse

log = logging.getLogger(__name__)
log.setLevel(logging.INFO)


def db_resource_name(instance_identifier: str) -> str:
    rds = boto3.client("rds")
    return rds.describe_db_instances(DBInstanceIdentifier=instance_identifier)[
        "DBInstances"
    ][0]["DbiResourceId"]


def lambda_handler(event: dict, context) -> None:
    physical_resource_id = None
    if "DBInstanceIdentifier" not in event["ResourceProperties"]:
        log.error("DBInstanceIdentifier not provided in ResourceProperties")
        status = cfnresponse.FAILED
    elif event["RequestType"] == "Delete":
        log.info("Stack is deleting. Skipping ID lookup.")
        status = cfnresponse.SUCCESS
    else:
        instance_identifier = event["ResourceProperties"]["DBInstanceIdentifier"]
        try:
            physical_resource_id = db_resource_name(instance_identifier)
            status = cfnresponse.SUCCESS
        except Exception:
            log.error(
                "Error looking up DB instance identifier for '%s'",
                instance_identifier,
                exc_info=True,
            )
            status = cfnresponse.FAILED

    cfnresponse.send(event, context, status, {}, physical_resource_id)

ipmb avatar Oct 24 '20 22:10 ipmb

+1

newtondev avatar Jan 12 '21 14:01 newtondev

+1

andrius-paurys avatar Apr 29 '21 18:04 andrius-paurys

+1

SamMcFaddenBJSS avatar May 12 '21 13:05 SamMcFaddenBJSS

@newtondev @andrius-paurys @SamMcFaddenBJSS

If you react with the 👍 button to the original issue, (the first comment, click on the smiley face if you're the first reacting), your votes can be used to sort issues and determine priorities.

A comment will send a notification to everyone (participants and watchers), but cannot be easily counted as a vote for an issue. Thus It's generally better to vote than to comment with "+1". To keep up to date, you can also add yourself as a watcher.

benbridts avatar May 12 '21 13:05 benbridts

+1

gietschess avatar May 20 '21 08:05 gietschess

+1

vospitannikov avatar Jul 06 '21 08:07 vospitannikov

+1

tbuckel avatar Jul 29 '21 08:07 tbuckel

+1

bpoole6 avatar Jul 30 '21 14:07 bpoole6

The issue with this identifier can be worked around by retrieving it using the CLI for example. (I needed this to be able to create the IAM policy from CI/CD pipeline.)

DB_RESOURCE_ID=$(aws rds describe-db-instances --db-instance-identifier <ID> --query 'DBInstances[0].DbiResourceId' --output text)

Then DB_RESOURCE_ID can be passed as parameter to Cloudformation.

The bigger problem is you still need to configure the database (at least for MySQL and Postgres) to enable IAM based authentication (and to do so you need the password). So this feature would need both

  1. ability to get the DbiResourceId in CF
  2. ability to create database user with the right grant

To make the setup fully Cloudformation based.

ragoncsa avatar Nov 26 '21 10:11 ragoncsa

TL;DR AWS have to expose the DbiResourceId as they already did for Endpoint.Address and Endpoint.Port

In the meantime (because a 2y+ opened issue is quite a long time to adopt a wait&see approach) you can follow one of this well described work around https://aws.amazon.com/blogs/mt/four-ways-to-retrieve-any-aws-service-property-using-aws-cloudformation-part-3-of-3/

It's not ideal but at least you can retrieve and use any attribute that Cloudformation don't expose.

mlvnds avatar Nov 26 '21 11:11 mlvnds

I also need this.

tinducvo avatar Mar 15 '22 17:03 tinducvo

I need this as well

erikordos avatar Jul 13 '22 19:07 erikordos

hey folks, thanks for your patience on this. this is coming soon. stay tuned!

TheDanBlanco avatar Nov 01 '22 19:11 TheDanBlanco

this is now available in all regions

TheDanBlanco avatar Nov 07 '22 20:11 TheDanBlanco

@TheDanBlanco thanks for the feature! Is there any chance we could get a similar functionality from AWS::RDS::DBProxy? It also has a ResourceId associated to it that can't be output as of right now

perrin4869 avatar Nov 08 '22 08:11 perrin4869

❤️

qoomon avatar Nov 08 '22 11:11 qoomon