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.

Friday, January 29, 2016

toco: DynamoDB-based user management for Django

I've been playing around a bit with Django websites lately, but I just can't stand relational databases.  I recognize that they're sometimes the right solution to a problem, but even the hosted solutions like AWS RDS add infrastructure overhead that managed NoSQL DBs like DynamoDB don't.  There are more creds to manage, annoying schemas to limit you, scaling pain that needs handled, all that jazz.  As such, I'm attempting to make a Django user management system based on DynamoDB.  I'm trying to make the underpinnings generic enough that it'll also be usable for other models, not just user auth and session management, but we'll see.

Repo is stevenorum/toco on GitHub.  Still very early on, just a couple basic things have been implemented so far, but hopefully it'll eventually be useful.

Tuesday, January 26, 2016

Chocolate raspberry stout (partial mash)

Grains:
Chocolate malt (40 oz)
Chocolate rye (48 oz)
Midnight wheat (12 oz)

Extract:
Wheat DME (6 lb)

Boil:
60 minutes:
Cocoa powder (8 oz)
Cascade hops (2 oz, ~5.5% alpha)

Yeast:

Water:
1 gallon spring water
4 gallons distilled water

OG: 1.07

Secondary fermentation will split the batch.  1 gallon is going to have raspberry flavor extract added to it; the other ~4 gallons are going to have ~10 lbs of raspberry puree added.  ETA for this is 31 January or 5 February.

Started late 24th/early 25th January 2016.  Updates to come as it progresses.