AWS Security Blog

How to rotate your Twitter API key and bearer token automatically with AWS Secrets Manager

Previously, I showed you how to rotate Amazon RDS database credentials automatically with AWS Secrets Manager. In addition to database credentials, AWS Secrets Manager makes it easier to rotate, manage, and retrieve API keys, OAuth tokens, and other secrets throughout their lifecycle. You can configure Secrets Manager to rotate these secrets automatically, which can help you meet your compliance needs. You can also use Secrets Manager to rotate secrets on demand, which can help you respond quickly to security events. In this post, I show you how to store an API key in Secrets Manager and use a custom Lambda function to rotate the key automatically. I’ll use a Twitter API key and bearer token as an example; you can reference this example to rotate other types of API keys.

The instructions are divided into four main phases:

  1. Store a Twitter API key and bearer token in Secrets Manager.
  2. Create a custom Lambda function to rotate the bearer token.
  3. Configure your application to retrieve the bearer token from Secrets Manager.
  4. Configure Secrets Manager to use the custom Lambda function to rotate the bearer token automatically.

For the purpose of this post, I use the placeholder Demo/Twitter_Api_Key to denote the API key, the placeholder Demo/Twitter_bearer_token to denote the bearer token, and placeholder Lambda_Rotate_Bearer_Token to denote the custom Lambda function. Be sure to replace these placeholders with the resource names from your account.

Phase 1: Store a Twitter API key and bearer token in Secrets Manager

Twitter enables developers to register their applications and retrieve an API key, which includes a consumer_key and consumer_secret. Developers use these to generate a bearer token that applications can then use to authenticate and retrieve information from Twitter. At any given point of time, you can use an API key to create only one valid bearer token.

Start by storing the API key in Secrets Manager. Here’s how:

  1. Open the Secrets Manager console and select Store a new secret.
     
    Figure 1: The "Store a new secret" button in the AWS Secrets Manager console

    Figure 1: The “Store a new secret” button in the AWS Secrets Manager console

  2. Select Other type of secrets (because you’re storing an API key).
  3. Input the consumer_key and consumer_secret, and then select Next.
     
    Figure 2: Select the consumer_key and the consumer_secret

    Figure 2: Select the consumer_key and the consumer_secret

  4. Specify values for Secret Name and Description, then select Next. For this example, I use Demo/Twitter_API_Key.
     
    Figure 3: Set values for "Secret Name" and "Description"

    Figure 3: Set values for “Secret Name” and “Description”

  5. On the next screen, keep the default setting, Disable automatic rotation, because you’ll use the same API key to rotate bearer tokens programmatically and automatically. Applications and employees will not retrieve this API key. Select Next.
     
    Figure 4: Keep the default "Disable automatic rotation" setting

    Figure 4: Keep the default “Disable automatic rotation” setting

  6. Review the information on the next screen and, if everything looks correct, select Store. You’ve now successfully stored a Twitter API key in Secrets Manager.

Next, store the bearer token in Secrets Manager. Here’s how:

  1. From the Secrets Manager console, select Store a new secret, select Other type of secrets, input details (access_token, token_type, and ARN of the API key) about the bearer token, and then select Next.
     
    Figure 5: Add details about the bearer token

    Figure 5: Add details about the bearer token

  2. Specify values for Secret Name and Description, and then select Next. For this example, I use Demo/Twitter_bearer_token.
     
    Figure 6: Again set values for "Secret Name" and "Description"

    Figure 6: Again set values for “Secret Name” and “Description”

  3. Keep the default rotation setting, Disable automatic rotation, and then select Next. You’ll enable rotation after you’ve updated the application to use Secrets Manager APIs to retrieve secrets.
  4. Review the information and select Store. You’ve now completed storing the bearer token in Secrets Manager.
    I take note of the sample code provided on the review page. I’ll use this code to update my application to retrieve the bearer token using Secrets Manager APIs.
     
    Figure 7: The sample code you can use in your app

    Figure 7: The sample code you can use in your app

