Saturday, February 6, 2016

Using KMS encryption contexts in AWS IAM policies

I use KMS encryption contexts in some of my projects, but the documentation available on how to embed them in IAM policies is incredibly minimal.  I couldn't find a single full example, just a vague description in the standard IAM docs.  So, here are some different example snippets I've come up with.

(Note: in all of the examples I'm going to talk about the permissions required to decrypt an encrypted blob, as that's the easiest to follow conceptually, but the same rules apply to the other actions included.)

--------------------

A statement to allow an IAM entity to perform all crypto operations on an object that was encrypted with a context including, but not limited to, {"Key":"Value"}:

{
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Key": "Value"
    }
  }
}

Acceptable contexts according to this policy:
{"Key":"Value"}
{"Key":"Value", "Marco":"Polo"}
{"Key":"Value", "Foo":"Bar"}
{"Key":"Value", "Foo":"Bar", "Marco":"Polo"}

Unacceptable contexts according to this policy:
{"Foo":"Bar"}
{"Marco":"Polo"}
{"Foo":"Bar", "Marco":"Polo"}

--------------------

One important note that you probably already know about decrypting with encryption contexts: when the actual decrypt call is being made, the entire context used to encrypt it must be provided, not just the part in the user's IAM policy.  I personally just store the encryption context alongside the encrypted blob; I believe this is both not a security risk and probably standard practice but someone please correct me if I'm wrong.

--------------------

If you wanted to limit the above policy to only be able to decrypt objects with the context {"Key":"Value", "Foo":"Bar"}, and not objects with just {"Key":"Value"} or just {"Foo":"Bar"}, you'd add another element to the StringEquals list like so:

{
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Key": "Value",
      "kms:EncryptionContext:Foo": "Bar"
    }
  }
}

Acceptable contexts according to this policy:
{"Key":"Value", "Foo":"Bar"}
{"Key":"Value", "Foo":"Bar", "Marco":"Polo"}

Unacceptable contexts according to this policy:
{"Key":"Value"}
{"Foo":"Bar"}
{"Marco":"Polo"}
{"Foo":"Bar", "Marco":"Polo"}
{"Key":"Value", "Marco":"Polo"}

--------------------

If you want a policy to allow a user to decrypt objects with contexts containing either {"Key":"Value"} OR {"Foo":"Bar"}, I'm pretty sure you'd need two policy statements:

{
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Key": "Value"
    }
  }
},
{
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Foo": "Bar"
    }
  }
}

Acceptable contexts according to this policy:
{"Key":"Value"}
{"Foo":"Bar"}
{"Key":"Value", "Marco":"Polo"}
{"Key":"Value", "Foo":"Bar"}
{"Foo":"Bar", "Marco":"Polo"}
{"Key":"Value", "Foo":"Bar", "Marco":"Polo"}

Unacceptable contexts according to this policy:
{"Marco":"Polo"}

--------------------

Finally, if you want to allow decryption of blobs encrypted with either {"Key":"Value"} or {"Foo":"Bar"}, but not both {"Key":"Value","Foo":"Bar"}, you could use a combination of the following three statements (note that the first two have the effect "Allow" but the third has the effect "Deny"):

{
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Key": "Value"
    }
  }
},
{
  "Effect": "Allow",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Foo": "Bar"
    }
  }
},
{
  "Effect": "Deny",
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:GenerateDataKey*"
    "kms:ReEncrypt*",
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:Key": "Value",
      "kms:EncryptionContext:Foo": "Bar"
    }
  }
}

Acceptable contexts according to this policy:
{"Key":"Value"}
{"Foo":"Bar"}
{"Key":"Value", "Marco":"Polo"}
{"Foo":"Bar", "Marco":"Polo"}

Unacceptable contexts according to this policy:
{"Marco":"Polo"}
{"Key":"Value", "Foo":"Bar"}
{"Key":"Value", "Foo":"Bar", "Marco":"Polo"}

--------------------

I think with the above examples you should be able to piece together any encryption-context-related IAM policy that you'd need.  If you come up with something that I didn't cover, though, definitely let me know and I'll try to put something together.

No comments: