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

huangapple go评论82阅读模式
英文:

Rotating secret key but keeping other database credentials?

问题

我目前正在使用AWS Lambda来轮换秘密密钥。我遵循了AWS网站上的指南,使用了SecretsManagerRotationTemplate。这可以很好地用新的秘密密钥轮换当前的秘密密钥。

问题在于,我还将用户名和数据库名称存储为凭据。SecretsManagerRotationTemplate会移除数据库连接的其他凭据/数据,仅显示新的秘密密钥。在使用Lambda函数进行SecretsManagerRotationTemplate时,是否有办法保留用户名、数据库名称等信息?

以下是模板的代码部分,不包括翻译:

# 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):
    # 代码内容

希望这能帮助您。如果您有任何其他问题,请随时提出。

英文:

I am currently using AWS Lambda for rotating the secret key. I followed the guide on the AWS website which uses the SecretsManagerRotationTemplate. This works well to rotate the current secret key with a new secret key.

The problem is that I have also stored the username and database name as credentials. The SecretsManagerRotationTemplate removes the other credentials / data for the database connection and simply shows the new secret key. Is there a way to keep the username, database name etc. while using the Lambda function for SecretsManagerRotationTemplate?

The code of the template is attached below:

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

答案1

得分: 1

以下是要翻译的内容:

"It looks like you're using this SecretsManagerRotationTemplate function [1]."

  • 你似乎正在使用这个 SecretsManagerRotationTemplate 函数[1]

"On line 103 [2] it makes a boto3 secrets_manager client put_secret_value call."

  • 在第 103 行[2],它调用了 boto3 secrets_manager 客户端的 put_secret_value 方法。

"The put_secret_value call creates a new version with a new secret value and attaches it to the secret [3]."

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

"You have apparently created a secret with 3 key/values (i.e. username, databasename, password). But the function you showed in your example only adds one value, the new secret value, to the new secret. You'll have to modify one of two things:"

  • 你显然创建了一个包含 3 个键/值对的秘密(即用户名、数据库名和密码)。但是你在示例中展示的函数只将一个值,即新的秘密值,添加到新秘密中。你需要修改以下两种方式之一:

"In order to work with this function as is, your secrets manager secrets should only contain the password."

  • 要使用这个函数,你的 Secrets Manager 秘密应该只包含密码。

"In order to update the password but keep the username and databasename in the secrets manager secret, you'll have to update this function to get the current values of those other key/value pairs, and then create a new secret version with all 3 key/values as you desired."

  • 要更新密码但保留用户名和数据库名称在 Secrets Manager 秘密中,你需要更新此函数以获取其他键/值对的当前值,然后创建一个包含所有 3 个键/值对的新秘密版本,如你所期望的。

"I've tested this using boto3 and the ipython CLI:"

  • 我使用 boto3 和 ipython CLI 进行了测试:

然后是一些代码示例,演示了如何获取和更新 Secrets Manager 秘密的值。

英文:

It looks like you're using this SecretsManagerRotationTemplate function [1].

On line 103 [2] it makes a boto3 secrets_manager client put_secret_value call. The put_secret_value call creates a new version with a new secret value and attaches it to the secret [3].

You have apparently created a secret with 3 key/values (i.e. username, databasename, password). But the function you showed in your example only adds one value, the new secret value, to the new secret. You'll have to modify one of two things:

  1. In order to work with this function as is, your secrets manager secrets should only contain the password.
  2. In order to update the password but keep the username and databasename in the secrets manager secret, you'll have to update this function to get the current values of those other key/value pairs, and then create a new secret version with all 3 key/values as you desired.

I've tested this using boto3 and the 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}}

This shows the current secret key value pairs, then creates a new secret version using the tag AWSPENDING, then shows that secret which illustrates the new secret that was just stored, in addition to the original key/values that you wanted to keep in your secret.

Hope this helps!

huangapple
  • 本文由 发表于 2023年3月1日 15:02:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/75600462.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定