Repository navigation
Conversation
…vider Add KmsTransport and TenantKmsClient so the ESDK-Python can call AWS KMS with per-tenant credentials over one shared connection pool, without a boto3 Session or client per tenant. Add a StrictAwsKmsMasterKeyProvider subclass that uses them, a design document, and offline and integration tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mple - Rename "Key Store" to "AWS KMS Key Store" and define it. - Apply review suggestions for the DynamoDB credentials section and the AWS SDK for Python v4 note. - Add 2.3 Author's conclusion: boto3 has no public per-request credentials, so other options are pursued. - Document which non-public interfaces the example depends on, and why the ESDK-Python legacy MKP risk is accepted. - Explain that the shared transport must only be closed at shutdown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oviders Add the multi-tenant AWS KMS Keyring example (with a ClientSupplier for the MRK Discovery Multi-Keyring) and the AWS KMS Hierarchical Keyring example (an AWS KMS Key Store per tenant, one shared DynamoDB client). Add the MRK-aware Strict, Discovery, and MRK-aware Discovery KMS Master Key Providers. The MRK-aware Discovery provider overrides _new_master_key_impl, as its parent constructs MRKAwareKMSMasterKeyConfig directly and ignores master_key_config_class. Apply the review's guidance to the new examples: close the shared transport only at shutdown, and say "AWS KMS Key Store". Fix "a AWS" in design.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Create one storm tracking cache, shared by every tenant's Hierarchical Keyring, so its fan out (thundering herd mitigation) applies across tenants and one entry capacity bounds every tenant's Branch Keys. - Isolate tenants within the shared cache with a Partition ID: the SHA-384 digest of the tenant's secret access key, so the keyring never holds the secret. Refreshed credentials yield a new Partition ID; the resulting cache miss is intended, tying cache usage to credential lifetimes. - Close the shared DynamoDB client at shutdown, alongside the transport. - Document the above in design.md 10.1-10.3; test the Partition ID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g cache Replace DefaultCache with a StormTrackingCache whose every parameter is explicit and explained in the source. Shared by every tenant, two MPL defaults do not fit: - fan_out 20 -> 100: the cap is now process wide; 20 would serialize a burst of tenants missing the cache at once. - in_flight_ttl 10 s -> 5 s: a failed fetch holds its fan out slot this long, so a failing tenant could hold slots other tenants need. Set time_units to MILLISECONDS explicitly, as StormTrackingCache defaults to seconds. Document that every value should be tuned to the application's usage pattern, and that MPL 1.11.3 or later is required (earlier Python releases sleep sleep_milli seconds; MPL #1940). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ples The Hierarchical Keyring examples create new Branch Keys on every run, so the shared example table grows without bound. - Port DeleteBranchKey from the MPL's Dafny test helpers (AwsCryptographyKeyStore/test/CleanupItems.dfy, mutations/mutations) to examples/test/branch_key_cleanup.py, with a best effort cleanup_branch_keys that logs failures and never raises. - Both hierarchical examples take an optional branch_key_ids dict, filled in as each Branch Key is created; their tests clean up in a finally, so keys are deleted even if the example raises. - Document that Branch Keys SHOULD NOT be deleted in production, as deleting one cryptographically shreds every message it protected. - Fix the Key Store table's key schema in both examples' docstrings: "branch-key-id" and "type", not "partition_key" and "sort_key". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The repo's isort-check (isort 5.11.4) places typing among third party imports; mark it with the same pylint wrong-import-order exemption as hierarchical_keyring_example.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 task
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue #, if available:
Description of changes:
boto3 binds AWS credentials to a client when the client is constructed, and every boto3 client (and Session) owns its own connection pool. So an application that must call KMS with per-tenant credentials needs a Session and a KMS client per tenant. Measured at ~239 ms and ~8.4 MiB each, that does not scale to hundreds or thousands of tenants. boto3 has no public way to override credentials per request (unlike the AWS SDK for Java's
overrideConfiguration), and a search of boto3 and botocore issues found no feature request for one.This adds examples that share one connection pool across every tenant.
examples/src/multi_tenant/design.mdrecords the options considered and the decisions made.Shared building blocks (
examples/src/multi_tenant/; Python 3.8+, no MPL import):kms_transport.py:KmsTransport, one per process. It owns:urllib3poolbotocore.auth.SigV4Authbotocore/httpsession.py)It rejects any region that could change the endpoint host, because Discovery keyrings and providers take the region from the ciphertext. HTTP(S) proxies are out of scope.
tenant_kms_client.py:TenantKmsClient, one per tenant per region. A boto3-shaped client forGenerateDataKey,GenerateDataKeyWithoutPlaintext,Encrypt,Decrypt, andReEncrypt. It holds only a reference to the transport, a region, and the tenant's credentials.Keyring examples (MPL):
multi_tenant_kms_keyring_example.py: an AWS KMS Keyring per tenant, plus aTenantClientSupplierso an AWS KMS MRK Discovery Multi-Keyring decrypts with the tenant's credentials.multi_tenant_hierarchical_keyring_example.py: an AWS KMS Key Store and Hierarchical Keyring per tenant, sharing one DynamoDB client and one cache:CacheTypeSharedcache, so the fan out limit (thundering herd mitigation) applies across tenants. EveryStormTrackingCacheparameter is set explicitly, and the source explains each one.fan_outis raised to 100 andin_flight_ttllowered to 5 s, because a cache shared by every tenant makes both limits process-wide (seedesign.md10.1.1). These values are illustrative and should be tuned to the application's usage.sleep_milliseconds instead of milliseconds (Python StormTrackingCMC follower wait sleeps in whole seconds, not milliseconds aws-cryptographic-material-providers-library#1940). This is documented, not enforced; the ESDK'sMPLextra still allows>=1.7.4.Master Key Provider examples (
examples/src/legacy/multi_tenant_kms_master_key_provider.py):TenantKmsClients.MRKAwareDiscoveryAwsKmsMasterKeyProvider._new_master_key_implconstructsMRKAwareKMSMasterKeyConfigdirectly instead of usingmaster_key_config_class, so its subclass overrides that method (seedesign.md4.2.2).Branch Key cleanup in tests (
examples/test/branch_key_cleanup.py):delete_branch_keyis a port ofDeleteBranchKeyfrom the MPL's Dafny test helpers (AwsCryptographyKeyStore/test/CleanupItems.dfy), andcleanup_branch_keyswraps it as best effort: it logs failures and never fails the test.Changes to existing files:
examples/src/hierarchical_keyring_example.py:branch_key_idsdict, so its test can clean up its Branch Keys (the test previously left two behind per run)branch-key-id/type, notpartition_key/sort_keyexamples/test/test_i_hierarchical_keyring_example.py: cleans up those Branch Keys in afinally.Testing
Offline: an in-memory KMS behind the transport's pool authorizes each KMS key for exactly one access key. The tests:
The Partition ID and the Branch Key cleanup also have offline tests (the latter use botocore's
Stubber). 63 pass.Against AWS KMS (local): the AWS KMS Keyring and MKP examples pass. Requests signed with two different sets of credentials shared one TLS connection.
Not yet verified against AWS:
GenerateDataKeyWithoutPlaintext,ReEncrypt, and the Hierarchical Keyring examples. The local test identity lacks KMS permissions on the hierarchical test key, and fails the same way with the existinghierarchical_keyring_example.py. The Branch Key cleanup needsdynamodb:Queryanddynamodb:DeleteItemonKeyStoreDdbTable.Static analysis: passes with the repo's pinned versions, including
flake8-examples,pylint-examples, andisort-check(isort 5.11.4).CI on the author's fork: the CodeBuild jobs fail because the fork has no
CI_AWS_ROLE_ARN. Separately, the decrypt-oracle jobs and the Python 3.8/3.9testsjobs fail inactions/setup-python, because those versions are not available on the Ubuntu 26.04 runner image. That is unrelated to this change.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
Check any applicable:
🤖 Generated with Claude Code