Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you associate a IAM Role with an Aurora Cluster using CloudFormation?

Following the instructions found here, I have created the following IAM Role

"DatabaseS3Role": {
    "Type": "AWS::IAM::Role",
    "Properties": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": ["rds.amazonaws.com"]
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        },
        "Policies": [
            {
                "PolicyName": "AllowAuroraToReadS3",
                "PolicyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Action": ["s3:GetObject", "s3:GetObjectVersion", "s3:ListBucket"],
                            "Resource": {"Fn::Join": ["", [
                                "arn:aws:s3:::",
                                {"Fn::Join": ["-",[
                                    {"Ref": "ClientName"}, 
                                    {"Ref": "SourceBucketName"},
                                    {"Ref": "EnvironmentType"},
                                    { "Fn::FindInMap" : [ "Regions", { "Ref" : "AWS::Region" }, "Name" ] }
                                ]]} ,
                                "*"
                            ]]}
                        }
                    ]
                }
            }
        ]
    }
}

I am able to add it to a cluster parameter group and associate it using the following.

"RDSDBClusterParameterGroup" : {
    "DependsOn": "DatabaseS3Role",
    "Type": "AWS::RDS::DBClusterParameterGroup",
    "Properties" : {
        "Description" : "CloudFormation Aurora Cluster Parameter Group",
        "Family" : "aurora5.6",
        "Parameters" : {
            "time_zone" : "US/Eastern",
            "aws_default_s3_role": {"Fn::GetAtt": ["DatabaseS3Role", "Arn"]}
        }
    }
},
"RDSAuroraCluster" : {
    "Type" : "AWS::RDS::DBCluster",
    "Properties" : {
        "MasterUsername" : { "Ref" : "Username" },
        "MasterUserPassword" : { "Ref" : "Password" },
        "Engine" : "aurora",
        "DBSubnetGroupName" : { "Ref" : "RDSSubnetGroup" },
        "DBClusterParameterGroupName" : { "Ref" : "RDSDBClusterParameterGroup" },
        "VpcSecurityGroupIds" : [ { "Ref" : "SecurityGroupId" } ],
        "Tags" : [
              { "Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ 
              { "Ref" : "ClientName" }, 
              "aurclstr001",
              {"Ref" : "EnvironmentType" },
              { "Fn::FindInMap" : [ "Regions", { "Ref" : "AWS::Region" }, "Name" ] }
          ] ] } }
        ]
    }
}

However Aurora still isn't able to connect to S3 unless I manually associate a role with the cluster through the console or with the cli command add-role-to-db-cluster.

Digging through the cloud formation documentation does not provide any means of doing this through the template. This documentation does not provide any parameter that allows a role to be associated.

How do I do this without having to add in a manual step to the deployment process?

like image 955
NMarshallx86 Avatar asked Mar 13 '17 17:03

NMarshallx86


1 Answers

As of August 29, 2019 this is finally supported!

There is a new attribute named AssociatedRoles that takes an array of DBClusterRoles. These are basically an object with a RoleArn and an optional FeatureName which can currently only be s3Import per this reference showing SupportedFeatureNames.member.N.

Original answer from 2017-06-30:

It's not a great solution but I decided to generate the command that is needed to run in the output. I will open a support request with Amazon to confirm there is no way add a role to the cluster via the DSL.

When I run aws rds describe-db-clusters I see an entry for "AssociatedRoles" that holds an array of objects with a Status and RoleArn.

PostRunCommand:
  Description: You must run this awscli command after the stack is created and may also need to reboot the cluster/instance.
  Value: !Join [" ", [
    "aws rds add-role-to-db-cluster --db-cluster-identifier",
    !Ref AuroraSandboxCluster,
    "--role-arn",
    !GetAtt AuroraS3Role.Arn,
    "--profile",
    !FindInMap [ AccountNameMap, !Ref AccountNamespace, profile ]
  ]]

You most likely won't need the last part WRT profile...

A followup after Amazon responded to me. They said:

I understand that you were looking for a way to associate an IAM role with an Aurora cluster in Cloudformation to access other AWS services on your behalf.

As you correctly state there's no role property for a RDS cluster resource as CloudFormation does not support it yet. There's an already open feature request for this as it's a very common issue and I have added your voice to it to add the feature with even more weight. As usual, I can't provide you with an ETA, however as soon as it's released it should be published in the Cloudformation release history page:

http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/ReleaseHistory.html

However you can create a CFN Template which creates a RDS Aurora setup and in the end of the template a Custom Resource 1, as a Lambda Function, which makes an API call to attach the IAM Role to the RDS Cluster, this way the whole RDS Aurora Cluster setup stay centralized inside the CFN Template without manual actions and the cluster will be able to invoke the Lambda Function.

