SecretsManager: Ensure AWSPREVIOUS is only ever tied to one version (#7439)

This commit is contained in:
Bert Blommers 2024-03-07 21:22:13 +00:00 committed by GitHub
parent 349353edaa
commit 0455ebb953
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 137 additions and 1 deletions

View File

@ -1011,6 +1011,14 @@ class SecretsManagerBackend(BaseBackend):
)
stages.remove(version_stage)
elif version_stage == "AWSCURRENT":
current_version = [
v
for v in secret.versions
if "AWSCURRENT" in secret.versions[v]["version_stages"]
][0]
err = f"The parameter RemoveFromVersionId can't be empty. Staging label AWSCURRENT is currently attached to version {current_version}, so you must explicitly reference that version in RemoveFromVersionId."
raise InvalidParameterException(err)
if move_to_version_id:
if move_to_version_id not in secret.versions:
@ -1026,6 +1034,9 @@ class SecretsManagerBackend(BaseBackend):
# Whenever you move AWSCURRENT, Secrets Manager automatically
# moves the label AWSPREVIOUS to the version that AWSCURRENT
# was removed from.
for version in secret.versions:
if "AWSPREVIOUS" in secret.versions[version]["version_stages"]:
secret.versions[version]["version_stages"].remove("AWSPREVIOUS")
secret.versions[remove_from_version_id]["version_stages"].append(
"AWSPREVIOUS"
)

View File

@ -1 +1,43 @@
# This file is intentionally left blank.
import os
from functools import wraps
from uuid import uuid4
import boto3
from moto import mock_aws
def secretsmanager_aws_verified(func):
"""
Function that is verified to work against AWS.
Can be run against AWS at any time by setting:
MOTO_TEST_ALLOW_AWS_REQUEST=true
If this environment variable is not set, the function runs in a `mock_aws` context.
"""
@wraps(func)
def pagination_wrapper():
allow_aws_request = (
os.environ.get("MOTO_TEST_ALLOW_AWS_REQUEST", "false").lower() == "true"
)
if allow_aws_request:
return create_secret_and_execute(func)
else:
with mock_aws():
return create_secret_and_execute(func)
def create_secret_and_execute(func):
sm_client = boto3.client("secretsmanager", "us-east-1")
secret_arn = sm_client.create_secret(
Name=f"moto_secret_{str(uuid4())[0:6]}",
SecretString="old_secret",
)["ARN"]
try:
return func(secret_arn)
finally:
sm_client.delete_secret(SecretId=secret_arn)
return pagination_wrapper

View File

@ -14,6 +14,8 @@ from freezegun import freeze_time
from moto import mock_aws, settings
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
from . import secretsmanager_aws_verified
DEFAULT_SECRET_NAME = "test-secret7"
@ -1733,3 +1735,84 @@ def test_update_secret_with_client_request_token():
assert pve.value.response["Error"]["Message"] == (
"ClientRequestToken must be 32-64 characters long."
)
@secretsmanager_aws_verified
@pytest.mark.aws_verified
def test_update_secret_version_stage_manually(secret_arn=None):
sm_client = boto3.client("secretsmanager", "us-east-1")
current_version = sm_client.put_secret_value(
SecretId=secret_arn,
SecretString="previous_secret",
VersionStages=["AWSCURRENT"],
)["VersionId"]
initial_secret = sm_client.get_secret_value(
SecretId=secret_arn, VersionStage="AWSCURRENT"
)
assert initial_secret["VersionStages"] == ["AWSCURRENT"]
assert initial_secret["SecretString"] == "previous_secret"
token = str(uuid4())
sm_client.put_secret_value(
SecretId=secret_arn,
ClientRequestToken=token,
SecretString="new_secret",
VersionStages=["AWSPENDING"],
)
pending_secret = sm_client.get_secret_value(
SecretId=secret_arn, VersionStage="AWSPENDING"
)
assert pending_secret["VersionStages"] == ["AWSPENDING"]
assert pending_secret["SecretString"] == "new_secret"
sm_client.update_secret_version_stage(
SecretId=secret_arn,
VersionStage="AWSCURRENT",
MoveToVersionId=token,
RemoveFromVersionId=current_version,
)
current_secret = sm_client.get_secret_value(
SecretId=secret_arn, VersionStage="AWSCURRENT"
)
assert list(sorted(current_secret["VersionStages"])) == ["AWSCURRENT", "AWSPENDING"]
assert current_secret["SecretString"] == "new_secret"
previous_secret = sm_client.get_secret_value(
SecretId=secret_arn, VersionStage="AWSPREVIOUS"
)
assert previous_secret["VersionStages"] == ["AWSPREVIOUS"]
assert previous_secret["SecretString"] == "previous_secret"
@secretsmanager_aws_verified
@pytest.mark.aws_verified
def test_update_secret_version_stage_dont_specify_current_stage(secret_arn=None):
sm_client = boto3.client("secretsmanager", "us-east-1")
current_version = sm_client.put_secret_value(
SecretId=secret_arn,
SecretString="previous_secret",
VersionStages=["AWSCURRENT"],
)["VersionId"]
token = str(uuid4())
sm_client.put_secret_value(
SecretId=secret_arn,
ClientRequestToken=token,
SecretString="new_secret",
VersionStages=["AWSPENDING"],
)
# Without specifying version that currently has stage AWSCURRENT
with pytest.raises(ClientError) as exc:
sm_client.update_secret_version_stage(
SecretId=secret_arn, VersionStage="AWSCURRENT", MoveToVersionId=token
)
err = exc.value.response["Error"]
assert err["Code"] == "InvalidParameterException"
assert (
err["Message"]
== f"The parameter RemoveFromVersionId can't be empty. Staging label AWSCURRENT is currently attached to version {current_version}, so you must explicitly reference that version in RemoveFromVersionId."
)