Protect Kasten K10 Snapshots Using AWS KMS 

Since version   4.0.13   (Release Date: 2021-09-28), Kasten by Veeam has supported using the AWS Key Management Service (KMS) to protect Kasten K10 encryption keys. This provides an alternative to the passphrase Passkey, which is used by default to protect the master key and encryption key for encrypting snapshots exported to external locations such as S3 or NFS.

When using the KMS integration, Kasten K10 sends the ciphertext of the master key to KMS and retrieves the decrypted master key. Encryption keys are derived for each policy based on the master key.

For more details on how encryption works in Kasten K10, see my previous post, “Deep Dive: Encryption in Kasten.”

When To Use AWS KMS

If you are running on AWS and have a requirement to manage your own cryptographic keys, using KMS and a Customer Managed Key is a great option.

Additionally, using KMS frees you from the need to store the passphrase and reduces the number of secrets used in the system.

Walkthrough: Adding KMS to an Existing Kasten Setup

Create AWS KMS Key

First, we need to set up KMS in AWS. How you do this will depend on how you manage your AWS resources (e.g. whether you use the Console, CLI, Terraform, CloudFront, etc.). For the purpose of this blog post, I’ll be using the AWS CLI, because it’s accessible and transparently shows what resources are created. If you want to use something else, it should be fairly straightforward to translate the steps. If you are using Terraform, you can also take a look at the KMS branch of my Terraform + Kasten demo repository.

In KMS, you have different options for creating Customer Managed Keys (CMK). First, it’s important to note that the key needs to be symmetric. For the key material, you can choose either of the options that KMS offers:

The complexity of the options varies. We will stick with the basic KMS generated keys for now.

Let’s go ahead and generate the key:

# Create key
aws kms create-key
# --> copy "KeyId" and "Arn" we will need this a couple more times

# Using the KeyId from before, assign the key alias for usability
aws kms create-alias \
--alias-name alias/kasten \
--target-key-id <kms_key_id>

Grant Kasten Access to KMS

Of course, the KMS key is sensitive and not everyone should be able to access it. So let’s create a fine-grained access policy. How you do this depends on how you installed Kasten K10. There are two ways:

It’s important that you know which method you’ll use ahead of time and have the Amazon Resource Name (ARN) of the IAM role/user on hand.

Now, we can add the Kasten user/role to the KMS key policy and to the permissions of the IAM user/role. KMS also uses access policies that restrict who can use each key.

It’s important that we keep the default policy, because otherwise, we would deny ourselves access to the key. To do that we first retrieve the current default key policy:

# Using the KeyId from before...
aws kms get-key-policy --key-id <kms_key_id> --policy-name default | jq .Policy -r | jq
# --> This should log a JSON document
# If you do not have jq installed you can also do it without, but it makes formating way nicer
We can now copy the old policy and add this additional statement:
{
  "Effect": "Allow",
  "Principal": {
    "AWS": "<kasten_iam_user_or_role_arn>"
  },
  "Action": "kms:*",
  "Resource": "*" 
}

The full policy should now look something like this and can be applied to the key:

# Using the KeyId and policy from before...
aws kms put-key-policy --key-id <kms_key_id> \
--policy-name default \
--policy '{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "<kasten_iam_user_or_role>"
            },
            "Action": "kms:*",
            "Resource": "*"
        }
    ]
}'

Next, we need to make sure our Kasten IAM user/role is authorized to perform KMS actions. To do this we need to add a statement to the policy of the IAM user/role. Instead of the ARN, we use the   kms:ResourceAliases   attribute so that we could recreate the key without needing to update the policy.

You should already have a policy that grants permissions for ec2:CreateSnapshot, ec2:CreateVolume, etc., where you can simply add this statement:

{
    "Effect": "Allow",
    "Action": [
        "kms:Decrypt",
        "kms:Encrypt"
    ],
    "Resource": "*",
    "Condition": {
        "ForAnyValue:StringEquals": {
            "kms:ResourceAliases": "alias/kasten"
        }
    }
}

The final Kasten IAM policy should look something like this with the correct KMS key ARN:

{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Effect": "Allow",
          "Action": [
              "ec2:CopySnapshot",
              "ec2:CreateSnapshot",
              "ec2:CreateTags",
              "ec2:CreateVolume",
              "ec2:DeleteTags",
              "ec2:DeleteVolume",
              "ec2:DescribeSnapshotAttribute",
              "ec2:ModifySnapshotAttribute",
              "ec2:DescribeAvailabilityZones",
              "ec2:DescribeSnapshots",
              "ec2:DescribeTags",
              "ec2:DescribeVolumeAttribute",
              "ec2:DescribeVolumesModifications",
              "ec2:DescribeVolumeStatus",
              "ec2:DescribeVolumes",
              "ec2:ResourceTag/*"
          ],
          "Resource": "*"
      },
      {
          "Effect": "Allow",
          "Action": "ec2:DeleteSnapshot",
          "Resource": "*",
          "Condition": {
              "StringLike": {
                  "ec2:ResourceTag/Name": "Kasten: Snapshot*"
              }
          }
      },
      {
          "Effect": "Allow",
          "Action": [
              "kms:Decrypt",
              "kms:Encrypt"
          ],
          "Resource": "*",
          "Condition": {
              "ForAnyValue:StringEquals": {
                  "kms:ResourceAliases": "alias/kasten"
              }
          }
      }
  ]
}

