Infrastructure & Automation

Securing passwords in AWS Quick Starts using AWS Secrets Manager

Automated deployments can give customers a streamlined way to set up infrastructure on the Amazon Web Services (AWS) Cloud. However, one common challenge in automation is securing passwords. In a typical automation workflow, you might need to use an existing user name and password, or you might need to create one. Usually, prompting for this information in an automation workflow is not desirable, especially if it causes the automation to wait. Saving a password in a clear text file is not a secure practice.

Today, I want to discuss how you can store secrets in Secrets Manager via AWS CloudFormation. Then I’ll show, using code examples, how to retrieve secrets. I’ll cover the following scenarios:

  • Generating a password
  • Securing a provided password
  • Retrieving secrets

A quick primer on Secrets Manager

First, let’s get a quick overview. The Secrets Manager service helps you protect secrets that are needed to access your applications, services, and IT resources. If an automation workflow needs a password, the automation can obtain the password via the Secrets Manager API. Secrets Manager then retrieves the secret, decrypts the protected secret text, and returns it over a secured (HTTPS with TLS) channel—assuming that the requestor has the appropriate AWS Identity and Access Management (IAM) privileges to access the secret. For more information about how Secrets Manager works, see the Secrets Manager documentation.

Generating a password

quick start logo
button to go to source code

A secure password is more than eight characters long and is complex—including lowercase letters, uppercase letters, numbers, and special characters. Complex passwords tend to be hard to remember, which is partly what makes them more secure. Complex passwords can also be hard to come up with. For these reasons, prompting a human for a password might not be suitable during an automated deployment.

Secrets Manager can generate a complex password and store it for use, which negates the need for human interaction in an automation workflow.

The Microsoft Active Directory Domain Services Quick Start template ad-1.template uses Secrets Manager to randomly generate a password for Directory Service Restore Mode Password (DSRM). Previously, the Quick Start prompted for this password, but this password is rarely used in Active Directory Administration, and it should be unique. For these reasons, I decided to use Secrets Manager to generate and store this password. The following code block demonstrates how this is done through AWS CloudFormation by using the AWS::SecretsManager::Secret resource type.

RestoreModeSecrets:
  Type: AWS::SecretsManager::Secret
  Properties:
    Name: !Sub 'RestoreModeSecrets-${AWS::StackName}'
    Description: Restore Mode Password for AD Quick Start
    GenerateSecretString:
      SecretStringTemplate: '{"username": "Administrator"}'
      GenerateStringKey: "password"
      PasswordLength: 30

Notice in the example that I’m using the GenerateSecretString property type.

By default, Secrets Manager will create a 32-character password that includes uppercase letters, lowercase letters, numbers, and special characters. You can also use several properties with the GenerateSecretString property type to customize the password as needed. The SecretStringTemplate in this case is setting the user name for this secret. The GenerateStringKey is specifying the key for which you want to generate the random string.

Securing a provided password

quick start logo
button to go to source code

Why prompt for a password? Why not randomly generate all passwords in automation? The answer comes down to use case and taking your customer into consideration. For example, in the Microsoft SQL Server on AWS Quick Start, I prompt for Administrator credentials and the SQL System Administrator (SA) password. I prompt for the Administrator credentials because these are a set of credentials that exist prior to our automation. These credentials could also be stored securely in Secrets Manager prior to running the Quick Start. However, I try to require little or no pre-work to launch a Quick Start, hence I prompt for the credentials. I also prompt for the SQL SA credentials so that after the Quick Start completes deployment, a customer can log in to SQL Server quickly without having to retrieve the password.

I prompt for the password through AWS CloudFormation parameters. Admin credentials are prompted for by using a code block that is similar to the following SQL Service parameter example.

SQLServiceAccount:
  AllowedPattern: '[a-zA-Z0-9]*'
  Default: sqlsa
  Description: User name for the SQL Server Service Account.
  MaxLength: '25'
  MinLength: '5'
  Type: String
SQLServiceAccountPassword:
  Description: Password for the SQL Service account.
  MaxLength: '32'
  MinLength: '8'
  NoEcho: 'true'
  Type: String

