aws2-3 Exercise 2: KMS Key Policies, IAM Policies and Grants as Tests, Plus Offline SDK and Linter Checks ======================================================================================================== Run with Python 3.14.0 on Windows 11 in a scratch venv: cryptography 50.0.2, aws-encryption-sdk 4.0.7, boto3/botocore 1.43.112 (offline, through botocore's Stubber), parliament 1.6.4 with setuptools 80.10.2 (parliament imports pkg_resources). Setup: py -m venv .venv ; .venv/Scripts/python.exe -m pip install cryptography aws-encryption-sdk boto3 parliament "setuptools<81". VERIFICATION LEVEL: 1 (run here; no AWS account, no AWS CLI, no AWS call). LocalKms, the policy evaluator and the rotation protocol are MODELS of AWS KMS / IAM / Secrets Manager documentation pages read on 2026-10-12 (through a page-fetch tool that summarises pages): they are not AWS, and AWS may change its rules, limits and prices. Docker is installed but its engine was not running, so no container was used. iam_eval.py is the file listed in aws2-2 Exercise 1: copy it next to these scripts. --- mk_pol.py (writes the policy files into pol/) --- import json, pathlib D = pathlib.Path(r"C:\lwaws\c3\pol"); D.mkdir(exist_ok=True) V = "2012-10-17" A, B = "111122223333", "444455556666" KEY = f"arn:aws:kms:eu-west-1:{A}:key/1234abcd-12ab-34cd-56ef-1234567890ab" def w(n, o): (D / n).write_text(json.dumps(o, indent=2), encoding="utf-8") # the documented default key policy for keys created through the API w("key_policy_default.json", {"Version": V, "Statement": [{"Sid": "Enable IAM User Permissions", "Effect": "Allow", "Principal": {"AWS": f"arn:aws:iam::{A}:root"}, "Action": "kms:*", "Resource": "*"}]}) # a console-style key policy: root statement, administrators, users, grants for AWS services (shapes from the Default key policy page) w("key_policy_console_style.json", {"Version": V, "Statement": [ {"Sid": "Enable IAM User Permissions", "Effect": "Allow", "Principal": {"AWS": f"arn:aws:iam::{A}:root"}, "Action": "kms:*", "Resource": "*"}, {"Sid": "Allow access for Key Administrators", "Effect": "Allow", "Principal": {"AWS": f"arn:aws:iam::{A}:role/KeyAdmin"}, "Action": ["kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*", "kms:Update*", "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*", "kms:TagResource", "kms:UntagResource", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion", "kms:RotateKeyOnDemand"], "Resource": "*"}, {"Sid": "Allow use of the key", "Effect": "Allow", "Principal": {"AWS": f"arn:aws:iam::{A}:role/KeyUser"}, "Action": ["kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey"], "Resource": "*"}, {"Sid": "Allow attachment of persistent resources", "Effect": "Allow", "Principal": {"AWS": f"arn:aws:iam::{A}:role/KeyUser"}, "Action": ["kms:CreateGrant", "kms:ListGrants", "kms:RevokeGrant"], "Resource": "*", "Condition": {"Bool": {"kms:GrantIsForAWSResource": True}}}]}) # letting a second account use the key: name its root, then that account must delegate with its own IAM policy w("key_policy_allow_other_account.json", {"Version": V, "Statement": [ {"Sid": "Enable IAM User Permissions", "Effect": "Allow", "Principal": {"AWS": f"arn:aws:iam::{A}:root"}, "Action": "kms:*", "Resource": "*"}, {"Sid": "Allow account B to use the key", "Effect": "Allow", "Principal": {"AWS": f"arn:aws:iam::{B}:root"}, "Action": ["kms:Decrypt", "kms:DescribeKey"], "Resource": "*"}]}) w("iam_policy_account_b_role.json", {"Version": V, "Statement": [{"Effect": "Allow", "Action": ["kms:Decrypt", "kms:DescribeKey"], "Resource": KEY}]}) # an identity policy that limits use to one encryption context (kms:EncryptionContext: is the documented condition key form) w("iam_policy_context_bound.json", {"Version": V, "Statement": [{"Effect": "Allow", "Action": ["kms:Decrypt", "kms:GenerateDataKey"], "Resource": KEY, "Condition": {"StringEquals": {"kms:EncryptionContext:app": "invoices"}}}]}) print("ok") --- kms_policy_model.py --- """kms_policy_model.py - KMS key policies, IAM policies and grants, run through the Chapter 2 evaluator (iam_eval.py), a MODEL of the documented rules. Sources (AWS KMS developer guide pages read 2026-10-12): Key policies, Default key policy, Grants, plus the IAM evaluation pages from Chapter 2. The rules being tested, each a sentence on those pages: * "Unless the key policy explicitly allows it, you cannot use IAM policies to allow access to a KMS key." * the default key policy statement (principal = the account root) enables IAM policies: it delegates, it grants no identity access itself * "You can use an IAM policy to deny a permission to a KMS key without permission from a key policy." * key administrators "do not have permissions to use the KMS key in cryptographic operations" but can change the key policy and create grants * cross-account: the key policy must name the other account AND that account's IAM policy must allow the use * a grant allows access (never denies), needs no key policy or IAM statement, and is deleted by retiring or revoking it * a key that is not enabled (pending deletion) cannot be used in cryptographic operations NOT modelled: grant constraints (raises), grant eventual consistency, ABAC tags/aliases, kms:ViaService (other than as a plain context key), custom key stores, SCP/RCP interplay with grants. """ import sys from iam_eval import decide, decide_cross, NotModelled A, B = "111122223333", "444455556666" KEY = f"arn:aws:kms:eu-west-1:{A}:key/1234abcd-12ab-34cd-56ef-1234567890ab" def role(acct, name): return {"type": "role-session", "arn": f"arn:aws:sts::{acct}:assumed-role/{name}/s", "account": acct} def role_arn(acct, name): return f"arn:aws:iam::{acct}:role/{name}" def req(who, action, **ctx): return {"who": who, "action": action, "resource": KEY, "ctx": ctx} def allow(actions, **kw): return dict(Effect="Allow", Action=actions, Resource="*", **kw) def kstmt(principal, actions, sid="x", **kw): return dict(Sid=sid, Effect="Allow", Principal={"AWS": principal}, Action=actions, Resource="*", **kw) ENABLE_IAM = kstmt(f"arn:aws:iam::{A}:root", "kms:*", "Enable IAM User Permissions") # the documented default key policy statement ADMIN_ACTIONS = ["kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*", "kms:Update*", "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*", "kms:TagResource", "kms:UntagResource", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion", "kms:RotateKeyOnDemand"] USE_ACTIONS = ["kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey"] GRANT_STMT_ACTIONS = ["kms:CreateGrant", "kms:ListGrants", "kms:RevokeGrant"] app, admin, user = role(A, "App"), role(A, "KeyAdmin"), role(A, "KeyUser") rows = [] def check(label, expected, fn): try: ok, why = fn() except NotModelled as e: ok, why = None, "NotModelled: " + str(e) rows.append((label, expected, ok, why)) def kms(who, action, key_policy, identity=(), state="Enabled", grants=(), **ctx): """same-account use of a KMS key. grants: list of dicts {grantee_arn, operations}; they add an Allow, never a Deny.""" crypto = action.split(":")[1].startswith(("Encrypt", "Decrypt", "ReEncrypt", "GenerateDataKey")) if crypto and state != "Enabled": return False, f"key state {state}: cryptographic operations are not possible" r = req(who, action, **ctx) ok, why = decide(r, {"identity": [list(identity)], "resource": [key_policy]}, explicit_resource_allow=True) if identity or key_policy else (False, "no policy") if ok: return ok, why from iam_eval import hits if hits([key_policy] + list(identity), r, "Deny"): return False, why # a grant cannot override an explicit Deny for g in grants: if g.get("constraint"): raise NotModelled("grant constraints") if g["grantee_arn"] in (who["arn"], role_arn(A, who["arn"].split("/")[1])) and action.split(":")[1] in g["operations"]: return True, "a grant allows it" return False, why # ---- 1. key policy versus IAM policy check("default key policy (root statement) + IAM policy allows kms:Decrypt for the role -> Decrypt", True, lambda: kms(app, "kms:Decrypt", [ENABLE_IAM], [allow("kms:Decrypt")])) check("default key policy, role has NO IAM policy -> Decrypt (the root statement only DELEGATES)", False, lambda: kms(app, "kms:Decrypt", [ENABLE_IAM], [])) check("key policy names only one user (no root statement); IAM policy allows the role -> Decrypt", False, lambda: kms(app, "kms:Decrypt", [kstmt(f"arn:aws:iam::{A}:user/alice", USE_ACTIONS)], [allow("kms:Decrypt")])) check("key policy names the role directly for Decrypt; role has no IAM policy -> Decrypt", True, lambda: kms(app, "kms:Decrypt", [kstmt(role_arn(A, "App"), USE_ACTIONS)], [])) check("key policy names the role for Decrypt only -> Encrypt (not listed)", False, lambda: kms(app, "kms:Encrypt", [kstmt(role_arn(A, "App"), ["kms:Decrypt"])], [allow("kms:*")])) check("IAM Deny, and the key policy has no statement for the role at all -> Decrypt (a Deny needs no key policy permission)", False, lambda: kms(app, "kms:Decrypt", [kstmt(f"arn:aws:iam::{A}:user/alice", USE_ACTIONS)], [allow("kms:*"), dict(Effect="Deny", Action="kms:Decrypt", Resource="*")])) check("key policy has an explicit Deny for the role even though IAM allows -> Decrypt", False, lambda: kms(app, "kms:Decrypt", [ENABLE_IAM, dict(Effect="Deny", Principal={"AWS": role_arn(A, "App")}, Action="kms:Decrypt", Resource="*")], [allow("kms:*")])) # ---- 2. key administrators versus key users (Default key policy page) # console-style key policy WITHOUT the root statement: one statement for administrators, one for users policy = [kstmt(role_arn(A, "KeyAdmin"), ADMIN_ACTIONS, "Allow access for Key Administrators"), kstmt(role_arn(A, "KeyUser"), USE_ACTIONS, "Allow use of the key")] check("administrator role: kms:Decrypt (the admins statement has no cryptographic actions)", False, lambda: kms(admin, "kms:Decrypt", policy, [])) check("administrator role: kms:PutKeyPolicy", True, lambda: kms(admin, "kms:PutKeyPolicy", policy, [])) check("administrator role: kms:CreateGrant (kms:Create* includes it)", True, lambda: kms(admin, "kms:CreateGrant", policy, [])) check("administrator role: kms:ScheduleKeyDeletion", True, lambda: kms(admin, "kms:ScheduleKeyDeletion", policy, [])) check("key user role: kms:Decrypt", True, lambda: kms(user, "kms:Decrypt", policy, [])) check("key user role: kms:GenerateDataKeyWithoutPlaintext (GenerateDataKey* wildcard)", True, lambda: kms(user, "kms:GenerateDataKeyWithoutPlaintext", policy, [])) check("key user role: kms:PutKeyPolicy", False, lambda: kms(user, "kms:PutKeyPolicy", policy, [])) check("key user role: kms:ScheduleKeyDeletion", False, lambda: kms(user, "kms:ScheduleKeyDeletion", policy, [])) # an administrator can give themselves use of the key: the documented warning, shown as a two-step path grant = {"grantee_arn": admin["arn"], "operations": ["Decrypt", "GenerateDataKey"]} check("the warning: an administrator creates a grant for themselves, THEN calls kms:Decrypt", True, lambda: kms(admin, "kms:Decrypt", policy, [], grants=[grant])) # ---- 3. cross-account use (Key policies / Encrypt API pages: key policy must allow the other account and that account's IAM policy must allow) other = role(B, "Reader") cross_policy = [ENABLE_IAM, kstmt(f"arn:aws:iam::{B}:root", USE_ACTIONS, "Allow external account")] def cross(key_policy, other_identity): return decide_cross(req(other, "kms:Decrypt"), {"identity": [other_identity]}, {"resource": [key_policy]}) check("key policy names account B's root AND B's role has an IAM policy for the key ARN -> Decrypt", True, lambda: cross(cross_policy, [dict(Effect="Allow", Action="kms:Decrypt", Resource=KEY)])) check("key policy names account B's root, but B's role has no IAM policy -> Decrypt", False, lambda: cross(cross_policy, [])) check("B's role has an IAM policy, but the key policy does not name account B -> Decrypt", False, lambda: cross([ENABLE_IAM], [dict(Effect="Allow", Action="kms:Decrypt", Resource=KEY)])) # ---- 4. grants and key state (Grants page, Deleting keys page) g_app = {"grantee_arn": role_arn(A, "App"), "operations": ["Decrypt", "GenerateDataKey"]} check("a grant for the role, and NO key policy statement or IAM policy for it -> Decrypt", True, lambda: kms(app, "kms:Decrypt", [ENABLE_IAM], [], grants=[g_app])) check("same grant -> Encrypt (not among the grant's operations)", False, lambda: kms(app, "kms:Encrypt", [ENABLE_IAM], [], grants=[g_app])) check("the grant, but an IAM Deny on kms:Decrypt -> Decrypt (a grant cannot override a Deny)", False, lambda: kms(app, "kms:Decrypt", [ENABLE_IAM], [dict(Effect="Deny", Action="kms:Decrypt", Resource="*")], grants=[g_app])) check("the grant is retired (removed from the list) -> Decrypt", False, lambda: kms(app, "kms:Decrypt", [ENABLE_IAM], [], grants=[])) check("key is pending deletion; policies and grants all allow -> Decrypt", False, lambda: kms(app, "kms:Decrypt", [ENABLE_IAM], [allow("kms:*")], state="PendingDeletion", grants=[g_app])) check("key is pending deletion -> kms:CancelKeyDeletion by the administrator (management, not cryptographic)", True, lambda: kms(admin, "kms:CancelKeyDeletion", policy, [], state="PendingDeletion")) check("a grant with a constraint -> the model refuses (not modelled)", None, lambda: kms(app, "kms:Decrypt", [ENABLE_IAM], [], grants=[dict(g_app, constraint={"EncryptionContextSubset": {"a": "b"}})])) # ---- 5. the grant-for-AWS-resources condition (Default key policy page: kms:GrantIsForAWSResource) svc_policy = [kstmt(role_arn(A, "KeyUser"), GRANT_STMT_ACTIONS, "Allow attachment of persistent resources", Condition={"Bool": {"kms:GrantIsForAWSResource": True}})] check("key user calls kms:CreateGrant directly (kms:GrantIsForAWSResource is absent)", False, lambda: kms(user, "kms:CreateGrant", svc_policy, [])) check("key user creates the grant through an AWS service (kms:GrantIsForAWSResource = true)", True, lambda: kms(user, "kms:CreateGrant", svc_policy, [], **{"kms:GrantIsForAWSResource": True})) # ---- 6. an identity policy bound to one encryption context (condition key form kms:EncryptionContext:, from the Condition page) ctx_policy = [dict(Effect="Allow", Action=["kms:Decrypt", "kms:GenerateDataKey"], Resource=KEY, Condition={"StringEquals": {"kms:EncryptionContext:app": "invoices"}})] check("identity policy requires context app=invoices; request carries app=invoices -> Decrypt", True, lambda: kms(app, "kms:Decrypt", [ENABLE_IAM], ctx_policy, **{"kms:EncryptionContext:app": "invoices"})) check("same policy; request carries app=payroll -> Decrypt", False, lambda: kms(app, "kms:Decrypt", [ENABLE_IAM], ctx_policy, **{"kms:EncryptionContext:app": "payroll"})) check("same policy; request carries no encryption context at all -> Decrypt (the key is missing, so the condition is false)", False, lambda: kms(app, "kms:Decrypt", [ENABLE_IAM], ctx_policy)) if __name__ == "__main__": flip = "--flip" in sys.argv bad = 0 w = max(len(r[0]) for r in rows) print(f"{'case (source: AWS KMS developer guide page named in the script)':<{w}} docs model result") for i, (label, exp, ok, why) in enumerate(rows): if flip and i == 0: exp = not exp res = "match" if ok == exp else "MISMATCH" bad += res != "match" d = {True: "Allow", False: "Deny ", None: "(err)"} print(f"{label:<{w}} {d[exp]:<6} {d[ok]:<6} {res}" + (f" {why}" if "--why" in sys.argv else "")) print(f"\n{len(rows)} cases, mismatches: {bad}") --- py -X utf8 kms_policy_model.py --- case (source: AWS KMS developer guide page named in the script) docs model result default key policy (root statement) + IAM policy allows kms:Decrypt for the role -> Decrypt Allow Allow match default key policy, role has NO IAM policy -> Decrypt (the root statement only DELEGATES) Deny Deny match key policy names only one user (no root statement); IAM policy allows the role -> Decrypt Deny Deny match key policy names the role directly for Decrypt; role has no IAM policy -> Decrypt Allow Allow match key policy names the role for Decrypt only -> Encrypt (not listed) Deny Deny match IAM Deny, and the key policy has no statement for the role at all -> Decrypt (a Deny needs no key policy permission) Deny Deny match key policy has an explicit Deny for the role even though IAM allows -> Decrypt Deny Deny match administrator role: kms:Decrypt (the admins statement has no cryptographic actions) Deny Deny match administrator role: kms:PutKeyPolicy Allow Allow match administrator role: kms:CreateGrant (kms:Create* includes it) Allow Allow match administrator role: kms:ScheduleKeyDeletion Allow Allow match key user role: kms:Decrypt Allow Allow match key user role: kms:GenerateDataKeyWithoutPlaintext (GenerateDataKey* wildcard) Allow Allow match key user role: kms:PutKeyPolicy Deny Deny match key user role: kms:ScheduleKeyDeletion Deny Deny match the warning: an administrator creates a grant for themselves, THEN calls kms:Decrypt Allow Allow match key policy names account B's root AND B's role has an IAM policy for the key ARN -> Decrypt Allow Allow match key policy names account B's root, but B's role has no IAM policy -> Decrypt Deny Deny match B's role has an IAM policy, but the key policy does not name account B -> Decrypt Deny Deny match a grant for the role, and NO key policy statement or IAM policy for it -> Decrypt Allow Allow match same grant -> Encrypt (not among the grant's operations) Deny Deny match the grant, but an IAM Deny on kms:Decrypt -> Decrypt (a grant cannot override a Deny) Deny Deny match the grant is retired (removed from the list) -> Decrypt Deny Deny match key is pending deletion; policies and grants all allow -> Decrypt Deny Deny match key is pending deletion -> kms:CancelKeyDeletion by the administrator (management, not cryptographic) Allow Allow match a grant with a constraint -> the model refuses (not modelled) (err) (err) match key user calls kms:CreateGrant directly (kms:GrantIsForAWSResource is absent) Deny Deny match key user creates the grant through an AWS service (kms:GrantIsForAWSResource = true) Allow Allow match identity policy requires context app=invoices; request carries app=invoices -> Decrypt Allow Allow match same policy; request carries app=payroll -> Decrypt Deny Deny match same policy; request carries no encryption context at all -> Decrypt (the key is missing, so the condition is false) Deny Deny match 31 cases, mismatches: 0 --- py -X utf8 kms_policy_model.py --flip (control: one expectation flipped) --- case (source: AWS KMS developer guide page named in the script) docs model result default key policy (root statement) + IAM policy allows kms:Decrypt for the role -> Decrypt Deny Allow MISMATCH default key policy, role has NO IAM policy -> Decrypt (the root statement only DELEGATES) Deny Deny match key policy names only one user (no root statement); IAM policy allows the role -> Decrypt Deny Deny match key policy names the role directly for Decrypt; role has no IAM policy -> Decrypt Allow Allow match key policy names the role for Decrypt only -> Encrypt (not listed) Deny Deny match IAM Deny, and the key policy has no statement for the role at all -> Decrypt (a Deny needs no key policy permission) Deny Deny match key policy has an explicit Deny for the role even though IAM allows -> Decrypt Deny Deny match administrator role: kms:Decrypt (the admins statement has no cryptographic actions) Deny Deny match administrator role: kms:PutKeyPolicy Allow Allow match administrator role: kms:CreateGrant (kms:Create* includes it) Allow Allow match administrator role: kms:ScheduleKeyDeletion Allow Allow match key user role: kms:Decrypt Allow Allow match key user role: kms:GenerateDataKeyWithoutPlaintext (GenerateDataKey* wildcard) Allow Allow match key user role: kms:PutKeyPolicy Deny Deny match key user role: kms:ScheduleKeyDeletion Deny Deny match the warning: an administrator creates a grant for themselves, THEN calls kms:Decrypt Allow Allow match key policy names account B's root AND B's role has an IAM policy for the key ARN -> Decrypt Allow Allow match key policy names account B's root, but B's role has no IAM policy -> Decrypt Deny Deny match B's role has an IAM policy, but the key policy does not name account B -> Decrypt Deny Deny match a grant for the role, and NO key policy statement or IAM policy for it -> Decrypt Allow Allow match same grant -> Encrypt (not among the grant's operations) Deny Deny match the grant, but an IAM Deny on kms:Decrypt -> Decrypt (a grant cannot override a Deny) Deny Deny match the grant is retired (removed from the list) -> Decrypt Deny Deny match key is pending deletion; policies and grants all allow -> Decrypt Deny Deny match key is pending deletion -> kms:CancelKeyDeletion by the administrator (management, not cryptographic) Allow Allow match a grant with a constraint -> the model refuses (not modelled) (err) (err) match key user calls kms:CreateGrant directly (kms:GrantIsForAWSResource is absent) Deny Deny match key user creates the grant through an AWS service (kms:GrantIsForAWSResource = true) Allow Allow match identity policy requires context app=invoices; request carries app=invoices -> Decrypt Allow Allow match same policy; request carries app=payroll -> Decrypt Deny Deny match same policy; request carries no encryption context at all -> Decrypt (the key is missing, so the condition is false) Deny Deny match 31 cases, mismatches: 1 --- kms_botocore_lint.py --- """kms_botocore_lint.py - two offline checks around KMS: what the AWS SDK for Python validates before sending a KMS request (botocore's Stubber, no network), and what 'parliament' says about the policy files in pol/. """ import json, pathlib, re, warnings warnings.filterwarnings("ignore") import boto3 from botocore.exceptions import ParamValidationError from botocore.stub import Stubber from parliament import analyze_policy_string CASES = [ ("Encrypt: 1 byte of plaintext", dict(KeyId="alias/x", Plaintext=b"a"), "ok"), ("Encrypt: 0 bytes (documented minimum is 1)", dict(KeyId="alias/x", Plaintext=b""), "reject"), ("Encrypt: 4,096 bytes (documented maximum)", dict(KeyId="alias/x", Plaintext=b"a" * 4096), "ok"), ("Encrypt: 4,097 bytes (over the documented maximum)", dict(KeyId="alias/x", Plaintext=b"a" * 4097), "reject (documented)"), ("Encrypt: encryption context with a non-string value", dict(KeyId="alias/x", Plaintext=b"a", EncryptionContext={"n": 5}), "reject"), ("Encrypt: 11 grant tokens (documented maximum 10)", dict(KeyId="alias/x", Plaintext=b"a", GrantTokens=["t"] * 11), "reject (documented)"), ("Encrypt: no KeyId", dict(Plaintext=b"a"), "reject"), ] def sdk(params): c = boto3.client("kms", region_name="eu-west-1", aws_access_key_id="AKIAFAKE", aws_secret_access_key="fake") with Stubber(c) as st: st.add_response("encrypt", {"CiphertextBlob": b"x", "KeyId": "k", "EncryptionAlgorithm": "SYMMETRIC_DEFAULT"}) try: c.encrypt(**params) return "accepted" except ParamValidationError as e: m = re.search(r"(Invalid [a-z ]+ for parameter \w+|Missing required parameter in input: \"\w+\"|Invalid type for parameter [\w.]+)", str(e)) return "REJECTED: " + (m.group(1) if m else str(e)[:50]) def lint(text): pol = analyze_policy_string(text) return sorted(f.issue + ((": " + str(f.detail)[:50]) if f.detail and str(f.detail) != "None" else "") for f in pol.findings) or ["(no findings)"] if __name__ == "__main__": import botocore print(f"botocore {botocore.__version__}; every call goes to Stubber, nothing is sent\n") print(f"{'case':<56}{'what the SDK did':<62}expectation from the docs") for label, params, exp in CASES: print(f"{label:<56}{sdk(params):<62}{exp}") print("\nparliament on the policy files in pol/ (it reads each as an IDENTITY policy)") for f in sorted(pathlib.Path(r"C:\lwaws\c3\pol").glob("*.json")): print(f" {f.name:<40}", lint(f.read_text(encoding="utf-8"))) --- py -X utf8 kms_botocore_lint.py --- botocore 1.43.112; every call goes to Stubber, nothing is sent case what the SDK did expectation from the docs Encrypt: 1 byte of plaintext accepted ok Encrypt: 0 bytes (documented minimum is 1) accepted reject Encrypt: 4,096 bytes (documented maximum) accepted ok Encrypt: 4,097 bytes (over the documented maximum) accepted reject (documented) Encrypt: encryption context with a non-string value REJECTED: Invalid type for parameter EncryptionContext reject Encrypt: 11 grant tokens (documented maximum 10) accepted reject (documented) Encrypt: no KeyId REJECTED: Missing required parameter in input: "KeyId" reject parliament on the policy files in pol/ (it reads each as an IDENTITY policy) iam_policy_account_b_role.json ['(no findings)'] iam_policy_context_bound.json ['(no findings)'] key_policy_allow_other_account.json ['INVALID_SID', 'INVALID_SID'] key_policy_console_style.json ['INVALID_SID', 'INVALID_SID', 'INVALID_SID', 'INVALID_SID'] key_policy_default.json ['INVALID_SID'] WHY THIS WORKS AS AN ANSWER --------------------------- Each expected result is a sentence from the KMS developer guide pages Key policies, Default key policy and Grants (or the IAM evaluation pages from aws2-2), written down before the model ran. All 31 match, and the control reports exactly one mismatch. The cases separate the ideas people mix up: the root statement delegates but grants nothing; a key policy that names only one user makes other principals' IAM allows useless while IAM denies still work; administrators can manage but not use a key yet can give themselves use through a grant; cross-account needs both sides; a grant allows without any policy statement but cannot beat a Deny; a key pending deletion refuses cryptographic operations; an identity policy can bind use to an encryption context and a missing context makes the condition false. Grants with constraints raise NotModelled on purpose. The offline checks are findings, not successes: botocore accepted a 0-byte and a 4,097-byte Plaintext and 11 grant tokens (the documentation forbids them) and checked only types and required fields; parliament flagged the documented default key policy's Sid ('Enable IAM User Permissions') as INVALID_SID because it applies identity-policy rules to a key policy. Not run: real KMS requests, grant propagation delays, ABAC with tags and aliases, kms:ViaService, custom key stores, RCP and SCP interplay with grants.