Skip to content

Commit 1dac283

Browse files
authored
feat: change KMS discovery keyring configuration path (#228)
* feat: change KMS discovery keyring configuration path * fix: clarify error message when insufficient configuration is provided to KmsKeyring
1 parent 31e4e49 commit 1dac283

File tree

2 files changed

+32
-15
lines changed

2 files changed

+32
-15
lines changed

src/aws_encryption_sdk/keyrings/aws_kms/__init__.py

+24-10
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,13 @@ class KmsKeyring(Keyring):
5555
but for the keyring to attempt to use them on decrypt
5656
you MUST specify the CMK ARN.
5757
58-
If you specify neither ``generator_key_id`` nor ``child_key_ids``
59-
then the keyring will operate in `discovery mode`_,
58+
If you specify ``is_discovery=True`` the keyring will be a KMS discovery keyring,
6059
doing nothing on encrypt and attempting to decrypt any AWS KMS-encrypted data key on decrypt.
6160
61+
.. notice::
62+
63+
You must either set ``is_discovery=True`` or provide key IDs.
64+
6265
You can use the :class:`ClientSupplier` to customize behavior further,
6366
such as to provide different credentials for different regions
6467
or to restrict which regions are allowed.
@@ -75,12 +78,14 @@ class KmsKeyring(Keyring):
7578
.. versionadded:: 1.5.0
7679
7780
:param ClientSupplier client_supplier: Client supplier that provides AWS KMS clients (optional)
81+
:param bool is_discovery: Should this be a discovery keyring (optional)
7882
:param str generator_key_id: Key ID of AWS KMS CMK to use when generating data keys (optional)
7983
:param List[str] child_key_ids: Key IDs that will be used to encrypt and decrypt data keys (optional)
8084
:param List[str] grant_tokens: AWS KMS grant tokens to include in requests (optional)
8185
"""
8286

8387
_client_supplier = attr.ib(default=attr.Factory(DefaultClientSupplier), validator=is_callable())
88+
_is_discovery = attr.ib(default=False, validator=instance_of(bool))
8489
_generator_key_id = attr.ib(default=None, validator=optional(instance_of(six.string_types)))
8590
_child_key_ids = attr.ib(
8691
default=attr.Factory(tuple),
@@ -93,6 +98,22 @@ class KmsKeyring(Keyring):
9398

9499
def __attrs_post_init__(self):
95100
"""Configure internal keyring."""
101+
key_ids_provided = self._generator_key_id is not None or self._child_key_ids
102+
both = key_ids_provided and self._is_discovery
103+
neither = not key_ids_provided and not self._is_discovery
104+
105+
if both:
106+
raise TypeError("is_discovery cannot be True if key IDs are provided")
107+
108+
if neither:
109+
raise TypeError("is_discovery cannot be False if no key IDs are provided")
110+
111+
if self._is_discovery:
112+
self._inner_keyring = _AwsKmsDiscoveryKeyring(
113+
client_supplier=self._client_supplier, grant_tokens=self._grant_tokens
114+
)
115+
return
116+
96117
if self._generator_key_id is None:
97118
generator_keyring = None
98119
else:
@@ -107,14 +128,7 @@ def __attrs_post_init__(self):
107128
for key_id in self._child_key_ids
108129
]
109130

110-
self._is_discovery = generator_keyring is None and not child_keyrings
111-
112-
if self._is_discovery:
113-
self._inner_keyring = _AwsKmsDiscoveryKeyring(
114-
client_supplier=self._client_supplier, grant_tokens=self._grant_tokens
115-
)
116-
else:
117-
self._inner_keyring = MultiKeyring(generator=generator_keyring, children=child_keyrings)
131+
self._inner_keyring = MultiKeyring(generator=generator_keyring, children=child_keyrings)
118132

119133
def on_encrypt(self, encryption_materials):
120134
# type: (EncryptionMaterials) -> EncryptionMaterials

test/unit/keyrings/test_aws_kms.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
pytest.param(dict(child_key_ids="some stuff"), id="child_key_ids is a string"),
2525
pytest.param(dict(grant_tokens=("foo", 5)), id="grant_tokens contains invalid values"),
2626
pytest.param(dict(grant_tokens="some stuff"), id="grant_tokens is a string"),
27+
pytest.param(dict(generator_key_id="foo", is_discovery=True), id="generator and discovery"),
28+
pytest.param(dict(child_key_ids=("foo",), is_discovery=True), id="child_key_ids and discovery"),
29+
pytest.param(dict(), id="nothing"),
2730
),
2831
)
2932
def test_kms_keyring_invalid_parameters(kwargs):
@@ -100,7 +103,7 @@ def test_kms_keyring_builds_correct_inner_keyring_discovery():
100103
grants = ("asdf", "fdas")
101104
supplier = DefaultClientSupplier()
102105

103-
test = KmsKeyring(grant_tokens=grants, client_supplier=supplier)
106+
test = KmsKeyring(is_discovery=True, grant_tokens=grants, client_supplier=supplier)
104107

105108
# We specified neither a generator nor children, so the inner keyring MUST be a discovery keyring
106109
assert isinstance(test._inner_keyring, _AwsKmsDiscoveryKeyring)
@@ -110,10 +113,10 @@ def test_kms_keyring_builds_correct_inner_keyring_discovery():
110113
assert test._inner_keyring._client_supplier is supplier
111114

112115

113-
def test_kms_keyring_on_encrypt(mocker):
116+
def test_kms_keyring_inner_keyring_on_encrypt(mocker):
114117
mock_keyring = mocker.Mock()
115118

116-
keyring = KmsKeyring()
119+
keyring = KmsKeyring(is_discovery=True)
117120
keyring._inner_keyring = mock_keyring
118121

119122
test = keyring.on_encrypt(encryption_materials=mocker.sentinel.encryption_materials)
@@ -123,10 +126,10 @@ def test_kms_keyring_on_encrypt(mocker):
123126
assert test is mock_keyring.on_encrypt.return_value
124127

125128

126-
def test_kms_keyring_on_decrypt(mocker):
129+
def test_kms_keyring_inner_keyring_on_decrypt(mocker):
127130
mock_keyring = mocker.Mock()
128131

129-
keyring = KmsKeyring()
132+
keyring = KmsKeyring(is_discovery=True)
130133
keyring._inner_keyring = mock_keyring
131134

132135
test = keyring.on_decrypt(

0 commit comments

Comments
 (0)