旋转密钥但保留其他数据库凭证?

问题描述 投票:0回答:1

我目前正在使用 AWS Lambda 轮换密钥。我遵循了使用 SecretsManagerRotationTemplate 的 AWS 网站上的指南。这很适合用新的密钥轮换当前的密钥。

问题是我还存储了用户名和数据库名称作为凭据。 SecretsManagerRotationTemplate 删除了数据库连接的其他凭据/数据,并仅显示新的密钥。有没有办法在使用 SecretsManagerRotationTemplate 的 Lambda 函数时保留用户名、数据库名称等?

下面附上模板代码:

 # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

import boto3
import logging
import os

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def lambda_handler(event, context):
    """Secrets Manager Rotation Template

    This is a template for creating an AWS Secrets Manager rotation lambda

    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 event parameters do not contain the expected keys

    """
    print(event)
    arn = event['SecretId']
    token = event['ClientRequestToken']
    step = event['Step']

    # Setup the client
    service_client = boto3.client('secretsmanager')#, endpoint_url=os.environ['SECRETS_MANAGER_ENDPOINT'])

    # 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))

    if step == "createSecret":
        create_secret(service_client, arn, token)

    elif step == "setSecret":
        set_secret(service_client, arn, token)

    elif step == "testSecret":
        test_secret(service_client, arn, token)

    elif step == "finishSecret":
        finish_secret(service_client, arn, token)

    else:
        raise ValueError("Invalid step parameter")


def create_secret(service_client, arn, token):
    """Create the secret

    This method first checks for the existence of a secret for the passed in token. If one does not exist, it will generate a
    new secret and put it with the passed in token.

    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

    """
    # Make sure the current secret exists
    service_client.get_secret_value(SecretId=arn, VersionStage="AWSCURRENT")

    # Now try to get the secret version, if that fails, put a new secret
    try:
        service_client.get_secret_value(SecretId=arn, VersionId=token, VersionStage="AWSPENDING")
        logger.info("createSecret: Successfully retrieved secret for %s." % arn)
    except service_client.exceptions.ResourceNotFoundException:
        # Get exclude characters from environment variable
        exclude_characters = os.environ['EXCLUDE_CHARACTERS'] if 'EXCLUDE_CHARACTERS' in os.environ else '/@"\'\\'
        # Generate a random password
        passwd = service_client.get_random_password(ExcludeCharacters=exclude_characters)

        # Put the secret
        service_client.put_secret_value(SecretId=arn, ClientRequestToken=token, SecretString=passwd['RandomPassword'], VersionStages=['AWSPENDING'])
        logger.info("createSecret: Successfully put secret for ARN %s and version %s." % (arn, token))


def set_secret(service_client, arn, token):
    secret = service_client.get_secret_value(SecretId=arn, VersionId=token, VersionStage="AWSPENDING")    
    logger.info(secret)
    pass
    """Set the secret

    This method should set the AWSPENDING secret in the service that the secret belongs to. For example, if the secret is a database
    credential, this method should take the value of the AWSPENDING secret and set the user's password to this value in the database.

    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

    """
    # This is where the secret should be set in the service
   # raise NotImplementedError


def test_secret(service_client, arn, token):
    pass
    """Test the secret

    This method should validate that the AWSPENDING secret works in the service that the secret belongs to. For example, if the secret
    is a database credential, this method should validate that the user can login with the password in AWSPENDING and that the user has
    all of the expected permissions against the database.

    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

    """
    # This is where the secret should be tested against the service
    #raise NotImplementedError