Phase 2: Create a custom Lambda function to rotate the bearer token

While Secrets Manager supports rotating credentials for databases hosted on Amazon RDS natively, it also enables you to meet your unique rotation-related use cases by authoring custom Lambda functions. Now that you’ve stored the API key and bearer token, you’ll create a Lambda function to rotate the bearer token. For this example, I’ll create my Lambda function using Python 3.6.

  1. Open the Lambda console and select Create function.
     
    Figure 8: In the Lambda console, select "Create function"

    Figure 8: In the Lambda console, select “Create function”

  2. Select Author from scratch. For this example, I use the name Lambda_Rotate_Bearer_Token for my Lambda function. I also set the Runtime environment as Python 3.6.
     
    Figure 9: Create a new function from scratch

    Figure 9: Create a new function from scratch

  3. This Lambda function requires permissions to call AWS resources on your behalf. To grant these permissions, select Create a custom role. This opens a console tab.
  4. Select Create a new IAM Role and specify the value for Role Name. For this example, I use Role_Lambda_Rotate_Twitter_Bearer_Token.
     
    Figure 10: For "IAM Role," select "Create a new IAM role"

    Figure 10: For “IAM Role,” select “Create a new IAM role”

  5. Next, to define the IAM permissions, copy and paste the following IAM policy in the View Policy Document text-entry field. Be sure to replace the placeholder ARN-OF-Demo/Twitter_API_Key with the ARN of your secret.
    
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                “Sid”: “AllowLambdaToStoreAndRetrieveSecrets”
    "Effect": "Allow",
                "Action": [
                    "secretsmanager:DescribeSecret",
                    "secretsmanager:GetSecretValue",
                    "secretsmanager:PutSecretValue",
                    "secretsmanager:UpdateSecretVersionStage"
                ],
                "Resource": "*"
            },
            {
                “Sid”: “GenerateARandomStringToExecuteRotation”
                "Effect": "Allow",
                "Action": [
                    "secretsmanager:GetRandomPassword"
                ],
                "Resource": "*"
            },
            {
                “Sid”: “AccessToNetworkInterface”
                "Action": [
                    "ec2:CreateNetworkInterface",
                    "ec2:DeleteNetworkInterface",
                    "ec2:DescribeNetworkInterfaces",
                    "ec2:DetachNetworkInterface"
                ],
                "Resource": "*",
                "Effect": "Allow"
            },
            {
                “Sid”: “RecordProgressInLogsForEasierDebugging”
                "Effect": "Allow",
                "Action": [
                    "logs:CreateLogGroup",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                ],
                "Resource": "*"
            },
            {
                “Sid”: “GetMasterCredential”
                "Effect": "Allow",
                "Action": [
                    "secretsmanager:GetSecretValue"
                ],
                "Resource": ARN-OF-Demo/Twitter_API_Key
            }
        ]
    }
    

    Here’s what it will look like:
     

    Figure 11: The IAM policy pasted in the "View Policy Document" text-entry field

    Figure 11: The IAM policy pasted in the “View Policy Document” text-entry field

  6. Now, select Allow. This brings me back to the Lambda console with the appropriate Role selected.
  7. Select Create function.
     
    Figure 12: Select the "Create function" button in the lower-right corner

    Figure 12: Select the “Create function” button in the lower-right corner

  8. Copy the following Python code and paste it in the Function code section.
    
    import base64
    import json
    import logging
    import os
    
    import boto3
    from botocore.vendored import requests
    
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    
    def lambda_handler(event, context):
        """Secrets Manager Twitter Bearer Token Handler
    
        This handler uses the master-user rotation scheme to rotate a bearer token of a Twitter app.
    
        The Secret PlaintextString is expected to be a JSON string with the following format:
        {
            'access_token': ,
            'token_type': ,
            'masterarn': 
        }
    
        Args:
            event (dict): Lambda dictionary of event parameters. These keys must include the following:
                - SecretId: The secret ARN or identifier
                - ClientRequestToken: The ClientRequestToken of the secret version
                - Step: The rotation step (one of createSecret, setSecret, testSecret, or finishSecret)
    
            context (LambdaContext): The Lambda runtime information
    
        Raises:
            ResourceNotFoundException: If the secret with the specified arn and stage does not exist
    
            ValueError: If the secret is not properly configured for rotation
    
            KeyError: If the secret json does not contain the expected keys
    
        """
        arn = event['SecretId']
        token = event['ClientRequestToken']
        step = event['Step']
    
        # Setup the client and environment variables
        service_client = boto3.client('secretsmanager', endpoint_url=os.environ['SECRETS_MANAGER_ENDPOINT'])
        oauth2_token_url = os.environ['TWITTER_OAUTH2_TOKEN_URL']
        oauth2_invalid_token_url = os.environ['TWITTER_OAUTH2_INVALID_TOKEN_URL']
        tweet_search_url = os.environ['TWITTER_SEARCH_URL']
    
        # Make sure the version is staged correctly
        metadata = service_client.describe_secret(SecretId=arn)
        if not metadata['RotationEnabled']:
            logger.error("Secret %s is not enabled for rotation" % arn)
            raise ValueError("Secret %s is not enabled for rotation" % arn)
        versions = metadata['VersionIdsToStages']
        if token not in versions:
            logger.error("Secret version %s has no stage for rotation of secret %s." % (token, arn))
            raise ValueError("Secret version %s has no stage for rotation of secret %s." % (token, arn))
        if "AWSCURRENT" in versions[token]:
            logger.info("Secret version %s already set as AWSCURRENT for secret %s." % (token, arn))
            return
        elif "AWSPENDING" not in versions[token]:
            logger.error("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn))
            raise ValueError("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn))
    
        # Call the appropriate step
        if step == "createSecret":
            create_secret(service_client, arn, token, oauth2_token_url, oauth2_invalid_token_url)
    
        elif step == "setSecret":
            set_secret(service_client, arn, token, oauth2_token_url)
    
        elif step == "testSecret":
            test_secret(service_client, arn, token, tweet_search_url)
    
        elif step == "finishSecret":
            finish_secret(service_client, arn, token)
    
        else:
            logger.error("lambda_handler: Invalid step parameter %s for secret %s" % (step, arn))
            raise ValueError("Invalid step parameter %s for secret %s" % (step, arn))
    
    
    def create_secret(service_client, arn, token, oauth2_token_url, oauth2_invalid_token_url):
        """Get a new bearer token from Twitter 
    
        This method invalidates existing bearer token for the Twitter app and retrieves a new one from Twitter.
        If a secret version with AWSPENDING stage exists, updates it with the newly retrieved bearer token and if 
        the AWSPENDING stage does not exist, creates a new version of the secret with that stage label. 
    
        Args:
            service_client (client): The secrets manager service client
    
            arn (string): The secret ARN or other identifier
    
            token (string): The ClientRequestToken associated with the secret version
    
            oauth2_token_url (string): The Twitter API endpoint to request a bearer token
    
            oauth2_invalid_token_url (string): The Twitter API endpoint to invalidate a bearer token
    
        Raises:
            ValueError: If the current secret is not valid JSON
    
            KeyError: If the secret json does not contain the expected keys
    
            ResourceNotFoundException: If the current secret is not found
    
        """
        # Make sure the current secret exists and try to get the master arn from the secret
        try:
          current_secret_dict = get_secret_dict(service_client, arn, "AWSCURRENT")
          master_arn = current_secret_dict['masterarn']
          logger.info("createSecret: Successfully retrieved secret for %s." % arn)
        except service_client.exceptions.ResourceNotFoundException:
          return
        # create bearer token credentials to be passed as authorization string to Twitter
        bearer_token_credentials = encode_credentials(service_client, master_arn, "AWSCURRENT")
        # get the bearer token from Twitter
        bearer_token_from_twitter = get_bearer_token(bearer_token_credentials,oauth2_token_url)
        # invalidate the current bearer token
        invalidate_bearer_token(oauth2_invalid_token_url,bearer_token_credentials,bearer_token_from_twitter)
        # get a new bearer token from Twitter
        new_bearer_token = get_bearer_token(bearer_token_credentials, oauth2_token_url)
        # if a secret version with AWSPENDING stage exists, update it with the lastest bearer token
        # if the AWSPENDING stage does not exist, then create the version with AWSPENDING stage
        try:
          pending_secret_dict = get_secret_dict(service_client, arn, "AWSPENDING", token)
          pending_secret_dict['access_token'] = new_bearer_token
          service_client.put_secret_value(SecretId=arn, ClientRequestToken=token, SecretString=json.dumps(pending_secret_dict), VersionStages=['AWSPENDING'])
          logger.info("createSecret: Successfully invalidated the bearer token of the secret %s and updated the pending version" % arn)
        except service_client.exceptions.ResourceNotFoundException:
          current_secret_dict['access_token'] = new_bearer_token
          service_client.put_secret_value(SecretId=arn, ClientRequestToken=token, SecretString=json.dumps(current_secret_dict), VersionStages=['AWSPENDING'])
          logger.info("createSecret: Successfully invalidated the bearer token of the secret %s and and created the pending version." % arn)
    
    def set_secret(service_client, arn, token, oauth2_token_url):
        """Validate the pending secret with that in Twitter
    
        This method checks wether the bearer token in Twitter is the same as the one in the version with AWSPENDING stage.
    
        Args:
            service_client (client): The secrets manager service client
    
            arn (string): The secret ARN or other identifier
    
            token (string): The ClientRequestToken associated with the secret version
    
            oauth2_token_url (string): The Twitter API endopoint to get a bearer token
    
        Raises:
            ResourceNotFoundException: If the secret with the specified arn and stage does not exist
    
            ValueError: If the secret is not valid JSON or master credentials could not be used to login to DB
    
            KeyError: If the secret json does not contain the expected keys
    
        """
        # First get the pending version of the bearer token and compare it with that in Twitter
        pending_secret_dict = get_secret_dict(service_client, arn, "AWSPENDING")
        master_arn = pending_secret_dict['masterarn']
        # create bearer token credentials to be passed as authorization string to Twitter
        bearer_token_credentials = encode_credentials(service_client, master_arn, "AWSCURRENT")
        # get the bearer token from Twitter
        bearer_token_from_twitter = get_bearer_token(bearer_token_credentials, oauth2_token_url)
        # if the bearer tokens are same, invalidate the bearer token in Twitter
        # if not, raise an exception that bearer token in Twitter was changed outside Secrets Manager
        if pending_secret_dict['access_token'] == bearer_token_from_twitter:
          logger.info("createSecret: Successfully verified the bearer token of arn %s" % arn)
        else:
          raise ValueError("The bearer token of the Twitter app was changed outside Secrets Manager. Please check.")
        
    def test_secret(service_client, arn, token, tweet_search_url):
        """Test the pending secret by calling a Twitter API
    
        This method tries to use the bearer token in the secret version with AWSPENDING stage and search for tweets
        with 'aws secrets manager' string. 
    
        Args:
            service_client (client): The secrets manager service client
    
            arn (string): The secret ARN or other identifier
    
            token (string): The ClientRequestToken associated with the secret version
    
        Raises:
            ResourceNotFoundException: If the secret with the specified arn and stage does not exist
    
            ValueError: If the secret is not valid JSON or pending credentials could not be used to login to the database
    
            KeyError: If the secret json does not contain the expected keys
    
        """
        # First get the pending version of the bearer token and compare it with that in Twitter
        pending_secret_dict = get_secret_dict(service_client, arn, "AWSPENDING", token)
        # Now verify you can search for tweets using the bearer token
        if verify_bearer_token(pending_secret_dict['access_token'], tweet_search_url):
            logger.info("testSecret: Successfully authorized with the pending secret in %s." % arn)
            return
        else:
            logger.error("testSecret: Unable to authorize with the pending secret of secret ARN %s" % arn)
            raise ValueError("Unable to connect to Twitter with pending secret of secret ARN %s" % arn)
    
    def finish_secret(service_client, arn, token):
        """Finish the rotation by marking the pending secret as current
    
        This method moves the secret from the AWSPENDING stage to the AWSCURRENT stage.
    
        Args:
            service_client (client): The secrets manager service client
    
            arn (string): The secret ARN or other identifier
    
            token (string): The ClientRequestToken associated with the secret version
    
        Raises:
            ResourceNotFoundException: If the secret with the specified arn and stage does not exist
    
        """
        # First describe the secret to get the current version
        metadata = service_client.describe_secret(SecretId=arn)
        current_version = None
        for version in metadata["VersionIdsToStages"]:
            if "AWSCURRENT" in metadata["VersionIdsToStages"][version]:
                if version == token:
                    # The correct version is already marked as current, return
                    logger.info("finishSecret: Version %s already marked as AWSCURRENT for %s" % (version, arn))
                    return
                current_version = version
                break
    
        # Finalize by staging the secret version current
        service_client.update_secret_version_stage(SecretId=arn, VersionStage="AWSCURRENT", MoveToVersionId=token, RemoveFromVersionId=current_version)
        logger.info("finishSecret: Successfully set AWSCURRENT stage to version %s for secret %s." % (version, arn))
    def encode_credentials(service_client, arn, stage):
      """Encodes the Twitter credentials
      This helper function encodes the Twitter credentials (consumer_key and consumer_secret) 
    
      Args:
        service_client (client):The secrets manager service client
    
        arn (string): The secret ARN or other identifier
    
        stage (stage): The stage identifying the secret version
      
      Returns:
        encoded_credentials (string): base64 encoded authorization string for Twitter
    
      Raises:
        KeyError: If the secret json does not contain the expected keys
      """
      required_fields = ['consumer_key','consumer_secret']
      master_secret_dict = get_secret_dict(service_client, arn, stage)
      for field in required_fields:
        if field not in master_secret_dict:
          raise KeyError("%s key is missing from the secret JSON" % field)
      encoded_credentials = base64.urlsafe_b64encode(
        '{}:{}'.format(master_secret_dict['consumer_key'], master_secret_dict['consumer_secret']).encode('ascii')).decode('ascii')
      return encoded_credentials
    def get_bearer_token(encoded_credentials, oauth2_token_url):
      """Gets a bearer token from Twitter
    
      This helper function retrieves the current bearer token from Twitter, given a set of credentials.
    
      Args:
        encoded_credentials (string): Twitter credentials for authentication
    
        oauth2_token_url (string): REST API endpoint to request a bearer token from Twitter
    
      Raises:
        KeyError: If the secret json does not contain the expected keys
      """
      headers = {
          'Authorization': 'Basic {}'.format(encoded_credentials),
          'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
      }
      data = 'grant_type=client_credentials'
      response = requests.post(oauth2_token_url, headers=headers, data=data)
      response_data = response.json()
      if response_data['token_type'] == 'bearer':
          bearer_token = response_data['access_token']
          return bearer_token
      else:
          raise RuntimeError('unexpected token type: {}'.format(response_data['token_type']))
    def invalidate_bearer_token(oauth2_invalid_token_url, bearer_token_credentials, bearer_token):
        """Invalidates a Bearer Token of a Twitter App
    
        This helper function invalidates a bearer token of a Twitter app. 
        If successful, it returns the invalidated bearer token, else None
    
        Args:
            oauth2_invalid_token_url (string): The Twitter API endpoint to invalidate a bearer token
    
            bearer_token_credentials (string): encoded consumer key and consumer secret to authenticate with Twitter
    
            bearer_token (string): The bearer token to be invalidated
    
        Returns:
            invalidated_bearer_token: The invalidated bearer token
    
        Raises:
            ResourceNotFoundException: If the secret with the specified arn and stage does not exist
    
            ValueError: If the secret is not valid JSON
    
            KeyError: If the secret json does not contain the expected keys
    
        """
        headers = {
          'Authorization': 'Basic {}'.format(bearer_token_credentials),
          'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
        }
        data = 'access_token=' + bearer_token
        invalidate_response = requests.post(oauth2_invalid_token_url, headers=headers, data=data)
        invalidate_response_data = invalidate_response.json()
        if invalidate_response_data:
          return
        else:
          raise RuntimeError('Invalidate bearer token request failed')
    def verify_bearer_token(bearer_token, tweet_search_url):
      """Verifies access to Twitter APIs using a bearer token
    
      This helper function verifies that the bearer token is valid by calling Twitter's search/tweets API endpoint
    
      Args:
          bearer_token (string): The current bearer token for the application
    
      Returns:
          True or False
    
      Raises:
          KeyError: If the response of search tweets API call fails
      """
      headers = {
        'Authorization' : 'Bearer {}'.format(bearer_token),
        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
      }
      search_results = requests.get(tweet_search_url, headers=headers)
      try:
        search_results.json()['statuses']
        return True
      except:
        return False
    
    def get_secret_dict(service_client, arn, stage, token=None):
        """Gets the secret dictionary corresponding for the secret arn, stage, and token
    
        This helper function gets credentials for the arn and stage passed in and returns the dictionary by parsing the JSON string
    
        Args:
            service_client (client): The secrets manager service client
    
            arn (string): The secret ARN or other identifier
    
            token (string): The ClientRequestToken associated with the secret version, or None if no validation is desired
    
            stage (string): The stage identifying the secret version
    
        Returns:
            SecretDictionary: Secret dictionary
    
        Raises:
            ResourceNotFoundException: If the secret with the specified arn and stage does not exist
    
            ValueError: If the secret is not valid JSON
    
        """
        # Only do VersionId validation against the stage if a token is passed in
        if token:
            secret = service_client.get_secret_value(SecretId=arn, VersionId=token, VersionStage=stage)
        else:
            secret = service_client.get_secret_value(SecretId=arn, VersionStage=stage)
        plaintext = secret['SecretString']
    
        # Parse and return the secret JSON string
        return json.loads(plaintext)
    
    

    Here’s what it will look like:
     

    Figure 13: The Python code pasted in the "Function code" section

    Figure 13: The Python code pasted in the “Function code” section

  9. On the same page, provide the following environment variables:

    Here’s what it will look like:
     

    Figure 14: Add the environment variables

    Figure 14: Add the environment variables

    Note: Resources used in this example are in US East (Ohio) region. If you intend to use another AWS Region, change the SECRETS_MANAGER_ENDPOINT set in the Environment variables to the appropriate region.

    You’ve now created a Lambda function that can rotate the bearer token:
     

    Figure 15: The new Lambda function

    Figure 15: The new Lambda function

  10. Before you can configure Secrets Manager to use this Lambda function, you need to update the function policy of the Lambda function. A function policy permits AWS services, such as Secrets Manager, to invoke a Lambda function on behalf of your application. You can attach a Lambda function policy from the AWS Command Line Interface (AWS CLI) or SDK. To attach a function policy, call the add-permission Lambda API from the AWS CLI.
    
    >aws lambda add-permission --function-name Lambda_Rotate_Bearer_Token --principal secretsmanager.amazonaws.com --action lambda:InvokeFunction --statement-id SecretsManagerAccess
    

Phase 3: Configure your application to retrieve the bearer token from Secrets Manager

Now that you’ve stored the bearer token in Secrets Manager, update the application to retrieve the bearer token from Secrets Manager instead of hard-coding this information in a configuration file or source code. For this example, I show you how to configure a Python application to retrieve this secret from Secrets Manager.

  1. Connect to your Amazon EC2 instance via Secure Shell (SSH). Previously, you configured the application to retrieve the bearer token from a configuration file. Below is the source code for my application.
    
    import config
    
    def no_secrets_manager_sample()
        
        # Get the bearer token from a config file. 
        Bearer_token = config.bearer_token
        
        # Use the bearer token to authenticate requests to Twitter 
    
  2. Use the sample code from section titled Phase 1 and update the application to retrieve the bearer token from Secrets Manager. The following code sets up the client and retrieves and decrypts the secret Demo/Twitter_bearer_token.
    
    # Use this code snippet in your app.
    import boto3
    from botocore.exceptions import ClientError
    
    
    def get_secret():
        secret_name = "Demo/Twitter_bearer_token"
        endpoint_url = "https://secretsmanager.us-east-2.amazonaws.com"
        region_name = "us-east-2"
    
        session = boto3.session.Session()
        client = session.client(
            service_name='secretsmanager',
            region_name=region_name,
            endpoint_url=endpoint_url
        )
    
        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:
            # Decrypted secret using the associated KMS CMK
            # Depending on whether the secret was a string or binary, one of these fields will be populated
            if 'SecretString' in get_secret_value_response:
                secret = get_secret_value_response['SecretString']
            else:
                binary_secret_data = get_secret_value_response['SecretBinary']
                
            # Your code goes here.
    
    
  3. Applications require permissions to access Secrets Manager. My application runs on Amazon EC2 and uses an IAM role to get access to AWS services. I’ll attach the following policy to my IAM role, and you should take a similar action with your IAM role. This policy uses the GetSecretValue action to grant my application permissions to read secrets from Secrets Manager. This policy also uses the resource element to limit my application to read only the Demo/Twitter_bearer_token secret from Secrets Manager. Read the AWS Secrets Manager documentation to understand the minimum IAM permissions required to retrieve a secret.
    
    {
    "Version": "2012-10-17",
    "Statement": {
    "Sid": "RetrieveBearerToken",
    "Effect": "Allow",
    "Action": "secretsmanager:GetSecretValue",
    "Resource": Input ARN of the secret Demo/Twitter_bearer_token here 
    }
    }
    

    Note: To improve the resiliency of your applications, associate your application with two API keys/bearer tokens. This is a higher availability option because you can continue to use one bearer token while Secrets Manager rotates the other token. Read the AWS documentation to learn how AWS Secrets Manager rotates your secrets.

Phase 4: Enable and verify rotation

Now that you’ve stored the secret in Secrets Manager and created a Lambda function to rotate this secret, configure Secrets Manager to rotate the secret Demo/Twitter_bearer_token.

  1. From the Secrets Manager console, go to the list of secrets and choose the secret you created in the first step (in my example, this is named Demo/Twitter_bearer_token).
  2. Scroll to Rotation configuration, and then select Edit rotation.
     
    Figure 16: Select the "Edit rotation" button

    Figure 16: Select the “Edit rotation” button

  3. To enable rotation, select Enable automatic rotation, and then choose how frequently you want Secrets Manager to rotate this secret. For this example, I set the rotation interval to 30 days. I also choose the rotation Lambda function, Lambda_Rotate_Bearer_Token, from the drop-down list.
     
    Figure 17: "Edit rotation configuration" options

    Figure 17: “Edit rotation configuration” options

The banner on the next screen confirms that I have successfully configured rotation and the first rotation is in progress, which enables you to verify that rotation is functioning as expected. Secrets Manager will rotate this credential automatically every 30 days.
 

Figure 18: Confirmation notice

Figure 18: Confirmation notice

Summary

In this post, I showed you how to configure Secrets Manager to manage and rotate an API key and bearer token used by applications to authenticate and retrieve information from Twitter. You can use the steps described in this blog to manage and rotate other API keys, as well.

Secrets Manager helps you protect access to your applications, services, and IT resources without the upfront investment and on-going maintenance costs of operating your own secrets management infrastructure. To get started, open the Secrets Manager console. To learn more, read the Secrets Manager documentation.

If you have comments about this post, submit them in the Comments section below. If you have questions about anything in this post, start a new thread on the Secrets Manager forum or contact AWS Support.

Want more AWS Security news? Follow us on Twitter.