Please find attached an "example" template of this workaround described above.

I will as well send a feedback on your behalf about the lack of the principal property in the examples which is needed to create a Role to Delegate Permissions to an AWS Service.

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS Cloud resources for DevTools services.",
"Metadata": {
    "Version": "0.2.0"
},
"Parameters": {
    "RDSPassword": {
        "Description": "Password for root user on the RDS instance.",
        "Type": "String",
        "NoEcho":"true"
    },
    "RDSDatabaseName": {
        "Description": "DB Identifier for RDS instance.",
        "Type": "String",
        "Default": "mydbname"
    },
    "RDSClass": {
        "Description": "RDS Instance Class",
        "Type": "String",
        "Default": "db.r3.xlarge"
    },
    "DBIdentifier": {
        "Description": "Database Instance Identifier",
        "Type": "String"
    },
    "DBSubnetGroupName": {
        "Description": " The Subnet group Group for the RDS instance",
        "Type": "String"
    },
    "RDSSecurityGroupId": {
        "Description": "Existing internal SG for RDS instance access",
        "Type": "AWS::EC2::SecurityGroup::Id"
    },
    "RDSRetention": {
        "Description": "How long to retain RDS snapshots",
        "Type": "String"
    },
    "RDSVpcId": {
        "Description": "VpcId for RDS instance",
        "Type": "AWS::EC2::VPC::Id"
    },
    "PubliclyAccessible": {
        "Description": "Set the RDS to be publically available",
        "Type": "String",
        "AllowedValues" : ["true", "false"],
        "Default": "true"
    },
    "DBClusterIdentifier": {
        "Description": "The name of the DBCluster",
        "Type": "String"
    },
    "RDSRoleTag": {
        "Description": "sets if the tag for dev/prod use",
        "Type": "String",
        "Default": "dev"
    }
},

