diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index e6cefd5a9..0fde0faaf 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -16,7 +16,7 @@ from .exceptions import ( InvalidRequestException, ClientError, ) -from .utils import random_password, secret_arn, get_secret_name_from_arn +from .utils import random_password, secret_arn, get_secret_name_from_partial_arn from .list_secrets.filters import ( filter_all, tag_key, @@ -176,25 +176,40 @@ class FakeSecret: class SecretsStore(dict): + # Parameters to this dictionary can be three possible values: + # names, full ARNs, and partial ARNs + # Every retrieval method should check which type of input it receives + def __setitem__(self, key, value): - new_key = get_secret_name_from_arn(key) - super().__setitem__(new_key, value) + super().__setitem__(key, value) def __getitem__(self, key): - new_key = get_secret_name_from_arn(key) - return super().__getitem__(new_key) + for secret in dict.values(self): + if secret.arn == key or secret.name == key: + return secret + name = get_secret_name_from_partial_arn(key) + return super().__getitem__(name) def __contains__(self, key): - new_key = get_secret_name_from_arn(key) - return dict.__contains__(self, new_key) + for secret in dict.values(self): + if secret.arn == key or secret.name == key: + return True + name = get_secret_name_from_partial_arn(key) + return dict.__contains__(self, name) def get(self, key, *args, **kwargs): - new_key = get_secret_name_from_arn(key) - return super().get(new_key, *args, **kwargs) + for secret in dict.values(self): + if secret.arn == key or secret.name == key: + return secret + name = get_secret_name_from_partial_arn(key) + return super().get(name, *args, **kwargs) def pop(self, key, *args, **kwargs): - new_key = get_secret_name_from_arn(key) - return super().pop(new_key, *args, **kwargs) + for secret in dict.values(self): + if secret.arn == key or secret.name == key: + key = secret.name + name = get_secret_name_from_partial_arn(key) + return super().pop(name, *args, **kwargs) class SecretsManagerBackend(BaseBackend): diff --git a/moto/secretsmanager/utils.py b/moto/secretsmanager/utils.py index faf767a40..215237110 100644 --- a/moto/secretsmanager/utils.py +++ b/moto/secretsmanager/utils.py @@ -68,17 +68,20 @@ def secret_arn(account_id, region, secret_id): ) -def get_secret_name_from_arn(secret_id): - # can fetch by both arn and by name - # but we are storing via name - # so we need to change the arn to name - # if it starts with arn then the secret id is arn - if secret_id.startswith("arn:aws:secretsmanager:"): +def get_secret_name_from_partial_arn(partial_arn: str) -> str: + # We can retrieve a secret either using a full ARN, or using a partial ARN + # name: testsecret + # full ARN: arn:aws:secretsmanager:us-west-2:123456789012:secret:testsecret-xxxxxx + # partial ARN: arn:aws:secretsmanager:us-west-2:123456789012:secret:testsecret + # + # This method only deals with partial ARN's, and will return the name: testsecret + # + # If you were to pass in full url, this method will return 'testsecret-xxxxxx' - which has no meaning on it's own + if partial_arn.startswith("arn:aws:secretsmanager:"): # split the arn by colon # then get the last value which is the name appended with a random string - # then remove the random string - secret_id = "-".join(secret_id.split(":")[-1].split("-")[:-1]) - return secret_id + return partial_arn.split(":")[-1] + return partial_arn def _exclude_characters(password, exclude_characters): diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index ec42aed7c..6eee8b762 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -547,17 +547,26 @@ def test_describe_secret(): @mock_secretsmanager -def test_describe_secret_with_arn(): +@pytest.mark.parametrize("name", ["testsecret", "test-secret"]) +def test_describe_secret_with_arn(name): conn = boto3.client("secretsmanager", region_name="us-west-2") - results = conn.create_secret(Name="test-secret", SecretString="foosecret") + results = conn.create_secret(Name=name, SecretString="foosecret") secret_description = conn.describe_secret(SecretId=results["ARN"]) assert secret_description # Returned dict is not empty - secret_description["Name"].should.equal("test-secret") + secret_description["Name"].should.equal(name) secret_description["ARN"].should.equal(results["ARN"]) conn.list_secrets()["SecretList"][0]["ARN"].should.equal(results["ARN"]) + # We can also supply a partial ARN + partial_arn = f"arn:aws:secretsmanager:us-west-2:{ACCOUNT_ID}:secret:{name}" + resp = conn.get_secret_value(SecretId=partial_arn) + assert resp["Name"] == name + + resp = conn.describe_secret(SecretId=partial_arn) + assert resp["Name"] == name + @mock_secretsmanager def test_describe_secret_with_KmsKeyId():