After updating the policy, Kasten K10 should have permission to use the KMS key.

Switching Encryption to KMS

Now that we’ve set up the key and configured the necessary policies, we can go ahead and create a new KMS Passkey. This will re-encrypt the master key using the KMS key and store the ciphertext. See the Kasten K10 docs on changing Passkeys for more details.

In the future, Kasten K10 will send the ciphertext of the master key to KMS whenever it needs to access it, for example, when Kasten K10 needs to derive an encryption key for protecting a snapshot that should be exported.

We will need to create a new Passkey to switch the default Passkey to a KMS Passkey. To do so, we will create   passkey-kms.yaml  :

apiVersion: vault.kio.kasten.io/v1alpha1
kind: Passkey
metadata:
  name: kmsKey
spec:
  awscmkkeyid: <kms_key_arn>
  usenow: true

We can then create the Passkey using this command:

kubectl create -f passkey-kms.yaml

The new key should now be the only key with   inuse:true  :

kubectl get passkeys.vault.kio.kasten.io -o yaml

Should the creation fail for whatever reason, we can look for more information in the logs of the crypto-svc service:

kubectl -n kasten-io logs -f -l run=crypto-svc -c crypto-svc

Kasten Kq10 will now use the new Passkey and AWS KMS for encryption. It’s now safe to delete the old Passkey.

Setting Up Kasten with KMS From Scratch

When setting up a fresh Kasten K10 installation, we can immediately connect Kasten K10 to KMS using a secret.

After you’ve created the KMS key with the necessary policies, we can create a special secret with the name   k10-cluster-passphrase  , and use that for the configuration:

kubectl create secret generic k10-cluster-passphrase \
  --namespace kasten-io \
  --from-literal awscmkkeyid=<kms_key_arn>

You will always have the option to either configure KMS directly in the Passkey or add a reference to a secret and add the configuration there. However, after you’ve installed Kasten K10, the Passkey API resource already exists and there is little value in going the extra step to create a secret that the Passkey points to.

Rotating the KMS Key

KMS has a feature called automatic key rotation. If enabled, AWS will generate new cryptographic material for the KMS key every year. For obvious reasons, the feature is only available for keys where the key material is generated by KMS (i.e. not external or CloudHMS keys). However, enabling or disabling this feature has little impact on the Kasten K10 setup.

When AWS generates new key material, the old key material doesn’t go away. When encrypting data, the latest key material is used. However, decrypting data encrypted using previous versions of the key still works. Because Kasten K10 isn’t aware that KMS rotated the key, it won’t re-encrypt the master key, and will keep using the old key material.

Kasten K10 can be forced to re-encrypt the master key by creating a new Passkey with a different name. You could create an automation that waits for the KMS key rotation event, for instance.

If you like to have more control and the ability to trigger a key rotation any time, you can perform a manual key rotation. A manual key rotation is a three-step process: create a new key, update all references to it, then remove the old key. I’ll demonstrate the manual key rotation process here:

1. Create the New Key

Let’s first get our key and the key policy:

aws kms describe-key --key-id alias/kasten
aws kms get-key-policy --key-id <old_kms_key_id> --policy-name default | jq .Policy -r | jq > policy.json

Now we can create a new key with the same policy:

aws kms create-key --policy "$(cat policy.json)"
# --> copy new "KeyId" and "Arn" we will need this a couple more times

Next, we update the alias:

aws kms update-alias --alias-name alias/kasten --target-key-id <new_kms_key_id>

2. Update All the Ciphertext

To update the   cyphertext  , we need to force Kasten to re-encrypt the master key. We do this by creating a new Passkey. You can edit    passkey-kms.yaml  , but rename the key to kmsKey_v2 and put the new key ARN as   awscmkkeyid  .

kubectl create -f passkey-kms.yaml
Now we can check that the new key is now also the used key:
kubectl get passkeys.vault.kio.kasten.io kmsKey_v2 -o yaml
# -> Should be "inuse: true"

# Optionally, you can now delete the old key:

3. Schedule Deletion for the Old Key

We can now remove the old KMS key:

aws kms schedule-key-deletion --key-id <old_kms_key_id> --pending-window-in-days 7

Automated Key Rotation

I’ve also created a script for automating the process outlined above. You can find it on github.com/MoritzKn/kasten-key-rotation.

Further Information

Exit mobile version