"Resources": {

  "LambdaRole" : {
    "Type" : "AWS::IAM::Role",
    "Properties" : {
        "AssumeRolePolicyDocument" : {
            "Version" : "2012-10-17",
            "Statement" : [
                {
                    "Effect" : "Allow",
                    "Principal" : {
                        "Service" : [
                            "lambda.amazonaws.com"
                        ]
                    },
                    "Action"    : [
                        "sts:AssumeRole"
                    ]
                }
            ]
        }
      }
    },

    "LambdaPolicy": {
      "Type" : "AWS::IAM::Policy",
      "Properties" : {
         "PolicyName" : "LambdaPolicy",
         "PolicyDocument" : {
            "Version" : "2012-10-17",
            "Statement": [ {
            "Effect"   : "Allow",
            "Action"   : [
               "iam:*",
               "ec2:*",
               "rds:*",
               "logs:*"
            ],
            "Resource" : "*"
            } ]
         },
         "Roles": [ { "Ref": "LambdaRole" } ]
      }
   },

    "LambdaFunction": {
      "Type" : "AWS::Lambda::Function",
      "DeletionPolicy" : "Delete",
      "DependsOn"      : [
          "LambdaRole"
      ],
      "Properties"     : {
          "Code" : {
              "ZipFile" : {
                  "Fn::Join" : [
                      "\n",
                      [
                        "          var AWS = require('aws-sdk');",
                        "          var rds = new AWS.RDS();",
                        "          var response = require('cfn-response');",
                        "          exports.handler = (event, context, callback) => {",
                        "              var rolearn = event.ResourceProperties.RDSRole;",
                        "              var dbclusteridentifier = event.ResourceProperties.DBClusterIdentifier;",
                        "              var responseData = {};",
                        "              console.log('Role ARN: ' + rolearn);",
                        "              console.log('DBClusterIdentifier: ' + dbclusteridentifier);",
                        "              var addroleparams = {",
                        "                  RoleArn: rolearn,",
                        "                  DBClusterIdentifier: dbclusteridentifier",
                        "                };",
                        "                if (event.RequestType == 'Delete') {",
                        "                  response.send(event, context, response.SUCCESS);",
                        "                  return;",
                        "                }",
                        "                rds.addRoleToDBCluster(addroleparams, function(err, data) {",
                        "                  if (err) {",
                        "                  console.log(err, err.stack); // an error occurred",
                        "                  responseData = {Error: 'Create call failed'};",
                        "                  response.send(event, context, response.FAILED, responseData);",
                        "                  }",
                        "                  else {",
                        "                  response.send(event, context, response.SUCCESS, responseData);",
                        "                  console.log(data);  // successful response",
                        "                  }",
                        "                });",
                        "          };",
                      ]
                  ]
              }
          },
          "Handler" : "index.handler",
          "MemorySize" : 128,
          "Role"       : {
              "Fn::GetAtt" : [
                  "LambdaRole",
                  "Arn"
              ]
          },
          "Runtime"    : "nodejs4.3",
          "Timeout"    : 10
      }
    },

    "RDSRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": ["rds.amazonaws.com"]
              },
              "Action": ["sts:AssumeRole"]
            }
          ]
        },
        "Path": "/"
      }
    },

    "RDSPolicy": {
       "Type" : "AWS::IAM::Policy",
       "Properties" : {
          "PolicyName" : "RDSPolicy",
          "PolicyDocument" : {
             "Version" : "2012-10-17",
             "Statement": [ {
             "Effect"   : "Allow",
             "Action"   : [
                "lambda:InvokeFunction"
             ],
             "Resource" : "*"
             } ]
          },
          "Roles": [ { "Ref": "RDSRole" } ]
       }
    },

    "RDSDBClusterParameterGroup" : {
      "Type" : "AWS::RDS::DBClusterParameterGroup",
      "Properties" : {
        "Parameters" : {
          "aws_default_lambda_role" : { "Fn::GetAtt" : [ "LambdaFunction", "Arn" ] }

        },
        "Family" : "aurora5.6",
        "Description" : "A sample parameter group"
      }
    },

    "RDSDBCluster": {
        "Type" : "AWS::RDS::DBCluster",
        "DeletionPolicy": "Retain",
        "Properties" : {
            "BackupRetentionPeriod" : { "Ref": "RDSRetention" },
            "DatabaseName": { "Ref": "RDSDatabaseName" },
            "DBSubnetGroupName": { "Ref": "DBSubnetGroupName" },
            "DBClusterParameterGroupName": { "Ref" : "RDSDBClusterParameterGroup" },
            "Engine" : "aurora",
            "StorageEncrypted" : true,
            "MasterUsername" : "sa",
            "MasterUserPassword" : { "Ref": "RDSPassword" },
            "Port" : 3306,
            "Tags": [
                { "Key": "Role", "Value": { "Ref": "RDSRoleTag" } }
                ],
            "VpcSecurityGroupIds": [{ "Ref": "RDSSecurityGroupId" } ]
        }
    },
    "RDSInstance": {
        "Type": "AWS::RDS::DBInstance",
        "DeletionPolicy": "Retain",
        "Properties": {
            "AllowMajorVersionUpgrade": false,
            "AutoMinorVersionUpgrade": true,
            "DBClusterIdentifier" : { "Ref": "RDSDBCluster" },
            "DBInstanceIdentifier": { "Ref": "DBIdentifier" },
            "DBInstanceClass": { "Ref": "RDSClass" },
            "Engine": "aurora",
            "PubliclyAccessible": { "Ref": "PubliclyAccessible" },
            "Tags": [
                { "Key": "Role", "Value": { "Ref": "RDSRoleTag" } }
                ]
        }
    },
    "RDSInstanceSecurityGroup": {
        "Type": "AWS::EC2::SecurityGroup",
        "DeletionPolicy": "Retain",
        "Properties": {
            "GroupDescription": "Security group for the RDSInstance resource",
            "SecurityGroupEgress": [
                {
                    "IpProtocol": "tcp",
                    "CidrIp": "127.0.0.1/32",
                    "FromPort": "1",
                    "ToPort": "1"
                }
            ],
            "SecurityGroupIngress": [
                {
                    "IpProtocol": "tcp",
                    "SourceSecurityGroupId": { "Ref": "RDSSecurityGroupId" },
                    "FromPort": "3306",
                    "ToPort": "3306"
                }
            ],
            "VpcId": { "Ref": "RDSVpcId" },
            "Tags": [
                { "Key": "Role", "Value": { "Ref": "RDSRoleTag" } }
                ]
        }
    },

    "AddRoleToDBCluster": {
        "DependsOn" : [
            "RDSDBCluster",
            "RDSInstance"
        ],
        "Type": "Custom::AddRoleToDBCluster",
        "Properties" : {
            "ServiceToken" : {
                "Fn::GetAtt" : [
                    "LambdaFunction",
                    "Arn"
                ]
            },
            "RDSRole" : { "Fn::GetAtt" : [ "RDSRole", "Arn" ] },
            "DBClusterIdentifier" : {"Ref":"RDSDBCluster"}
        }
    }
}

}

like image 97
runamok Avatar answered Sep 28 '22 06:09

runamok