How To Create AWS RDS MySQL Instance Master Password With CloudFormation

Cover Image for How To Create AWS RDS MySQL Instance Master Password With CloudFormation
Image generated by DALLE
Visit for my recent projects

Recently, I’m working on creating a MySQL RDS instance with CloudFormation template. Although there have been tons of documents from both AWS officials or the community talking about this, I feel there are not many documents diving deep about making MasterUserPassword secure.

When creating a MySQL instance, RDS will create a “master user” with all the permissions needed for managing the database. Disregarding the CloudFormation reference saying MasterUsername and MasterUserPassword are not required, they are required to create a MySQL RDS instance. You will get an error “Property MasterUserPassword cannot be empty” if they’re not provided in the template. It might be okay to put a plaintext password in a temporary template that will be thrown away, but I must be crazy to do that if I’m going to commit the template to any source control system. Also, I’d like to store that password safely at somewhere better than a sticky note.

Here the question comes: how can I specify a MasterUserPassword that is not plaintext and can be stored on AWS? The answer is another AWS service — Secrets Manager.

I’m not going to write up what is already in Secrets Manager’s official documents. Instead, let’s jump to the conclusion that we will need a Secret in Secrets Manager, a KMS key for encrypting and decrypting the secret, and a way to use the Secret when creating the RDS instance.

And here is the CloudFormation template:

    Type: String
    Default: admin
    Description: Database admin user name for MySQL
    Type: AWS::KMS::Key
          - Sid: "Enable IAM User Permissions"
            Effect: "Allow"
              AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
            Action: "kms:*"
            Resource: "*"

    Type: AWS::SecretsManager::Secret
      KmsKeyId: !Ref MySQLSecretKey
        SecretStringTemplate: !Join [ '', [ '{"username": "', !Ref MySQLMasterUserName, '"}' ] ]
        GenerateStringKey: 'password'
        PasswordLength: 16
        ExcludeCharacters: '"@/\'

    Type: AWS::RDS::DBInstance
      DBInstanceClass: !Ref MySQLDBInstanceClass
      DBName: !Ref MySQLDBName
      Engine: "MySQL"
      EngineVersion: "8.0.20"
      MasterUsername: !Ref MySQLMasterUserName
      MasterUserPassword: !Join [ '', [ '{{resolve:secretsmanager:', !Ref MySQLSecret, ':SecretString:password}}' ] ]
      StorageType: gp2
      AllocatedStorage: 20
      AvailabilityZone: !GetAtt PrivateSubnet1.AvailabilityZone
      MultiAZ: False
      Port: !Ref MySQLPort
      DBSubnetGroupName: !Ref MySQLSubnetGroup
      PubliclyAccessible: True
        - !Ref MySQLSecurityGroup

There are some elements that I’d like to explain further in this template:

  1. The secret will be generated as a JSON string like {“username”: “admin”, “password”: “super_secure”}. And RDS will put some other information into this secret as well, like the hostname of the RDS instance, DB name, etc. I can retrieve the password in the AWS console, or do it programmatically by calling API GetSecretValue. But since this is the master user credentials, I won’t be using it anywhere in the application, and it should only be used when I need to manage the DB instance.
  2. In the template of MySQL instance, referencing the secret in Secrets Manager with {{resolve:secretsmanager:SECRET_ARN:SecretString:password}} is the magic ingredient. Please refer to this documentation for some further explanation.

There are some other topics around this template, like associating it with a security group in VPC, automatically rotating the secret, etc. I guess I’ll touch them in later articles then. I hope this article can help you a bit!