In the SQLServiceAccountPassword parameter, I use the NoEcho property type, which ensures that the password doesn’t appear in clear text while the template is launched.

After this information is captured, I store the password in Secrets Manager, as shown in the next code block.

SQLSecrets:
  Type: AWS::SecretsManager::Secret
  Properties:
    Name: !Sub 'AWSQuickStart/${AWS::StackName}/SQLSecrets'
    Description: MS SQL Credentials for Quick Start
    SecretString: !Sub '{ "username" : "${SQLServiceAccount}", "password" : "${SQLServiceAccountPassword}" }'

As in the randomly generated password example, I’m using the AWS::SecretsManager::Secret resource type. However, instead of using the GenerateSecretString property type, I use the SecretString property type. The value of the SecretString property type is a JSON string with a username key and a password key, where I sub in the values from the parameters. The value of SecretString is then stored securely in Secrets Manager.

Now that we have covered how to store randomly generated passwords and prompted passwords securely in Secrets Manager with AWS CloudFormation, let’s look at how to retrieve these credentials.

Retrieving secrets

quick start logo
button to go to source code

You can retrieve secrets from Secrets Manager by using Windows PowerShell and Python within a script. And you can retrieve secrets from Secrets Manager for use within AWS CloudFormation.

One method of retrieving a secret is to pass the Amazon Resource Name (ARN) for the secret or the name of the secret to your script by using the Ref or the Sub intrinsic function. An example of this can be seen in the SQL Server on AWS Quick Start, where the ARN is passed to a cmdlet in EC2 user data. In this Quick Start, I use the AWS Tools for PowerShell and the Get-SECSecretValue cmdlet, and I provide the SecretID parameter with the ARN of the secret.

The next few examples demonstrate that Secrets Manager is not limited to Windows workloads on AWS.

In Python, you can leverage the get_secret_value method of the SecretsManager client.

The following sample code, which can also be found within boto documentation, retrieves a password.

import boto3
from botocore.exceptions import ClientError

def get_secret():
    secret_name = "MySecretName"
    region_name = "us-west-2"

    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name=region_name,
    )

    try:
        get_secret_value_response = client.get_secret_value(
            SecretId=secret_name
        )
    except ClientError as e:
        if e.response['Error']['Code'] == 'ResourceNotFoundException':
            print("The requested secret " + secret_name + " was not found")
        elif e.response['Error']['Code'] == 'InvalidRequestException':
            print("The request was invalid due to:", e)
        elif e.response['Error']['Code'] == 'InvalidParameterException':
            print("The request had invalid params:", e)
    else:
        # Secrets Manager decrypts the secret value using the associated KMS CMK
        # Depending on whether the secret was a string or binary, only one of these fields will be populated
        if 'SecretString' in get_secret_value_response:
            text_secret_data = get_secret_value_response['SecretString']
        else:
            binary_secret_data = get_secret_value_response['SecretBinary']

        # Your code goes here.

Another way to pass secrets in AWS CloudFormation is by using dynamic references in the stack template. You can see an example of this in the SUSE Cloud Application Platform Quick Start, where a password is automatically generated with Secrets Manager, and then the secret values are passed into another nested stack.

The dynamic references topic in the AWS documentation provides the following code example.

  MyRDSInstance:
    Type: 'AWS::RDS::DBInstance'
    Properties:
      DBName: MyRDSInstance
      AllocatedStorage: '20'
      DBInstanceClass: db.t2.micro
      Engine: mysql
      MasterUsername: '{{resolve:secretsmanager:MyRDSSecret:SecretString:username}}'
      MasterUserPassword: '{{resolve:secretsmanager:MyRDSSecret:SecretString:password}}'

Conclusion

In this blog post, we discussed generating a random password using Secrets Manager, as well as capturing passwords through AWS CloudFormation parameters and securely storing those passwords in Secrets Manager. We then discussed retrieving passwords, and how to pass them into your script or use them within AWS CloudFormation.

By using these patterns, you get the benefit of not hardcoding passwords into code or storing passwords in an unsecure manner in your automation workflows. Also, as with many AWS services, you get the added benefit of having all these API calls tracked in AWS CloudTrail for auditing.