def finish_secret(service_client, arn, token):
    """Finish the secret

    This method finalizes the rotation process by marking the secret version passed in as the AWSCURRENT secret.

    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 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." % (token, arn))
python database amazon-web-services aws-lambda rotation
1个回答
0
投票

看起来你正在使用这个 SecretsManagerRotationTemplate 函数 [1].

在第 103 行[2] 上,它调用了 boto3 secrets_manager 客户端

put_secret_value
put_secret_value
调用创建一个具有新秘密值的新版本并将其附加到秘密 [3].

您显然已经创建了一个包含 3 个键/值(即用户名、数据库名、密码)的秘密。但是您在示例中显示的函数仅向新秘密添加一个值,即新秘密值。您必须修改以下两项之一:

  1. 为了按原样使用此功能,您的机密管理器机密应仅包含密码。
  2. 为了更新密码但保持机密管理器中的用户名和数据库名称保密,您必须更新此功能以获取其他键/值对的当前值,然后创建一个包含所有 3 个的新机密版本根据需要设置键/值。

我已经使用 boto3 和 ipython CLI 对此进行了测试:

In [1]: import json

In [2]: import boto3

In [3]: service_client = boto3.client('secretsmanager')

In [4]: arn = "arn:aws:secretsmanager:us-west-2:12345:secret:test/secret1-GYO9wE"

In [5]: service_client.get_secret_value(SecretId=arn, VersionStage="AWSCURRENT")
Out[5]: 
{'ARN': 'arn:aws:secretsmanager:us-west-2:12345:secret:test/secret1-GYO9wE',
 'Name': 'test/secret1',
 'VersionId': 'a305d8fc-0bc3-4f5f-997b-b9bbe14022c8',
 'SecretString': '{\n"database":"ImportantDatabaseUrl",\n"username":"user1",\n"password":"aaabbbcccddd"\n}',
 'VersionStages': ['AWSCURRENT'],
 'CreatedDate': datetime.datetime(2023, 3, 1, 19, 29, 59, 751000, tzinfo=tzlocal()),
 'ResponseMetadata': {'RequestId': 'b37e283c-31fb-441d-abb1-e4d0d09dcdb0',
  'HTTPStatusCode': 200,
  'HTTPHeaders': {'x-amzn-requestid': 'b37e283c-31fb-441d-abb1-e4d0d09dcdb0',
   'content-type': 'application/x-amz-json-1.1',
   'content-length': '335',
   'date': 'Thu, 02 Mar 2023 02:45:41 GMT'},
  'RetryAttempts': 0}}

In [6]: response = service_client.put_secret_value(
   ...: SecretId=arn,
   ...: SecretString=json.dumps({"database":"ImportantDatabaseUrl","username":"user1","password":"jjjjjjjjjjkkk"}),
   ...: VersionStages=['AWSPENDING'])

In [7]: response
Out[7]: 
{'ARN': 'arn:aws:secretsmanager:us-west-2:12345:secret:test/secret1-GYO9wE',
 'Name': 'test/secret1',
 'VersionId': '55e3ef1f-708d-419a-bfaa-8c07bd471afb',
 'VersionStages': ['AWSPENDING'],
 'ResponseMetadata': {'RequestId': '9794e795-c198-44a9-971c-2da6a812fddc',
  'HTTPStatusCode': 200,
  'HTTPHeaders': {'x-amzn-requestid': '9794e795-c198-44a9-971c-2da6a812fddc',
   'content-type': 'application/x-amz-json-1.1',
   'content-length': '186',
   'date': 'Thu, 02 Mar 2023 02:45:50 GMT'},
  'RetryAttempts': 0}}

In [9]: service_client.get_secret_value(SecretId=arn, VersionStage="AWSPENDING")
Out[9]: 
{'ARN': 'arn:aws:secretsmanager:us-west-2:12345:secret:test/secret1-GYO9wE',
 'Name': 'test/secret1',
 'VersionId': '55e3ef1f-708d-419a-bfaa-8c07bd471afb',
 'SecretString': '{"database": "ImportantDatabaseUrl", "username": "user1", "password": "jjjjjjjjjjkkk"}',
 'VersionStages': ['AWSPENDING'],
 'CreatedDate': datetime.datetime(2023, 3, 1, 19, 45, 51, 164000, tzinfo=tzlocal()),
 'ResponseMetadata': {'RequestId': 'a77de580-ab95-4416-a8e9-83c07ab5cf5c',
  'HTTPStatusCode': 200,
  'HTTPHeaders': {'x-amzn-requestid': 'a77de580-ab95-4416-a8e9-83c07ab5cf5c',
   'content-type': 'application/x-amz-json-1.1',
   'content-length': '333',
   'date': 'Thu, 02 Mar 2023 02:46:21 GMT'},
  'RetryAttempts': 0}}

这显示了当前的密钥值对,然后使用标签 AWSPENDING 创建了一个新的密钥版本,然后显示了说明刚刚存储的新密钥的密钥,以及您想要保留在您的文件中的原始密钥/值秘密。

希望这有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.