SecretsManager: Ensure AWSPREVIOUS is only ever tied to one version (#7439)
This commit is contained in:
parent
349353edaa
commit
0455ebb953
@ -1011,6 +1011,14 @@ class SecretsManagerBackend(BaseBackend):
|
|||||||
)
|
)
|
||||||
|
|
||||||
stages.remove(version_stage)
|
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:
|
||||||
if move_to_version_id not in secret.versions:
|
if move_to_version_id not in secret.versions:
|
||||||
@ -1026,6 +1034,9 @@ class SecretsManagerBackend(BaseBackend):
|
|||||||
# Whenever you move AWSCURRENT, Secrets Manager automatically
|
# Whenever you move AWSCURRENT, Secrets Manager automatically
|
||||||
# moves the label AWSPREVIOUS to the version that AWSCURRENT
|
# moves the label AWSPREVIOUS to the version that AWSCURRENT
|
||||||
# was removed from.
|
# 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(
|
secret.versions[remove_from_version_id]["version_stages"].append(
|
||||||
"AWSPREVIOUS"
|
"AWSPREVIOUS"
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -14,6 +14,8 @@ from freezegun import freeze_time
|
|||||||
from moto import mock_aws, settings
|
from moto import mock_aws, settings
|
||||||
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
|
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
|
||||||
|
|
||||||
|
from . import secretsmanager_aws_verified
|
||||||
|
|
||||||
DEFAULT_SECRET_NAME = "test-secret7"
|
DEFAULT_SECRET_NAME = "test-secret7"
|
||||||
|
|
||||||
|
|
||||||
@ -1733,3 +1735,84 @@ def test_update_secret_with_client_request_token():
|
|||||||
assert pve.value.response["Error"]["Message"] == (
|
assert pve.value.response["Error"]["Message"] == (
|
||||||
"ClientRequestToken must be 32-64 characters long."
|
"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."
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user